@letta-ai/dreams 0.0.1 → 0.0.3
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 +74 -20
- package/dist/auth/commands.js +46 -4
- package/dist/auth/credentials.js +28 -8
- package/dist/cli.js +198 -8
- 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 +608 -0
- package/dist/local/bindings.js +30 -0
- package/dist/local/claude.js +61 -163
- package/dist/local/codex.js +313 -0
- package/dist/local/commands.js +129 -10
- package/dist/local/db.js +64 -0
- package/dist/local/detect.js +144 -0
- package/dist/local/discovered.js +6 -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 +58 -0
- package/dist/local/state-lock.js +88 -0
- package/dist/local/sync-control.js +381 -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 +281 -16
- package/dist/snapshots.js +210 -0
- package/dist/sync.js +790 -0
- package/package.json +1 -1
package/dist/skill.js
CHANGED
|
@@ -35,6 +35,8 @@ 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;
|
|
38
40
|
exports.installedSkillVersion = installedSkillVersion;
|
|
39
41
|
exports.installSkill = installSkill;
|
|
40
42
|
const crypto = __importStar(require("node:crypto"));
|
|
@@ -50,6 +52,12 @@ function skillDir() {
|
|
|
50
52
|
function skillFilePath() {
|
|
51
53
|
return path.join(skillDir(), "SKILL.md");
|
|
52
54
|
}
|
|
55
|
+
function initSnapshotTemplatePath() {
|
|
56
|
+
return path.join(skillDir(), "references", "init-snapshot.template.json");
|
|
57
|
+
}
|
|
58
|
+
function claudeHooksReferencePath() {
|
|
59
|
+
return path.join(skillDir(), "references", "hooks-claude.md");
|
|
60
|
+
}
|
|
53
61
|
function skillContent(version) {
|
|
54
62
|
return `---
|
|
55
63
|
name: dreams-setup
|
|
@@ -59,28 +67,276 @@ version: ${version}
|
|
|
59
67
|
|
|
60
68
|
# Dreams setup
|
|
61
69
|
|
|
62
|
-
|
|
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.
|
|
71
|
+
|
|
72
|
+
## Step 0 — Local bootstrap (optional repair)
|
|
73
|
+
|
|
74
|
+
If the durable CLI or this skill may be missing, run:
|
|
63
75
|
|
|
64
76
|
\`\`\`bash
|
|
65
77
|
npx @letta-ai/dreams@latest onboard --json
|
|
66
78
|
\`\`\`
|
|
67
79
|
|
|
68
|
-
\`onboard\` installs
|
|
80
|
+
\`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.
|
|
81
|
+
|
|
82
|
+
## Step 1 — Authenticate
|
|
83
|
+
|
|
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.
|
|
87
|
+
|
|
88
|
+
## Step 2 — Detect and register source
|
|
89
|
+
|
|
90
|
+
Run:
|
|
91
|
+
|
|
92
|
+
\`\`\`bash
|
|
93
|
+
dreams source detect --json
|
|
94
|
+
\`\`\`
|
|
95
|
+
|
|
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.
|
|
97
|
+
|
|
98
|
+
## Step 3 — Init snapshot
|
|
99
|
+
|
|
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):
|
|
105
|
+
|
|
106
|
+
\`\`\`bash
|
|
107
|
+
dreams snapshots create --type init --file /path/to/approved-init.json --client-request-id <unique-id> --json
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
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.
|
|
69
111
|
|
|
70
|
-
##
|
|
112
|
+
## Step 4 — Install live sync hooks (Claude Code)
|
|
71
113
|
|
|
72
|
-
|
|
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.
|
|
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\`.
|
|
76
115
|
|
|
77
|
-
|
|
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.
|
|
78
117
|
|
|
79
|
-
|
|
118
|
+
Then:
|
|
80
119
|
|
|
81
|
-
|
|
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\`).
|
|
82
125
|
|
|
83
|
-
|
|
126
|
+
Rules:
|
|
127
|
+
|
|
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).
|
|
131
|
+
|
|
132
|
+
### Hook diagnostics & repair
|
|
133
|
+
|
|
134
|
+
When live sync seems broken, use this ladder (do **not** put version checks inside the hot hook command):
|
|
135
|
+
|
|
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.
|
|
145
|
+
|
|
146
|
+
## Step 5 — Activate live sync
|
|
147
|
+
|
|
148
|
+
With user consent for privacy roots:
|
|
149
|
+
|
|
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.
|
|
155
|
+
|
|
156
|
+
## Step 6 — Optional: import recent history in the background
|
|
157
|
+
|
|
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 process — sleep, 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.
|
|
159
|
+
|
|
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.
|
|
161
|
+
|
|
162
|
+
If the user opts in:
|
|
163
|
+
|
|
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.
|
|
174
|
+
|
|
175
|
+
## Re-entry
|
|
176
|
+
|
|
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.
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
function initSnapshotTemplate() {
|
|
181
|
+
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.",
|
|
183
|
+
repositories: [
|
|
184
|
+
{
|
|
185
|
+
display_name: "my-repo",
|
|
186
|
+
git_remote_sanitized: "github.com/acme/my-repo",
|
|
187
|
+
primary_language: "typescript",
|
|
188
|
+
build_system: "nx",
|
|
189
|
+
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
|
+
},
|
|
195
|
+
],
|
|
196
|
+
harnesses: [{ name: "claude-code", version: "1.2.3" }],
|
|
197
|
+
skills: [{ name: "deploy", description: "deploys things" }],
|
|
198
|
+
identity_signals: [{ type: "git_email", value: "dev@acme.com" }],
|
|
199
|
+
transcript_inventory: [{ source_key: "sess-1", session_count: 3, segment_count: 10 }],
|
|
200
|
+
teammates: [
|
|
201
|
+
{
|
|
202
|
+
display_name: "Alex",
|
|
203
|
+
identity_signals: [{ type: "git_email", value: "alex@acme.com" }],
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
scope: { approved_roots: [] },
|
|
207
|
+
cli_version: "0.0.3",
|
|
208
|
+
}, null, 2)}\n`;
|
|
209
|
+
}
|
|
210
|
+
function claudeHooksReference() {
|
|
211
|
+
return `# Claude Code — Dreams live sync hooks
|
|
212
|
+
|
|
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.
|
|
214
|
+
|
|
215
|
+
## Goal
|
|
216
|
+
|
|
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.
|
|
218
|
+
|
|
219
|
+
## Platform-specific commands
|
|
220
|
+
|
|
221
|
+
Replace \`CLI_PATH\` with the absolute \`cli_path\` from \`dreams onboard --json\`.
|
|
222
|
+
|
|
223
|
+
### macOS / Linux (bash)
|
|
224
|
+
|
|
225
|
+
Always quote the launcher path (it may contain spaces):
|
|
226
|
+
|
|
227
|
+
\`\`\`bash
|
|
228
|
+
"CLI_PATH" sync >/dev/null 2>&1 || true # dreams-sync-hook
|
|
229
|
+
\`\`\`
|
|
230
|
+
|
|
231
|
+
### Windows (PowerShell)
|
|
232
|
+
|
|
233
|
+
Use Claude's \`shell: "powershell"\` so the command is not run through bash redirects:
|
|
234
|
+
|
|
235
|
+
\`\`\`powershell
|
|
236
|
+
& 'CLI_PATH' sync *> $null; exit 0 # dreams-sync-hook
|
|
237
|
+
\`\`\`
|
|
238
|
+
|
|
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
|
+
## Example merge fragment (macOS / Linux)
|
|
247
|
+
|
|
248
|
+
\`\`\`json
|
|
249
|
+
{
|
|
250
|
+
"hooks": {
|
|
251
|
+
"SessionStart": [
|
|
252
|
+
{
|
|
253
|
+
"hooks": [
|
|
254
|
+
{
|
|
255
|
+
"type": "command",
|
|
256
|
+
"command": "\\"CLI_PATH\\" sync >/dev/null 2>&1 || true # dreams-sync-hook"
|
|
257
|
+
}
|
|
258
|
+
]
|
|
259
|
+
}
|
|
260
|
+
],
|
|
261
|
+
"Stop": [
|
|
262
|
+
{
|
|
263
|
+
"hooks": [
|
|
264
|
+
{
|
|
265
|
+
"type": "command",
|
|
266
|
+
"command": "\\"CLI_PATH\\" sync >/dev/null 2>&1 || true # dreams-sync-hook"
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
## Example merge fragment (Windows)
|
|
276
|
+
|
|
277
|
+
\`\`\`json
|
|
278
|
+
{
|
|
279
|
+
"hooks": {
|
|
280
|
+
"SessionStart": [
|
|
281
|
+
{
|
|
282
|
+
"hooks": [
|
|
283
|
+
{
|
|
284
|
+
"type": "command",
|
|
285
|
+
"shell": "powershell",
|
|
286
|
+
"command": "& 'CLI_PATH' sync *> $null; exit 0 # dreams-sync-hook"
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
],
|
|
291
|
+
"Stop": [
|
|
292
|
+
{
|
|
293
|
+
"hooks": [
|
|
294
|
+
{
|
|
295
|
+
"type": "command",
|
|
296
|
+
"shell": "powershell",
|
|
297
|
+
"command": "& 'CLI_PATH' sync *> $null; exit 0 # dreams-sync-hook"
|
|
298
|
+
}
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
\`\`\`
|
|
305
|
+
|
|
306
|
+
## Idempotent merge checklist
|
|
307
|
+
|
|
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\`
|
|
316
|
+
|
|
317
|
+
## Notes
|
|
318
|
+
|
|
319
|
+
### No-op contracts
|
|
320
|
+
|
|
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
|
|
328
|
+
|
|
329
|
+
### Robustness
|
|
330
|
+
|
|
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\`.
|
|
335
|
+
|
|
336
|
+
### Other
|
|
337
|
+
|
|
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.
|
|
84
340
|
`;
|
|
85
341
|
}
|
|
86
342
|
function readFile(file) {
|
|
@@ -127,12 +383,21 @@ function installedSkillVersion() {
|
|
|
127
383
|
const match = text.match(/^version:\s*(\S+)\s*$/m);
|
|
128
384
|
return match ? match[1] : null;
|
|
129
385
|
}
|
|
386
|
+
function desiredArtifacts() {
|
|
387
|
+
return [
|
|
388
|
+
{ path: skillFilePath(), contents: skillContent((0, version_1.packageVersion)()) },
|
|
389
|
+
{ path: initSnapshotTemplatePath(), contents: initSnapshotTemplate() },
|
|
390
|
+
{ path: claudeHooksReferencePath(), contents: claudeHooksReference() },
|
|
391
|
+
];
|
|
392
|
+
}
|
|
130
393
|
function installSkill() {
|
|
131
394
|
removeLegacyGeneratedSkill();
|
|
132
|
-
const
|
|
133
|
-
const existing =
|
|
134
|
-
|
|
395
|
+
const artifacts = desiredArtifacts();
|
|
396
|
+
const existing = artifacts.map((artifact) => readFile(artifact.path));
|
|
397
|
+
const allSame = artifacts.every((artifact, index) => existing[index] === artifact.contents);
|
|
398
|
+
if (allSame)
|
|
135
399
|
return "unchanged";
|
|
136
|
-
|
|
137
|
-
|
|
400
|
+
for (const artifact of artifacts)
|
|
401
|
+
writeAtomic(artifact.path, artifact.contents);
|
|
402
|
+
return existing[0] === null ? "installed" : "updated";
|
|
138
403
|
}
|
|
@@ -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
|
+
}
|