@titan-design/active-work 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/aw.js +75 -32
- package/dist/aw.js.map +1 -1
- package/dist/{chunk-FM2KVFDO.js → chunk-BK25ASXA.js} +393 -232
- package/dist/chunk-BK25ASXA.js.map +1 -0
- package/dist/cli.js +3581 -1333
- package/dist/cli.js.map +1 -1
- package/dist/dashboard/index.html +13 -5
- package/docs/cli-reference.md +1090 -0
- package/package.json +21 -2
- package/scripts/gen-cli-reference.mjs +23 -2
- package/dist/chunk-FM2KVFDO.js.map +0 -1
package/README.md
CHANGED
|
@@ -106,7 +106,7 @@ See [`docs/architecture.md`](docs/architecture.md) for the full breakdown — la
|
|
|
106
106
|
|
|
107
107
|
## Commands
|
|
108
108
|
|
|
109
|
-
The most-used surface, grouped by purpose. Run `active-work <command> --help` for flags or
|
|
109
|
+
The most-used surface, grouped by purpose. Run `active-work <command> --help` for flags, or `active-work help --markdown` for the whole list. That same list ships as `docs/cli-reference.md` inside the published package; it is generated at release rather than committed, so it is not browsable in this repo.
|
|
110
110
|
|
|
111
111
|
| Group | Command | Purpose |
|
|
112
112
|
|---|---|---|
|
|
@@ -167,7 +167,7 @@ pnpm build # tsup CLI bundle + vite dashboard bundle
|
|
|
167
167
|
|
|
168
168
|
Node 22+, pnpm. The build produces `dist/cli.js` (single ESM bundle) and `dist/dashboard/index.html` (single-file React app).
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
The CLI reference is generated, gitignored, and written during release. Adding a command needs no doc commit. To read it locally:
|
|
171
171
|
|
|
172
172
|
```bash
|
|
173
173
|
pnpm build # ensure dist/cli.js is current
|
package/dist/aw.js
CHANGED
|
@@ -2,51 +2,29 @@
|
|
|
2
2
|
import {
|
|
3
3
|
EXIT,
|
|
4
4
|
acquireLease,
|
|
5
|
+
buildClaudeArgs,
|
|
5
6
|
color,
|
|
6
7
|
formatError,
|
|
7
8
|
getActiveRoot,
|
|
8
9
|
open_default,
|
|
10
|
+
parseLauncherFlags,
|
|
9
11
|
releaseLease,
|
|
10
|
-
releaseLeaseSync
|
|
11
|
-
|
|
12
|
+
releaseLeaseSync,
|
|
13
|
+
resolveLaunchCwd,
|
|
14
|
+
resume_default
|
|
15
|
+
} from "./chunk-BK25ASXA.js";
|
|
12
16
|
|
|
13
17
|
// src/aw.ts
|
|
14
18
|
import { spawn } from "child_process";
|
|
15
19
|
import * as clackPrompts from "@clack/prompts";
|
|
16
20
|
|
|
17
|
-
// src/launcher-args.ts
|
|
18
|
-
function buildChannelArgs(channels) {
|
|
19
|
-
if (!channels || channels.length === 0) return [];
|
|
20
|
-
const targets = channels.map((raw) => /^(server|plugin):/.test(raw) ? raw : `server:${raw}`);
|
|
21
|
-
const plugins = targets.filter((t) => t.startsWith("plugin:"));
|
|
22
|
-
const servers = targets.filter((t) => !t.startsWith("plugin:"));
|
|
23
|
-
return [
|
|
24
|
-
...plugins.length > 0 ? ["--channels", ...plugins] : [],
|
|
25
|
-
...servers.length > 0 ? ["--dangerously-load-development-channels", ...servers] : []
|
|
26
|
-
];
|
|
27
|
-
}
|
|
28
|
-
function buildClaudeArgs(prompt, channels) {
|
|
29
|
-
return [...buildChannelArgs(channels), "--", prompt];
|
|
30
|
-
}
|
|
31
|
-
var ADHOC_FLAGS = ["--adhoc", "--ad-hoc"];
|
|
32
|
-
function parseLauncherFlags(args) {
|
|
33
|
-
const known = /* @__PURE__ */ new Set(["--pick", ...ADHOC_FLAGS]);
|
|
34
|
-
const positional = args.filter((a) => !known.has(a));
|
|
35
|
-
return {
|
|
36
|
-
pick: args.includes("--pick"),
|
|
37
|
-
adhoc: args.some((a) => ADHOC_FLAGS.includes(a)),
|
|
38
|
-
positional,
|
|
39
|
-
usageError: positional.some((a) => a.startsWith("-")) || positional.length > 1
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
21
|
// src/launcher-lease.ts
|
|
44
22
|
var LEASE_ENV_VAR = "AW_LEASE_ID";
|
|
45
23
|
function buildLauncherEnv(base, leaseId) {
|
|
46
24
|
if (!leaseId) return { ...base };
|
|
47
25
|
return { ...base, [LEASE_ENV_VAR]: leaseId };
|
|
48
26
|
}
|
|
49
|
-
var CLEANUP_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
27
|
+
var CLEANUP_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
50
28
|
async function withLauncherLease(input, run) {
|
|
51
29
|
const { activeRoot, slug, cwd, pid = process.pid } = input;
|
|
52
30
|
let leaseId;
|
|
@@ -159,6 +137,62 @@ function spawnClaude(prompt, cwd, channels, leaseId) {
|
|
|
159
137
|
});
|
|
160
138
|
});
|
|
161
139
|
}
|
|
140
|
+
function spawnClaudeResume(sessionId, cwd) {
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
const child = spawn("claude", ["--resume", sessionId], {
|
|
143
|
+
cwd,
|
|
144
|
+
stdio: "inherit",
|
|
145
|
+
env: process.env
|
|
146
|
+
});
|
|
147
|
+
child.on("error", (err) => {
|
|
148
|
+
const e = err;
|
|
149
|
+
if (e.code === "ENOENT") {
|
|
150
|
+
process.stderr.write(color.red("error: `claude` not found on PATH.\n"));
|
|
151
|
+
resolve(127);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
process.stderr.write(color.red(`error: failed to launch claude: ${err.message}
|
|
155
|
+
`));
|
|
156
|
+
resolve(EXIT.GENERIC);
|
|
157
|
+
});
|
|
158
|
+
child.on("exit", (code, signal) => {
|
|
159
|
+
if (signal) {
|
|
160
|
+
resolve(128 + (signal === "SIGINT" ? 2 : 1));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
resolve(code ?? 0);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function runResume(argv) {
|
|
168
|
+
const rest = argv.slice(3);
|
|
169
|
+
if (rest.length !== 1 || rest[0].startsWith("-")) {
|
|
170
|
+
process.stderr.write(color.red("usage: aw resume <session_id>\n"));
|
|
171
|
+
process.exit(EXIT.USAGE);
|
|
172
|
+
}
|
|
173
|
+
const sessionId = rest[0];
|
|
174
|
+
const ctx = {
|
|
175
|
+
activeRoot: getActiveRoot(),
|
|
176
|
+
warnings: [],
|
|
177
|
+
format: "json",
|
|
178
|
+
cwd: process.cwd()
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
const parsed = resume_default.args.parse({ session_id: sessionId });
|
|
182
|
+
const resolved = await resume_default.run(parsed, ctx);
|
|
183
|
+
process.stderr.write(
|
|
184
|
+
color.dim(`Resuming ${sessionId} in ${resolved.cwd} (found via ${resolved.source}).
|
|
185
|
+
`)
|
|
186
|
+
);
|
|
187
|
+
const code = await spawnClaudeResume(sessionId, resolved.cwd);
|
|
188
|
+
process.exit(code);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
const { message, code } = formatError(err);
|
|
191
|
+
process.stderr.write(color.red(`error: ${message}
|
|
192
|
+
`));
|
|
193
|
+
process.exit(code);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
162
196
|
function printHelp() {
|
|
163
197
|
process.stdout.write(
|
|
164
198
|
[
|
|
@@ -173,6 +207,9 @@ function printHelp() {
|
|
|
173
207
|
" Frame the session as ad-hoc work on the workstream",
|
|
174
208
|
" (awaiting your task), not a handoff continuation.",
|
|
175
209
|
" `--ad-hoc` is accepted as an alias.",
|
|
210
|
+
" aw resume <session_id>",
|
|
211
|
+
" Find the directory a session id ran in (active-work's",
|
|
212
|
+
" session log, then ~/.claude/projects) and resume it there.",
|
|
176
213
|
" aw --help Show this message.",
|
|
177
214
|
" aw --version Print version.",
|
|
178
215
|
"",
|
|
@@ -191,6 +228,10 @@ async function main(argv) {
|
|
|
191
228
|
process.stdout.write("0.1.0\n");
|
|
192
229
|
process.exit(EXIT.OK);
|
|
193
230
|
}
|
|
231
|
+
if (args[0] === "resume") {
|
|
232
|
+
await runResume(argv);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
194
235
|
const { pick, adhoc, positional, usageError } = parseLauncherFlags(args);
|
|
195
236
|
if (usageError) {
|
|
196
237
|
process.stderr.write(
|
|
@@ -218,13 +259,15 @@ async function main(argv) {
|
|
|
218
259
|
} else {
|
|
219
260
|
opened = await runOpen({ slug: positional[0], adhoc });
|
|
220
261
|
}
|
|
262
|
+
const activeRoot = getActiveRoot();
|
|
263
|
+
const launchCwd = resolveLaunchCwd(activeRoot, opened.slug);
|
|
221
264
|
const code = await withLauncherLease(
|
|
222
265
|
{
|
|
223
|
-
activeRoot
|
|
266
|
+
activeRoot,
|
|
224
267
|
slug: opened.slug,
|
|
225
|
-
cwd:
|
|
268
|
+
cwd: launchCwd
|
|
226
269
|
},
|
|
227
|
-
(leaseId) => spawnClaude(opened.prompt,
|
|
270
|
+
(leaseId) => spawnClaude(opened.prompt, launchCwd, opened.channels, leaseId)
|
|
228
271
|
);
|
|
229
272
|
process.exit(code);
|
|
230
273
|
} catch (err) {
|
package/dist/aw.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/aw.ts","../src/launcher-args.ts","../src/launcher-lease.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `aw` — thin launcher that bootstraps a Claude Code session for an\n * active-work initiative.\n *\n * Usage:\n * aw [slug] Launch claude with the bootstrap prompt and the\n * initiative's worktree as cwd. Omit the slug to pick\n * one interactively.\n *\n * For the management CLI (new, task, focus, session, etc.), use\n * `active-work`. This launcher deliberately rejects sub-command\n * invocations so the two surfaces stay distinct.\n */\nimport { spawn } from 'node:child_process';\nimport * as clackPrompts from '@clack/prompts';\nimport openCommand from './commands/open.js';\nimport { buildClaudeArgs, parseLauncherFlags } from './launcher-args.js';\nimport { buildLauncherEnv, withLauncherLease } from './launcher-lease.js';\nimport { getActiveRoot } from './utils/paths.js';\nimport { formatError, EXIT } from './errors.js';\nimport { color } from './utils/color.js';\nimport type { CommandContext } from './registry/index.js';\n\ninterface InitiativeSummary {\n slug: string;\n title: string;\n state: 'focused' | 'backburner' | 'paused' | 'done';\n rank?: number;\n}\n\ninterface OpenSuccess {\n slug: string;\n prompt: string;\n cwd_hint: string;\n channels?: string[];\n resolved_from?: 'slug' | 'cwd';\n}\n\ninterface PickerResult {\n picker: true;\n initiatives: InitiativeSummary[];\n}\n\ntype OpenResult = OpenSuccess | PickerResult;\n\nasync function runOpen(\n opts: { slug?: string; pick?: boolean; adhoc?: boolean } = {},\n): Promise<OpenResult> {\n const ctx: CommandContext = {\n activeRoot: getActiveRoot(),\n warnings: [],\n format: 'json',\n cwd: process.cwd(),\n };\n const parsed = openCommand.args.parse({\n ...(opts.slug ? { slug: opts.slug } : {}),\n ...(opts.pick ? { pick: true } : {}),\n ...(opts.adhoc ? { adhoc: true } : {}),\n // This launcher records a `launcher` lease of its own around the spawned\n // session, so `open` must not also file a `oneshot` one — two leases for\n // one session make it its own sibling on the next bootstrap.\n lease_mode: 'defer' as const,\n });\n return (await openCommand.run(parsed, ctx)) as OpenResult;\n}\n\nconst STATE_LABEL: Record<InitiativeSummary['state'], string> = {\n focused: 'focused',\n backburner: 'backburner',\n paused: 'paused',\n done: 'done',\n};\n\nasync function pickInitiative(initiatives: InitiativeSummary[]): Promise<string | null> {\n if (initiatives.length === 0) {\n process.stderr.write(\n color.red('No initiatives found. Create one with `active-work new <slug>`.\\n'),\n );\n return null;\n }\n const choice = await clackPrompts.select({\n message: 'Pick an initiative to open',\n options: initiatives.map((i) => {\n const rank = i.rank !== undefined ? ` · rank ${i.rank}` : '';\n return {\n value: i.slug,\n label: `${i.title} (${i.slug})`,\n hint: `${STATE_LABEL[i.state]}${rank}`,\n };\n }),\n });\n if (clackPrompts.isCancel(choice)) return null;\n return String(choice);\n}\n\nfunction spawnClaude(\n prompt: string,\n cwd: string,\n channels?: string[],\n leaseId?: string,\n): Promise<number> {\n return new Promise((resolve) => {\n const child = spawn('claude', buildClaudeArgs(prompt, channels), {\n cwd,\n stdio: 'inherit',\n // Explicit env (the default is an implicit `process.env`) so the session\n // can recognize its own lease and not warn about itself.\n env: buildLauncherEnv(process.env, leaseId),\n });\n child.on('error', (err) => {\n const e = err as NodeJS.ErrnoException;\n if (e.code === 'ENOENT') {\n process.stderr.write(\n color.red(\n 'error: `claude` not found on PATH. Install Claude Code, ' +\n 'or run `active-work open <slug>` to print the prompt.\\n',\n ),\n );\n resolve(127);\n return;\n }\n process.stderr.write(color.red(`error: failed to launch claude: ${err.message}\\n`));\n resolve(EXIT.GENERIC);\n });\n child.on('exit', (code, signal) => {\n if (signal) {\n resolve(128 + (signal === 'SIGINT' ? 2 : 1));\n return;\n }\n resolve(code ?? 0);\n });\n });\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n [\n 'aw — launch a Claude session for an active-work initiative.',\n '',\n 'Usage:',\n ' aw [slug] Bootstrap and launch a Claude session for <slug>.',\n ' Omit slug to resolve the initiative from the current',\n ' directory, falling back to an interactive picker.',\n ' aw --pick Skip cwd resolution and always show the picker.',\n ' aw <slug> --adhoc',\n ' Frame the session as ad-hoc work on the workstream',\n ' (awaiting your task), not a handoff continuation.',\n ' `--ad-hoc` is accepted as an alias.',\n ' aw --help Show this message.',\n ' aw --version Print version.',\n '',\n 'For the full management CLI (new, task, focus, session, …) use `active-work`.',\n '',\n ].join('\\n'),\n );\n}\n\nexport async function main(argv: string[]): Promise<void> {\n const args = argv.slice(2);\n if (args.includes('--help') || args.includes('-h')) {\n printHelp();\n process.exit(EXIT.OK);\n }\n if (args.includes('--version') || args.includes('-V')) {\n process.stdout.write('0.1.0\\n');\n process.exit(EXIT.OK);\n }\n // `--pick` forces the interactive picker instead of resolving from cwd;\n // `--adhoc` (alias `--ad-hoc`) reframes the prompt as ad-hoc work.\n const { pick, adhoc, positional, usageError } = parseLauncherFlags(args);\n if (usageError) {\n process.stderr.write(\n color.red(\n 'error: `aw` only launches a Claude session for an initiative. ' +\n 'Use `active-work` for other commands.\\n',\n ),\n );\n process.exit(EXIT.USAGE);\n }\n\n try {\n let opened: OpenSuccess;\n if (positional.length === 0) {\n // No slug: `open` first tries to resolve the initiative from the\n // current directory (unless `--pick`). It returns the picker list only\n // when the cwd doesn't uniquely match a worktree.\n const result = await runOpen({ pick, adhoc });\n if ('picker' in result) {\n const choice = await pickInitiative(result.initiatives);\n if (!choice) {\n process.exit(EXIT.OK);\n }\n opened = (await runOpen({ slug: choice, adhoc })) as OpenSuccess;\n } else {\n opened = result;\n process.stderr.write(color.dim(`Opening ${opened.slug} — matched current directory.\\n`));\n }\n } else {\n opened = (await runOpen({ slug: positional[0], adhoc })) as OpenSuccess;\n }\n const code = await withLauncherLease(\n {\n activeRoot: getActiveRoot(),\n slug: opened.slug,\n cwd: opened.cwd_hint,\n },\n (leaseId) => spawnClaude(opened.prompt, opened.cwd_hint, opened.channels, leaseId),\n );\n process.exit(code);\n } catch (err) {\n const { message, code } = formatError(err);\n process.stderr.write(color.red(`error: ${message}\\n`));\n process.exit(code);\n }\n}\n\nvoid main(process.argv);\n","/**\n * Pure helpers for assembling the `claude` argv the `aw` launcher spawns.\n * Kept side-effect-free (no top-level `main()`) so they are unit-testable\n * without executing the launcher on import.\n */\n\n/**\n * Build the channel flags for an initiative's MCP push channels.\n *\n * Each frontmatter `channels` entry is a target: an explicit\n * `server:<name>` / `plugin:<name>@<marketplace>`, or a bare server name that\n * is normalized to `server:<name>`.\n *\n * The two target kinds take different flags, and the choice is not cosmetic.\n * Claude Code's channel gate only has an allowlist path for plugin targets:\n * `plugin:` entries go under `--channels`, which is satisfied by an\n * `allowedChannelPlugins` entry in managed settings, while `server:` entries\n * have no allowlist path at all and must use the dev flag. Routing a properly\n * allowlisted plugin through the dev flag still works, but re-triggers the\n * development-channels dialog that packaging it as a plugin exists to avoid.\n *\n * Both flags are variadic, so callers must keep the prompt behind a `--`.\n */\nexport function buildChannelArgs(channels: string[] | undefined): string[] {\n if (!channels || channels.length === 0) return [];\n const targets = channels.map((raw) => (/^(server|plugin):/.test(raw) ? raw : `server:${raw}`));\n const plugins = targets.filter((t) => t.startsWith('plugin:'));\n const servers = targets.filter((t) => !t.startsWith('plugin:'));\n return [\n ...(plugins.length > 0 ? ['--channels', ...plugins] : []),\n ...(servers.length > 0 ? ['--dangerously-load-development-channels', ...servers] : []),\n ];\n}\n\n/**\n * Assemble the full `claude` argv. The prompt always follows a `--` so the\n * variadic channel flags can never swallow it as a channel target — the bug\n * that made `aw <slug>` collide the channel name with the bootstrap prompt.\n */\nexport function buildClaudeArgs(prompt: string, channels?: string[]): string[] {\n return [...buildChannelArgs(channels), '--', prompt];\n}\n\n/**\n * `--adhoc` is the canonical spelling; `--ad-hoc` is accepted as an alias so\n * the natural hyphenated form doesn't trip the unknown-flag guard below.\n */\nexport const ADHOC_FLAGS = ['--adhoc', '--ad-hoc'];\n\nexport interface LauncherFlags {\n pick: boolean;\n adhoc: boolean;\n positional: string[];\n /** True when a positional looks like an unknown flag, or more than one slug was given. */\n usageError: boolean;\n}\n\n/**\n * Parse the `aw` launcher flags out of `argv.slice(2)`. Pure and\n * side-effect-free so the flag handling — including the `--adhoc` / `--ad-hoc`\n * alias — is unit-testable without executing the launcher.\n */\nexport function parseLauncherFlags(args: string[]): LauncherFlags {\n const known = new Set(['--pick', ...ADHOC_FLAGS]);\n const positional = args.filter((a) => !known.has(a));\n return {\n pick: args.includes('--pick'),\n adhoc: args.some((a) => ADHOC_FLAGS.includes(a)),\n positional,\n usageError: positional.some((a) => a.startsWith('-')) || positional.length > 1,\n };\n}\n","/**\n * Lease lifecycle for the `aw` launcher.\n *\n * Kept out of `src/aw.ts` because that module calls `main()` on import and so\n * cannot be pulled into a unit test — the same reason `launcher-args.ts` and\n * `registry/cli-options.ts` exist.\n *\n * `aw` spawns `claude` with `stdio: 'inherit'` and awaits its exit, so the\n * launcher's own process lifetime brackets the session exactly. That is what\n * makes a `launcher` lease a fact rather than a guess — and it is also what\n * makes cleanup fiddly: an inherited stdio child shares the foreground process\n * group, so Ctrl-C is delivered to `aw` as well, and `aw` may be gone before\n * any promise-based cleanup gets a turn. Hence three layers:\n *\n * 1. the normal path — `finally`, async unlink after the child exits;\n * 2. signals — release synchronously, then re-raise so the default action\n * still terminates the process (installing a handler suppresses it);\n * 3. `process.on('exit')` — the last-resort synchronous sweep, which is the\n * only thing that runs under `process.exit()` from an error path.\n *\n * Every layer tolerates the lease already being gone.\n */\nimport { acquireLease, releaseLease, releaseLeaseSync } from './sessions/lease.js';\n\n/** Env var carrying this session's lease id into the spawned `claude`. */\nexport const LEASE_ENV_VAR = 'AW_LEASE_ID';\n\n/**\n * The child's environment, with the lease id added.\n *\n * The child is the Claude session this lease describes, so any `open` /\n * `prompt` run *inside* it must exclude this lease rather than report the\n * session as its own sibling.\n */\nexport function buildLauncherEnv(\n base: NodeJS.ProcessEnv,\n leaseId: string | undefined,\n): NodeJS.ProcessEnv {\n if (!leaseId) return { ...base };\n return { ...base, [LEASE_ENV_VAR]: leaseId };\n}\n\nconst CLEANUP_SIGNALS: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];\n\nexport interface LauncherLeaseInput {\n activeRoot: string;\n slug: string;\n cwd: string;\n /** Defaults to this process — the one whose lifetime brackets the session. */\n pid?: number;\n}\n\n/**\n * Hold a `launcher` lease for the duration of `run`.\n *\n * A lease that cannot be written is not an error: `run` is invoked with\n * `undefined` and the session proceeds without a lease. The warning it would\n * have produced is advisory, and refusing to launch over it would be absurd.\n */\nexport async function withLauncherLease<T>(\n input: LauncherLeaseInput,\n run: (leaseId: string | undefined) => Promise<T>,\n): Promise<T> {\n const { activeRoot, slug, cwd, pid = process.pid } = input;\n let leaseId: string | undefined;\n try {\n ({ leaseId } = await acquireLease({\n activeRoot,\n slug,\n cwd,\n mode: 'launcher',\n pid,\n }));\n } catch {\n return run(undefined);\n }\n\n const id = leaseId;\n const onExit = (): void => releaseLeaseSync(activeRoot, slug, id);\n const onSignal = (signal: NodeJS.Signals): void => {\n releaseLeaseSync(activeRoot, slug, id);\n // Restore the default action and re-raise: swallowing the signal here would\n // leave `aw` alive after a Ctrl-C that already killed the session below it.\n process.removeListener(signal, onSignal);\n process.kill(process.pid, signal);\n };\n\n process.on('exit', onExit);\n for (const signal of CLEANUP_SIGNALS) process.on(signal, onSignal);\n\n try {\n return await run(id);\n } finally {\n process.removeListener('exit', onExit);\n for (const signal of CLEANUP_SIGNALS) process.removeListener(signal, onSignal);\n await releaseLease(activeRoot, slug, id).catch(() => {});\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/aw.ts","../src/launcher-lease.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `aw` — thin launcher that bootstraps a Claude Code session for an\n * active-work initiative.\n *\n * Usage:\n * aw [slug] Launch claude with the bootstrap prompt and the\n * initiative's own directory under the active root as cwd.\n * Omit the slug to pick one interactively.\n *\n * For the management CLI (new, task, focus, session, etc.), use\n * `active-work`. This launcher deliberately rejects sub-command\n * invocations so the two surfaces stay distinct.\n */\nimport { spawn } from 'node:child_process';\nimport * as clackPrompts from '@clack/prompts';\nimport openCommand from './commands/open.js';\nimport resumeCommand from './commands/resume.js';\nimport { resolveLaunchCwd } from './commands/_open-helpers.js';\nimport { buildClaudeArgs, parseLauncherFlags } from './launcher-args.js';\nimport { buildLauncherEnv, withLauncherLease } from './launcher-lease.js';\nimport { getActiveRoot } from './utils/paths.js';\nimport { formatError, EXIT } from './errors.js';\nimport { color } from './utils/color.js';\nimport type { CommandContext } from './registry/index.js';\n\ninterface InitiativeSummary {\n slug: string;\n title: string;\n state: 'focused' | 'backburner' | 'paused' | 'done';\n rank?: number;\n}\n\ninterface OpenSuccess {\n slug: string;\n prompt: string;\n cwd_hint: string;\n channels?: string[];\n resolved_from?: 'slug' | 'cwd';\n}\n\ninterface PickerResult {\n picker: true;\n initiatives: InitiativeSummary[];\n}\n\ntype OpenResult = OpenSuccess | PickerResult;\n\nasync function runOpen(\n opts: { slug?: string; pick?: boolean; adhoc?: boolean } = {},\n): Promise<OpenResult> {\n const ctx: CommandContext = {\n activeRoot: getActiveRoot(),\n warnings: [],\n format: 'json',\n cwd: process.cwd(),\n };\n const parsed = openCommand.args.parse({\n ...(opts.slug ? { slug: opts.slug } : {}),\n ...(opts.pick ? { pick: true } : {}),\n ...(opts.adhoc ? { adhoc: true } : {}),\n // This launcher records a `launcher` lease of its own around the spawned\n // session, so `open` must not also file a `oneshot` one — two leases for\n // one session make it its own sibling on the next bootstrap.\n lease_mode: 'defer' as const,\n });\n return (await openCommand.run(parsed, ctx)) as OpenResult;\n}\n\nconst STATE_LABEL: Record<InitiativeSummary['state'], string> = {\n focused: 'focused',\n backburner: 'backburner',\n paused: 'paused',\n done: 'done',\n};\n\nasync function pickInitiative(initiatives: InitiativeSummary[]): Promise<string | null> {\n if (initiatives.length === 0) {\n process.stderr.write(\n color.red('No initiatives found. Create one with `active-work new <slug>`.\\n'),\n );\n return null;\n }\n const choice = await clackPrompts.select({\n message: 'Pick an initiative to open',\n options: initiatives.map((i) => {\n const rank = i.rank !== undefined ? ` · rank ${i.rank}` : '';\n return {\n value: i.slug,\n label: `${i.title} (${i.slug})`,\n hint: `${STATE_LABEL[i.state]}${rank}`,\n };\n }),\n });\n if (clackPrompts.isCancel(choice)) return null;\n return String(choice);\n}\n\nfunction spawnClaude(\n prompt: string,\n cwd: string,\n channels?: string[],\n leaseId?: string,\n): Promise<number> {\n return new Promise((resolve) => {\n const child = spawn('claude', buildClaudeArgs(prompt, channels), {\n cwd,\n stdio: 'inherit',\n // Explicit env (the default is an implicit `process.env`) so the session\n // can recognize its own lease and not warn about itself.\n env: buildLauncherEnv(process.env, leaseId),\n });\n child.on('error', (err) => {\n const e = err as NodeJS.ErrnoException;\n if (e.code === 'ENOENT') {\n process.stderr.write(\n color.red(\n 'error: `claude` not found on PATH. Install Claude Code, ' +\n 'or run `active-work open <slug>` to print the prompt.\\n',\n ),\n );\n resolve(127);\n return;\n }\n process.stderr.write(color.red(`error: failed to launch claude: ${err.message}\\n`));\n resolve(EXIT.GENERIC);\n });\n child.on('exit', (code, signal) => {\n if (signal) {\n resolve(128 + (signal === 'SIGINT' ? 2 : 1));\n return;\n }\n resolve(code ?? 0);\n });\n });\n}\n\nfunction spawnClaudeResume(sessionId: string, cwd: string): Promise<number> {\n return new Promise((resolve) => {\n const child = spawn('claude', ['--resume', sessionId], {\n cwd,\n stdio: 'inherit',\n env: process.env,\n });\n child.on('error', (err) => {\n const e = err as NodeJS.ErrnoException;\n if (e.code === 'ENOENT') {\n process.stderr.write(color.red('error: `claude` not found on PATH.\\n'));\n resolve(127);\n return;\n }\n process.stderr.write(color.red(`error: failed to launch claude: ${err.message}\\n`));\n resolve(EXIT.GENERIC);\n });\n child.on('exit', (code, signal) => {\n if (signal) {\n resolve(128 + (signal === 'SIGINT' ? 2 : 1));\n return;\n }\n resolve(code ?? 0);\n });\n });\n}\n\n/**\n * `aw resume <session_id>` — find the directory a session id belongs to\n * (active-work's own session log, then a direct match under\n * `~/.claude/projects`) and launch `claude --resume` there, so the caller\n * doesn't need to already know or remember where the session ran.\n */\nasync function runResume(argv: string[]): Promise<void> {\n const rest = argv.slice(3);\n if (rest.length !== 1 || rest[0]!.startsWith('-')) {\n process.stderr.write(color.red('usage: aw resume <session_id>\\n'));\n process.exit(EXIT.USAGE);\n }\n const sessionId = rest[0]!;\n const ctx: CommandContext = {\n activeRoot: getActiveRoot(),\n warnings: [],\n format: 'json',\n cwd: process.cwd(),\n };\n try {\n const parsed = resumeCommand.args.parse({ session_id: sessionId });\n const resolved = (await resumeCommand.run(parsed, ctx)) as { cwd: string; source: string };\n process.stderr.write(\n color.dim(`Resuming ${sessionId} in ${resolved.cwd} (found via ${resolved.source}).\\n`),\n );\n const code = await spawnClaudeResume(sessionId, resolved.cwd);\n process.exit(code);\n } catch (err) {\n const { message, code } = formatError(err);\n process.stderr.write(color.red(`error: ${message}\\n`));\n process.exit(code);\n }\n}\n\nfunction printHelp(): void {\n process.stdout.write(\n [\n 'aw — launch a Claude session for an active-work initiative.',\n '',\n 'Usage:',\n ' aw [slug] Bootstrap and launch a Claude session for <slug>.',\n ' Omit slug to resolve the initiative from the current',\n ' directory, falling back to an interactive picker.',\n ' aw --pick Skip cwd resolution and always show the picker.',\n ' aw <slug> --adhoc',\n ' Frame the session as ad-hoc work on the workstream',\n ' (awaiting your task), not a handoff continuation.',\n ' `--ad-hoc` is accepted as an alias.',\n ' aw resume <session_id>',\n \" Find the directory a session id ran in (active-work's\",\n ' session log, then ~/.claude/projects) and resume it there.',\n ' aw --help Show this message.',\n ' aw --version Print version.',\n '',\n 'For the full management CLI (new, task, focus, session, …) use `active-work`.',\n '',\n ].join('\\n'),\n );\n}\n\nexport async function main(argv: string[]): Promise<void> {\n const args = argv.slice(2);\n if (args.includes('--help') || args.includes('-h')) {\n printHelp();\n process.exit(EXIT.OK);\n }\n if (args.includes('--version') || args.includes('-V')) {\n process.stdout.write('0.1.0\\n');\n process.exit(EXIT.OK);\n }\n if (args[0] === 'resume') {\n await runResume(argv);\n return;\n }\n // `--pick` forces the interactive picker instead of resolving from cwd;\n // `--adhoc` (alias `--ad-hoc`) reframes the prompt as ad-hoc work.\n const { pick, adhoc, positional, usageError } = parseLauncherFlags(args);\n if (usageError) {\n process.stderr.write(\n color.red(\n 'error: `aw` only launches a Claude session for an initiative. ' +\n 'Use `active-work` for other commands.\\n',\n ),\n );\n process.exit(EXIT.USAGE);\n }\n\n try {\n let opened: OpenSuccess;\n if (positional.length === 0) {\n // No slug: `open` first tries to resolve the initiative from the\n // current directory (unless `--pick`). It returns the picker list only\n // when the cwd doesn't uniquely match a worktree.\n const result = await runOpen({ pick, adhoc });\n if ('picker' in result) {\n const choice = await pickInitiative(result.initiatives);\n if (!choice) {\n process.exit(EXIT.OK);\n }\n opened = (await runOpen({ slug: choice, adhoc })) as OpenSuccess;\n } else {\n opened = result;\n process.stderr.write(color.dim(`Opening ${opened.slug} — matched current directory.\\n`));\n }\n } else {\n opened = (await runOpen({ slug: positional[0], adhoc })) as OpenSuccess;\n }\n // Not `opened.cwd_hint`: that is the dispatch answer (a registered\n // worktree, for agents that must commit). An operator's session belongs in\n // the initiative's own directory — see resolveLaunchCwd (AW-115).\n const activeRoot = getActiveRoot();\n const launchCwd = resolveLaunchCwd(activeRoot, opened.slug);\n const code = await withLauncherLease(\n {\n activeRoot,\n slug: opened.slug,\n cwd: launchCwd,\n },\n (leaseId) => spawnClaude(opened.prompt, launchCwd, opened.channels, leaseId),\n );\n process.exit(code);\n } catch (err) {\n const { message, code } = formatError(err);\n process.stderr.write(color.red(`error: ${message}\\n`));\n process.exit(code);\n }\n}\n\nvoid main(process.argv);\n","/**\n * Lease lifecycle for the `aw` launcher.\n *\n * Kept out of `src/aw.ts` because that module calls `main()` on import and so\n * cannot be pulled into a unit test — the same reason `launcher-args.ts` and\n * the registry package's own CLI helpers exist.\n *\n * `aw` spawns `claude` with `stdio: 'inherit'` and awaits its exit, so the\n * launcher's own process lifetime brackets the session exactly. That is what\n * makes a `launcher` lease a fact rather than a guess — and it is also what\n * makes cleanup fiddly: an inherited stdio child shares the foreground process\n * group, so Ctrl-C is delivered to `aw` as well, and `aw` may be gone before\n * any promise-based cleanup gets a turn. Hence three layers:\n *\n * 1. the normal path — `finally`, async unlink after the child exits;\n * 2. signals — release synchronously, then re-raise so the default action\n * still terminates the process (installing a handler suppresses it);\n * 3. `process.on('exit')` — the last-resort synchronous sweep, which is the\n * only thing that runs under `process.exit()` from an error path.\n *\n * Every layer tolerates the lease already being gone.\n */\nimport { acquireLease, releaseLease, releaseLeaseSync } from './sessions/lease.js';\n\n/** Env var carrying this session's lease id into the spawned `claude`. */\nexport const LEASE_ENV_VAR = 'AW_LEASE_ID';\n\n/**\n * The child's environment, with the lease id added.\n *\n * The child is the Claude session this lease describes, so any `open` /\n * `prompt` run *inside* it must exclude this lease rather than report the\n * session as its own sibling.\n */\nexport function buildLauncherEnv(\n base: NodeJS.ProcessEnv,\n leaseId: string | undefined,\n): NodeJS.ProcessEnv {\n if (!leaseId) return { ...base };\n return { ...base, [LEASE_ENV_VAR]: leaseId };\n}\n\n// SIGHUP is what a closed terminal/iTerm pane sends the foreground process\n// group. Without a listener here, the default disposition kills `aw` before\n// the event loop gets a turn — 'exit' never fires for that abrupt a death —\n// so the lease is orphaned. Its pid then keeps warning future sessions of a\n// \"live\" sibling until LAUNCHER_MAX_AGE_MS elapses or the pid gets recycled\n// by an unrelated process (see readLiveLeases in sessions/lease.ts).\nconst CLEANUP_SIGNALS: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGHUP'];\n\nexport interface LauncherLeaseInput {\n activeRoot: string;\n slug: string;\n cwd: string;\n /** Defaults to this process — the one whose lifetime brackets the session. */\n pid?: number;\n}\n\n/**\n * Hold a `launcher` lease for the duration of `run`.\n *\n * A lease that cannot be written is not an error: `run` is invoked with\n * `undefined` and the session proceeds without a lease. The warning it would\n * have produced is advisory, and refusing to launch over it would be absurd.\n */\nexport async function withLauncherLease<T>(\n input: LauncherLeaseInput,\n run: (leaseId: string | undefined) => Promise<T>,\n): Promise<T> {\n const { activeRoot, slug, cwd, pid = process.pid } = input;\n let leaseId: string | undefined;\n try {\n ({ leaseId } = await acquireLease({\n activeRoot,\n slug,\n cwd,\n mode: 'launcher',\n pid,\n }));\n } catch {\n return run(undefined);\n }\n\n const id = leaseId;\n const onExit = (): void => releaseLeaseSync(activeRoot, slug, id);\n const onSignal = (signal: NodeJS.Signals): void => {\n releaseLeaseSync(activeRoot, slug, id);\n // Restore the default action and re-raise: swallowing the signal here would\n // leave `aw` alive after a Ctrl-C that already killed the session below it.\n process.removeListener(signal, onSignal);\n process.kill(process.pid, signal);\n };\n\n process.on('exit', onExit);\n for (const signal of CLEANUP_SIGNALS) process.on(signal, onSignal);\n\n try {\n return await run(id);\n } finally {\n process.removeListener('exit', onExit);\n for (const signal of CLEANUP_SIGNALS) process.removeListener(signal, onSignal);\n await releaseLease(activeRoot, slug, id).catch(() => {});\n }\n}\n"],"mappings":"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","names":[]}
|