@letta-ai/dreams 0.0.1 → 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/README.md +111 -35
- package/bin/dreams-codex-hook.cjs +91 -0
- package/bin/dreams.cjs +21 -0
- package/dist/auth/commands.js +67 -5
- package/dist/auth/credentials.js +101 -22
- package/dist/auth/index.js +106 -13
- package/dist/cli.js +239 -13
- package/dist/cloud/client.js +89 -5
- package/dist/cloud/upload.js +150 -70
- package/dist/development-reset.js +359 -0
- package/dist/launcher.js +311 -0
- package/dist/local/backfill.js +625 -0
- package/dist/local/bindings.js +30 -0
- package/dist/local/claude-hooks.js +169 -0
- package/dist/local/claude.js +130 -176
- package/dist/local/codex-hooks.js +235 -0
- package/dist/local/codex.js +510 -0
- package/dist/local/commands.js +153 -19
- package/dist/local/db.js +68 -0
- package/dist/local/detect.js +148 -0
- package/dist/local/discovered.js +6 -0
- package/dist/local/hooks-install.js +102 -0
- package/dist/local/leases.js +3 -1
- package/dist/local/scan.js +99 -17
- package/dist/local/source-adapter.js +8 -0
- package/dist/local/source-adapters.js +71 -0
- package/dist/local/state-lock.js +88 -0
- package/dist/local/sync-control.js +432 -0
- package/dist/local/transcript-bytes.js +204 -0
- package/dist/managed-install.js +74 -86
- package/dist/onboard.js +26 -72
- package/dist/skill.js +373 -16
- package/dist/snapshots.js +210 -0
- package/dist/suppress-sqlite-warning.js +34 -0
- package/dist/sync.js +950 -0
- package/package.json +1 -1
package/dist/skill.js
CHANGED
|
@@ -35,6 +35,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.skillDir = skillDir;
|
|
37
37
|
exports.skillFilePath = skillFilePath;
|
|
38
|
+
exports.initSnapshotTemplatePath = initSnapshotTemplatePath;
|
|
39
|
+
exports.claudeHooksReferencePath = claudeHooksReferencePath;
|
|
40
|
+
exports.codexHooksReferencePath = codexHooksReferencePath;
|
|
41
|
+
exports.codexSyncHookScriptPath = codexSyncHookScriptPath;
|
|
38
42
|
exports.installedSkillVersion = installedSkillVersion;
|
|
39
43
|
exports.installSkill = installSkill;
|
|
40
44
|
const crypto = __importStar(require("node:crypto"));
|
|
@@ -50,6 +54,18 @@ function skillDir() {
|
|
|
50
54
|
function skillFilePath() {
|
|
51
55
|
return path.join(skillDir(), "SKILL.md");
|
|
52
56
|
}
|
|
57
|
+
function initSnapshotTemplatePath() {
|
|
58
|
+
return path.join(skillDir(), "references", "init-snapshot.template.json");
|
|
59
|
+
}
|
|
60
|
+
function claudeHooksReferencePath() {
|
|
61
|
+
return path.join(skillDir(), "references", "hooks-claude.md");
|
|
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
|
+
}
|
|
53
69
|
function skillContent(version) {
|
|
54
70
|
return `---
|
|
55
71
|
name: dreams-setup
|
|
@@ -59,30 +75,360 @@ version: ${version}
|
|
|
59
75
|
|
|
60
76
|
# Dreams setup
|
|
61
77
|
|
|
62
|
-
|
|
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.
|
|
88
|
+
|
|
89
|
+
## Step 0 — Local bootstrap (optional repair)
|
|
90
|
+
|
|
91
|
+
If the durable CLI or this skill may be missing, run:
|
|
63
92
|
|
|
64
93
|
\`\`\`bash
|
|
65
94
|
npx @letta-ai/dreams@latest onboard --json
|
|
66
95
|
\`\`\`
|
|
67
96
|
|
|
68
|
-
\`onboard\` installs
|
|
97
|
+
\`onboard\` installs or repairs the durable CLI, installation identity, this skill (including reference files), and local SQLite. It does **not** check authentication or choose the next setup step. On success it returns \`status: "ready"\` with absolute \`cli_path\` and \`skill.path\`. Use that \`cli_path\` for later Dreams commands and for harness hook commands.
|
|
98
|
+
|
|
99
|
+
## Step 1 — Authenticate
|
|
100
|
+
|
|
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.
|
|
108
|
+
|
|
109
|
+
## Step 2 — Detect and register sources
|
|
110
|
+
|
|
111
|
+
Detect each registered harness with an **explicit** adapter. Do **not** install hooks here.
|
|
112
|
+
|
|
113
|
+
\`\`\`bash
|
|
114
|
+
"<cli_path>" source detect --source claude-code --json
|
|
115
|
+
"<cli_path>" source detect --source codex --json
|
|
116
|
+
\`\`\`
|
|
117
|
+
|
|
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)
|
|
121
|
+
|
|
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.
|
|
123
|
+
|
|
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:
|
|
132
|
+
|
|
133
|
+
\`\`\`bash
|
|
134
|
+
"<cli_path>" snapshots create --type init --file /path/to/approved-init.json --client-request-id <unique-id> --json
|
|
135
|
+
\`\`\`
|
|
136
|
+
|
|
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.
|
|
146
|
+
|
|
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:
|
|
150
|
+
|
|
151
|
+
\`\`\`bash
|
|
152
|
+
"<cli_path>" source approve-dir "<path>" --json
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
(\`source approve-root\` still works as a synonym.)
|
|
156
|
+
|
|
157
|
+
### 4b — Install hooks (required for live sync)
|
|
158
|
+
|
|
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).
|
|
160
|
+
|
|
161
|
+
1. **Dry-run first** — show the user the exact planned commands/files:
|
|
162
|
+
|
|
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
|
+
\`\`\`
|
|
169
|
+
|
|
170
|
+
2. After approval, install (CLI owns the merge — do not hand-edit when the CLI succeeds):
|
|
171
|
+
|
|
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.
|
|
180
|
+
|
|
181
|
+
### Hook diagnostics (reference)
|
|
182
|
+
|
|
183
|
+
When live sync seems broken:
|
|
184
|
+
|
|
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 \`{}\`.
|
|
191
|
+
|
|
192
|
+
## Step 5 — Optional: import recent history
|
|
193
|
+
|
|
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.
|
|
195
|
+
|
|
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 background — it’s resumable, so a wider window is a safe pick.”
|
|
197
|
+
|
|
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.
|
|
201
|
+
|
|
202
|
+
If the user opts in:
|
|
203
|
+
|
|
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.
|
|
219
|
+
|
|
220
|
+
## Re-entry
|
|
221
|
+
|
|
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.
|
|
223
|
+
`;
|
|
224
|
+
}
|
|
225
|
+
function initSnapshotTemplate() {
|
|
226
|
+
return `${JSON.stringify({
|
|
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
|
+
},
|
|
239
|
+
repositories: [
|
|
240
|
+
{
|
|
241
|
+
display_name: "my-repo",
|
|
242
|
+
git_remote_sanitized: "github.com/acme/my-repo",
|
|
243
|
+
primary_language: "typescript",
|
|
244
|
+
build_system: "nx",
|
|
245
|
+
instruction_files: [{ name: "AGENTS.md", size_bytes: 1024 }],
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
harnesses: [{ name: "claude-code", version: "1.2.3" }],
|
|
249
|
+
skills: [
|
|
250
|
+
{
|
|
251
|
+
directory: "my-repo/.claude/skills",
|
|
252
|
+
skill_count: 1,
|
|
253
|
+
sample_names: ["deploy"],
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
identity_signals: [{ type: "git_email", value: "dev@acme.com" }],
|
|
257
|
+
transcript_inventory: [{ source_key: "claude-code", session_count: 3 }],
|
|
258
|
+
teammates: [
|
|
259
|
+
{
|
|
260
|
+
display_name: "Alex",
|
|
261
|
+
identity_signals: [{ type: "git_email", value: "alex@acme.com" }],
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
scope: { approved_roots: [] },
|
|
265
|
+
cli_version: (0, version_1.packageVersion)(),
|
|
266
|
+
}, null, 2)}\n`;
|
|
267
|
+
}
|
|
268
|
+
function claudeHooksReference() {
|
|
269
|
+
return `# Claude Code — Dreams live sync hooks
|
|
270
|
+
|
|
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.
|
|
69
272
|
|
|
70
|
-
##
|
|
273
|
+
## Goal
|
|
71
274
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
3. Run the exact command in \`next_action.command\`.
|
|
75
|
-
4. Use the absolute \`cli_path\` from the response for later Dreams commands.
|
|
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.
|
|
76
277
|
|
|
77
|
-
|
|
278
|
+
## Platform-specific commands
|
|
78
279
|
|
|
79
|
-
|
|
280
|
+
Replace \`CLI_PATH\` with the absolute \`cli_path\` from \`dreams onboard --json\`.
|
|
80
281
|
|
|
81
|
-
|
|
282
|
+
### macOS / Linux (bash)
|
|
82
283
|
|
|
83
|
-
|
|
284
|
+
\`\`\`bash
|
|
285
|
+
"CLI_PATH" wake >/dev/null 2>&1 || true # dreams-sync-hook
|
|
286
|
+
"CLI_PATH" sync >/dev/null 2>&1 || true # dreams-sync-hook
|
|
287
|
+
\`\`\`
|
|
288
|
+
|
|
289
|
+
### Windows (PowerShell)
|
|
290
|
+
|
|
291
|
+
\`\`\`powershell
|
|
292
|
+
& 'CLI_PATH' wake *> $null; exit 0 # dreams-sync-hook
|
|
293
|
+
& 'CLI_PATH' sync *> $null; exit 0 # dreams-sync-hook
|
|
294
|
+
\`\`\`
|
|
295
|
+
|
|
296
|
+
## Example merge fragment (macOS / Linux)
|
|
297
|
+
|
|
298
|
+
\`\`\`json
|
|
299
|
+
{
|
|
300
|
+
"hooks": {
|
|
301
|
+
"SessionStart": [
|
|
302
|
+
{
|
|
303
|
+
"hooks": [
|
|
304
|
+
{
|
|
305
|
+
"type": "command",
|
|
306
|
+
"command": "\\"CLI_PATH\\" wake >/dev/null 2>&1 || true # dreams-sync-hook"
|
|
307
|
+
}
|
|
308
|
+
]
|
|
309
|
+
}
|
|
310
|
+
],
|
|
311
|
+
"Stop": [
|
|
312
|
+
{
|
|
313
|
+
"hooks": [
|
|
314
|
+
{
|
|
315
|
+
"type": "command",
|
|
316
|
+
"command": "\\"CLI_PATH\\" sync >/dev/null 2>&1 || true # dreams-sync-hook"
|
|
317
|
+
}
|
|
318
|
+
]
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
\`\`\`
|
|
324
|
+
|
|
325
|
+
## Example merge fragment (Windows)
|
|
326
|
+
|
|
327
|
+
\`\`\`json
|
|
328
|
+
{
|
|
329
|
+
"hooks": {
|
|
330
|
+
"SessionStart": [
|
|
331
|
+
{
|
|
332
|
+
"hooks": [
|
|
333
|
+
{
|
|
334
|
+
"type": "command",
|
|
335
|
+
"shell": "powershell",
|
|
336
|
+
"command": "& 'CLI_PATH' wake *> $null; exit 0 # dreams-sync-hook"
|
|
337
|
+
}
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
],
|
|
341
|
+
"Stop": [
|
|
342
|
+
{
|
|
343
|
+
"hooks": [
|
|
344
|
+
{
|
|
345
|
+
"type": "command",
|
|
346
|
+
"shell": "powershell",
|
|
347
|
+
"command": "& 'CLI_PATH' sync *> $null; exit 0 # dreams-sync-hook"
|
|
348
|
+
}
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
\`\`\`
|
|
355
|
+
|
|
356
|
+
## Notes
|
|
357
|
+
|
|
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\`.
|
|
84
362
|
`;
|
|
85
363
|
}
|
|
364
|
+
function codexHooksReference() {
|
|
365
|
+
return `# Codex — Dreams live sync hooks
|
|
366
|
+
|
|
367
|
+
Install **user-level** hooks in \`~/.codex/hooks.json\`. Prefer \`dreams hooks install --dry-run\` / \`dreams hooks install\`.
|
|
368
|
+
|
|
369
|
+
Also ensure:
|
|
370
|
+
|
|
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
|
+
\`\`\`
|
|
389
|
+
|
|
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
|
+
\`\`\`
|
|
418
|
+
|
|
419
|
+
## Trust (\`/hooks\`)
|
|
420
|
+
|
|
421
|
+
Ask the user to run \`/hooks\`, review, and trust. File write ≠ trusted ≠ Cloud upload succeeded.
|
|
422
|
+
|
|
423
|
+
## Notes
|
|
424
|
+
|
|
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).
|
|
427
|
+
`;
|
|
428
|
+
}
|
|
429
|
+
function codexSyncHookScript() {
|
|
430
|
+
return fs.readFileSync(path.join(__dirname, "..", "bin", "dreams-codex-hook.cjs"), "utf8");
|
|
431
|
+
}
|
|
86
432
|
function readFile(file) {
|
|
87
433
|
try {
|
|
88
434
|
return fs.readFileSync(file, "utf8");
|
|
@@ -127,12 +473,23 @@ function installedSkillVersion() {
|
|
|
127
473
|
const match = text.match(/^version:\s*(\S+)\s*$/m);
|
|
128
474
|
return match ? match[1] : null;
|
|
129
475
|
}
|
|
476
|
+
function desiredArtifacts() {
|
|
477
|
+
return [
|
|
478
|
+
{ path: skillFilePath(), contents: skillContent((0, version_1.packageVersion)()) },
|
|
479
|
+
{ path: initSnapshotTemplatePath(), contents: initSnapshotTemplate() },
|
|
480
|
+
{ path: claudeHooksReferencePath(), contents: claudeHooksReference() },
|
|
481
|
+
{ path: codexHooksReferencePath(), contents: codexHooksReference() },
|
|
482
|
+
{ path: codexSyncHookScriptPath(), contents: codexSyncHookScript() },
|
|
483
|
+
];
|
|
484
|
+
}
|
|
130
485
|
function installSkill() {
|
|
131
486
|
removeLegacyGeneratedSkill();
|
|
132
|
-
const
|
|
133
|
-
const existing =
|
|
134
|
-
|
|
487
|
+
const artifacts = desiredArtifacts();
|
|
488
|
+
const existing = artifacts.map((artifact) => readFile(artifact.path));
|
|
489
|
+
const allSame = artifacts.every((artifact, index) => existing[index] === artifact.contents);
|
|
490
|
+
if (allSame)
|
|
135
491
|
return "unchanged";
|
|
136
|
-
|
|
137
|
-
|
|
492
|
+
for (const artifact of artifacts)
|
|
493
|
+
writeAtomic(artifact.path, artifact.contents);
|
|
494
|
+
return existing[0] === null ? "installed" : "updated";
|
|
138
495
|
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `dreams snapshots create` — narrow CLI transport for skill-authored snapshots.
|
|
4
|
+
*
|
|
5
|
+
* Keeps credential handling, base URL selection, size checking, JSON-object
|
|
6
|
+
* validation, and idempotency keys inside the CLI. The skill authors the
|
|
7
|
+
* payload and obtains user approval before invoking this command.
|
|
8
|
+
*
|
|
9
|
+
* Workspace id is taken from the local binding written by `source detect`
|
|
10
|
+
* (or upload registration), matching the Cloud path
|
|
11
|
+
* POST /v1/dreams/workspaces/{workspaceId}/snapshots.
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.commandSnapshotsCreate = commandSnapshotsCreate;
|
|
48
|
+
exports.commandSnapshots = commandSnapshots;
|
|
49
|
+
const fs = __importStar(require("node:fs"));
|
|
50
|
+
const index_1 = require("./auth/index");
|
|
51
|
+
const client_1 = require("./cloud/client");
|
|
52
|
+
const db_1 = require("./local/db");
|
|
53
|
+
const state_lock_1 = require("./local/state-lock");
|
|
54
|
+
const store_1 = require("./store");
|
|
55
|
+
const MAX_SNAPSHOT_BYTES = 256 * 1024;
|
|
56
|
+
const MAX_TYPE_LEN = 64;
|
|
57
|
+
const MAX_CLIENT_REQUEST_ID_LEN = 128;
|
|
58
|
+
const TYPE_RE = /^[a-z][a-z0-9_-]*$/;
|
|
59
|
+
const CLIENT_REQUEST_ID_RE = /^[A-Za-z0-9._:-]+$/;
|
|
60
|
+
function emit(json, payload, human) {
|
|
61
|
+
if (json)
|
|
62
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
63
|
+
else
|
|
64
|
+
console.log(human());
|
|
65
|
+
}
|
|
66
|
+
function validateType(type) {
|
|
67
|
+
if (!TYPE_RE.test(type) || type.length > MAX_TYPE_LEN) {
|
|
68
|
+
return `invalid --type (expected <=${MAX_TYPE_LEN} chars matching ${TYPE_RE})`;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
function validateClientRequestId(id) {
|
|
73
|
+
if (!CLIENT_REQUEST_ID_RE.test(id) || id.length > MAX_CLIENT_REQUEST_ID_LEN) {
|
|
74
|
+
return `invalid --client-request-id (expected <=${MAX_CLIENT_REQUEST_ID_LEN} chars matching ${CLIENT_REQUEST_ID_RE})`;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function loadPayload(file) {
|
|
79
|
+
let raw;
|
|
80
|
+
try {
|
|
81
|
+
raw = fs.readFileSync(file, "utf8");
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return { ok: false, message: `could not read --file: ${error instanceof Error ? error.message : String(error)}` };
|
|
85
|
+
}
|
|
86
|
+
const bytes = Buffer.byteLength(raw, "utf8");
|
|
87
|
+
if (bytes > MAX_SNAPSHOT_BYTES) {
|
|
88
|
+
return { ok: false, message: `snapshot exceeds ${MAX_SNAPSHOT_BYTES} byte UTF-8 cap (${bytes} bytes)` };
|
|
89
|
+
}
|
|
90
|
+
let parsed;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(raw);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return { ok: false, message: `snapshot is not valid JSON: ${error instanceof Error ? error.message : String(error)}` };
|
|
96
|
+
}
|
|
97
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
98
|
+
return { ok: false, message: "snapshot payload must be a JSON object" };
|
|
99
|
+
}
|
|
100
|
+
return { ok: true, data: parsed, bytes };
|
|
101
|
+
}
|
|
102
|
+
function resolveWorkspaceId(explicit) {
|
|
103
|
+
if (explicit)
|
|
104
|
+
return explicit;
|
|
105
|
+
const installation = (0, store_1.loadOrCreateInstallation)();
|
|
106
|
+
const db = (0, db_1.openDb)({
|
|
107
|
+
installationId: installation.installation_id,
|
|
108
|
+
installationCreatedAt: installation.created_at,
|
|
109
|
+
});
|
|
110
|
+
try {
|
|
111
|
+
const row = db.prepare("SELECT workspace_id FROM workspace_bindings WHERE id = 1").get();
|
|
112
|
+
return row?.workspace_id ?? null;
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
(0, db_1.closeDb)();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function commandSnapshotsCreate(args) {
|
|
119
|
+
if (!args.type || !args.file || !args.clientRequestId) {
|
|
120
|
+
process.stderr.write("Usage: dreams snapshots create --type <type> --file <path> --client-request-id <id> [--workspace-id <id>] [--json]\n");
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
const typeError = validateType(args.type);
|
|
124
|
+
if (typeError) {
|
|
125
|
+
emit(args.json, { status: "error", code: "invalid_type", message: typeError }, () => typeError);
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
const idError = validateClientRequestId(args.clientRequestId);
|
|
129
|
+
if (idError) {
|
|
130
|
+
emit(args.json, { status: "error", code: "invalid_client_request_id", message: idError }, () => idError);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
const loaded = loadPayload(args.file);
|
|
134
|
+
if (!loaded.ok) {
|
|
135
|
+
emit(args.json, { status: "error", code: "invalid_payload", message: loaded.message }, () => loaded.message);
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
const lock = (0, state_lock_1.acquireLocalStateLock)();
|
|
139
|
+
if (lock === null) {
|
|
140
|
+
const message = "Another Dreams command is using local state. Try again after it finishes.";
|
|
141
|
+
emit(args.json, { status: "error", code: "local_state_busy", message }, () => message);
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const workspaceId = resolveWorkspaceId(args.workspaceId);
|
|
146
|
+
if (!workspaceId) {
|
|
147
|
+
const message = "no workspace binding found; run `dreams source detect` first (or pass --workspace-id)";
|
|
148
|
+
emit(args.json, { status: "error", code: "workspace_unresolved", message }, () => message);
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const result = await (0, client_1.createSnapshot)({
|
|
153
|
+
workspaceId,
|
|
154
|
+
type: args.type,
|
|
155
|
+
clientRequestId: args.clientRequestId,
|
|
156
|
+
data: loaded.data,
|
|
157
|
+
});
|
|
158
|
+
emit(args.json, {
|
|
159
|
+
status: "ok",
|
|
160
|
+
id: result.id,
|
|
161
|
+
type: result.type,
|
|
162
|
+
workspace_id: result.workspaceId,
|
|
163
|
+
client_request_id: result.clientRequestId,
|
|
164
|
+
snapshot_status: result.status,
|
|
165
|
+
created_at: result.createdAt,
|
|
166
|
+
bytes: loaded.bytes,
|
|
167
|
+
}, () => [
|
|
168
|
+
"Snapshot stored",
|
|
169
|
+
"",
|
|
170
|
+
` Id: ${result.id}`,
|
|
171
|
+
` Type: ${result.type}`,
|
|
172
|
+
` Workspace: ${result.workspaceId}`,
|
|
173
|
+
` Client request id: ${result.clientRequestId}`,
|
|
174
|
+
` Status: ${result.status}`,
|
|
175
|
+
` Bytes: ${loaded.bytes}`,
|
|
176
|
+
].join("\n"));
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (error instanceof index_1.NotAuthenticatedError) {
|
|
181
|
+
const message = "not authenticated: run `dreams auth login`";
|
|
182
|
+
emit(args.json, { status: "error", code: "not_authenticated", message }, () => message);
|
|
183
|
+
return 1;
|
|
184
|
+
}
|
|
185
|
+
if (error instanceof client_1.CloudAuthError) {
|
|
186
|
+
const message = `authentication failed: ${error.message}`;
|
|
187
|
+
emit(args.json, { status: "error", code: error.code ?? "auth_failed", message }, () => message);
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
if (error instanceof client_1.CloudError) {
|
|
191
|
+
const message = error.message;
|
|
192
|
+
emit(args.json, { status: "error", code: error.retryable ? "retryable" : "cloud_error", message, retryable: error.retryable }, () => message);
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function commandSnapshots(subcommand, args) {
|
|
203
|
+
switch (subcommand) {
|
|
204
|
+
case "create":
|
|
205
|
+
return commandSnapshotsCreate(args);
|
|
206
|
+
default:
|
|
207
|
+
process.stderr.write(`Unknown snapshots subcommand: ${subcommand ?? "(none)"} — expected create\n`);
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -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();
|