@letta-ai/dreams 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/skill.js CHANGED
@@ -37,6 +37,8 @@ exports.skillDir = skillDir;
37
37
  exports.skillFilePath = skillFilePath;
38
38
  exports.initSnapshotTemplatePath = initSnapshotTemplatePath;
39
39
  exports.claudeHooksReferencePath = claudeHooksReferencePath;
40
+ exports.codexHooksReferencePath = codexHooksReferencePath;
41
+ exports.codexSyncHookScriptPath = codexSyncHookScriptPath;
40
42
  exports.installedSkillVersion = installedSkillVersion;
41
43
  exports.installSkill = installSkill;
42
44
  const crypto = __importStar(require("node:crypto"));
@@ -58,6 +60,12 @@ function initSnapshotTemplatePath() {
58
60
  function claudeHooksReferencePath() {
59
61
  return path.join(skillDir(), "references", "hooks-claude.md");
60
62
  }
63
+ function codexHooksReferencePath() {
64
+ return path.join(skillDir(), "references", "hooks-codex.md");
65
+ }
66
+ function codexSyncHookScriptPath() {
67
+ return path.join(skillDir(), "references", "codex-sync-hook.cjs");
68
+ }
61
69
  function skillContent(version) {
62
70
  return `---
63
71
  name: dreams-setup
@@ -67,7 +75,16 @@ version: ${version}
67
75
 
68
76
  # Dreams setup
69
77
 
70
- CLI owns side effects. This skill owns sequencing and user conversation. There is no durable resume pointer: in a new conversation, start at step 1 and skip work that durable facts show is already satisfied. Never blindly repeat mutations. Never print or summarize access tokens, refresh tokens, credential files, or keychain contents.
78
+ CLI owns side effects. This skill owns sequencing and user conversation. There is no durable resume pointer: in a new conversation, start at step 1 and skip work that durable facts show is already satisfied. Never blindly repeat mutations. Never print or summarize access tokens, refresh tokens, credential files, or keychain contents. Never interpolate credential env vars into shell commands, even partially.
79
+
80
+ ## Invariants
81
+
82
+ - Quote absolute \`cli_path\` from onboard in every hook/command.
83
+ - Never print tokens. Never invent teammate emails — derive them from git (step 3).
84
+ - Deny-by-default: without an approved directory, nothing uploads. Do not offer “continue without approving a directory.”
85
+ - Live hooks require consent. Do not offer “skip hooks and continue setup” — if the user declines hooks, exit setup (they can re-enter later).
86
+ - SessionStart → \`dreams wake\`; Stop → \`dreams sync\`. Do not install \`sync\` on SessionStart.
87
+ - Prefer \`dreams hooks install --dry-run\` before writing hook files; CLI owns the merge.
71
88
 
72
89
  ## Step 0 — Local bootstrap (optional repair)
73
90
 
@@ -81,105 +98,144 @@ npx @letta-ai/dreams@latest onboard --json
81
98
 
82
99
  ## Step 1 — Authenticate
83
100
 
84
- 1. Run \`dreams auth status --json\`.
85
- 2. If unauthenticated, run \`dreams auth login --json\`, wait for browser approval (JSON Lines: \`awaiting_approval\` then \`authenticated\`), then re-check \`auth status\`.
86
- 3. Prefer an existing usable credential; do not force a new login when status is already authenticated.
101
+ 1. Run \`"<cli_path>" auth status --json\` (or \`dreams auth status --json\` after onboard).
102
+ 2. Read the inventory, not only the top-level winner:
103
+ - \`credentials[]\` lists every discovered backend (\`env\`, \`keychain\`, \`file\`) with \`present\`, \`selected\`, masked token, and \`reason\`.
104
+ - \`active\` is which credential won for **this process** and why.
105
+ - \`hooks\` explains that live sync inherits the **coding-agent harness environment**. Verify the credential hooks will use, not only an interactive CLI.
106
+ 3. If unauthenticated, run \`dreams auth login --json\`, wait for browser approval (JSON Lines: \`awaiting_approval\` then \`authenticated\`), then re-check \`auth status\`.
107
+ 4. Prefer an existing usable credential; do not force a new login when already authenticated.
87
108
 
88
- ## Step 2 — Detect and register source
109
+ ## Step 2 — Detect and register sources
89
110
 
90
- Run:
111
+ Detect each registered harness with an **explicit** adapter. Do **not** install hooks here.
91
112
 
92
113
  \`\`\`bash
93
- dreams source detect --json
114
+ "<cli_path>" source detect --source claude-code --json
115
+ "<cli_path>" source detect --source codex --json
94
116
  \`\`\`
95
117
 
96
- This detects Claude Code from harness filesystem facts under \`~/.claude\` only (no transcript reads), registers the installation-scoped source with Cloud as \`detected\`, and persists workspace/source ids locally. It is idempotent: re-running refreshes metadata and must not regress lifecycle state. Do not scan, approve roots, or upload in this step.
118
+ Record which harnesses were present (\`harness_present: true\` / exit 0). A missing harness is expected on machines that only run one coding agent continue with whichever registered successfully.
119
+
120
+ ## Step 3 — Init snapshot (seed smarter memory)
97
121
 
98
- ## Step 3Init snapshot
122
+ **Why this step exists (tell the user):** A short workspace snapshot helps Dreams start with useful structure so early memories are more targeted and relevant to their real repos, teammates, and tools — instead of a blank slate. Prefer asking over inventing. Memories are more meaningful when they begin from a smart seed of how this person actually works.
99
123
 
100
- 1. Read the reference template at \`references/init-snapshot.template.json\` in this skill directory.
101
- 2. Gather workspace facts with the user (repos, harnesses, skills, identity signals, teammates, approved roots intent). Prefer asking over inventing.
102
- 3. Write a concrete JSON object (do **not** include teammate \`role\` or \`scope.mode\`; keep \`scope.approved_roots\`).
103
- 4. Show the JSON to the user and obtain explicit approval.
104
- 5. Submit with the narrow CLI transport (not raw HTTP). Workspace id comes from the local binding created in step 2 (override with \`--workspace-id\` only if needed):
124
+ 1. Read \`references/init-snapshot.template.json\` (field ranking is in \`$comment\` / \`$field_guide\`).
125
+ 2. **Approved directories (required for later sync):** identify which work directories should be eligible (usually the user’s main code tree). You will approve them in step 4 with \`source approve-dir\`. Put the intended paths in \`scope.approved_roots\` (JSON key unchanged for Cloud; user-facing language is **directories**, not “roots”).
126
+ 3. **Repositories:** prefer deeper metadata for directories with recent Claude/Codex sessions; name-only or light metadata for the long tail is fine. Deduplicate multi-clone remotes (one logical repo + optional \`local_clone_count\`).
127
+ 4. **Skills:** recursively search the repositories you listed for skill directories (\`.claude/skills\`, \`.agents/skills\`, \`**/skills\` when clearly agent-skill trees). Prefer \`{ directory, skill_count, sample_names }\` over dumping every name when large.
128
+ 5. **Teammates do not AskUserQuestion.** Derive from recent git commits in the target repos (\`git log\` / shortlog). Include display name + email identity signals. **Exclude bots and agents** (e.g. names/emails containing \`[bot]\`, \`dependabot\`, \`github-actions\`, \`renovate\`, \`cursoragent\`, \`noreply\` automation accounts). Cap to a reasonable set of frequent human contributors.
129
+ 6. **Omit unknowns** rather than writing \`0\` or empty arrays for optional fields. Do **not** invent \`letta_agent_count\`, \`letta_memory_artifact_count\`, or \`allowlisted_file_names\`.
130
+ 7. Include every harness detected in step 2. Write concrete JSON (no teammate \`role\`; no \`scope.mode\`).
131
+ 8. Show the JSON and obtain explicit approval, then submit:
105
132
 
106
133
  \`\`\`bash
107
- dreams snapshots create --type init --file /path/to/approved-init.json --client-request-id <unique-id> --json
134
+ "<cli_path>" snapshots create --type init --file /path/to/approved-init.json --client-request-id <unique-id> --json
108
135
  \`\`\`
109
136
 
110
- Reuse the same \`client-request-id\` only when retrying the exact same approved payload. Never resubmit a new or edited snapshot without showing it and getting approval again. Snapshot submit stores the fact; it does not create the shared-memory repository.
137
+ Reuse the same \`client-request-id\` only when retrying the exact same approved payload. Snapshot submit stores the fact; it does not create the shared-memory repository. If snapshot submit fails with auth/role errors, explain briefly and **continue** — directories + hooks + history still deliver value.
138
+
139
+ ## Step 4 — Enable live sync (directories + hooks)
140
+
141
+ This step turns on ongoing sync. There is no separate “activate / sync --force” ceremony afterward.
142
+
143
+ ### 4a — Approve directories (required)
144
+
145
+ Deny-by-default: nothing leaves the machine until at least one work directory is approved.
111
146
 
112
- ## Step 4 Install live sync hooks (Claude Code)
147
+ 1. Explain in plain language: Dreams will only consider transcripts whose working directory sits under an approved folder.
148
+ 2. Obtain consent for specific paths (from step 3 intent / session inventory). **Do not** offer “proceed without approving a directory.”
149
+ 3. Approve with the preferred alias:
113
150
 
114
- Wire Claude Code hooks so live turns wake \`dreams sync\` through the durable CLI path from onboard (\`cli_path\`). There is **no** \`dreams hooks install\` command — you edit \`~/.claude/settings.json\` using \`references/hooks-claude.md\`.
151
+ \`\`\`bash
152
+ "<cli_path>" source approve-dir "<path>" --json
153
+ \`\`\`
115
154
 
116
- **Consent first:** before editing global harness config, explicitly explain that this enables **ongoing transcript sync** on future Claude Code sessions, and obtain the user's approval. Do not install hooks without that consent. Hooks go in **before** root activation so live coverage has no setup gap.
155
+ (\`source approve-root\` still works as a synonym.)
117
156
 
118
- Then:
157
+ ### 4b — Install hooks (required for live sync)
119
158
 
120
- 1. Read \`references/hooks-claude.md\`.
121
- 2. Substitute the absolute \`cli_path\` from onboard into every hook command (quote the path).
122
- 3. Choose the **platform-appropriate** command from the reference (POSIX vs PowerShell). Do not install \`>/dev/null\` redirects on Windows.
123
- 4. Merge **idempotently** into \`~/.claude/settings.json\`: if a Dreams sync hook is already present (command contains \`# dreams-sync-hook\` or the same \`cli_path\` + \`sync\`), **update that handler in place** and leave unrelated hooks untouched — never append duplicates.
124
- 5. Confirm through the durable launcher: \`"<cli_path>" sync status --json\` (hooks themselves should remain silent and exit 0). Read \`sync.effective_state\` (\`paused\` / \`running\` / \`pending\` / \`not_due\` / \`degraded\` / \`idle\`).
159
+ **Consent:** name which harnesses from step 2 will get live sync (Claude Code and/or Codex). Explain this enables **ongoing transcript sync** on future sessions. If the user declines, **exit setup** (do not continue as if onboarding succeeded). Per-harness opt-out is OK when both were detected (wire only the consented ones).
125
160
 
126
- Rules:
161
+ 1. **Dry-run first** — show the user the exact planned commands/files:
127
162
 
128
- - Call the durable \`cli_path\` with \`sync\` (pipe hook stdin). Never \`npx\`, never \`jq\`.
129
- - Suppress stdout/stderr and force exit 0 so harnesses do not treat sync JSON as hook-control output.
130
- - Install Claude Code \`SessionStart\` + \`Stop\` only. **Codex hooks are deferred** until Dreams has a Codex source adapter (today's sync/live hints are Claude-only).
163
+ \`\`\`bash
164
+ "<cli_path>" hooks install --dry-run --json
165
+ # or per source:
166
+ "<cli_path>" hooks install --source claude-code --dry-run --json
167
+ "<cli_path>" hooks install --source codex --dry-run --json
168
+ \`\`\`
131
169
 
132
- ### Hook diagnostics & repair
170
+ 2. After approval, install (CLI owns the merge — do not hand-edit when the CLI succeeds):
133
171
 
134
- When live sync seems broken, use this ladder (do **not** put version checks inside the hot hook command):
172
+ \`\`\`bash
173
+ "<cli_path>" hooks install --json
174
+ \`\`\`
175
+
176
+ 3. Confirm wiring from references if you need detail: \`references/hooks-claude.md\`, \`references/hooks-codex.md\`, and the Codex launcher \`references/codex-sync-hook.cjs\`.
177
+ - **SessionStart → \`wake\`** (CLI/skill repair + kick pending backfill/sync worker)
178
+ - **Stop → \`sync\`** (live turn; stdin session hint)
179
+ 4. For Codex: ask the user to run \`/hooks\`, review, and **trust** the handlers. File write ≠ trusted ≠ Cloud upload succeeded.
135
180
 
136
- 1. Verify the durable launcher itself: \`"<cli_path>" status --json\` (expects a ready CLI + skill paths). A wrong or nonexistent path means Dreams never starts, so **no** sync diagnostics can be recorded — re-run \`npx @letta-ai/dreams@latest onboard --json\` and re-substitute the new absolute \`cli_path\` into hooks.
137
- 2. Interpret local sync diagnostics: \`"<cli_path>" sync status --json\`
138
- - \`effective_state: "paused"\` → expected no-op until \`sync resume\`
139
- - \`not_due\` → cadence/backoff (including a retained trigger waiting for \`next_due_at\`); quiet hook wakes are expected
140
- - \`pending\` → trigger is runnable now (forced, cadence due, or retry due) but no worker lease is held yet
141
- - \`running\` → detached worker lease is active
142
- - \`degraded\` → inspect \`last_error_code\` / \`last_error_message\` (structured \`spawn_failed\` when the parent could not detach a worker)
143
- - \`idle\` → no pause, no pending trigger, and nothing waiting on cadence
144
- 3. Silence from hooks is **required** (exit 0, no stdout/stderr). Silence means “don’t interrupt Claude,” **not** proof of a successful Cloud upload.
181
+ ### Hook diagnostics (reference)
145
182
 
146
- ## Step 5 Activate live sync
183
+ When live sync seems broken:
147
184
 
148
- With user consent for privacy roots:
185
+ 1. \`"<cli_path>" status --json\`
186
+ 2. \`"<cli_path>" sync status --source claude-code --json\` / \`--source codex\`
187
+ - \`paused\` / \`not_due\` → expected quiet wake
188
+ - \`pending\` / \`running\` → work in flight
189
+ - \`degraded\` → inspect \`last_error_code\` (e.g. \`spawn_failed\`)
190
+ 3. Claude hooks must stay silent (exit 0). Codex launcher prints only \`{}\`.
149
191
 
150
- 1. Approve privacy roots: \`"<cli_path>" source approve-root "<path>" --json\` (repeat as needed). Deny-by-default: nothing leaves the machine until roots are approved.
151
- 2. Wake live-only scheduling through the durable launcher:
152
- \`"<cli_path>" sync --force --json\`
153
- This records a durable trigger and may spawn the detached worker. It does **not** run an unrestricted historical scan — live mode only touches already-tracked sessions plus a validated current-session hint from hooks. Do **not** run \`dreams source scan\` here (it performs unrestricted historical discovery under approved roots). Do **not** invoke \`dreams upload\` separately during activation — \`sync\` owns live-first upload ordering for whatever is already queued.
154
- 3. Treat this wake as a **local scheduling check**. Step 2's \`source detect\` already exercised Cloud registration; the first real hook-driven upload (when the user has an active Claude session under an approved root) confirms the end-to-end path. Do not present \`sync --force\` as a Cloud smoke test — on a fresh device with nothing queued it may perform no network I/O.
192
+ ## Step 5 Optional: import recent history
155
193
 
156
- ## Step 6 Optional: import recent history in the background
194
+ Only if the user wants older transcripts. Historical import is **optional**, runs in the **background**, and is **resumable** across later \`wake\`/\`sync\` — sleep and restarts are fine. Choose the history that is useful; do not pressure for a tiny window.
157
195
 
158
- Only if the user wants older transcripts included. Historical import is **optional**, runs in the **background**, and drains in **incremental bounded slices** across later \`dreams sync\` wakes (including SessionStart/Stop). It does **not** need to finish in one processsleep, offline time, and restarts are fine; work resumes from durable local state. That steadiness is why a wider window is a reasonable choice: choose the history that is useful, not only what might finish tonight.
196
+ **Copy for the user:** calm and encouraging. Avoid harsh openers like “Optional; drains in bounded slices over time.” Prefer: “We can import recent history in the backgroundit’s resumable, so a wider window is a safe pick.”
159
197
 
160
- When offering this step, use calm language. Watching the first background sync settle is a reassuring sign that imports are moving; it is not a separate scary connection ritual.
198
+ **Window choices to offer:** 30 days · 90 days · 180 days · all available history (still bounded by what exists on disk; CLI freezes \`until_at = now\`). When possible, give a rough sense of scale (how many sessions / projects sit under approved directories) before they choose.
199
+
200
+ Live-hook consent does **not** grant historical-import consent. Ask which **detected** sources should import history.
161
201
 
162
202
  If the user opts in:
163
203
 
164
- 1. Agree a start bound. Describe it accurately as **sessions modified since \`<RFC3339>\` through now** (the CLI freezes \`until_at = now\`). Selection is by transcript-file mtime for whole sessions — not per-message timestamps inside a file.
165
- 2. Obtain explicit consent to queue and upload those historical transcript bytes (approved roots + \`.dreamignore\` still apply). Do not start an open-ended “import everything” without a bound.
166
- 3. Initialize the frozen window and queue one bounded local slice:
167
- \`"<cli_path>" backfill start --since <RFC3339> --json\`
168
- 4. Immediately wake the sync worker so draining does not wait for a future hook:
169
- \`"<cli_path>" sync --force --json\`
170
- 5. Explain what happens next: live turns stay first; remaining capacity steadily continues the historical frontier in the background (newer sessions in the window tend to be imported before older ones; bytes within a session still move forward). Users can pause or resume without discarding queued bytes or frontier progress:
171
- \`"<cli_path>" sync pause\`
172
- \`"<cli_path>" sync resume\`
173
- \`"<cli_path>" sync status --json\` shows \`effective_state\` while import runs.
204
+ 1. Agree \`--since <RFC3339>\` (sessions modified since that time through now; selection is by transcript-file mtime).
205
+ 2. For each selected source, queue a slice, then **\`wake\`** so draining does not wait for the next session:
206
+
207
+ \`\`\`bash
208
+ "<cli_path>" backfill start --source claude-code --since <RFC3339> --json
209
+ "<cli_path>" wake --source claude-code --json
210
+ "<cli_path>" backfill start --source codex --since <RFC3339> --json
211
+ "<cli_path>" wake --source codex --json
212
+ \`\`\`
213
+
214
+ 3. Explain: live turns stay first; history continues in the background. Pause/resume without discarding progress:
215
+ \`"<cli_path>" sync pause\` / \`"<cli_path>" sync resume\`
216
+ Monitor: \`"<cli_path>" sync status --source claude-code --json\` (and codex as needed).
217
+
218
+ Do **not** run unrestricted \`source scan\` or a ceremonial \`sync --force\` “smoke test” during setup. \`wake\` after backfill is the right kick; the first real Stop-hook \`sync\` under an approved directory confirms the live path.
174
219
 
175
220
  ## Re-entry
176
221
 
177
- If unsure what is already done, inspect durable facts (\`dreams auth status\`, \`dreams source list\`, \`dreams status\`, \`dreams sync status\`) and continue at the first incomplete step. Repeated \`onboard\`, \`source detect\`, approved snapshot retries, and idempotent hook merges are safe when they follow the rules above. Never “catch up” by running unrestricted \`source scan\` after hooks are installed — use live sync and, if desired, bounded background backfill instead.
222
+ Inspect durable facts (\`auth status\`, \`source list\`, \`status\`, per-source \`sync status\`) and continue at the first incomplete step. Repeated \`onboard\`, \`source detect\`, approved snapshot retries, \`hooks install\`, and \`approve-dir\` are safe when they follow the rules above.
178
223
  `;
179
224
  }
180
225
  function initSnapshotTemplate() {
181
226
  return `${JSON.stringify({
182
- $comment: "Non-enforced reference template for Dream init snapshots (LET-9833). Cloud accepts any JSON object + 256 KiB cap; this documents the expected Init fields for the skill/CLI. Do NOT include role on teammates, and do NOT include scope.mode keep scope.approved_roots.",
227
+ $comment: "Non-enforced init snapshot template. Cloud accepts any JSON object + 256 KiB cap. User-facing language: approved directories (JSON key remains scope.approved_roots). Do NOT include teammate role or scope.mode. Omit unknown optional fields rather than inventing zeros.",
228
+ $field_guide: {
229
+ high_value: "repositories (with remotes/languages for active work), harnesses, identity_signals, teammates (from git, humans only), scope.approved_roots, transcript_inventory, skills (repo-scanned counts), cli_version",
230
+ repository_detail: "Full metadata for repos with recent agent sessions; name/remote/language is enough for the long tail. Deduplicate clones; optional local_clone_count.",
231
+ instruction_files: "Agent instruction docs found in the repo (AGENTS.md, CLAUDE.md, MEMORY.md, etc.). Include name + size_bytes when present.",
232
+ omit_if_unknown: [
233
+ "letta_agent_count",
234
+ "letta_memory_artifact_count",
235
+ "allowlisted_file_names",
236
+ "skill_names on repositories (use top-level skills instead)",
237
+ ],
238
+ },
183
239
  repositories: [
184
240
  {
185
241
  display_name: "my-repo",
@@ -187,16 +243,18 @@ function initSnapshotTemplate() {
187
243
  primary_language: "typescript",
188
244
  build_system: "nx",
189
245
  instruction_files: [{ name: "AGENTS.md", size_bytes: 1024 }],
190
- skill_names: ["deploy"],
191
- letta_agent_count: 2,
192
- letta_memory_artifact_count: 0,
193
- allowlisted_file_names: [],
194
246
  },
195
247
  ],
196
248
  harnesses: [{ name: "claude-code", version: "1.2.3" }],
197
- skills: [{ name: "deploy", description: "deploys things" }],
249
+ skills: [
250
+ {
251
+ directory: "my-repo/.claude/skills",
252
+ skill_count: 1,
253
+ sample_names: ["deploy"],
254
+ },
255
+ ],
198
256
  identity_signals: [{ type: "git_email", value: "dev@acme.com" }],
199
- transcript_inventory: [{ source_key: "sess-1", session_count: 3, segment_count: 10 }],
257
+ transcript_inventory: [{ source_key: "claude-code", session_count: 3 }],
200
258
  teammates: [
201
259
  {
202
260
  display_name: "Alex",
@@ -204,17 +262,18 @@ function initSnapshotTemplate() {
204
262
  },
205
263
  ],
206
264
  scope: { approved_roots: [] },
207
- cli_version: "0.0.3",
265
+ cli_version: (0, version_1.packageVersion)(),
208
266
  }, null, 2)}\n`;
209
267
  }
210
268
  function claudeHooksReference() {
211
269
  return `# Claude Code — Dreams live sync hooks
212
270
 
213
- Install **globally** in \`~/.claude/settings.json\` (user scope). Merge into the existing \`hooks\` object; never replace the whole file or delete unrelated hook entries.
271
+ Install **globally** in \`~/.claude/settings.json\` (user scope). Prefer \`dreams hooks install --dry-run\` / \`dreams hooks install\` so the CLI owns the merge. Manual merge is fallback only.
214
272
 
215
273
  ## Goal
216
274
 
217
- Wake the durable Dreams CLI on \`SessionStart\` (recover pending work after crashes/restarts) and \`Stop\` (after each live turn). Pipe the hook JSON stdin into \`dreams sync\` so the current \`session_id\` can be recorded as a live hint.
275
+ - **SessionStart \`dreams wake\`** repair managed CLI/skill quietly; kick pending sync/backfill worker (no live-turn stdin hint).
276
+ - **Stop → \`dreams sync\`** — after each live turn; pipe hook JSON stdin so \`session_id\` can be a live hint.
218
277
 
219
278
  ## Platform-specific commands
220
279
 
@@ -222,27 +281,18 @@ Replace \`CLI_PATH\` with the absolute \`cli_path\` from \`dreams onboard --json
222
281
 
223
282
  ### macOS / Linux (bash)
224
283
 
225
- Always quote the launcher path (it may contain spaces):
226
-
227
284
  \`\`\`bash
285
+ "CLI_PATH" wake >/dev/null 2>&1 || true # dreams-sync-hook
228
286
  "CLI_PATH" sync >/dev/null 2>&1 || true # dreams-sync-hook
229
287
  \`\`\`
230
288
 
231
289
  ### Windows (PowerShell)
232
290
 
233
- Use Claude's \`shell: "powershell"\` so the command is not run through bash redirects:
234
-
235
291
  \`\`\`powershell
292
+ & 'CLI_PATH' wake *> $null; exit 0 # dreams-sync-hook
236
293
  & 'CLI_PATH' sync *> $null; exit 0 # dreams-sync-hook
237
294
  \`\`\`
238
295
 
239
- Rules:
240
-
241
- - Pipe stdin from the harness into that command (Claude already provides JSON on stdin to command hooks).
242
- - Do **not** use \`npx\`, \`jq\`, or shell JSON parsing.
243
- - Suppress stdout/stderr and force exit 0. Sync JSON must not enter Claude context.
244
- - Keep the \`# dreams-sync-hook\` marker. On re-run, **update** the existing Dreams handler in place (including switching POSIX ↔ PowerShell when the host OS differs) instead of appending another copy.
245
-
246
296
  ## Example merge fragment (macOS / Linux)
247
297
 
248
298
  \`\`\`json
@@ -253,7 +303,7 @@ Rules:
253
303
  "hooks": [
254
304
  {
255
305
  "type": "command",
256
- "command": "\\"CLI_PATH\\" sync >/dev/null 2>&1 || true # dreams-sync-hook"
306
+ "command": "\\"CLI_PATH\\" wake >/dev/null 2>&1 || true # dreams-sync-hook"
257
307
  }
258
308
  ]
259
309
  }
@@ -283,7 +333,7 @@ Rules:
283
333
  {
284
334
  "type": "command",
285
335
  "shell": "powershell",
286
- "command": "& 'CLI_PATH' sync *> $null; exit 0 # dreams-sync-hook"
336
+ "command": "& 'CLI_PATH' wake *> $null; exit 0 # dreams-sync-hook"
287
337
  }
288
338
  ]
289
339
  }
@@ -303,42 +353,82 @@ Rules:
303
353
  }
304
354
  \`\`\`
305
355
 
306
- ## Idempotent merge checklist
356
+ ## Notes
307
357
 
308
- 1. Obtain explicit user consent for ongoing transcript sync (see skill Step 4).
309
- 2. Read \`~/.claude/settings.json\` (create \`{}\` if absent).
310
- 3. Preserve every existing top-level key and every non-Dreams hook matcher/handler.
311
- 4. For \`SessionStart\` and \`Stop\`, find a handler containing \`dreams-sync-hook\` (or the same \`CLI_PATH\` + \` sync\`) and update it in place; otherwise append one host-appropriate Dreams handler.
312
- 5. Write the file back atomically.
313
- 6. Optional check through the durable launcher:
314
- - macOS / Linux: \`"CLI_PATH" sync status --json\`
315
- - Windows PowerShell: \`& 'CLI_PATH' sync status --json\`
358
+ - Prefer \`dreams hooks install\` (with \`--dry-run\` for consent). Keep \`# dreams-sync-hook\` markers; update in place; never append duplicates.
359
+ - Hooks must stay silent and exit 0. Silence is not proof of Cloud upload success.
360
+ - \`dreams sync pause\` / \`dreams sync resume\` toggle without uninstalling.
361
+ - Diagnostics: \`dreams sync status --json\`.
362
+ `;
363
+ }
364
+ function codexHooksReference() {
365
+ return `# Codex Dreams live sync hooks
316
366
 
317
- ## Notes
367
+ Install **user-level** hooks in \`~/.codex/hooks.json\`. Prefer \`dreams hooks install --dry-run\` / \`dreams hooks install\`.
368
+
369
+ Also ensure:
318
370
 
319
- ### No-op contracts
371
+ \`\`\`toml
372
+ [features]
373
+ hooks = true
374
+ \`\`\`
375
+
376
+ ## Goal
377
+
378
+ - **SessionStart → \`wake --source codex\`** via the Node launcher
379
+ - **Stop → \`sync --source codex\`** via the Node launcher (forwards stdin; prints \`{}\`)
380
+
381
+ ## Why a Node launcher
382
+
383
+ Codex Stop expects exactly one JSON object on stdout. Use \`references/codex-sync-hook.cjs\`:
384
+
385
+ \`\`\`bash
386
+ node "HOOK_SCRIPT" "CLI_PATH" wake # dreams-sync-hook-codex
387
+ node "HOOK_SCRIPT" "CLI_PATH" sync # dreams-sync-hook-codex
388
+ \`\`\`
320
389
 
321
- - Hook commands must stay **silent** and **exit 0** (including when sync is paused or not due). That silence protects Claude’s context; it is **not** proof the Cloud upload succeeded.
322
- - \`dreams sync pause\` / \`dreams sync resume\` toggle hook wakes without uninstalling handlers.
323
- - Distinguish expected no-ops from failure with \`dreams sync status --json\`:
324
- - \`effective_state: "paused"\` or \`"not_due"\` → expected quiet wake
325
- - \`pending\` / \`running\` → runnable work recorded or worker in flight
326
- - \`idle\` → nothing waiting
327
- - \`degraded\` + structured \`last_error_code\` (e.g. \`spawn_failed\`) → failure
390
+ ## Example merge fragment
391
+
392
+ \`\`\`json
393
+ {
394
+ "hooks": {
395
+ "SessionStart": [
396
+ {
397
+ "hooks": [
398
+ {
399
+ "type": "command",
400
+ "command": "node \\"HOOK_SCRIPT\\" \\"CLI_PATH\\" wake # dreams-sync-hook-codex"
401
+ }
402
+ ]
403
+ }
404
+ ],
405
+ "Stop": [
406
+ {
407
+ "hooks": [
408
+ {
409
+ "type": "command",
410
+ "command": "node \\"HOOK_SCRIPT\\" \\"CLI_PATH\\" sync # dreams-sync-hook-codex"
411
+ }
412
+ ]
413
+ }
414
+ ]
415
+ }
416
+ }
417
+ \`\`\`
328
418
 
329
- ### Robustness
419
+ ## Trust (\`/hooks\`)
330
420
 
331
- - \`session_id\` in hook stdin is an **optional** live hint. Malformed/missing stdin must still issue a generic \`sync\` wake for tracked sessions (live mode — no silent historical ingest).
332
- - Always quote the durable absolute launcher path, suppress stdout/stderr, and force exit 0.
333
- - Preserve unrelated handlers; update the marked Dreams handler (\`# dreams-sync-hook\`) **in place** — never append duplicates.
334
- - Do not put version checks in the hot hook path. If the launcher path is wrong/nonexistent, Dreams never starts and cannot record diagnostics — repair with \`dreams onboard --json\` / \`dreams status --json\` and re-substitute \`cli_path\`.
421
+ Ask the user to run \`/hooks\`, review, and trust. File write trusted Cloud upload succeeded.
335
422
 
336
- ### Other
423
+ ## Notes
337
424
 
338
- - Users can pause hooks without uninstalling: \`dreams sync pause\` / \`dreams sync resume\`.
339
- - Codex hook wiring is intentionally deferred until Dreams ships a Codex source adapter.
425
+ - Launcher stdout must be \`{}\\n\` with exit 0.
426
+ - After backfill, kick with \`dreams wake --source codex --json\` (not a ceremonial sync --force smoke test).
340
427
  `;
341
428
  }
429
+ function codexSyncHookScript() {
430
+ return fs.readFileSync(path.join(__dirname, "..", "bin", "dreams-codex-hook.cjs"), "utf8");
431
+ }
342
432
  function readFile(file) {
343
433
  try {
344
434
  return fs.readFileSync(file, "utf8");
@@ -388,6 +478,8 @@ function desiredArtifacts() {
388
478
  { path: skillFilePath(), contents: skillContent((0, version_1.packageVersion)()) },
389
479
  { path: initSnapshotTemplatePath(), contents: initSnapshotTemplate() },
390
480
  { path: claudeHooksReferencePath(), contents: claudeHooksReference() },
481
+ { path: codexHooksReferencePath(), contents: codexHooksReference() },
482
+ { path: codexSyncHookScriptPath(), contents: codexSyncHookScript() },
391
483
  ];
392
484
  }
393
485
  function installSkill() {
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * node:sqlite still emits ExperimentalWarning on load in Node 22.x even though
4
+ * the flag is no longer required at >= 22.13. Filter only that warning so agent
5
+ * transcripts and --json workflows stay clean; leave other warnings alone.
6
+ *
7
+ * Must run before the first `node:sqlite` import (see cli.ts import order and
8
+ * bin/dreams.cjs preflight).
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.suppressSqliteExperimentalWarning = suppressSqliteExperimentalWarning;
12
+ const SQLITE_EXPERIMENTAL = /SQLite is an experimental feature|ExperimentalWarning:.*SQLite|node:sqlite/i;
13
+ let installed = false;
14
+ function suppressSqliteExperimentalWarning() {
15
+ if (installed)
16
+ return;
17
+ installed = true;
18
+ const originalEmit = process.emit.bind(process);
19
+ function filteredEmit(event, ...args) {
20
+ if (event === "warning" && args.length > 0) {
21
+ const warning = args[0];
22
+ if (warning &&
23
+ typeof warning === "object" &&
24
+ warning.name === "ExperimentalWarning" &&
25
+ typeof warning.message === "string" &&
26
+ SQLITE_EXPERIMENTAL.test(warning.message)) {
27
+ return false;
28
+ }
29
+ }
30
+ return Reflect.apply(originalEmit, process, [event, ...args]);
31
+ }
32
+ process.emit = filteredEmit;
33
+ }
34
+ suppressSqliteExperimentalWarning();