@launchsecure/launch-kit 0.0.27 → 0.0.29

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.
Files changed (48) hide show
  1. package/dist/beacon/beacon.mjs +1003 -440
  2. package/dist/beacon/beacon.mjs.map +1 -1
  3. package/dist/beacon/beacon.umd.js +45 -24
  4. package/dist/beacon/beacon.umd.js.map +1 -1
  5. package/dist/beacon/types/capture/events.d.ts +20 -0
  6. package/dist/beacon/types/capture/events.d.ts.map +1 -0
  7. package/dist/beacon/types/element.d.ts +1 -0
  8. package/dist/beacon/types/element.d.ts.map +1 -1
  9. package/dist/beacon/types/index.d.ts +2 -1
  10. package/dist/beacon/types/index.d.ts.map +1 -1
  11. package/dist/beacon/types/monitor/dom.d.ts +13 -0
  12. package/dist/beacon/types/monitor/dom.d.ts.map +1 -0
  13. package/dist/beacon/types/monitor/index.d.ts +19 -0
  14. package/dist/beacon/types/monitor/index.d.ts.map +1 -0
  15. package/dist/beacon/types/monitor/network.d.ts +12 -0
  16. package/dist/beacon/types/monitor/network.d.ts.map +1 -0
  17. package/dist/beacon/types/monitor/transport.d.ts +27 -0
  18. package/dist/beacon/types/monitor/transport.d.ts.map +1 -0
  19. package/dist/beacon/types/monitor/types.d.ts +117 -0
  20. package/dist/beacon/types/monitor/types.d.ts.map +1 -0
  21. package/dist/beacon/types/types.d.ts +10 -0
  22. package/dist/beacon/types/types.d.ts.map +1 -1
  23. package/dist/beacon/types/ui/drawer.d.ts +3 -1
  24. package/dist/beacon/types/ui/drawer.d.ts.map +1 -1
  25. package/dist/beacon/types/ui/monitor-panel.d.ts +19 -0
  26. package/dist/beacon/types/ui/monitor-panel.d.ts.map +1 -0
  27. package/dist/server/beacon-monitor-entry.js +353 -0
  28. package/dist/server/chart-serve.js +3 -1
  29. package/dist/server/cli.js +276 -218
  30. package/dist/server/course-entry.js +246 -0
  31. package/dist/server/graph-mcp-entry.js +35 -72
  32. package/dist/server/init-entry.js +1051 -122
  33. package/dist/server/orbit-entry.js +187 -24
  34. package/package.json +5 -3
  35. package/scaffolds/ls-marketplace/.claude-plugin/marketplace.json +15 -0
  36. package/scaffolds/ls-marketplace/plugins/kit/.claude-plugin/plugin.json +19 -0
  37. package/scaffolds/ls-marketplace/plugins/kit/commands/activate-beacon.md +216 -0
  38. package/scaffolds/ls-marketplace/plugins/kit/commands/activate-statusline.md +46 -0
  39. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-array.md +92 -0
  40. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-clear.md +68 -0
  41. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-pulse.md +80 -0
  42. package/scaffolds/ls-marketplace/plugins/kit/commands/beacon-scan.md +62 -0
  43. package/scaffolds/ls-marketplace/plugins/kit/commands/deactivate-statusline.md +34 -0
  44. package/scaffolds/ls-marketplace/plugins/kit/commands/show-mcp-status.md +109 -0
  45. package/scaffolds/ls-marketplace/plugins/kit/commands/standup.md +191 -0
  46. package/scaffolds/recall-hook/scripts/ensure-recall.sh +69 -0
  47. package/scaffolds/statusline/statusline-mcp.sh +192 -0
  48. package/scaffolds/statusline/statusline-wrapper.sh +50 -0
@@ -0,0 +1,109 @@
1
+ ---
2
+ description: Show the health of launch-kit's daemon-style MCPs (today just launch-recall). Default output is a terse one-line summary per daemon; pass `full` for expanded details — PID, pidfile, last snapshot, debounce, shadow repo size, recent activity. Read-only; never mutates state.
3
+ ---
4
+
5
+ # /kit:show-mcp-status
6
+
7
+ Reports the liveness and recent activity of launch-kit's long-running MCP daemons. The recall watcher is the only daemon today; this command is structured so additional daemons can be added without changing its interface.
8
+
9
+ Read $ARGUMENTS to decide output verbosity:
10
+ - empty (`/kit:show-mcp-status`) → terse one-liner per daemon. Goal: fits in one or two visual lines.
11
+ - `full` (`/kit:show-mcp-status full`) → expanded report per daemon. Goal: enough detail to diagnose health problems.
12
+
13
+ ## Preflight
14
+
15
+ 1. Verify the launch-recall MCP is wired by checking whether `mcp__launch-recall__recall_status` (or `mcp__local-launch-recall__recall_status` for dev repos) is callable. If neither is available, say so plainly: "launch-recall MCP not wired in this project — nothing to report" and stop.
16
+ 2. Pick whichever recall MCP is available. Prefer the project-level published one (`mcp__launch-recall__*`) unless only the local dev one is wired.
17
+
18
+ ## Daemons
19
+
20
+ Today: **launch-recall** (the shadow-git file watcher).
21
+
22
+ Structure your output so adding more daemons later is a copy-paste — one section per daemon, same shape.
23
+
24
+ ## Default output (no arguments)
25
+
26
+ Call `mcp__launch-recall__recall_status`. Format as ONE LINE per daemon, then a single summary line. Example:
27
+
28
+ ```
29
+ recall ✓ alive pid 12456 last snap 2m ago
30
+
31
+ All daemons healthy.
32
+ ```
33
+
34
+ Or if dead:
35
+
36
+ ```
37
+ recall ✗ dead pidfile missing/stale
38
+
39
+ 1 daemon down. Run /kit:show-mcp-status full for details, or restart the watcher (kill any stale pid, then either start a new Claude Code session — the SessionStart hook respawns it — or run `node packages/cli/dist/server/recall-entry.js watch` from this repo).
40
+ ```
41
+
42
+ Rules for the default view:
43
+ - One line per daemon — name, glyph, status word, key metric.
44
+ - `✓` (green) for alive, `✗` (red) for dead.
45
+ - "X ago" for timestamps — relative time, easier to scan than ISO.
46
+ - Summary line on a fresh line at the end. If anything is wrong, include a one-sentence recovery hint.
47
+ - No JSON, no PID files paths, no debug info — that's for `full`.
48
+
49
+ ## Full output (`/kit:show-mcp-status full`)
50
+
51
+ For each daemon, call BOTH `recall_status` AND `recall_report`. Combine into a per-daemon block:
52
+
53
+ ```
54
+ recall — launch-recall watcher (shadow-git backup)
55
+
56
+ Status: ✓ alive
57
+ PID: 12456
58
+ Watch tree: /Users/prajyot/Documents/Work/AutomateWithUs/launchsecure-v2
59
+ Shadow repo: /Users/prajyot/Documents/Work/AutomateWithUs/launchsecure-v2/.recall/repo.git
60
+
61
+ Last snapshot: 2026-05-21T12:05:45+05:30 (2m ago)
62
+ Total snaps: 1,247
63
+ Shadow size: 1.4 MB
64
+ Debounce: 3000ms
65
+ Retention: keepLast 5000, maxAgeDays 30
66
+
67
+ Recent activity (last 5 snaps):
68
+ a1b2c3d 2m ago snap 2026-05-21T06:35:45Z
69
+ 9f8e7d6 14m ago snap 2026-05-21T06:21:42Z
70
+ 2557799 53m ago snap 2026-05-21T05:52:35Z
71
+ 8349edb 17h ago snap 2026-05-20T13:36:39Z
72
+ e5a05cf 17h ago snap 2026-05-20T13:32:05Z
73
+
74
+ All daemons healthy.
75
+ ```
76
+
77
+ If a daemon is dead, the block should still appear but with status `✗ dead` and as many fields as can be derived from the on-disk shadow repo + pidfile. Include a concrete recovery command at the end of the block (the same SessionStart restart path from the default view, plus the manual command for impatient users).
78
+
79
+ Rules for the full view:
80
+ - Two-column field labels — visually aligned, easier to skim.
81
+ - Times: ISO timestamp + relative ("2m ago") in parens.
82
+ - Sizes: human-readable (KB/MB), not bytes.
83
+ - Last 5 snapshots only — newer first.
84
+ - One blank line between daemon blocks (for when there are more than one).
85
+ - Summary line at the end, same as the default view.
86
+
87
+ ## Health classification
88
+
89
+ Use these rules to decide alive vs dead vs degraded:
90
+
91
+ - **alive**: `recall_status.running === true` AND `lastSnapshotAt` is within the last 6 hours (heuristic — for an active repo, snaps happen every few minutes; 6h of silence suggests the watcher is wedged or no files have changed, both worth surfacing).
92
+ - **dead**: `recall_status.running === false` OR `recall_status` errors out.
93
+ - **degraded** (still print as alive but note it): `running === true` BUT `lastSnapshotAt` is older than 6h. Add a single inline note `(no activity in 6h — possibly idle or wedged)`.
94
+
95
+ Don't over-warn — if a repo has genuinely been idle (e.g. you opened it for the first time today after a week), a 6h+ gap is normal. The note is informational, not an error.
96
+
97
+ ## Constraints
98
+
99
+ - **Read-only.** This command never mutates state, never restarts watchers, never modifies config. Recovery is a recommendation in the output, not an automatic action.
100
+ - **Fast.** Default view should be sub-second. The MCP tools are cheap; don't add scrubbing or heavy formatting.
101
+ - **Plain text.** No markdown headers, no fenced blocks in the actual output — Claude Code renders them but the human eye scans monospace plain text fastest.
102
+ - **Output what the user asked for.** If they ran the default, don't dump the full report "just in case." If they ran `full`, don't abbreviate.
103
+
104
+ ## Notes for the assistant
105
+
106
+ - Use the wired `launch-recall` MCP tools — do not shell out to `node packages/cli/dist/server/recall-entry.js status` even though it works. Going through MCP makes this command portable across projects that have launch-kit init'd via npx vs dev-build.
107
+ - When listing recent snaps, use the `recentSnapshots` array from `recall_report`. It's already sorted newest-first; just truncate.
108
+ - For "X ago" formatting, do the math yourself. Don't fetch any time service.
109
+ - If you find yourself wanting to add features beyond status display (restart the watcher, prune snaps, etc.) — stop. Those belong in separate `/kit:*` commands. This one is a status pane.
@@ -0,0 +1,191 @@
1
+ ---
2
+ description: Draft a daily standup from work done since the last push, surface in-progress local work and follow-ups, show the draft, and post to LaunchSecure Comm Hub as a daily_update after explicit confirmation.
3
+ ---
4
+
5
+ # Standup
6
+
7
+ Generates a daily-update comment for the LaunchSecure Comm Hub. Pulls commits since the last push (with fallbacks), surfaces what's still in-flight locally (uncommitted work, explicit follow-ups, in-progress work items), drafts a summary in the project's house style, shows it to the user, and posts only after the user confirms. Never posts without explicit "yes".
8
+
9
+ ## Preflight
10
+
11
+ 1. Verify `.launch-secure.cred.config` exists at the repo root. If missing, abort and tell the user to run `npx @launchsecure/launch-kit init` first — without the cred file the `launch-secure` MCP cannot authenticate, so we cannot read prior standups or post the new one.
12
+ 2. Verify we are inside a git repo (`git rev-parse --git-dir`). If not, abort with a clear message.
13
+ 3. Record the current branch (`git rev-parse --abbrev-ref HEAD`) and current git email (`git config user.email`).
14
+
15
+ ## Gather
16
+
17
+ Pull data from layered sources, in this order. Default to git + commit messages — they already carry the structure. Only reach for `launch-chart` in the explicit case described in step 3.
18
+
19
+ ### 1. Determine the window
20
+
21
+ - **Primary**: `git log --reverse --pretty=format:'%h%x09%an%x09%ae%x09%s%n%b%n%x00' @{push}..HEAD`. The `@{push}` revspec resolves to where this branch was last pushed; the diff is commits ahead of upstream.
22
+ - **Fallback 1**: if the primary returns empty OR `@{push}` errors with `unknown revision`, query the `launch-secure` MCP via `communication_read({ tag: "daily_update", limit: 1 })`. Take the most recent comment's `createdAt`; gather commits since then via `git log --since="<that timestamp>"`.
23
+ - **Fallback 2**: if no prior `daily_update` exists either, use `git log --since="24 hours ago"`.
24
+
25
+ Tell the user which window was used in one short line ("Using commits since last push (12 commits)" or "No commits since last push — falling back to commits since 2026-05-20 14:01 (last standup)").
26
+
27
+ ### 2. Collect change context
28
+
29
+ - `git diff --stat @{push}..HEAD` (or whichever window was chosen) — file paths + line counts. Drives theme grouping.
30
+ - `git log --pretty=format:'%h %s' <window>` — short subject lines for quick scan.
31
+ - `git log --pretty=format:'%h%n%B%n---' <window>` — full commit bodies. Needed for step 4 (follow-up detection).
32
+ - Branch names + PR references in commit messages (look for `#<digits>`, `LS-<id>`, branch slugs like `fix/foo-bar`). These are work-item handles.
33
+
34
+ ### 3. Group into themes
35
+
36
+ **Default: group by commit-message scope.** Conventional-commit subjects like `feat(launch-kit): …`, `refactor(server): …`, `chore(lint): …` already declare the theme — use the scope as the theme name. Multiple commits with the same scope collapse into one `→ <Scope>` section.
37
+
38
+ **Only escalate to `launch-chart` when ALL of these are true:**
39
+ - Window has ≥20 changed files across ≥5 top-level directories, AND
40
+ - Commit scopes are missing, inconsistent, or too coarse to reveal structure (e.g. everything is `chore: …` or untagged).
41
+
42
+ In that case: call `mcp__launch-chart__detect_project_stack` to verify the chart is wired, then for each changed file call `mcp__launch-chart__read_graph({ search: "<basename>" })` and group by the resolved node's `module` field. Common LS modules: `auth`, `pda`, `pda-guides`, `pda-shell`, `radar`, `chart`, `orbit`, `recall`, `comms`, `board`, `webhooks`, `feedback`, `briefs`, `mcp`.
43
+
44
+ Files that don't resolve in the chart (new files, non-TS, config) bucket under "Misc" or by directory.
45
+
46
+ ### 4. Collect in-progress signals (the "what's still in flight" data)
47
+
48
+ This is the data that makes the standup honest about what's still open, not just what shipped. Always run all three:
49
+
50
+ **4a. Uncommitted local work** — `git status --short`. Group by area (e.g. `scripts/`, `.claude/`, untracked dirs). Filter out build artifacts that don't represent intentional work (`.launchsecure/graphs/*.json`, `node_modules`, `dist`, `.next`). What remains is in-flight code or scaffolds — surface it under "Pending / in-progress".
51
+
52
+ **4b. Follow-up flags in commit bodies** — grep the full commit bodies pulled in step 2 for these patterns (case-insensitive): `follow-up`, `followup`, `TODO`, `flagged`, `deferred`, `not patched`, `rolled back`, `revert`, `queued`, `to be solved`, `next pass`. Each hit is a self-declared open item by the author — surface the sentence verbatim or paraphrased under "Pending / in-progress".
53
+
54
+ **4c. In-progress work items assigned to current user** — call `mcp__launch-secure__work_items_list({ assignee_email: "<git email from preflight>", status: "IN_PROGRESS" })`. For each returned item, capture title + id. Don't list more than 5; if more exist, list 5 + "(N more)".
55
+
56
+ If any of 4a/4b/4c return nothing, that section just doesn't appear in the draft. Don't fabricate filler.
57
+
58
+ ### 5. Work-item closure linkage
59
+
60
+ For each work-item handle found in commit messages from step 2 (regex `#\d+|LS-\w+`), call `mcp__launch-secure__work_item_get({ id: "<id>" })` (or `work_items_list` with id filter). If any status changed to DONE/COMPLETED, call them out in the closing `----` block as a one-liner.
61
+
62
+ Skip this step if no handles were found in commit messages — don't pull the full work-items list to fish for closures.
63
+
64
+ ### 6. Release detection
65
+
66
+ A commit qualifies the post for the `release` tag if ANY of the following are true:
67
+ - `package.json` `version` field changed in `git diff @{push}..HEAD -- package.json`
68
+ - A migration file under `prisma/migrations/` was added or `prisma/schema.prisma` changed
69
+ - A deploy/publish was mentioned in commit subjects (regex: `\b(publish|release|deploy|bump)\b`)
70
+ - A new bin or export was added to a package's `package.json`
71
+
72
+ If any are true, set `addReleaseTag = true`. Otherwise `false`.
73
+
74
+ ## Draft
75
+
76
+ Produce the standup in the **exact** house format. This is the format Prajyot uses for human-written daily updates (verified against the May 18, 19, 20, 21 posts in the Comm Hub):
77
+
78
+ ```
79
+ Hey @everyone
80
+
81
+ Pushed <N> commits to <branch> today. Highlights:
82
+
83
+ → <Theme 1>
84
+ - <outcome bullet, not a commit message>
85
+ - <outcome bullet>
86
+
87
+ → <Theme 2>
88
+ - <outcome bullet>
89
+
90
+ → <Theme N>
91
+ - <outcome bullet>
92
+
93
+ ----
94
+
95
+ Pending / in-progress:
96
+
97
+ → <Pending area 1>
98
+ - <one-line item — what's open, why, where>
99
+
100
+ → <Pending area 2>
101
+ - <one-line item>
102
+
103
+ ----
104
+
105
+ <one-line PSA or deploy-safety note. Examples: "TS clean, no schema/migration changes, safe to deploy." or "Includes prisma migration <name> — run migrate-with-backup.sh before deploy.">
106
+
107
+ Thanks
108
+ ```
109
+
110
+ **Strict format rules:**
111
+
112
+ - `Hey @everyone` — exactly this. Not "Hey team", not "Hi everyone". Single line, blank line after.
113
+ - `Pushed N commits to <branch> today. Highlights:` — replace `<branch>` with the actual branch from preflight. If the window was a fallback (not "since last push"), say "Drafted N commits' worth of work on <branch> today. Highlights:" instead — be honest about the window.
114
+ - `→ <Theme>` — right-arrow + space + theme name. Theme names come from commit-message scope (default) or chart module grouping (escalated case). Capitalize the first letter.
115
+ - `- <bullet>` — plain hyphen + space. Bullets are **outcomes**, not commit messages. Translate "feat(radar): add transcript view" into "Structured transcript view with overlay reply drawer". Drop scope prefixes and verb tense.
116
+ - Blank line between themes.
117
+ - `----` — four hyphens, on its own line, blank line above and below. Used twice: once to open the Pending block, once to close it.
118
+ - **Pending / in-progress block** — omit entirely if steps 4a/4b/4c all returned nothing. If present, populate from those three sources. Each `→` sub-heading names an area (e.g. `→ Feedback widget`, `→ Codex config writer`, `→ Beacon monitor UX`). Bullets are short, factual, name the file or symbol when relevant.
119
+ - Closing block: one short line is fine. Don't pad. Mention work-item closures + any deploy-affecting changes here.
120
+ - `Thanks` — exact word, no comma, no name signature (the post's author is attached automatically).
121
+
122
+ **Constraints:**
123
+
124
+ - Plain text only. No `**bold**`, no `*italic*`, no backticks, no `# headers`, no markdown link syntax. The Comm Hub renders as plain text.
125
+ - Aim for ≤ 400 words total (was 350; bumped to accommodate the Pending block). Standups are skim-able; trim aggressively.
126
+ - Top 3–6 themes is the sweet spot. If you have 20 commits in 12 different modules, collapse the small ones into a "→ Misc" theme.
127
+ - Bullets should be **outcome-shaped**: what landed in the product, not what the diff did. "Multi-select EventTypesPicker on subscription forms" beats "modified components/webhooks/EventTypesPicker.tsx".
128
+ - Pending items are honest snapshots, not commitments. "Drawer fix reverted — to be solved fresh" is fine; "will be fixed by EOD" is not.
129
+
130
+ ## Confirm
131
+
132
+ Show the draft to the user verbatim in a code-fenced block (so they see plain-text formatting as-is), then ask **exactly**:
133
+
134
+ > "Post this as a daily_update to LS Comm Hub? Reply `yes` to post, `edit` to revise, or `cancel` to abort."
135
+
136
+ Responses:
137
+
138
+ - **`yes`, `y`, `ok`, `post it`** — proceed to duplicate check + post.
139
+ - **`edit`, `change <thing>`, free-form revisions** — apply the edits, regenerate the draft, re-show, re-ask.
140
+ - **`cancel`, `no`, `nope`** — abort. Don't post. Don't keep partial state.
141
+
142
+ Anything ambiguous → treat as "edit, what would you like changed?".
143
+
144
+ ## Duplicate check
145
+
146
+ Before posting, call `mcp__launch-secure__communication_read({ tag: "daily_update", limit: 5 })`. If any returned comment has:
147
+ - `resourceType: "comment"` (not `"daily_update"` — that's bot-only, distinct stream)
148
+ - `author.email` matches the current user's email (from preflight)
149
+ - `createdAt` within the last 12 hours
150
+
151
+ …then a manual daily_update already exists from today. Offer:
152
+
153
+ > "A daily_update already exists from you today (posted at <time>, <preview-50-chars>). Choose: `replace` to update it (communication_update), `append` to post another, `cancel` to stop."
154
+
155
+ - **replace** → call `mcp__launch-secure__communication_update({ id: "<existing-id>", content: <new-draft> })`. Preserve tags.
156
+ - **append** → proceed to post.
157
+ - **cancel** → abort.
158
+
159
+ ## Post
160
+
161
+ Call `mcp__launch-secure__communication_write`:
162
+
163
+ ```
164
+ {
165
+ resourceType: "comment",
166
+ content: "<the final draft, plain text>",
167
+ tags: addReleaseTag ? ["daily_update", "release"] : ["daily_update"]
168
+ }
169
+ ```
170
+
171
+ `org_slug` and `project_slug` are auto-supplied from the cred config via the MCP's headersHelper — do NOT pass them yourself.
172
+
173
+ On success: report the comment ID and a friendly "posted to LS Comm Hub" line. If the response includes a URL field, surface it.
174
+
175
+ On failure: surface the error verbatim. Don't retry automatically — auth or schema errors deserve human attention. The draft is preserved in chat so the user can retry manually.
176
+
177
+ ## Idempotency
178
+
179
+ Re-running `/kit:standup`:
180
+ - Always re-pulls the window fresh. No cached state.
181
+ - Duplicate check handles same-day reposts.
182
+ - The draft is never written to disk — only shown in chat. User can copy it manually if they cancel.
183
+
184
+ ## Notes for the assistant
185
+
186
+ - **Default to git, not the chart.** Conventional-commit scopes already declare themes; only reach for `launch-chart` when scopes are missing or inconsistent across ≥20 files / ≥5 directories. This was the most common failure of the previous prompt — chart calls produced no different grouping than commit-scope grouping would have.
187
+ - **The Pending / in-progress block is the standup's main job, not the shipped-themes block.** Shipped work is visible in `git log`; what's still open isn't. Always run the three step-4 gathers.
188
+ - **Don't ask clarifying questions before drafting.** Produce a first cut from whatever data step 1–6 returned, then iterate. The draft is cheap; the conversation isn't.
189
+ - **Outcome bullet, not commit message** is the single most important transform. A standup full of "feat(x): add y" reads like a changelog, not a status.
190
+ - **Never assume `release` tag without evidence** (see step 6). False positives confuse downstream consumers.
191
+ - **If a step's tool fails**, note it inline and continue with the remaining sources — don't abort the whole standup over one failed call.
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env bash
2
+ # Ensures the launch-recall watcher is running. Designed for the Claude Code
3
+ # SessionStart hook (.claude/settings.json). Idempotent: silently no-ops if
4
+ # recall isn't configured, logs OK if watcher is already alive, spawns a
5
+ # detached watcher if it isn't.
6
+ #
7
+ # Portable across two layouts:
8
+ # - Dev: project has packages/cli/dist/server/recall-entry.js (e.g.
9
+ # launchsecure-v2 itself) → spawn via `node`.
10
+ # - Customer: project doesn't ship launch-kit → spawn via
11
+ # `npx -y -p @launchsecure/launch-kit launch-recall watch`. First run
12
+ # downloads from npm (slow); subsequent runs use the npx cache.
13
+ #
14
+ # Exits 0 in all cases — recall failing should never block Claude Code startup.
15
+
16
+ set -u
17
+
18
+ REPO_ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
19
+ RECALL_DIR="$REPO_ROOT/.recall"
20
+ PIDFILE="$RECALL_DIR/watch.pid"
21
+ LOCAL_RECALL_BIN="$REPO_ROOT/packages/cli/dist/server/recall-entry.js"
22
+
23
+ # No recall configured here — exit quietly. Hook is safe to ship in any repo;
24
+ # it only acts when there's a shadow-git to protect.
25
+ if [[ ! -d "$RECALL_DIR/repo.git" ]]; then
26
+ exit 0
27
+ fi
28
+
29
+ # Watcher already alive?
30
+ if [[ -f "$PIDFILE" ]]; then
31
+ pid="$(cat "$PIDFILE" 2>/dev/null || true)"
32
+ if [[ -n "${pid:-}" ]] && kill -0 "$pid" 2>/dev/null; then
33
+ echo "[recall] watcher alive (pid $pid)" >&2
34
+ exit 0
35
+ fi
36
+ fi
37
+
38
+ # Pick spawn command. Prefer the local dev build (instant) over npx (slow on
39
+ # first run because of the npm download).
40
+ if [[ -f "$LOCAL_RECALL_BIN" ]]; then
41
+ SPAWN_CMD=(node "$LOCAL_RECALL_BIN" watch)
42
+ via="local dev build"
43
+ else
44
+ SPAWN_CMD=(npx -y -p @launchsecure/launch-kit launch-recall watch)
45
+ via="npx (initial download may take a moment)"
46
+ fi
47
+
48
+ echo "[recall] starting watcher via $via" >&2
49
+
50
+ # Spawn detached. nohup + redirected fds + & keeps the watcher alive past this
51
+ # script's exit. disown removes it from the shell's job table.
52
+ nohup "${SPAWN_CMD[@]}" >/dev/null 2>&1 &
53
+ disown 2>/dev/null || true
54
+
55
+ # Brief check — long enough for the local-dev path, not long enough to block
56
+ # session start when npx is downloading.
57
+ sleep 0.5
58
+
59
+ if [[ -f "$PIDFILE" ]]; then
60
+ newpid="$(cat "$PIDFILE" 2>/dev/null || true)"
61
+ if [[ -n "${newpid:-}" ]] && kill -0 "$newpid" 2>/dev/null; then
62
+ echo "[recall] watcher started (pid $newpid)" >&2
63
+ exit 0
64
+ fi
65
+ fi
66
+
67
+ # Spawn is still in flight (likely npx download). Don't block session start.
68
+ echo "[recall] watcher spawn initiated — check with /ls:show-mcp-status in a moment" >&2
69
+ exit 0
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env bash
2
+ # launch-kit MCP chip generator.
3
+ # Emits a colored one-line summary of MCP daemon health, one chip per daemon.
4
+ # Composes into any existing statusline via the launch-kit wrapper.
5
+ #
6
+ # Usage:
7
+ # statusline-mcp.sh # all known chips (recall, chart, deck, council)
8
+ # statusline-mcp.sh --show=recall,chart # only the listed chips
9
+ # statusline-mcp.sh --compact # collapse to `mcp <up>/<total>`
10
+ # green when all up, red if any down
11
+ #
12
+ # Project root is inferred from $LK_STATUSLINE_CWD (set by the wrapper) or
13
+ # $PWD by walking up until a `.launchsecure` dir is found. Silent exit when
14
+ # the cwd is not inside a launchsecure project.
15
+ set -u
16
+
17
+ show=""
18
+ compact=0
19
+ for arg in "$@"; do
20
+ case "$arg" in
21
+ --show=*) show="${arg#--show=}" ;;
22
+ --compact) compact=1 ;;
23
+ esac
24
+ done
25
+ [ -z "$show" ] && show="recall,chart,deck,council"
26
+
27
+ GREEN=$'\033[32m'
28
+ ORANGE=$'\033[33m'
29
+ RED=$'\033[31m'
30
+ RESET=$'\033[0m'
31
+
32
+ find_project_root() {
33
+ # `.recall` only exists at the repo root (launch-recall init creates it
34
+ # there). `.launchsecure` can appear in subdirs too (e.g. packages/cli
35
+ # when graphs are generated for a sub-project), so we prefer .recall as
36
+ # the canonical root marker. Fallback: outermost `.launchsecure` ancestor
37
+ # (walk up and keep overwriting → keeps the highest match).
38
+ local d="${1:-$PWD}"
39
+ local recall_root=""
40
+ local outermost_launchsecure=""
41
+ while [ -n "$d" ] && [ "$d" != "/" ]; do
42
+ if [ -z "$recall_root" ] && [ -d "$d/.recall" ]; then
43
+ recall_root="$d"
44
+ fi
45
+ if [ -d "$d/.launchsecure" ]; then
46
+ outermost_launchsecure="$d"
47
+ fi
48
+ d=$(dirname "$d")
49
+ done
50
+ if [ -n "$recall_root" ]; then echo "$recall_root"; return 0; fi
51
+ if [ -n "$outermost_launchsecure" ]; then echo "$outermost_launchsecure"; return 0; fi
52
+ return 1
53
+ }
54
+
55
+ PROJECT_ROOT=$(find_project_root "${LK_STATUSLINE_CWD:-$PWD}") || exit 0
56
+
57
+ iso_to_epoch() {
58
+ # Input is always UTC (ISO timestamps from .freshness.json end in Z).
59
+ # macOS BSD `date` defaults to local TZ, so without TZ=UTC the timestamp
60
+ # gets misread as local time → wildly wrong age (e.g. 6h instead of 55m).
61
+ local ts="$1"
62
+ ts="${ts%%.*}"; ts="${ts%%Z*}"; ts="${ts%%+*}"
63
+ TZ=UTC date -j -f "%Y-%m-%dT%H:%M:%S" "$ts" +%s 2>/dev/null
64
+ }
65
+
66
+ fmt_age() {
67
+ local then="$1"
68
+ local now diff
69
+ now=$(date +%s)
70
+ diff=$((now - then))
71
+ if [ "$diff" -lt 60 ]; then echo "${diff}s"
72
+ elif [ "$diff" -lt 3600 ]; then echo "$((diff/60))m"
73
+ elif [ "$diff" -lt 86400 ]; then echo "$((diff/3600))h"
74
+ else echo "$((diff/86400))d"
75
+ fi
76
+ }
77
+
78
+ pid_from_lockfile() {
79
+ grep -o '"pid"[^,}]*' "$1" 2>/dev/null | head -1 | sed 's/[^0-9]//g'
80
+ }
81
+
82
+ # Each chip_* function sets two globals so the caller can use both the
83
+ # colored display string (verbose mode) and the abstract state (compact mode)
84
+ # without re-running the underlying probe. Must be called as a bare command
85
+ # (no subshell) so the assignments survive.
86
+ _state=""
87
+ _display=""
88
+
89
+ chip_recall() {
90
+ local pidfile="$PROJECT_ROOT/.recall/watch.pid"
91
+ local repo="$PROJECT_ROOT/.recall/repo.git"
92
+ if [ ! -f "$pidfile" ]; then _state="red"; _display="${RED}recall${RESET}"; return; fi
93
+ local pid
94
+ pid=$(cat "$pidfile" 2>/dev/null) || pid=""
95
+ if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
96
+ _state="red"; _display="${RED}recall${RESET}"
97
+ return
98
+ fi
99
+ if [ -d "$repo" ]; then
100
+ local last
101
+ last=$(git --git-dir="$repo" log -1 --format=%ct 2>/dev/null)
102
+ if [ -n "$last" ]; then
103
+ local now diff age
104
+ now=$(date +%s); diff=$((now - last)); age=$(fmt_age "$last")
105
+ if [ "$diff" -gt 21600 ]; then
106
+ _state="orange"; _display="${ORANGE}recall(${age})${RESET}"
107
+ else
108
+ _state="green"; _display="${GREEN}recall(${age})${RESET}"
109
+ fi
110
+ return
111
+ fi
112
+ fi
113
+ _state="green"; _display="${GREEN}recall${RESET}"
114
+ }
115
+
116
+ chip_chart() {
117
+ local freshness="$PROJECT_ROOT/.launchsecure/graphs/.freshness.json"
118
+ if [ ! -f "$freshness" ]; then _state="red"; _display="${RED}chart${RESET}"; return; fi
119
+ local last
120
+ last=$(grep -o '"lastFullRegenAt"[^,}]*' "$freshness" | head -1 | sed 's/.*"\([0-9TZ:.+-]*\)".*/\1/')
121
+ local lock="$PROJECT_ROOT/.launchsecure/launch-chart.lock"
122
+ local color="$ORANGE"; local state="orange"
123
+ if [ -f "$lock" ]; then
124
+ local pid
125
+ pid=$(pid_from_lockfile "$lock")
126
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
127
+ color="$GREEN"; state="green"
128
+ fi
129
+ fi
130
+ if [ -n "$last" ] && [ "$last" != "null" ]; then
131
+ local epoch age
132
+ epoch=$(iso_to_epoch "$last")
133
+ if [ -n "$epoch" ]; then
134
+ age=$(fmt_age "$epoch")
135
+ _state="$state"; _display="${color}chart(${age})${RESET}"
136
+ return
137
+ fi
138
+ fi
139
+ _state="$state"; _display="${color}chart${RESET}"
140
+ }
141
+
142
+ chip_deck() {
143
+ local lock="$PROJECT_ROOT/.launchsecure/launch-deck.lock"
144
+ if [ -f "$lock" ]; then
145
+ local pid; pid=$(pid_from_lockfile "$lock")
146
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
147
+ _state="green"; _display="${GREEN}deck${RESET}"; return
148
+ fi
149
+ fi
150
+ _state="orange"; _display="${ORANGE}deck${RESET}"
151
+ }
152
+
153
+ chip_council() {
154
+ local lock="$PROJECT_ROOT/.launchsecure/launch-council.lock"
155
+ if [ -f "$lock" ]; then
156
+ local pid; pid=$(pid_from_lockfile "$lock")
157
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
158
+ _state="green"; _display="${GREEN}council${RESET}"; return
159
+ fi
160
+ fi
161
+ _state="orange"; _display="${ORANGE}council${RESET}"
162
+ }
163
+
164
+ show_list=$(echo "$show" | tr ',' ' ')
165
+ total=0
166
+ up=0
167
+ out=""
168
+ for d in $show_list; do
169
+ case "$d" in
170
+ recall) chip_recall ;;
171
+ chart) chip_chart ;;
172
+ deck) chip_deck ;;
173
+ council) chip_council ;;
174
+ *) continue ;;
175
+ esac
176
+ total=$((total + 1))
177
+ [ "$_state" = "green" ] && up=$((up + 1))
178
+ if [ "$compact" = "0" ]; then
179
+ if [ -z "$out" ]; then out="$_display"; else out="${out} · ${_display}"; fi
180
+ fi
181
+ done
182
+
183
+ if [ "$compact" = "1" ]; then
184
+ if [ "$total" -eq 0 ]; then exit 0; fi
185
+ if [ "$up" -eq "$total" ]; then
186
+ printf '%smcp %d/%d%s' "$GREEN" "$up" "$total" "$RESET"
187
+ else
188
+ printf '%smcp %d/%d%s' "$RED" "$up" "$total" "$RESET"
189
+ fi
190
+ else
191
+ printf '%s' "$out"
192
+ fi
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # launch-kit statusline wrapper.
3
+ # Runs the user's original statusline command, then appends MCP chip output.
4
+ # Reads Claude Code's statusline JSON from stdin once, fans it out.
5
+ #
6
+ # The original command is stored under `_launchKitStatuslineOriginal.command`
7
+ # in ~/.claude/settings.json (written by `launch-kit statusline activate`).
8
+ # `launch-kit statusline deactivate` restores it to `statusLine.command`.
9
+ set -u
10
+
11
+ input=$(cat)
12
+ settings="$HOME/.claude/settings.json"
13
+
14
+ original_cmd=""
15
+ if [ -f "$settings" ] && command -v jq >/dev/null 2>&1; then
16
+ original_cmd=$(jq -r '._launchKitStatuslineOriginal.command // empty' "$settings" 2>/dev/null || true)
17
+ fi
18
+
19
+ original_output=""
20
+ if [ -n "$original_cmd" ]; then
21
+ original_output=$(printf '%s' "$input" | bash -c "$original_cmd" 2>/dev/null || true)
22
+ fi
23
+
24
+ cwd=""
25
+ if command -v jq >/dev/null 2>&1; then
26
+ cwd=$(printf '%s' "$input" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null || true)
27
+ fi
28
+
29
+ chips=""
30
+ if [ -x "$HOME/.launchsecure/statusline-mcp.sh" ]; then
31
+ # Build args array conditionally; `set -u` + empty `"${args[@]}"` would
32
+ # error on bash 4 (default macOS bash), so test length before expanding.
33
+ args=()
34
+ [ -n "${LK_STATUSLINE_SHOW:-}" ] && args+=("--show=$LK_STATUSLINE_SHOW")
35
+ [ "${LK_STATUSLINE_COMPACT:-0}" = "1" ] && args+=("--compact")
36
+ if [ "${#args[@]}" -eq 0 ]; then
37
+ chips=$(LK_STATUSLINE_CWD="$cwd" "$HOME/.launchsecure/statusline-mcp.sh" 2>/dev/null || true)
38
+ else
39
+ chips=$(LK_STATUSLINE_CWD="$cwd" "$HOME/.launchsecure/statusline-mcp.sh" "${args[@]}" 2>/dev/null || true)
40
+ fi
41
+ fi
42
+
43
+ sep=$'\033[2m | \033[0m'
44
+ if [ -n "$original_output" ] && [ -n "$chips" ]; then
45
+ printf '%s%s%s' "$original_output" "$sep" "$chips"
46
+ elif [ -n "$original_output" ]; then
47
+ printf '%s' "$original_output"
48
+ elif [ -n "$chips" ]; then
49
+ printf '%s' "$chips"
50
+ fi