@titan-design/active-work 0.1.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.
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Generate `docs/cli-reference.md` by walking `active-work --help` and capturing
4
+ * `--help` output for every leaf sub-command.
5
+ *
6
+ * Usage:
7
+ * node scripts/gen-cli-reference.mjs # writes docs/cli-reference.md
8
+ * node scripts/gen-cli-reference.mjs --stdout # prints to stdout
9
+ *
10
+ * Requires `pnpm build` to have run so `dist/cli.js` exists.
11
+ */
12
+ import { spawnSync } from 'node:child_process';
13
+ import { writeFileSync, existsSync } from 'node:fs';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { dirname, resolve } from 'node:path';
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const REPO_ROOT = resolve(__dirname, '..');
19
+ const CLI = resolve(REPO_ROOT, 'dist/cli.js');
20
+ const OUT = resolve(REPO_ROOT, 'docs/cli-reference.md');
21
+
22
+ if (!existsSync(CLI)) {
23
+ console.error(`error: ${CLI} not found. Run \`pnpm build\` first.`);
24
+ process.exit(1);
25
+ }
26
+
27
+ /** Run the built CLI and return its stdout (help is written to stdout by commander). */
28
+ function runHelp(args) {
29
+ const result = spawnSync(process.execPath, [CLI, ...args, '--help'], {
30
+ encoding: 'utf8',
31
+ env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },
32
+ });
33
+ if (result.status !== 0 && result.status !== null) {
34
+ // commander exits 0 for help; treat anything else as a soft warning.
35
+ process.stderr.write(
36
+ `warn: \`active-work ${args.join(' ')} --help\` exited ${result.status}\n${result.stderr}\n`,
37
+ );
38
+ }
39
+ return (result.stdout || '').trimEnd();
40
+ }
41
+
42
+ /**
43
+ * Parse a `Commands:` block out of a help blob. Each entry is one or more
44
+ * lines: the first starts with two-space indent, then the command name,
45
+ * then a description that may wrap to subsequent indented lines.
46
+ *
47
+ * Returns an array of `{ name, description }`. Sub-command groups (their
48
+ * description is exactly `<name> commands`) are flagged via `isGroup`.
49
+ */
50
+ function parseCommands(helpText) {
51
+ const lines = helpText.split('\n');
52
+ const startIdx = lines.findIndex((l) => l.trim() === 'Commands:');
53
+ if (startIdx === -1) return [];
54
+
55
+ const entries = [];
56
+ let current = null;
57
+ for (let i = startIdx + 1; i < lines.length; i++) {
58
+ const line = lines[i];
59
+ if (line.trim() === '') {
60
+ if (current) {
61
+ entries.push(current);
62
+ current = null;
63
+ }
64
+ continue;
65
+ }
66
+ // Lines starting with two spaces and a letter begin a new command;
67
+ // subsequent indented continuation lines extend the description.
68
+ const match = line.match(/^ {2}([a-z][a-z0-9-]*)(?:\s+\[options])?(?:\s+[^ ].*?)?\s{2,}(.*)$/);
69
+ if (match) {
70
+ if (current) entries.push(current);
71
+ current = { name: match[1], description: match[2].trim() };
72
+ } else if (current && /^\s{4,}/.test(line)) {
73
+ current.description += ' ' + line.trim();
74
+ }
75
+ }
76
+ if (current) entries.push(current);
77
+
78
+ return entries
79
+ .filter((e) => e.name !== 'help')
80
+ .map((e) => ({
81
+ ...e,
82
+ isGroup: /^[a-z]+ commands$/.test(e.description),
83
+ }));
84
+ }
85
+
86
+ /**
87
+ * Walk help recursively to enumerate every leaf command path.
88
+ * A leaf is any command whose own help has no `Commands:` section
89
+ * (or only `help` as a sub-command).
90
+ */
91
+ function enumerateLeaves(prefix = []) {
92
+ const help = runHelp(prefix);
93
+ const cmds = parseCommands(help);
94
+ if (cmds.length === 0) {
95
+ // Leaf — return the prefix itself (or empty for root with no children).
96
+ return prefix.length === 0 ? [] : [prefix];
97
+ }
98
+ const leaves = [];
99
+ for (const cmd of cmds) {
100
+ leaves.push(...enumerateLeaves([...prefix, cmd.name]));
101
+ }
102
+ return leaves;
103
+ }
104
+
105
+ function renderReference() {
106
+ const header = `# CLI reference
107
+
108
+ Generated from \`active-work --help\` and individual command \`--help\` outputs.
109
+ Re-run \`node scripts/gen-cli-reference.mjs\` to refresh after the CLI
110
+ surface changes.
111
+
112
+ `;
113
+
114
+ const rootHelp = runHelp([]);
115
+ const sections = [
116
+ `## active-work\n\nTop-level help. Run \`active-work <command> --help\` for command-specific options.\n\n\`\`\`\n${rootHelp}\n\`\`\`\n`,
117
+ ];
118
+
119
+ const leaves = enumerateLeaves();
120
+ // Sort alphabetically by joined path.
121
+ leaves.sort((a, b) => a.join(' ').localeCompare(b.join(' ')));
122
+
123
+ for (const leaf of leaves) {
124
+ const path = leaf.join(' ');
125
+ const help = runHelp(leaf);
126
+ sections.push(`## active-work ${path}\n\n\`\`\`\n${help}\n\`\`\`\n`);
127
+ }
128
+
129
+ return header + sections.join('\n');
130
+ }
131
+
132
+ const out = renderReference();
133
+
134
+ if (process.argv.includes('--stdout')) {
135
+ process.stdout.write(out);
136
+ } else {
137
+ writeFileSync(OUT, out, 'utf8');
138
+ process.stderr.write(`wrote ${OUT}\n`);
139
+ }
@@ -0,0 +1,48 @@
1
+ // Copy the bundled Claude Code skill into ~/.claude/skills/active-work/ and the
2
+ // /aw-prompt slash command into ~/.claude/commands/ when ~/.claude exists. Skip
3
+ // silently otherwise. Fail-soft: never abort an npm install if a copy fails.
4
+ import { existsSync, mkdirSync, cpSync, copyFileSync, rmSync } from 'node:fs';
5
+ import { join, dirname } from 'node:path';
6
+ import { homedir } from 'node:os';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const skillSource = join(__dirname, '..', 'skill');
12
+ const commandSource = join(__dirname, '..', 'claude-commands', 'aw-prompt.md');
13
+ const claudeDir = join(homedir(), '.claude');
14
+ const claudeSkillsDir = join(claudeDir, 'skills', 'active-work');
15
+ const claudeCommandsDir = join(claudeDir, 'commands');
16
+
17
+ if (!existsSync(claudeDir)) {
18
+ // User doesn't have Claude Code installed — skip silently.
19
+ process.exit(0);
20
+ }
21
+
22
+ // Skill and command installs are independent — a missing source for one must
23
+ // not skip the other.
24
+ if (existsSync(skillSource)) {
25
+ try {
26
+ // Remove existing install to ensure a clean copy.
27
+ if (existsSync(claudeSkillsDir)) {
28
+ rmSync(claudeSkillsDir, { recursive: true, force: true });
29
+ }
30
+ mkdirSync(claudeSkillsDir, { recursive: true });
31
+ cpSync(skillSource, claudeSkillsDir, { recursive: true });
32
+ console.log(`active-work: installed Claude Code skill to ${claudeSkillsDir}`);
33
+ } catch (err) {
34
+ // Don't fail npm install if skill copy fails.
35
+ console.error(`active-work: skill install skipped (${err.message})`);
36
+ }
37
+ }
38
+
39
+ try {
40
+ if (existsSync(commandSource)) {
41
+ mkdirSync(claudeCommandsDir, { recursive: true });
42
+ copyFileSync(commandSource, join(claudeCommandsDir, 'aw-prompt.md'));
43
+ console.log(`active-work: installed /aw-prompt command to ${claudeCommandsDir}`);
44
+ }
45
+ } catch (err) {
46
+ // Don't fail npm install if the command copy fails.
47
+ console.error(`active-work: command install skipped (${err.message})`);
48
+ }
@@ -0,0 +1,15 @@
1
+ // Remove ~/.claude/skills/active-work/ on package removal. Fail-soft.
2
+ import { existsSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+
6
+ const claudeSkillsDir = join(homedir(), '.claude', 'skills', 'active-work');
7
+
8
+ try {
9
+ if (existsSync(claudeSkillsDir)) {
10
+ rmSync(claudeSkillsDir, { recursive: true, force: true });
11
+ console.log(`active-work: removed Claude Code skill from ${claudeSkillsDir}`);
12
+ }
13
+ } catch (err) {
14
+ console.error(`active-work: skill removal skipped (${err.message})`);
15
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: active-work
3
+ description: |
4
+ Maintain durable per-initiative workspace state (brief, handoff, tasks, sessions, artifacts) so engineering work picks up cleanly across Claude Code sessions. Use when the user mentions: "active work", "what am I working on", "bootstrap session", "new initiative", "record this session", "update handoff", "archive initiative", "check active", "audit my workstreams", "find untracked work", "set up active-work", "add a task", "mark X done", "what's blocking me", "wrap up", "I'm done", or types /active-work.
5
+ ---
6
+
7
+ # active-work — durable workspace state across Claude sessions
8
+
9
+ `active-work` keeps a small filesystem-backed record for every engineering initiative the user has in flight. Files live under `$XDG_DATA_HOME/active-work/<slug>/` and include `brief.md` (frontmatter + prose), `handoff.md` (free prose), `tasks/*.yml`, `sessions/*.md`, `artifacts.yml`, and `sources/`. The CLI is `active-work`; `aw <slug>` is a thin launcher that bootstraps a Claude session for an initiative. A long-running daemon, `active-work mcp serve`, exposes MCP tools to Claude Code over HTTP and serves a read-only dashboard at `http://127.0.0.1:7400/ui`.
10
+
11
+ ## When to engage
12
+
13
+ Engage whenever the user signals they want to inspect, mutate, or hand off persistent workspace state. Trigger phrases:
14
+
15
+ - "active work" / "what am I working on" — list initiatives, surface current focus
16
+ - "bootstrap session" / `/active-work` — load the bootstrap prompt for an initiative
17
+ - "new initiative" — scaffold a slug with `active-work new`
18
+ - "add a task" / "mark X done" / "what's blocking me" — task ops via `active-work task`
19
+ - "record this session" / "update handoff" / "wrap up" / "I'm done" — session capture + handoff
20
+ - "archive initiative" — move a slug to the archived state
21
+ - "check active" / "audit my workstreams" — `active-work audit` health check
22
+ - "find untracked work" — `active-work discover` across configured sources
23
+ - "set up active-work" — first-time install / `active-work setup`
24
+
25
+ ## Core rules
26
+
27
+ 1. **Edits route through the CLI.** Use `active-work new`, `active-work set`, `active-work task add`, `active-work task done`, `active-work artifact add`, etc. Direct `Edit`/`Write` on `tasks/*.yml`, `artifacts.yml`, or the frontmatter of `brief.md` bypasses validation and corrupts the schema. Prose bodies (`brief.md` body, `handoff.md`, session summaries) can be edited directly, but prefer `active-work edit` for `brief.md` because it re-validates frontmatter on save.
28
+ 2. **LLM writes prose; CLI handles structure.** Task ordering, session filenames, frontmatter dates, slug normalization, and rank reflow are CLI primitives. Don't compute them yourself.
29
+ 3. **`active-work --help` is the canonical command reference.** This skill intentionally doesn't duplicate the surface; run `active-work --help` or `active-work <command> --help` when you need flags.
30
+ 4. **Session capture at end.** When wrapping up, run `active-work session record <slug>` with a 3-5 bullet summary of what happened, what changed, and what's next. Auto-prompt this when you detect the user winding down ("I'm done", "let's stop", "wrap up", inactivity after a chunk of work).
31
+ 5. **`active-work mcp status` first.** If MCP tools aren't responding, the daemon may not be running. Start it with `active-work mcp serve --detach` before retrying.
32
+
33
+ ## Bootstrap flow (`aw <slug>` / `active-work open <slug>`)
34
+
35
+ `aw <slug>` is the operator-facing launcher: it assembles the bootstrap prompt and execs `claude` with the initiative's worktree as cwd. Omit the slug and it resolves the initiative from the caller's cwd (matching against each brief's registered worktrees), falling back to the interactive picker when nothing matches uniquely; `aw --pick` forces the picker. (Register a worktree so this resolution works with `active-work worktree set <slug> <path>`, or at creation via `new --worktree` / `track --worktree`.) `active-work open <slug>` is the same assembly logic, but prints the prompt to stdout instead of spawning Claude — use it from MCP / scripts / any caller that wants to handle the spawn itself (pass `--cwd <dir>` when the caller's process cwd isn't the user's shell cwd, e.g. the daemon). The bootstrap prompt inlines:
36
+
37
+ - The full `handoff.md` text
38
+ - A brief excerpt (frontmatter summary + first prose paragraph)
39
+ - The most recent session summary
40
+ - The top N open tasks (rank-sorted)
41
+ - Open artifacts with status
42
+ - Time since the last session
43
+
44
+ To re-seed context **mid-session** (a session that wasn't started via `aw`, or one that has drifted), run `active-work prompt` — it prints the same bootstrap prompt to stdout, cwd-resolved and side-effect-free (no auto-archive). The bundled `/aw-prompt` slash command wraps it and injects the output straight into the session.
45
+
46
+ Because handoff and brief excerpt are already in your context, **do not re-read `brief.md` or `handoff.md`** at the top of the session. Jump straight to the highest-rank open task unless the user redirects you. If the user opens a slug without further instruction, ask "continue with `<top task title>`?" and proceed on confirmation.
47
+
48
+ **Ad-hoc sessions** (`aw <slug> --adhoc`, also `open`/`prompt --adhoc`): the opening and closing directives change to say the session is scoped to ad-hoc work on the workstream — the context is background, *not* a directive. Do **not** offer to continue the top task; wait for the user to describe the specific ad-hoc task, then work it with the workstream context in mind. The bootstrap prompt itself carries this instruction, so follow whichever framing it renders.
49
+
50
+ ## Reference docs
51
+
52
+ - [onboarding.md](references/onboarding.md) — first-time setup walkthrough
53
+ - [auditing-existing-work.md](references/auditing-existing-work.md) — discover + triage flow for catching up on untracked work
54
+ - [cli-dev.md](references/cli-dev.md) — internal architecture for skill maintainers and CLI contributors
@@ -0,0 +1,125 @@
1
+ # Auditing existing work — discover + triage
2
+
3
+ If the user has a pile of in-flight work but nothing tracked in `active-work` (or only partially tracked), this walkthrough drives them through discovery and triage in three categories: **Track**, **Fold**, **Drop**.
4
+
5
+ ## When to use
6
+
7
+ - "I have a bunch of stuff in flight and I want to get it all into active-work"
8
+ - "audit my workstreams"
9
+ - "find untracked work"
10
+ - "what am I forgetting"
11
+ - After a long break — sync state with reality before continuing
12
+
13
+ ## 1. Discover
14
+
15
+ `active-work discover` walks the configured discovery sources and returns candidate references (PRs, branches, local repos, recent Claude sessions, project directories). Sources are configured in the user's `$XDG_CONFIG_HOME/active-work/config.json` under `discovery`:
16
+
17
+ ```json
18
+ {
19
+ "discovery": {
20
+ "githubRepos": ["hjewkes/active-work", "hjewkes/brain"],
21
+ "localRepos": ["~/Documents/projects/active-work", "~/Documents/projects/brain"],
22
+ "projectsRoot": "~/Documents/projects"
23
+ }
24
+ }
25
+ ```
26
+
27
+ Run:
28
+
29
+ ```bash
30
+ active-work discover
31
+ ```
32
+
33
+ Output lists each hit with a stable `ref` (e.g. `gh:hjewkes/active-work#42`, `git:active-work@feat/dashboard`, `dir:/Users/h/projects/foo`, `claude:session/abc123`), a short title, and a guess at the freshest activity date.
34
+
35
+ ## 2. Triage each hit
36
+
37
+ For every reference, decide one of three actions:
38
+
39
+ ### Track — it's a real initiative
40
+
41
+ If the work is meaningful and ongoing, give it a slug and a title:
42
+
43
+ ```bash
44
+ active-work track gh:hjewkes/active-work#42 \
45
+ --slug dashboard-perf \
46
+ --title "Dashboard load is sluggish on cold open"
47
+ ```
48
+
49
+ `active-work track` scaffolds the initiative (`brief.md` with frontmatter, empty `handoff.md`, empty `tasks/`, an `artifacts.yml` seeded with the source ref), then prints the new slug. Open it next:
50
+
51
+ ```bash
52
+ active-work open dashboard-perf
53
+ ```
54
+
55
+ ### Fold — it belongs under something you already track
56
+
57
+ If the ref is part of an initiative you already have (e.g. one of three PRs against the same effort), fold it in as an artifact:
58
+
59
+ ```bash
60
+ active-work fold gh:hjewkes/active-work#43 \
61
+ --into dashboard-perf \
62
+ --note "follow-up PR for the WS reconnection fix"
63
+ ```
64
+
65
+ This appends to `artifacts.yml` and writes a row in `sources/discovery.yml` so the same ref won't reappear on the next `active-work discover`.
66
+
67
+ ### Drop — not real / not yours / abandoned
68
+
69
+ Mark it dismissed so future `active-work discover` runs ignore it:
70
+
71
+ ```bash
72
+ active-work drop gh:upstream/repo#99 --reason "upstream issue, not actionable"
73
+ ```
74
+
75
+ The ref is recorded in `sources/discovery.yml` with `dismissed: true` and the reason. You can resurrect it with `active-work track <ref>` later if you change your mind.
76
+
77
+ ## 3. Verify state
78
+
79
+ After triage, run the audit to catch issues:
80
+
81
+ ```bash
82
+ active-work audit
83
+ ```
84
+
85
+ Audit checks:
86
+
87
+ - Every active slug has a non-empty `handoff.md`
88
+ - Every active slug has at least one open task (otherwise: should it be archived?)
89
+ - `artifacts.yml` references resolve (PRs exist, branches exist locally, etc.)
90
+ - Last session timestamp isn't stale beyond the configured threshold
91
+ - No frontmatter validation errors
92
+
93
+ Warnings are non-fatal. Fix them iteratively with `active-work set <slug> ...`, `active-work task add`, or `active-work archive`.
94
+
95
+ ## Worked example
96
+
97
+ ```text
98
+ $ active-work discover
99
+ gh:hjewkes/active-work#42 Dashboard cold-load perf 2d ago
100
+ gh:hjewkes/active-work#43 Fix WS reconnect after sleep 2d ago
101
+ git:brain@feat/inbox-rewrite feat/inbox-rewrite (4 commits) 5d ago
102
+ dir:~/projects/scratch-jq scratch-jq 11d ago
103
+ claude:session/0192abc "look at this gnarly stack trace" 23d ago
104
+
105
+ $ active-work track gh:hjewkes/active-work#42 --slug dashboard-perf --title "Dashboard cold-load perf"
106
+ created: dashboard-perf
107
+
108
+ $ active-work fold gh:hjewkes/active-work#43 --into dashboard-perf --note "WS reconnect follow-up"
109
+ folded into dashboard-perf
110
+
111
+ $ active-work track git:brain@feat/inbox-rewrite --slug brain-inbox-rewrite --title "Brain inbox rewrite"
112
+ created: brain-inbox-rewrite
113
+
114
+ $ active-work drop dir:~/projects/scratch-jq --reason "scratch repo, not real work"
115
+ dropped
116
+
117
+ $ active-work drop claude:session/0192abc --reason "one-off debugging, no follow-up"
118
+ dropped
119
+
120
+ $ active-work audit
121
+ brain-inbox-rewrite: handoff.md is empty — add a one-paragraph status
122
+ ok: 1 warning across 2 initiatives
123
+ ```
124
+
125
+ The user is now caught up. Continue with `active-work open dashboard-perf` (or whichever slug they want to push on first).
@@ -0,0 +1,63 @@
1
+ # CLI dev — internal architecture for skill maintainers
2
+
3
+ If you are modifying the `active-work` skill, adding a CLI command, extending the daemon, or wiring a new MCP tool, this is the orientation doc. The CLI source lives in `hjewkes/active-work`.
4
+
5
+ ## Directory map
6
+
7
+ | Path | What's here |
8
+ |---|---|
9
+ | `src/cli.ts` | Entrypoint. Wires commander to the command registry. |
10
+ | `src/registry/` | Command registry types, JSON envelope, dispatcher contract. The single source of truth for both CLI and MCP. |
11
+ | `src/commands/` | One file per CLI command; each exports a `defineCommand({...})` entry. |
12
+ | `src/commands/index.ts` | The aggregate export — register a new command here. |
13
+ | `src/schemas/` | Zod schemas for `brief.md` frontmatter, tasks, sessions, artifacts, state. Every write goes through a validator. |
14
+ | `src/utils/` | `fs-atomic` (atomic writes + flock), `paths`, `slug`, `gray-matter-io`, `yaml-io`, `today`, `color`. |
15
+ | `src/server/` | hono HTTP + WS + MCP-over-HTTP daemon. |
16
+ | `src/dashboard/` | React (react-native-web) read-only dashboard; built via vite. |
17
+ | `src/migrations/` | Schema migrations keyed by `from` version. |
18
+ | `src/lint/` | Per-artifact lint rules (warn-only). |
19
+ | `src/templates/` | Mustache templates for scaffolding new initiatives. |
20
+ | `src/bootstrap/` | Bootstrap prompt assembly used by `active-work open`. |
21
+ | `src/discover/` | Discovery sources (gh, git, projects, Claude sessions). |
22
+ | `skill/` | This skill. Copied into `~/.claude/skills/active-work/` by `scripts/postinstall.js`. |
23
+ | `scripts/` | npm lifecycle hooks: `postinstall.js`, `preuninstall.js`. |
24
+ | `__tests__/` | Vitest tests; fixtures under `__tests__/fixtures/`. |
25
+
26
+ ## Adding a new command
27
+
28
+ 1. Create `src/commands/<name>.ts` exporting a `defineCommand({...})` entry. Define:
29
+ - `name` (e.g. `"task add"`)
30
+ - `description`
31
+ - `input` zod schema
32
+ - `output` zod schema
33
+ - `handler(input, ctx)` returning the validated output
34
+ 2. Add the import + registration in `src/commands/index.ts`.
35
+ 3. The CLI dispatcher and MCP server both pick it up automatically — do not hand-maintain MCP tool definitions.
36
+ 4. Add a test under `__tests__/commands/<name>.test.ts`. Use `withTempActiveRoot` / `withEmptyActiveRoot` helpers from `__tests__/setup/test-helpers.ts` for filesystem isolation.
37
+
38
+ ## Schema + write discipline
39
+
40
+ - Every write to `brief.md` frontmatter, `tasks/*.yml`, `sessions/*.md` (frontmatter), or `artifacts.yml` must go through a validator in `src/schemas/`.
41
+ - Read-modify-write must use `withFileLock` from `src/utils/fs-atomic.ts` to take a per-initiative POSIX flock.
42
+ - Paths are computed via `env-paths` through `src/utils/paths.ts`. Never hardcode `~/.local/share/...`.
43
+
44
+ ## Daemon
45
+
46
+ `src/server/` runs hono on `127.0.0.1:7400` by default (override with `AW_PORT`). It serves:
47
+
48
+ - `/rpc/<command>` — REST for every registry entry
49
+ - `/ws` — WebSocket live feed (chokidar-backed filesystem events)
50
+ - `/mcp` — MCP-over-HTTP transport
51
+ - `/ui` — bundled dashboard SPA
52
+
53
+ The same daemon process can also speak MCP-over-stdio when invoked as `active-work mcp serve --stdio`.
54
+
55
+ ## Skill content
56
+
57
+ This skill is just three files: `SKILL.md` and three references. Keep `SKILL.md` skim-able (target ~150 lines, hard cap ~200). Push depth into references and link from `SKILL.md`. The frontmatter `description` is what Claude Code uses to decide whether to load the skill, so keep trigger phrases there fresh and aligned with the CLI command surface.
58
+
59
+ When you add a trigger phrase to `SKILL.md`, ask: is there an existing CLI command that handles it? If not, add the command first, then the trigger.
60
+
61
+ ## Release flow
62
+
63
+ Changes to skill content ship in the next `@titan-design/active-work` release. The `postinstall.js` hook reinstalls the skill on every update; users do not need to do anything beyond `npm install -g @titan-design/active-work@latest` (or the pnpm equivalent).
@@ -0,0 +1,72 @@
1
+ # Onboarding — first-time `active-work` setup
2
+
3
+ This walkthrough takes a brand-new machine to a working `active-work` install with the Claude Code skill available.
4
+
5
+ ## 1. Verify prerequisites
6
+
7
+ `active-work` requires Node 22 or newer. `pnpm` is recommended for global installs because it manages multiple Node versions cleanly, but `npm` works too.
8
+
9
+ ```bash
10
+ node --version # expect v22.x or newer
11
+ pnpm --version # any recent version, e.g. 9.x or 10.x
12
+ ```
13
+
14
+ If Node is older than 22, install a current LTS via `nvm`, `fnm`, or `volta` before continuing.
15
+
16
+ ## 2. Install the package globally
17
+
18
+ ```bash
19
+ npm install -g @titan-design/active-work
20
+ # or
21
+ pnpm add -g @titan-design/active-work
22
+ ```
23
+
24
+ The package ships the CLI binary (`active-work`), a thin Claude-session launcher (`aw <slug>`), the MCP server, the dashboard bundle, and the Claude skill content.
25
+
26
+ ## 3. Postinstall hook copies the skill
27
+
28
+ On install, `scripts/postinstall.js` runs automatically. It looks for `~/.claude/` and, if present, copies the bundled `skill/` directory into `~/.claude/skills/active-work/`. If `~/.claude/` does not exist (Claude Code not installed), the hook exits silently so the npm install never fails.
29
+
30
+ You can verify the skill landed:
31
+
32
+ ```bash
33
+ ls ~/.claude/skills/active-work/
34
+ # expect: SKILL.md references/
35
+ ```
36
+
37
+ ## 4. Run `active-work setup`
38
+
39
+ `active-work setup` (Wave 6) is the interactive wizard that initializes data roots, registers the MCP server with Claude Code, and offers to install the daemon launchd plist. **It is not yet implemented.** Until it lands, you can prepare the data root manually:
40
+
41
+ ```bash
42
+ mkdir -p "${XDG_DATA_HOME:-$HOME/Library/Application Support}/active-work"
43
+ ```
44
+
45
+ (On Linux this resolves to `~/.local/share/active-work/`; on macOS to `~/Library/Application Support/active-work/`.)
46
+
47
+ Register the MCP server with Claude Code manually for now:
48
+
49
+ ```bash
50
+ claude mcp add --user @hjewkes/active-work -- active-work mcp serve --stdio
51
+ ```
52
+
53
+ ## 5. Verify the install
54
+
55
+ ```bash
56
+ active-work --help # prints the command surface
57
+ active-work mcp status # reports daemon state
58
+ ```
59
+
60
+ `active-work mcp status` will report "not running" until you start the daemon (next step).
61
+
62
+ ## 6. Optional — start the daemon
63
+
64
+ The daemon hosts MCP-over-HTTP, the REST API, the WebSocket live feed, and the dashboard.
65
+
66
+ ```bash
67
+ active-work mcp serve --detach
68
+ ```
69
+
70
+ Then visit `http://127.0.0.1:7400/ui` to see the dashboard. Stop it with `active-work mcp stop`.
71
+
72
+ You're done. Open Claude Code in any directory and ask "what am I working on?" — the `active-work` skill should engage and surface your initiatives.