claude-sessions spawn: add --headless/--create for non-interactive use
Claude-Session: https://claude.ai/code/session_01JXneLTuvb5Gxim3DTN2tQa
This commit is contained in:
@@ -16,6 +16,10 @@ Python script (`scripts/claude-sessions`, symlinked to `~/sbin/`). Manages Claud
|
|||||||
- **Folder browser.** `zenity --file-selection --directory` when `DISPLAY`/`WAYLAND_DISPLAY` is set and zenity exists (has Create Folder). Otherwise a terminal browser: number = open, `u` up, `n` new folder, `/text` filter, `>` `<` page, `h` hidden, `~`/`/path` jump, Enter/`s` select, `q` cancel.
|
- **Folder browser.** `zenity --file-selection --directory` when `DISPLAY`/`WAYLAND_DISPLAY` is set and zenity exists (has Create Folder). Otherwise a terminal browser: number = open, `u` up, `n` new folder, `/text` filter, `>` `<` page, `h` hidden, `~`/`/path` jump, Enter/`s` select, `q` cancel.
|
||||||
- **Topic.** Becomes the tmux session name and the Remote Control name (passed as `--name` to `claude-tmux`). Defaults to the Title-Cased folder name. If that name is already a live tmux session, no default is offered and a unique topic is required. Topics containing `.` or `:` are rejected because tmux rewrites them to `_`.
|
- **Topic.** Becomes the tmux session name and the Remote Control name (passed as `--name` to `claude-tmux`). Defaults to the Title-Cased folder name. If that name is already a live tmux session, no default is offered and a unique topic is required. Topics containing `.` or `:` are rejected because tmux rewrites them to `_`.
|
||||||
|
|
||||||
|
## Headless spawn
|
||||||
|
|
||||||
|
`claude-sessions spawn <folder> --headless [--name topic] [--create] [--mode m] [--profile p]` — no prompts, no attach, for scripts and the `/spawn-session` skill. Exit 1 = folder missing (or `--create` needs an absolute path); exit 2 = topic in use (default topic is the Title-Cased folder name; no `#N` suffix is added). Without `--headless`, `spawn` behaves as before.
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
- "In use" means a live tmux session. Resumable registry entries do not block a topic.
|
- "In use" means a live tmux session. Resumable registry entries do not block a topic.
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ from the Claude app.
|
|||||||
|
|
||||||
- `/spawn-session <project>` — spawn for a named project folder
|
- `/spawn-session <project>` — spawn for a named project folder
|
||||||
- `/spawn-session <project> --mode <mode>` — override engagement mode
|
- `/spawn-session <project> --mode <mode>` — override engagement mode
|
||||||
- `/spawn-session <project> --attach` — attach after spawning
|
|
||||||
- `/spawn-session` — lists projects and asks which one
|
- `/spawn-session` — lists projects and asks which one
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
Thin wrapper over the `claude-tmux` script (in `small-scripts`, symlinked to `~/sbin`).
|
Calls `claude-sessions spawn <folder> --headless [--name topic] [--create]` (since 2026-10-26),
|
||||||
The skill only resolves the project name from `$ARGUMENTS` (or asks) and shells out to
|
which wraps `claude-tmux` (in `small-scripts`, symlinked to `~/sbin`). The skill only resolves
|
||||||
`claude-tmux <project> [flags]`. All real logic — folder resolution, mode selection
|
the folder from `$ARGUMENTS` (or asks). Headless mode never prompts or attaches, and exits 2
|
||||||
|
if the topic is already a live tmux session (the skill then asks for a unique topic). All real logic — folder resolution, mode selection
|
||||||
(profile `last-mode` unless `--mode`), Title-Casing, ` #N` auto-increment, tmux creation,
|
(profile `last-mode` unless `--mode`), Title-Casing, ` #N` auto-increment, tmux creation,
|
||||||
and launching `claude --remote-control` via `claude-profile` — lives in the script.
|
and launching `claude --remote-control` via `claude-profile` — lives in the script.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Commands:
|
|||||||
(no args) Interactive menu (new session: folder browser + unique topic)
|
(no args) Interactive menu (new session: folder browser + unique topic)
|
||||||
list Show live Claude tmux sessions and resumable registry entries
|
list Show live Claude tmux sessions and resumable registry entries
|
||||||
recover [--days N] Detect sessions active before a restart
|
recover [--days N] Detect sessions active before a restart
|
||||||
spawn <project> Spawn a new session (wraps claude-tmux)
|
spawn <project> Spawn a new session (wraps claude-tmux); --headless for scripted use
|
||||||
resume <session-id> Resume a past session by conversation ID
|
resume <session-id> Resume a past session by conversation ID
|
||||||
register Record a new session in the registry (called by claude-tmux)
|
register Record a new session in the registry (called by claude-tmux)
|
||||||
gc [--days N] Prune stale entries and backfill session IDs
|
gc [--days N] Prune stale entries and backfill session IDs
|
||||||
@@ -650,13 +650,39 @@ def cmd_spawn(args) -> None:
|
|||||||
claude_tmux = subprocess.run(["which", "claude-tmux"], capture_output=True, text=True).stdout.strip()
|
claude_tmux = subprocess.run(["which", "claude-tmux"], capture_output=True, text=True).stdout.strip()
|
||||||
if not claude_tmux:
|
if not claude_tmux:
|
||||||
die("claude-tmux not found in PATH")
|
die("claude-tmux not found in PATH")
|
||||||
cmd = [claude_tmux, args.project]
|
project = args.project
|
||||||
|
name = args.name
|
||||||
|
|
||||||
|
if getattr(args, "headless", False):
|
||||||
|
# Non-interactive: no prompts, no attach. Errors exit non-zero instead of asking.
|
||||||
|
folder = resolve_project_dir(project)
|
||||||
|
if folder is None and args.create:
|
||||||
|
folder = Path(project).expanduser()
|
||||||
|
if not folder.is_absolute():
|
||||||
|
die(f"--create needs an absolute path (got {project!r})")
|
||||||
|
try:
|
||||||
|
folder.mkdir(parents=True)
|
||||||
|
except OSError as e:
|
||||||
|
die(f"Could not create {folder}: {e}")
|
||||||
|
folder = folder.resolve()
|
||||||
|
if folder is None:
|
||||||
|
die(f"Folder not found: {project} (use --create with an absolute path to make it)")
|
||||||
|
name = name or title_case(folder.name)
|
||||||
|
if topic_in_use(name, list_tmux_sessions()):
|
||||||
|
print(f"{RED}Error:{NC} topic '{name}' is already in use — pass a unique --name", file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
if any(c in name for c in ".:"):
|
||||||
|
die("Topic must not contain '.' or ':' (tmux rewrites them)")
|
||||||
|
project = str(folder)
|
||||||
|
args.attach = False
|
||||||
|
|
||||||
|
cmd = [claude_tmux, project]
|
||||||
if args.mode:
|
if args.mode:
|
||||||
cmd += ["--mode", args.mode]
|
cmd += ["--mode", args.mode]
|
||||||
if args.profile:
|
if args.profile:
|
||||||
cmd += ["--profile", args.profile]
|
cmd += ["--profile", args.profile]
|
||||||
if args.name:
|
if name:
|
||||||
cmd += ["--name", args.name]
|
cmd += ["--name", name]
|
||||||
if args.attach:
|
if args.attach:
|
||||||
cmd += ["--attach"]
|
cmd += ["--attach"]
|
||||||
os.execvp(claude_tmux, cmd)
|
os.execvp(claude_tmux, cmd)
|
||||||
@@ -919,6 +945,10 @@ def main() -> None:
|
|||||||
sp.add_argument("--profile", default=None)
|
sp.add_argument("--profile", default=None)
|
||||||
sp.add_argument("--name", default=None)
|
sp.add_argument("--name", default=None)
|
||||||
sp.add_argument("--attach", action="store_true")
|
sp.add_argument("--attach", action="store_true")
|
||||||
|
sp.add_argument("--headless", action="store_true",
|
||||||
|
help="Non-interactive: never prompt or attach; fail (exit 2) if the topic is in use")
|
||||||
|
sp.add_argument("--create", action="store_true",
|
||||||
|
help="With --headless: create the folder (absolute path) if it does not exist")
|
||||||
|
|
||||||
rsp = sub.add_parser("resume", help="Resume a past conversation by session ID")
|
rsp = sub.add_parser("resume", help="Resume a past conversation by session ID")
|
||||||
rsp.add_argument("session_id", help="Session UUID or prefix")
|
rsp.add_argument("session_id", help="Session UUID or prefix")
|
||||||
|
|||||||
Reference in New Issue
Block a user