create-egregore 0.19.2 → 0.19.5

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 (38) hide show
  1. package/assets/egregore-launcher.js +3 -1
  2. package/bin/cli.js +4 -1
  3. package/lib/local.js +78 -12
  4. package/lib/setup.js +15 -0
  5. package/package.json +2 -2
  6. package/runtime/codex/.codex/hooks/search-hint.js +65 -0
  7. package/runtime/codex/.codex/hooks.json +13 -0
  8. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +14 -10
  9. package/runtime/codex/.codex/skills/minion/SKILL.md +25 -0
  10. package/runtime/codex/.codex/skills/scroll/SKILL.md +168 -0
  11. package/runtime/codex/.codex/skills/scroll/agents/openai.yaml +4 -0
  12. package/runtime/codex/.codex/skills/scroll/assets/shell.html +542 -0
  13. package/runtime/codex/.codex/skills/search/SKILL.md +56 -0
  14. package/runtime/codex/.codex/skills/session-view/SKILL.md +25 -0
  15. package/runtime/codex/.codex/skills/thread/SKILL.md +25 -0
  16. package/runtime/codex/.codex/skills/view/SKILL.md +147 -32
  17. package/runtime/codex/.codex/skills/view/agents/openai.yaml +4 -0
  18. package/runtime/codex/.codex/skills/view/assets/compose-scaffold.html +221 -0
  19. package/runtime/codex/.codex/spec-manifest.json +3 -3
  20. package/runtime/codex/AGENTS.md +24 -3
  21. package/runtime/codex/bin/activity-data.sh +46 -4
  22. package/runtime/codex/bin/agent.sh +10 -2
  23. package/runtime/codex/bin/codex-install-skills.sh +1 -1
  24. package/runtime/codex/bin/codex-render-spec.mjs +18 -1
  25. package/runtime/codex/bin/codex-session-start.sh +1 -1
  26. package/runtime/codex/bin/codex-skill-render.mjs +13 -7
  27. package/runtime/codex/bin/codex-sync-skills.sh +4 -0
  28. package/runtime/codex/bin/graph-op.sh +56 -5
  29. package/runtime/codex/bin/graph.sh +36 -0
  30. package/runtime/codex/bin/handoff-run.sh +157 -7
  31. package/runtime/codex/bin/handoff-save-egregore.sh +6 -1
  32. package/runtime/codex/bin/index-handoff.sh +9 -3
  33. package/runtime/codex/bin/lib/context.sh +89 -17
  34. package/runtime/codex/bin/lib/dashboard-artifact.sh +1 -1
  35. package/runtime/codex/bin/lib/git-sync.sh +35 -9
  36. package/runtime/codex/bin/lib/greeting.sh +25 -3
  37. package/runtime/codex/bin/publish-artifact.sh +29 -1
  38. package/runtime/codex/manifest.json +16 -1
@@ -1,41 +1,156 @@
1
1
  ---
2
2
  name: view
3
- description: 'Fallback Codex adapter for the Egregore view workflow.'
3
+ description: 'Render Egregore data and documents as branded browser artifacts. Use when the user invokes /view or $view, asks to show something visually, open it in a browser, make a document readable or presentable, render a quest/handoff/activity/board/network, or synthesize a visual artifact from a topic.'
4
4
  ---
5
5
 
6
- <!-- generated-by: bin/codex-sync-skills.sh -->
6
+ # Egregore View
7
7
 
8
- # Egregore view Adapter
8
+ Native Codex Egregore skill. Use only inside an Egregore checkout.
9
9
 
10
- This is fallback coverage for a long-tail Egregore workflow that has not been
11
- ported to a hand-written Codex-native skill yet.
12
-
13
- Use the project shell and filesystem directly. Do not invoke Claude Code
14
- commands. Translate interactive choices to structured Codex question tooling
15
- when it is available; otherwise render compact numbered choices with an
16
- `Other:` option and wait for the user.
10
+ Resolve a file or live Egregore surface, render it with the in-repo artifact
11
+ tooling, open it, and report the concrete output path. Run the workflow inline;
12
+ do not invoke Claude Code commands or delegate through Loom.
17
13
 
18
14
  ## Structured UX parity
19
15
 
20
- This workflow has a Claude skill with user-visible structured output. After
21
- reading `.claude/skills/view/SKILL.md`, reproduce the same visible UX in Codex:
22
-
23
- - Preserve TUI boxes, markdown tables, rich cards, browser artifact rendering,
24
- exact confirmation blocks, and "no preamble" rules from the source skill.
25
- - Use the source skill's frame width, section order, labels, status footer,
26
- and examples as the contract for the final response.
27
- - Never replace a required box/table/card/artifact view with a prose summary
28
- unless the user explicitly asks for a summary.
29
- - When the source says to output a TUI box directly, paste that box as the
30
- visible response, preferably in a `text` fenced block.
31
- - Never show raw JSON, raw command output, or unformatted script output when
32
- the source skill requires formatted status or rendered output.
33
-
34
- 1. Read `.claude/skills/view/SKILL.md` for the workflow details.
35
- 2. Run the referenced `bin/` scripts directly from Codex.
36
- 3. Treat graph, publish, and notify steps as best-effort unless that workflow
37
- explicitly says they are required.
38
- 4. Keep local-mode behavior filesystem-first and avoid graph or notification
39
- calls when `egregore.json` declares `"mode": "local"`.
40
- 5. Never call the deprecated `egregore-handoff` CLI for Egregore project
41
- handoffs.
16
+ The visible result is the browser artifact and a compact receipt. Preserve the
17
+ typed artifact templates, designed document composition, exact file path, and
18
+ stable board URL when available. Do not replace the artifact with a prose
19
+ summary or expose raw renderer output.
20
+
21
+ ## Resolve the request
22
+
23
+ 1. Verify `bin/agent.sh` exists. If not, say this is not an Egregore checkout.
24
+ 2. Parse an optional type followed by a name:
25
+ - `quest` from `memory/quests/`
26
+ - `handoff` from `memory/handoffs/` recursively
27
+ - `activity`, `board`, or `network` with no source file
28
+ - `document` for any markdown file
29
+ 3. Treat an argument containing `/` or ending in `.md` as a direct path. Use it
30
+ if it exists and infer the type from its directory.
31
+ 4. With no explicit type, search in this order: quests, handoffs, knowledge.
32
+ Prefer exact basename matches, then case-insensitive partial matches. For
33
+ handoffs, prefer the newest matching dated path.
34
+ 5. If several files match, use structured Codex question tooling when
35
+ available; otherwise ask one concise plain-text question listing the paths.
36
+ 6. If no file matches and the input is a topic or question, synthesize a
37
+ temporary markdown document from relevant memory, repository files, and
38
+ conversation context at
39
+ `/tmp/egregore-artifacts/synthesized-<slug>.md`. If the input clearly names
40
+ a missing file, report it as missing instead of inventing a document.
41
+
42
+ ## Select the renderer
43
+
44
+ Prefer the checked-out package so local changes are exercised and no package
45
+ download is needed:
46
+
47
+ ```bash
48
+ RENDER="npx egregore-artifacts@latest"
49
+ if [ -f packages/egregore-artifacts/bin/cli.js ] && \
50
+ [ -d packages/egregore-artifacts/node_modules/react ]; then
51
+ RENDER="node packages/egregore-artifacts/bin/cli.js"
52
+ fi
53
+ ```
54
+
55
+ If the local package exists without dependencies, install its dependencies or
56
+ use the `npx` fallback. Do not block a render on optional connected services.
57
+
58
+ ## Render typed artifacts
59
+
60
+ Run the matching command:
61
+
62
+ ```bash
63
+ $RENDER quest <quest-file>
64
+ $RENDER handoff <handoff-file>
65
+ $RENDER activity
66
+ $RENDER board
67
+ $RENDER network
68
+ $RENDER document <markdown-file>
69
+ ```
70
+
71
+ Use the renderer's reported HTML path rather than guessing it.
72
+
73
+ For documents, walk the generative-UI synthesis graph in
74
+ `packages/design-system/generative-ui/skill/SKILL.md`: select one id each for
75
+ objective, audience, register, palette, and grammar from the document's
76
+ substance, record one line of judgment for each, resolve the brief, and pass it
77
+ with `--brief`. If that layer is unavailable, render without the brief.
78
+
79
+ ## Compose flagship documents
80
+
81
+ Composition is the default for a multi-section strategy, briefing, explainer,
82
+ analysis, decision document, or anything explicitly requested as composed,
83
+ presentable, client-facing, designed, or `--compose`. Use the fast renderer for
84
+ typed artifacts, short utility documents, `--fast`, or `--floor`.
85
+
86
+ For composition:
87
+
88
+ 1. Read the source document in full.
89
+ 2. Copy `.codex/skills/view/assets/compose-scaffold.html` to
90
+ `/tmp/egregore-artifacts/composed-<slug>.html`.
91
+ 3. Fill every placeholder and walk the generative-UI graph for the manifest's
92
+ register, palette, grammar, and trail.
93
+ 4. Transform rather than mirror the markdown:
94
+ - write a two-tone claim-led hero and italic standfirst;
95
+ - make section headings state findings, not repeat source headings;
96
+ - choose components by meaning: ledger for Q/A, claims for numbers, panels
97
+ for options, steps for sequences, feature rows for capability/status,
98
+ threads for decisions, gap cards for negatives, and a readout for the
99
+ honest bottom line;
100
+ - create one anchored navigation item per composed section.
101
+ 5. Keep every theme-sensitive color as `var(--token)`. Never emit inline hex
102
+ colors in composed content. Preserve the scaffold's no-flash theme restore,
103
+ `<html data-theme>`, and light/auto/dark behavior.
104
+ 6. Verify no placeholders remain, open the HTML with the platform browser
105
+ opener, and report `Renderer: composed (band 5, inline)`.
106
+
107
+ ## Publish the stable board
108
+
109
+ After `$RENDER board`, publish best-effort only when `egregore.json` declares a
110
+ connected configuration:
111
+
112
+ ```bash
113
+ _API_URL=$(jq -r '.api_url // empty' egregore.json 2>/dev/null)
114
+ _MODE=$(jq -r '.mode // empty' egregore.json 2>/dev/null)
115
+ if [ -n "$_API_URL" ] && [ "$_MODE" != "local" ]; then
116
+ ORG_NAME=$(jq -r '.org_name // .slug' egregore.json)
117
+ bash bin/publish-artifact.sh board memory/board/board.json \
118
+ --id board \
119
+ --title "Project Board — $ORG_NAME" \
120
+ --author "$ORG_NAME" \
121
+ --description "Latest board for $ORG_NAME" 2>/dev/null &
122
+ fi
123
+ ```
124
+
125
+ The local artifact remains the successful result if publishing fails. In local
126
+ mode, do not call graph or notification services.
127
+
128
+ ## Report
129
+
130
+ Return only the useful receipt after the browser opens:
131
+
132
+ ```text
133
+ ✓ Artifact opened in browser
134
+ File: /tmp/egregore-artifacts/<rendered-file>.html
135
+ ```
136
+
137
+ For a connected board, append:
138
+
139
+ ```text
140
+ ◆ https://egregore.xyz/view/<org-slug>/board (stable — refresh for latest)
141
+ ```
142
+
143
+ Emit telemetry best-effort after completion:
144
+
145
+ ```bash
146
+ bash bin/telemetry.sh emit "command" '{"command":"view"}' 2>/dev/null &
147
+ ```
148
+
149
+ ## Rules
150
+
151
+ - Keep filesystem resolution sufficient in local mode; the graph is optional.
152
+ - Preserve auto-linked backtick references handled by
153
+ `bin/publish-references.sh` when publishing markdown.
154
+ - Never install a global renderer package as the first choice.
155
+ - Never call the deprecated `egregore-handoff` CLI.
156
+ - Never claim publishing succeeded unless a URL was actually returned.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Egregore View"
3
+ short_description: "Render Egregore artifacts in the browser"
4
+ default_prompt: "Use $view to render this as a branded browser artifact."
@@ -0,0 +1,221 @@
1
+ <!DOCTYPE html>
2
+ <!--
3
+ EGREGORE COMPOSITION SCAFFOLD — the reusable chrome + component kit for
4
+ band-5 composed artifacts (`$view <doc> --compose`).
5
+
6
+ How to use: copy this file, then
7
+ 1. fill <title>, the manifest `register`/`grammar`, nav brand + links,
8
+ the hero (series / h1 / standfirst / chips), and the footer.
9
+ 2. replace the <!-- COMPONENTS: … --> region with composed sections built
10
+ from the component kit below — do NOT mirror the markdown; transform
11
+ the substance into the grammar (statement titles, panels, cards, …).
12
+ 3. every theme-sensitive color MUST be a var(--token). Never inline hex
13
+ in content — dark mode breaks. The kit already does this.
14
+ The token blocks (vellum/nocturne) + toggle JS are complete — leave them.
15
+ -->
16
+ <html lang="en" data-theme="vellum" data-theme-pair="meridian" data-theme-mode="auto">
17
+ <head>
18
+ <meta charset="utf-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1">
20
+ <title>__TITLE__ — Egregore</title>
21
+ <link rel="preconnect" href="https://fonts.googleapis.com">
22
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
23
+ <link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,300..900;1,9..144,400..700&family=Hanken+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
24
+ <script type="application/json" id="egregore-design-manifest">{"system":"egregore-design-convention","version":"0.3.0","lineage":"meridian","pair":"meridian","mode":"auto","resolvedTheme":"vellum","target":"browser","register":"__REGISTER__","grammar":"__GRAMMAR__","composed":true}</script>
25
+ <style>
26
+ :root{--serif:"Fraunces",Georgia,serif;--sans:"Hanken Grotesk",system-ui,sans-serif;--mono:"IBM Plex Mono",ui-monospace,monospace;--ease:cubic-bezier(.22,.61,.36,1)}
27
+ [data-theme="vellum"]{--paper:#eceef2;--paper-2:#e2e6ec;--card:#f8fafc;--ink:#15212e;--ink-soft:#4d5a68;--ink-faint:#8492a0;--line:#cbd4de;--line-soft:#dde3ea;--prussian:#2c4a6b;--prussian-bright:#3f74ad;--brass:#9a7521;--brass-bright:#c0962f;--teal:#1d7a6e;--rose:#a8493e;--t1:#9a7521;--t1-bg:#f3ead0;--t2:#1d7a6e;--t2-bg:#ddefea;--t3:#a8493e;--t3-bg:#f4ddd7;--contour:#bcc6d2;--shadow:0 1px 2px rgba(21,33,46,.06),0 12px 34px -18px rgba(21,33,46,.34)}
28
+ [data-theme="nocturne"]{--paper:#0e151d;--paper-2:#131c26;--card:#18222e;--ink:#e7ecf2;--ink-soft:#aab6c2;--ink-faint:#76838f;--line:#2a3744;--line-soft:#202b36;--prussian:#5b8cc0;--prussian-bright:#7aa9d8;--brass:#d8b25a;--brass-bright:#e7c878;--teal:#3fb0a0;--rose:#d2796b;--t1:#d8b25a;--t1-bg:#2c2616;--t2:#3fb0a0;--t2-bg:#152b29;--t3:#d2796b;--t3-bg:#2c1c18;--contour:#283543;--shadow:0 1px 2px rgba(0,0,0,.4),0 18px 40px -20px rgba(0,0,0,.7)}
29
+ [data-theme="loam"]{--paper:#f0ece2;--paper-2:#e7e1d3;--card:#faf7f0;--ink:#241d12;--ink-soft:#5c4f3a;--ink-faint:#8f8168;--line:#d3c9b4;--line-soft:#e2dac8;--prussian:#5a6b3a;--brass:#9a6b21;--teal:#3a7a5c;--rose:#a85a3e;--t1:#9a6b21;--t1-bg:#efe2c8;--t2:#3a7a5c;--t2-bg:#dcebe0;--t3:#a85a3e;--t3-bg:#f2ded4;--contour:#c8bca0;--shadow:0 1px 2px rgba(36,29,18,.06),0 12px 34px -18px rgba(36,29,18,.3)}
30
+
31
+ /* ── base ── */
32
+ *{box-sizing:border-box;margin:0;padding:0}
33
+ html{scroll-behavior:smooth}
34
+ body{background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:16.5px;line-height:1.62;-webkit-font-smoothing:antialiased;transition:background .3s var(--ease),color .3s var(--ease)}
35
+ a{color:var(--prussian-bright);text-decoration:none}
36
+ .wrap{max-width:1080px;margin:0 auto;padding:0 32px}
37
+ .col{max-width:860px}
38
+ .contours{position:fixed;inset:0;z-index:0;pointer-events:none;opacity:.5}
39
+ .contours path{fill:none;stroke:var(--contour);stroke-width:1}
40
+ main{position:relative;z-index:1}
41
+ .rule{border:none;border-top:1px solid var(--line-soft);margin:60px 0 0}
42
+ code{font-family:var(--mono);font-size:.82em;background:var(--paper-2);border:1px solid var(--line-soft);border-radius:5px;padding:1px 6px;color:var(--prussian-bright)}
43
+ p{color:var(--ink-soft)} p b,li b,.lede b{color:var(--ink);font-weight:600}
44
+
45
+ /* ── nav (sticky section rail) ── */
46
+ nav{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:28px;padding:13px 32px;background:color-mix(in srgb,var(--paper) 86%,transparent);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--line-soft)}
47
+ nav .brand{font:500 .68rem/1 var(--mono);letter-spacing:2px;text-transform:uppercase;color:var(--ink-soft);white-space:nowrap}
48
+ nav .brand b{color:var(--prussian-bright);font-weight:600}
49
+ nav .links{display:flex;gap:20px;margin-left:auto;overflow-x:auto;scrollbar-width:none}
50
+ nav .links::-webkit-scrollbar{display:none}
51
+ nav .links a{font:400 .7rem/1 var(--mono);color:var(--ink-soft);white-space:nowrap;padding:4px 0;border-bottom:1px solid transparent}
52
+ nav .links a:hover{color:var(--ink);border-bottom-color:var(--t1)}
53
+ .toggle{flex:0 0 auto;font:500 .66rem/1 var(--mono);letter-spacing:.5px;color:var(--ink-soft);background:var(--card);border:1px solid var(--line);border-radius:20px;padding:6px 12px;cursor:pointer}
54
+ .toggle:hover{color:var(--ink);border-color:var(--ink-faint)}
55
+
56
+ /* ── hero ── */
57
+ header.hero{padding:92px 0 60px}
58
+ .series{display:flex;align-items:center;gap:14px;font:500 .7rem/1 var(--mono);letter-spacing:2.5px;text-transform:uppercase;color:var(--t1);margin-bottom:32px}
59
+ .series::before{content:"";width:44px;height:1px;background:var(--t1)}
60
+ h1{font-family:var(--serif);font-weight:420;font-size:clamp(2.6rem,5.4vw,4rem);line-height:1.06;letter-spacing:-.02em;max-width:19ch}
61
+ h1 em{font-style:italic;font-weight:430;color:var(--prussian-bright)}
62
+ .standfirst{font-family:var(--serif);font-style:italic;font-size:1.3rem;line-height:1.44;color:var(--ink-soft);max-width:56ch;margin:26px 0 28px}
63
+
64
+ /* ── section frame ── */
65
+ section{padding:72px 0 6px}
66
+ .seclabel{display:flex;align-items:center;gap:10px;font:500 .72rem/1 var(--mono);letter-spacing:2.5px;text-transform:uppercase;color:var(--t1);margin-bottom:16px}
67
+ .seclabel .n{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:1px solid var(--t1);border-radius:50%;font-size:.62rem}
68
+ h2{font-family:var(--serif);font-weight:430;font-size:clamp(1.9rem,3.4vw,2.6rem);line-height:1.12;letter-spacing:-.015em;max-width:24ch;margin-bottom:12px}
69
+ .lede{font-size:1.05rem;color:var(--ink-soft);max-width:62ch;margin-bottom:24px}
70
+
71
+ /* ── chips / pills / badges ── */
72
+ .chips{display:flex;flex-wrap:wrap;gap:9px}
73
+ .chip{font:500 .68rem/1 var(--mono);letter-spacing:.4px;padding:7px 13px;border-radius:20px;border:1px solid var(--line);background:var(--card);color:var(--ink-soft);white-space:nowrap}
74
+ .chip.ok{border-color:var(--t2);color:var(--t2)} .chip.ok::before{content:"● ";font-size:.6rem}
75
+ .chip.brass{border-color:var(--t1);color:var(--t1)} .chip.brass::before{content:"◇ ";font-size:.66rem}
76
+ .chip.warn{border-color:var(--t3);color:var(--t3)}
77
+ .bd{display:inline-flex;align-items:center;gap:5px;font:600 .64rem/1 var(--mono);letter-spacing:.4px;text-transform:uppercase;padding:3px 8px;border-radius:5px;white-space:nowrap}
78
+ .bd.ok{background:var(--t2-bg);color:var(--t2)} .bd.half{background:var(--t1-bg);color:var(--t1)} .bd.no{background:var(--t3-bg);color:var(--t3)} .bd.neu{background:var(--paper-2);color:var(--ink-faint)}
79
+
80
+ /* ── claim / stat cards ── */
81
+ .claims{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin:26px 0 8px}
82
+ .claims.four{grid-template-columns:repeat(4,1fr)} .claims.two{grid-template-columns:1fr 1fr}
83
+ @media(max-width:760px){.claims,.claims.four,.claims.two{grid-template-columns:1fr 1fr}}
84
+ .claim{background:var(--card);border:1px solid var(--line-soft);border-radius:12px;padding:20px 22px;box-shadow:var(--shadow)}
85
+ .claim .v{font-family:var(--serif);font-size:1.55rem;line-height:1.1;color:var(--t1)}
86
+ .claim.teal .v{color:var(--t2)} .claim.rose .v{color:var(--t3)}
87
+ .claim .l{font-size:.84rem;color:var(--ink-soft);margin-top:8px;line-height:1.5}
88
+
89
+ /* ── labeled tag card ── */
90
+ .tagcard{background:var(--card);border:1px solid var(--line-soft);border-radius:14px;padding:22px 26px;margin:14px 0;box-shadow:var(--shadow)}
91
+ .tagcard .tag{display:inline-block;font:600 .62rem/1 var(--mono);letter-spacing:1.5px;text-transform:uppercase;color:#fff;border-radius:6px;padding:5px 9px;margin-right:12px;vertical-align:2px;background:var(--prussian)}
92
+ .tagcard .tag.teal{background:var(--teal)} .tagcard .tag.rose{background:var(--rose)} .tagcard .tag.brass{background:var(--brass)}
93
+ .tagcard h3{display:inline;font-family:var(--serif);font-weight:500;font-size:1.16rem}
94
+ .tagcard p{margin-top:12px;font-size:.95rem}
95
+ .tagcard .verdict{margin-top:12px;font:600 .74rem/1.5 var(--mono);letter-spacing:.3px}
96
+ .tagcard .verdict.ok{color:var(--t2)} .tagcard .verdict.no{color:var(--t3)}
97
+
98
+ /* ── comparison panels (2–3 up) ── */
99
+ .panels{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin:26px 0 10px}
100
+ .panels.three{grid-template-columns:repeat(3,1fr)}
101
+ @media(max-width:760px){.panels,.panels.three{grid-template-columns:1fr}}
102
+ .panel{border:1px solid var(--line);border-radius:14px;overflow:hidden;background:var(--card);box-shadow:var(--shadow);display:flex;flex-direction:column}
103
+ .panel .ph{font:600 .7rem/1 var(--mono);letter-spacing:1.5px;text-transform:uppercase;color:#fff;padding:13px 18px;background:var(--prussian)}
104
+ .panel.b .ph{background:var(--teal)} .panel.c .ph{background:var(--ink-faint)} .panel.warn .ph{background:var(--rose)}
105
+ .panel .pb{padding:16px 20px;font-size:.9rem;color:var(--ink-soft);flex:1}
106
+ .panel .pb b{color:var(--ink)}
107
+ .panel .price{font-family:var(--serif);font-size:1.3rem;color:var(--t1);margin:2px 0 10px}
108
+ .panel ul{list-style:none;margin-top:8px} .panel li{padding:5px 0 5px 18px;position:relative;line-height:1.5}
109
+ .panel li::before{content:"";position:absolute;left:2px;top:12px;width:5px;height:5px;border-radius:50%;background:var(--t1)}
110
+ .panel .pr{display:grid;grid-template-columns:110px 1fr;gap:12px;padding:11px 0;border-bottom:1px solid var(--line-soft)}
111
+ .panel .pr:last-child{border-bottom:none} .panel .pr .k{font:500 .68rem/1.4 var(--mono);color:var(--ink-faint);text-transform:uppercase;letter-spacing:.4px}
112
+ .panel .pf{padding:12px 20px;border-top:1px solid var(--line-soft);font:500 .68rem/1.5 var(--mono);color:var(--ink-faint)}
113
+ .verdict-band{background:var(--t2-bg);border:1px solid var(--t2);border-radius:12px;padding:16px 22px;font:500 .82rem/1.7 var(--mono);color:var(--ink)}
114
+ .verdict-band b{color:var(--t2)}
115
+
116
+ /* ── highlight callout ── */
117
+ .hl{background:var(--t2-bg);border:1px solid var(--t2);border-radius:14px;padding:22px 26px;margin:18px 0}
118
+ .hl.brass{background:var(--t1-bg);border-color:var(--t1)} .hl.rose{background:var(--t3-bg);border-color:var(--t3)}
119
+ .hl .k{font:600 .66rem/1 var(--mono);letter-spacing:1.5px;text-transform:uppercase;color:var(--t2)}
120
+ .hl.brass .k{color:var(--t1)} .hl.rose .k{color:var(--t3)}
121
+ .hl h3{font-family:var(--serif);font-weight:500;font-size:1.3rem;margin:8px 0 10px}
122
+ .hl p{font-size:.96rem} .hl .num{font-family:var(--serif);font-style:italic;color:var(--ink)}
123
+
124
+ /* ── Q/A ledger ── */
125
+ .ledger{margin:24px 0;border-top:1px solid var(--line)}
126
+ .ledger .row{display:grid;grid-template-columns:230px 1fr;gap:24px;padding:16px 4px;border-bottom:1px solid var(--line-soft)}
127
+ @media(max-width:680px){.ledger .row{grid-template-columns:1fr}}
128
+ .ledger .q{font:500 .78rem/1.5 var(--mono);color:var(--ink-faint);padding-top:3px}
129
+ .ledger .a{font-size:.95rem;color:var(--ink-soft)} .ledger .a b{color:var(--ink)}
130
+
131
+ /* ── numbered steps ── */
132
+ .steps{margin:24px 0;counter-reset:st}
133
+ .step{display:grid;grid-template-columns:40px 1fr;gap:16px;padding:14px 0;border-bottom:1px solid var(--line-soft)}
134
+ .step:last-child{border-bottom:none}
135
+ .step .b{counter-increment:st;font-family:var(--serif);font-size:1.25rem;color:var(--t1)} .step .b::before{content:counter(st,decimal-leading-zero)}
136
+ .step h4{font-family:var(--serif);font-weight:500;font-size:1.05rem;margin-bottom:3px} .step h4 .min{font:500 .64rem/1 var(--mono);color:var(--ink-faint);letter-spacing:.5px;margin-left:8px}
137
+ .step p{font-size:.9rem}
138
+
139
+ /* ── feature rows w/ trailing badges ── */
140
+ .feat{display:grid;grid-template-columns:1fr auto;gap:14px 20px;align-items:baseline;padding:14px 0;border-bottom:1px solid var(--line-soft)}
141
+ .feat:last-child{border-bottom:none} .feat .fx{font-size:.98rem;color:var(--ink-soft)} .feat .fx b{color:var(--ink);font-weight:600}
142
+ .feat .fb{display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end}
143
+
144
+ /* ── checkbox thread list (decisions) ── */
145
+ .threads{list-style:none;margin:24px 0}
146
+ .threads li{display:flex;gap:14px;padding:12px 0;border-bottom:1px solid var(--line-soft);font-size:.98rem;color:var(--ink-soft);align-items:baseline}
147
+ .threads li::before{content:"";flex:0 0 auto;width:12px;height:12px;border:1.5px solid var(--t1);border-radius:3px;transform:translateY(1px)}
148
+ .threads b{color:var(--ink)}
149
+
150
+ /* ── gap / negative callout ── */
151
+ .gap{background:var(--t3-bg);border:1px solid var(--t3);border-radius:14px;padding:22px 26px;margin:22px 0}
152
+ .gap h3{font-family:var(--serif);font-weight:500;font-size:1.15rem;color:var(--t3);margin-bottom:12px}
153
+ .gap ul{list-style:none} .gap li{padding:5px 0 5px 20px;position:relative;font-size:.94rem;color:var(--ink-soft)}
154
+ .gap li::before{content:"×";position:absolute;left:2px;color:var(--t3);font-weight:600} .gap li b{color:var(--ink)}
155
+
156
+ /* ── status / summary table ── */
157
+ .sumtable{width:100%;border-collapse:collapse;margin:24px 0;font-size:.9rem}
158
+ .sumtable th{text-align:left;font:600 .64rem/1 var(--mono);letter-spacing:.8px;text-transform:uppercase;color:var(--ink-faint);padding:0 12px 12px;border-bottom:1px solid var(--line)}
159
+ .sumtable th.c,.sumtable td.c{text-align:center}
160
+ .sumtable td{padding:11px 12px;border-bottom:1px solid var(--line-soft);color:var(--ink-soft);vertical-align:middle}
161
+ .sumtable tr td:first-child{color:var(--ink);font-weight:500}
162
+ .sumtable .tier{font:500 .64rem/1 var(--mono);text-transform:uppercase;letter-spacing:.4px;color:var(--ink-faint)}
163
+ .dot{display:inline-block;width:18px;height:18px;line-height:18px;border-radius:50%;font-size:.72rem;font-weight:700}
164
+ .dot.ok{background:var(--t2-bg);color:var(--t2)} .dot.half{background:var(--t1-bg);color:var(--t1)} .dot.no{background:var(--t3-bg);color:var(--t3)} .dot.neu{background:var(--paper-2);color:var(--ink-faint)}
165
+
166
+ /* ── accent-left readout ── */
167
+ .readout{background:var(--card);border:1px solid var(--line);border-left:3px solid var(--t1);border-radius:4px;padding:20px 24px;margin:28px 0;font-size:1rem;color:var(--ink-soft)}
168
+ .readout b{color:var(--ink)}
169
+
170
+ /* ── footer ── */
171
+ footer{padding:64px 0 90px;font:400 .74rem/1.8 var(--mono);color:var(--ink-faint)}
172
+ footer .tags{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}
173
+ footer .tg{border:1px solid var(--line-soft);border-radius:6px;padding:3px 10px;background:var(--card)}
174
+ </style>
175
+ </head>
176
+ <body>
177
+ <svg class="contours" width="100%" height="100%" preserveAspectRatio="none" viewBox="0 0 1400 900"><path d="M-50,160 C300,90 700,240 1450,130"/><path d="M-50,440 C400,360 900,540 1450,420"/><path d="M-50,720 C350,660 800,820 1450,710"/></svg>
178
+ <main>
179
+ <nav>
180
+ <span class="brand">EGREGORE · <b>__BRAND__</b></span>
181
+ <div class="links"><!-- NAV: one <a href="#anchor">Short label</a> per section --></div>
182
+ <button class="toggle" onclick="__t()">◐ <span id="tl">nocturne</span></button>
183
+ </nav>
184
+
185
+ <div class="wrap col">
186
+
187
+ <header class="hero">
188
+ <div class="series">__SERIES_KICKER__</div>
189
+ <h1>__LEAD__. <em>__ACCENT_PHRASE__</em></h1>
190
+ <p class="standfirst">__STANDFIRST__</p>
191
+ <div class="chips"><!-- CHIPS: from status/meta --></div>
192
+ </header>
193
+
194
+ <!-- COMPONENTS: compose sections here. Each section:
195
+ <hr class="rule"><section id="anchor">
196
+ <div class="seclabel"><span class="n">01</span> LABEL</div>
197
+ <h2>Statement title — the finding, not the raw heading.</h2>
198
+ … pick from the kit: .ledger .tagcard .claims .panels+.verdict-band
199
+ .hl .steps .feat .threads .gap .sumtable .readout …
200
+ </section>
201
+ -->
202
+
203
+ <footer>
204
+ __FOOTER_LINE__
205
+ <div class="tags"><span class="tg">__KIND__</span><span class="tg">__AUTHOR__</span><span class="tg">ugi · __TRAIL__</span><span class="tg">composed</span></div>
206
+ </footer>
207
+
208
+ </div>
209
+ </main>
210
+ <script>
211
+ (function(){
212
+ var saved=localStorage.getItem('eg-theme-mode');
213
+ var prefersDark=window.matchMedia('(prefers-color-scheme: dark)').matches;
214
+ var t=saved==='dark'||(saved!=='light'&&prefersDark)?'nocturne':'vellum';
215
+ apply(t);
216
+ window.__t=function(){apply(document.documentElement.dataset.theme==='vellum'?'nocturne':'vellum',true)};
217
+ function apply(theme,persist){document.documentElement.dataset.theme=theme;var el=document.getElementById('tl');if(el)el.textContent=theme==='vellum'?'nocturne':'vellum';if(persist)localStorage.setItem('eg-theme-mode',theme==='nocturne'?'dark':'light')}
218
+ })();
219
+ </script>
220
+ </body>
221
+ </html>
@@ -77,13 +77,13 @@
77
77
  {
78
78
  "section": "Working Conventions",
79
79
  "action": "keep",
80
- "sourceHash": "37a5318e9ee0",
80
+ "sourceHash": "2e4ec18b72a6",
81
81
  "note": "Kept verbatim."
82
82
  },
83
83
  {
84
84
  "section": "Command Awareness",
85
85
  "action": "keep",
86
- "sourceHash": "d3ea48ebf938",
86
+ "sourceHash": "e1db878ccaee",
87
87
  "note": "Prepended the Codex command surface (skill tokens, native vs adapter split, $save abstraction); disambiguation map kept with /x -> $x token rendering. Tokens with no Codex skill (e.g. /loop, a Claude Code harness feature) are left as-is."
88
88
  },
89
89
  {
@@ -95,7 +95,7 @@
95
95
  {
96
96
  "section": "Telemetry",
97
97
  "action": "keep",
98
- "sourceHash": "f8b242d0fd64",
98
+ "sourceHash": "f45afa2da8ac",
99
99
  "note": "Kept verbatim — bin/telemetry.sh is runtime-neutral."
100
100
  },
101
101
  {
@@ -29,6 +29,12 @@ portable memory protocol below.
29
29
  Use the runtime-neutral bridge instead of Claude Code slash commands:
30
30
 
31
31
  ```bash
32
+ bin/agent.sh branch --topic "auth review"
33
+ bin/agent.sh save --message "Save: auth review" --topic "auth review"
34
+ bin/agent.sh wrap --from alice --topic "auth review" \
35
+ --summary "Implemented the OAuth callback parser and documented follow-ups." \
36
+ --body "Open threads: review error cases and browser redirects."
37
+
32
38
  bin/agent.sh handoff --from alice --to bob --topic "auth review" \
33
39
  --body "Implemented the OAuth callback parser. Bob should review error cases."
34
40
 
@@ -44,6 +50,17 @@ Each command writes to the existing Git-backed `memory/` repository and pushes
44
50
  when the memory repo has an `origin` remote. Agents that cannot run shell may
45
51
  write the same markdown files directly, following `docs/AGENT-PROTOCOL.md`.
46
52
 
53
+ ## Handoffs vs Emissaries
54
+
55
+ `bin/agent.sh handoff` is the runtime-neutral path for internal team
56
+ session-handoffs: it writes to `memory/handoffs/`, updates the index, and uses
57
+ the same `bin/handoff-run.sh` machinery as Claude Code's `/handoff`.
58
+
59
+ Portable external capsules are **emissaries**, not team handoffs. Claude Code
60
+ routes those through `/emissary`; non-Claude agents should use the
61
+ `egregore-emissary` CLI/skill when it is installed. Do not use the deprecated
62
+ `egregore-handoff` CLI for Egregore project `/handoff` work.
63
+
47
64
  ## Compatibility
48
65
 
49
66
  Claude Code users continue using `/handoff`, `/activity`, `/ask`, `/save`, and
@@ -230,7 +247,7 @@ Repos in `egregore.json` → `repos[]` are cloned as siblings (`../{repo}/`). Ea
230
247
 
231
248
  ## Working Conventions
232
249
 
233
- - Check `memory/knowledge/` before starting unfamiliar work
250
+ - Check memory before starting unfamiliar work — `bash bin/search.sh query "topic"` (hybrid search over all of `memory/`)
234
251
  - Document significant decisions in `memory/knowledge/decisions/`
235
252
  - After substantial sessions, log to `memory/handoffs/` and update `index.md`
236
253
 
@@ -238,12 +255,12 @@ Repos in `egregore.json` → `repos[]` are cloned as siblings (`../{repo}/`). Ea
238
255
 
239
256
  ## Command Awareness
240
257
 
241
- Codex reserves leading `/` for built-ins, so Egregore workflows are **skills**, not slash commands. Invoke them with the matching `$name` skill token or from natural language intent ("show activity", "make a handoff"). Hand-written native Codex skills: `$activity`, `$handoff`, `$wrap`, `$announce`, `$harvest`, `$the-spiral`, `$dashboard`, `$deep-reflect`, `$quest`, `$invite`, `$ask`, `$save`. Every other workflow has a generated adapter skill of the same name. `$save` is the user-facing abstraction for committing, pushing, opening or reusing pull requests, and syncing memory — never make users manage the git workflow by hand.
258
+ Codex reserves leading `/` for built-ins, so Egregore workflows are **skills**, not slash commands. Invoke them with the matching `$name` skill token or from natural language intent ("show activity", "make a handoff"). Hand-written native Codex skills: `$activity`, `$handoff`, `$wrap`, `$announce`, `$harvest`, `$the-spiral`, `$dashboard`, `$deep-reflect`, `$quest`, `$invite`, `$ask`, `$save`, `$view`, and `$scroll`. Every other workflow has a generated adapter skill of the same name. `$save` is the user-facing abstraction for committing, pushing, opening or reusing pull requests, and syncing memory — never make users manage the git workflow by hand.
242
259
 
243
260
  Invoke commands from user intent — don't wait for the slash. Each command file has a `## When to invoke` section. Load it for the full spec.
244
261
 
245
262
  **Core loop** — `$activity` `$dashboard` `$handoff` `$wrap` `$save` `$reflect` `$todo`
246
- **Knowledge** — `$deep-reflect` `$archive` `$note` `$add` `$meeting` `$ingest`
263
+ **Knowledge** — `$search` `$deep-reflect` `$archive` `$note` `$add` `$meeting` `$ingest` `$scroll`
247
264
  **Identity** — `$me` (view profile or set display name)
248
265
  **Coordination** — `$ask` `$quest` `$issue` `$invite` `$delete-user` `$announce`
249
266
  **Connectors** — `$telegram-connect` (Telegram group setup)
@@ -253,6 +270,9 @@ Invoke commands from user intent — don't wait for the slash. Each command file
253
270
 
254
271
  **Disambiguation:**
255
272
  - Knowledge: `$reflect` (share-ready) · `$note` (half-baked) · `$deep-reflect` (cross-reference) · `$archive` (AI patterns)
273
+ - Finding things — the retrieval layers: **Grep/ls** (Claude Code / Codex file ops — always available, OSS) · **`bin/graph.sh`** (indexed relevance + relationships/status — paid). Use grep proactively; the graph deepens it when connected. (Semantic `$search` is dormant — evals showed it only pays off on massive unstructured data.)
274
+ - Finding **something you generated** (a scroll, handoff, emissary, decision, any hosted egregore.xyz link): `bash bin/artifacts.sh find <query>`. Every generative surface already records into memory — hosted artifacts self-register into `memory/artifacts/` at publish (and the record is committed+pushed, so it's findable from *any* session, not just the one that made it); handoffs land in `memory/handoffs/`, emissaries in `memory/handoffs/outbound/`, decisions/findings/patterns in `memory/knowledge/`. The finder searches **all** of them, ranks title/topic matches above passing body mentions, tags each hit by type (`[document]`/`[handoff]`/`[emissary]`/`[decision]`), and surfaces the shareable URL. Findable by **content, not just name**, across all three layers — grep-first for OSS, folding in search + graph when available. *"bring me the artifact where I laid out our GTM plan"* → this.
275
+ - Artifacts with questions: `$scroll` (living paper + embedded harvest, updates in place) · `$view` (static render) · `$harvest` (elicitation, no published face)
256
276
  - Status: `$dashboard` (personal) · `$activity` (org-wide)
257
277
  - Ending: `$wrap` (personal closure) · `$handoff` (notes for others) · `$save` (still working)
258
278
  - Tasks: `$todo` (personal) · `$quest` (team exploration) · `$issue` (something broken)
@@ -284,6 +304,7 @@ Privacy-respecting, opt-out telemetry. After every slash command, emit fire-and-
284
304
  `bash bin/telemetry.sh emit "command" '{"command":"save"}' 2>/dev/null &`
285
305
 
286
306
  Never collected: file contents, code, env var values, conversation content.
307
+ Command events may carry optional model/tier/routing/duration fields per `.claude/context/telemetry.md`.
287
308
  On first session (if `telemetry_noticed` not set in state file), mention the notice once, then set `telemetry_noticed: true`. Full spec: `.claude/context/telemetry.md`.
288
309
 
289
310
  ---
@@ -13,6 +13,8 @@ API_KEY=$(grep '^EGREGORE_API_KEY=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -
13
13
  ORG=$(jq -r '.org_name // "Egregore"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "Egregore")
14
14
  DATE=$(date '+%b %d')
15
15
 
16
+ source "$SCRIPT_DIR/bin/lib/handoff-meta.sh" >/dev/null 2>/dev/null || true
17
+
16
18
  # --- Detect user ---
17
19
  GH_USER="${1:-}"
18
20
  if [ -z "$GH_USER" ]; then
@@ -159,8 +161,8 @@ PRS=$(cat "$TMPDIR/prs.json" 2>/dev/null || echo "[]")
159
161
  echo "$PRS" | jq . >/dev/null 2>&1 || PRS="[]"
160
162
 
161
163
  # Resolve PR author GitHub logins -> egregore display names. `gh pr list` returns
162
- # the raw github login (e.g. "fcdagdelen"); without this it leaks into activity
163
- # instead of the person's display name ("cem"). Map via the graph when connected;
164
+ # the raw github login (e.g. "alice-gh"); without this it leaks into activity
165
+ # instead of the person's display name ("Alice"). Map via the graph when connected;
164
166
  # fall back to the github profile name, then the login. Author becomes a string.
165
167
  if [ -n "$PRS" ] && [ "$PRS" != "[]" ]; then
166
168
  AUTHOR_MAP='{}'
@@ -182,8 +184,28 @@ DISK_DECISIONS=$(cat "$TMPDIR/disk_decisions.txt" 2>/dev/null || echo "")
182
184
  LOCAL_SESSIONS=$(cat "$TMPDIR/local_sessions.json" 2>/dev/null || echo '{"my_sessions":[],"team_sessions":[]}')
183
185
  echo "$LOCAL_SESSIONS" | jq . >/dev/null 2>&1 || LOCAL_SESSIONS='{"my_sessions":[],"team_sessions":[]}'
184
186
 
187
+ handoff_metadata_map() {
188
+ local json="$1"
189
+
190
+ if ! declare -F eg_handoff_metadata_json >/dev/null 2>&1; then
191
+ echo "{}"
192
+ return
193
+ fi
194
+
195
+ printf '%s\n' "$json" | jq -r '
196
+ [(.handoffs_to_me[]?.filePath // empty), (.all_handoffs[]?.filePath // empty)]
197
+ | map(select(. != ""))
198
+ | unique[]
199
+ ' 2>/dev/null | while IFS= read -r rel; do
200
+ [ -z "$rel" ] && continue
201
+ file="$SCRIPT_DIR/memory/$rel"
202
+ [ -f "$file" ] || continue
203
+ eg_handoff_metadata_json "$file" | jq -c --arg rel "$rel" '{key:$rel, value:{author:.author, topic:.topic, date:.date}}'
204
+ done | jq -sc 'map({(.key): .value}) | add // {}' 2>/dev/null || echo "{}"
205
+ }
206
+
185
207
  # --- Merge server response + local git/disk data ---
186
- jq -n \
208
+ FINAL_JSON=$(jq -n \
187
209
  --arg org "$ORG" \
188
210
  --arg date "$DATE" \
189
211
  --slurpfile activity "$TMPDIR/activity.json" \
@@ -197,4 +219,24 @@ jq -n \
197
219
  prs: $prs,
198
220
  disk: {handoffs: $disk_handoffs, decisions: $disk_decisions},
199
221
  local_sessions: $local_sessions
200
- }'
222
+ }')
223
+
224
+ HANDOFF_META="$(handoff_metadata_map "$FINAL_JSON")"
225
+ [ -z "$HANDOFF_META" ] && HANDOFF_META="{}"
226
+
227
+ printf '%s\n' "$FINAL_JSON" | jq --argjson handoff_meta "$HANDOFF_META" '
228
+ def enrich_handoff_sender:
229
+ (.filePath // "") as $file
230
+ | if $file != "" and ($handoff_meta[$file] // null) != null then
231
+ ($handoff_meta[$file] // {}) as $meta
232
+ | .author = ($meta.author // .author)
233
+ | .from = ($meta.author // .from)
234
+ | .topic = (.topic // $meta.topic)
235
+ | .date = (.date // $meta.date)
236
+ else
237
+ .
238
+ end;
239
+
240
+ .handoffs_to_me = ((.handoffs_to_me // []) | map(enrich_handoff_sender))
241
+ | .all_handoffs = ((.all_handoffs // []) | map(enrich_handoff_sender))
242
+ '
@@ -28,6 +28,8 @@ Runtime-neutral commands:
28
28
  protocol Print the portable agent protocol
29
29
  sync Pull latest shared memory
30
30
  activity [--for <person>] Show recent handoffs and questions
31
+ search "query" [-n N] [--fast|--semantic]
32
+ Hybrid recall over shared memory (qmd)
31
33
  people List known people from memory/people
32
34
  branch --topic TEXT Create or reuse a task branch/worktree
33
35
  save [--message TEXT] Commit and push current repo + memory changes
@@ -165,8 +167,9 @@ Egregore portable agent protocol
165
167
  4. Use `bin/agent.sh handoff` to leave structured context for another agent.
166
168
  5. Use `bin/agent.sh ask` and `bin/agent.sh answer` for asynchronous questions.
167
169
  6. Run `bin/agent.sh activity --for <person>` to see pending work.
168
- 7. Run `bin/agent.sh branch --topic "<what you are working on>"` before code changes when you need a task branch/worktree.
169
- 8. Run `bin/agent.sh save` and `bin/agent.sh wrap` for portable save/wrap flows.
170
+ 7. Run `bin/agent.sh search "<query>"` to recall shared memory (decisions, handoffs, knowledge) before answering questions about past work.
171
+ 8. Run `bin/agent.sh branch --topic "<what you are working on>"` before code changes when you need a task branch/worktree.
172
+ 9. Run `bin/agent.sh save` and `bin/agent.sh wrap` for portable save/wrap flows.
170
173
 
171
174
  This protocol is runtime-neutral. Claude Code may still use .claude hooks and
172
175
  skills, while Codex or another agent can call this script directly.
@@ -653,11 +656,16 @@ cmd_answer() {
653
656
  echo "answered: memory/$rel"
654
657
  }
655
658
 
659
+ cmd_search() {
660
+ bash "$SCRIPT_DIR/bin/search.sh" query "$@"
661
+ }
662
+
656
663
  case "${1:-}" in
657
664
  --help|-h|help|"") usage ;;
658
665
  protocol) shift; cmd_protocol "$@" ;;
659
666
  sync) shift; cmd_sync "$@" ;;
660
667
  activity) shift; cmd_activity "$@" ;;
668
+ search) shift; cmd_search "$@" ;;
661
669
  people) shift; cmd_people "$@" ;;
662
670
  branch) shift; cmd_branch "$@" ;;
663
671
  save) shift; cmd_save "$@" ;;