@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/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
- Start or repair the local setup with:
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 an exact CLI version at a stable local path, creates or reuses the installation identity, installs this skill, initializes local SQLite, and checks authentication. It does not log in or run \`orient\` itself.
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
- ## Follow the response
273
+ ## Goal
71
274
 
72
- 1. Parse the single JSON document from stdout.
73
- 2. Briefly explain \`agent_instructions\` to the user without pasting raw JSON.
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
- When \`state\` is \`awaiting_auth\`, run the returned \`auth login --json\` command and wait for the user to approve in the browser. That login command emits JSON Lines: first \`awaiting_approval\`, then \`authenticated\`. After it succeeds, run the stable CLI's \`onboard --json\` command again.
278
+ ## Platform-specific commands
78
279
 
79
- When \`state\` is \`ready_for_orient\`, run the returned \`orient\` command.
280
+ Replace \`CLI_PATH\` with the absolute \`cli_path\` from \`dreams onboard --json\`.
80
281
 
81
- Repeated \`onboard\` calls are safe. They repair missing local setup and return the same next action until authentication state changes.
282
+ ### macOS / Linux (bash)
82
283
 
83
- Never print or summarize access tokens, refresh tokens, credential files, or keychain contents.
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 desired = skillContent((0, version_1.packageVersion)());
133
- const existing = readFile(skillFilePath());
134
- if (existing === desired)
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
- writeAtomic(skillFilePath(), desired);
137
- return existing === null ? "installed" : "updated";
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();