@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/auth/index.js
CHANGED
|
@@ -38,28 +38,121 @@ async function getAuthHeader() {
|
|
|
38
38
|
const { token } = await resolveAccessToken();
|
|
39
39
|
return { authorization: `Bearer ${token}` };
|
|
40
40
|
}
|
|
41
|
+
const HOOKS_NOTE = "Hooks invoke `dreams sync` with the coding-agent harness process environment. " +
|
|
42
|
+
"If that environment sets LETTA_API_KEY, it wins over keychain/file — verify the " +
|
|
43
|
+
"credential hooks will use (harness env), not only an interactive CLI shell.";
|
|
44
|
+
function maskedStored(credentials) {
|
|
45
|
+
if (!credentials) {
|
|
46
|
+
return { access_token: null, expires_at: null, has_refresh_token: false };
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
access_token: credentials.access_token ? (0, credentials_1.maskToken)(credentials.access_token) : null,
|
|
50
|
+
expires_at: credentials.expires_at,
|
|
51
|
+
has_refresh_token: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function keychainReason(status, selected) {
|
|
55
|
+
if (status === "not_checked") {
|
|
56
|
+
return "not checked because DREAMS_CREDENTIALS_BACKEND=file";
|
|
57
|
+
}
|
|
58
|
+
if (status === "unavailable") {
|
|
59
|
+
return "OS keychain unavailable (@napi-rs/keyring missing or unreadable)";
|
|
60
|
+
}
|
|
61
|
+
if (status === "absent") {
|
|
62
|
+
return "no Dreams refresh token in OS keychain";
|
|
63
|
+
}
|
|
64
|
+
if (selected) {
|
|
65
|
+
return "selected: highest-precedence stored credential (no LETTA_API_KEY)";
|
|
66
|
+
}
|
|
67
|
+
return "present but shadowed by LETTA_API_KEY";
|
|
68
|
+
}
|
|
69
|
+
function fileReason(present, selected, keychainPresent, envPresent) {
|
|
70
|
+
if (!present)
|
|
71
|
+
return "no credentials file at ~/.dreams/credentials.json";
|
|
72
|
+
if (selected) {
|
|
73
|
+
return "selected: file fallback (no LETTA_API_KEY; keychain empty or not used)";
|
|
74
|
+
}
|
|
75
|
+
if (envPresent)
|
|
76
|
+
return "present but shadowed by LETTA_API_KEY";
|
|
77
|
+
if (keychainPresent)
|
|
78
|
+
return "present but shadowed by keychain for stored-credential resolution";
|
|
79
|
+
return "present but not selected";
|
|
80
|
+
}
|
|
41
81
|
/** Snapshot of auth state for `dreams auth status` and `dreams status`. Never refreshes. */
|
|
42
82
|
function authStatusInfo() {
|
|
43
83
|
const device_id = (0, store_1.loadOrCreateInstallation)().installation_id;
|
|
44
84
|
const envKey = process.env.LETTA_API_KEY;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
85
|
+
const envPresent = typeof envKey === "string" && envKey.length > 0;
|
|
86
|
+
const probes = (0, credentials_1.probeStoredCredentialBackends)();
|
|
87
|
+
const keychainPresent = probes.keychain.status === "present";
|
|
88
|
+
const filePresent = probes.file.status === "present";
|
|
89
|
+
let active = null;
|
|
90
|
+
if (envPresent) {
|
|
91
|
+
active = {
|
|
48
92
|
source: "env",
|
|
49
|
-
|
|
50
|
-
expires_at: null,
|
|
51
|
-
device_id,
|
|
93
|
+
reason: "LETTA_API_KEY is set and takes highest precedence",
|
|
52
94
|
};
|
|
53
95
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
96
|
+
else if (keychainPresent) {
|
|
97
|
+
active = {
|
|
98
|
+
source: "keychain",
|
|
99
|
+
reason: "no LETTA_API_KEY; OS keychain has a Dreams refresh token",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
else if (filePresent) {
|
|
103
|
+
active = {
|
|
104
|
+
source: "file",
|
|
105
|
+
reason: "no LETTA_API_KEY; credentials file present (keychain empty or unavailable)",
|
|
106
|
+
};
|
|
57
107
|
}
|
|
108
|
+
const keychainMasked = maskedStored(probes.keychain.credentials);
|
|
109
|
+
const fileMasked = maskedStored(probes.file.credentials);
|
|
110
|
+
const credentials = [
|
|
111
|
+
{
|
|
112
|
+
source: "env",
|
|
113
|
+
present: envPresent,
|
|
114
|
+
selected: active?.source === "env",
|
|
115
|
+
access_token: envPresent && envKey ? (0, credentials_1.maskToken)(envKey) : null,
|
|
116
|
+
expires_at: null,
|
|
117
|
+
has_refresh_token: false,
|
|
118
|
+
reason: envPresent
|
|
119
|
+
? "highest precedence; used for CLI Cloud calls in this process and any child that inherits this environment"
|
|
120
|
+
: "LETTA_API_KEY is unset in this process",
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
source: "keychain",
|
|
124
|
+
present: keychainPresent,
|
|
125
|
+
selected: active?.source === "keychain",
|
|
126
|
+
access_token: keychainMasked.access_token,
|
|
127
|
+
expires_at: keychainMasked.expires_at,
|
|
128
|
+
has_refresh_token: keychainMasked.has_refresh_token,
|
|
129
|
+
reason: keychainReason(probes.keychain.status, active?.source === "keychain"),
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
source: "file",
|
|
133
|
+
present: filePresent,
|
|
134
|
+
selected: active?.source === "file",
|
|
135
|
+
access_token: fileMasked.access_token,
|
|
136
|
+
expires_at: fileMasked.expires_at,
|
|
137
|
+
has_refresh_token: fileMasked.has_refresh_token,
|
|
138
|
+
reason: fileReason(filePresent, active?.source === "file", keychainPresent, envPresent),
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
const winner = active
|
|
142
|
+
? credentials.find((c) => c.source === active.source)
|
|
143
|
+
: null;
|
|
58
144
|
return {
|
|
59
|
-
authenticated:
|
|
60
|
-
source:
|
|
61
|
-
access_token:
|
|
62
|
-
expires_at:
|
|
145
|
+
authenticated: active !== null,
|
|
146
|
+
source: active?.source ?? null,
|
|
147
|
+
access_token: winner?.access_token ?? null,
|
|
148
|
+
expires_at: winner?.expires_at ?? null,
|
|
63
149
|
device_id,
|
|
150
|
+
active,
|
|
151
|
+
credentials,
|
|
152
|
+
hooks: {
|
|
153
|
+
inherits_process_env: true,
|
|
154
|
+
effective_source_in_this_process: active?.source ?? null,
|
|
155
|
+
note: HOOKS_NOTE,
|
|
156
|
+
},
|
|
64
157
|
};
|
|
65
158
|
}
|
package/dist/cli.js
CHANGED
|
@@ -2,49 +2,158 @@
|
|
|
2
2
|
/** @letta-ai/dreams command-line entry point. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.main = main;
|
|
5
|
+
// Must be first: filters node:sqlite ExperimentalWarning before other imports load it.
|
|
6
|
+
require("./suppress-sqlite-warning");
|
|
5
7
|
const index_1 = require("./auth/index");
|
|
6
8
|
const commands_1 = require("./auth/commands");
|
|
9
|
+
const development_reset_1 = require("./development-reset");
|
|
7
10
|
const commands_2 = require("./local/commands");
|
|
8
11
|
const db_1 = require("./local/db");
|
|
12
|
+
const state_lock_1 = require("./local/state-lock");
|
|
13
|
+
const launcher_1 = require("./launcher");
|
|
9
14
|
const managed_install_1 = require("./managed-install");
|
|
10
15
|
const onboard_1 = require("./onboard");
|
|
16
|
+
const snapshots_1 = require("./snapshots");
|
|
11
17
|
const skill_1 = require("./skill");
|
|
12
18
|
const store_1 = require("./store");
|
|
19
|
+
const backfill_1 = require("./local/backfill");
|
|
20
|
+
const hooks_install_1 = require("./local/hooks-install");
|
|
21
|
+
const sync_1 = require("./sync");
|
|
13
22
|
const version_1 = require("./version");
|
|
14
23
|
const USAGE = `Usage: dreams <command> [options]
|
|
15
24
|
|
|
16
25
|
Commands:
|
|
17
|
-
onboard Install Dreams locally and
|
|
26
|
+
onboard Install Dreams locally and hand off to the setup skill
|
|
27
|
+
rollback Reactivate the previous installed CLI version
|
|
18
28
|
status Show installation, skill, CLI, database, and auth state
|
|
19
29
|
auth login Connect this device to Letta (OAuth device code flow)
|
|
20
30
|
auth status Show authentication state (never prints tokens)
|
|
21
31
|
auth logout Revoke Dreams' refresh token and clear stored credentials
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
source
|
|
32
|
+
dev reset local Preview/reset local SQLite and blob state
|
|
33
|
+
dev reset identity Preview/reset local state, credentials, and device identity
|
|
34
|
+
source detect Detect a harness and register a detected Cloud source
|
|
35
|
+
source scan Discover sessions and build the local upload queue
|
|
36
|
+
source approve-root <path> Approve a work directory so its transcripts become eligible
|
|
37
|
+
source approve-dir <path> Alias for approve-root (preferred user-facing name)
|
|
38
|
+
source list Show local sources, approved directories, and the pending queue
|
|
39
|
+
snapshots create Submit a skill-authored snapshot JSON file to Cloud
|
|
40
|
+
hooks install Install or preview harness live-sync hooks (Claude/Codex)
|
|
41
|
+
wake SessionStart housekeeping + kick pending sync/backfill
|
|
42
|
+
sync Schedule a live-turn sync worker (Stop hook; then active backfill)
|
|
43
|
+
sync pause|resume|status Pause/resume hooks or show sync diagnostics
|
|
44
|
+
backfill start Queue a bounded historical backfill window (local only)
|
|
25
45
|
upload Upload pending queued objects to Letta Cloud
|
|
26
46
|
|
|
27
47
|
Options:
|
|
28
|
-
--json
|
|
29
|
-
--
|
|
30
|
-
--
|
|
48
|
+
--json Machine-readable JSON output
|
|
49
|
+
--confirm Apply a development reset preview
|
|
50
|
+
--dry-run Preview hooks install without writing files
|
|
51
|
+
--force Bypass sync cadence (never bypasses pause/privacy)
|
|
52
|
+
--source <type> Source adapter (claude-code default; codex) for sync/wake, source detect/scan, upload, backfill, hooks install
|
|
53
|
+
--since <RFC3339> Backfill window start (with backfill start)
|
|
54
|
+
--type <type> Snapshot type (with snapshots create)
|
|
55
|
+
--file <path> Snapshot JSON file (with snapshots create)
|
|
56
|
+
--client-request-id <id> Idempotency key (with snapshots create)
|
|
57
|
+
--workspace-id <id> Override workspace id (defaults to local binding)
|
|
58
|
+
--version Print the CLI version
|
|
59
|
+
--help Show this help
|
|
31
60
|
`;
|
|
32
|
-
const COMMANDS_WITH_SUBCOMMANDS = new Set(["auth", "source"]);
|
|
61
|
+
const COMMANDS_WITH_SUBCOMMANDS = new Set(["auth", "dev", "source", "snapshots", "sync", "backfill", "hooks"]);
|
|
33
62
|
function parseArgs(argv) {
|
|
34
63
|
const parsed = {
|
|
35
64
|
positionals: [],
|
|
36
65
|
json: false,
|
|
66
|
+
confirm: false,
|
|
67
|
+
dryRun: false,
|
|
37
68
|
help: false,
|
|
38
69
|
version: false,
|
|
70
|
+
force: false,
|
|
71
|
+
worker: false,
|
|
72
|
+
source: undefined,
|
|
73
|
+
since: undefined,
|
|
74
|
+
type: undefined,
|
|
75
|
+
file: undefined,
|
|
76
|
+
clientRequestId: undefined,
|
|
77
|
+
workspaceId: undefined,
|
|
39
78
|
unknown: [],
|
|
40
79
|
};
|
|
41
|
-
for (
|
|
80
|
+
for (let i = 0; i < argv.length; i++) {
|
|
81
|
+
const arg = argv[i];
|
|
42
82
|
if (arg === "--json")
|
|
43
83
|
parsed.json = true;
|
|
84
|
+
else if (arg === "--confirm")
|
|
85
|
+
parsed.confirm = true;
|
|
86
|
+
else if (arg === "--dry-run")
|
|
87
|
+
parsed.dryRun = true;
|
|
88
|
+
else if (arg === "--force")
|
|
89
|
+
parsed.force = true;
|
|
90
|
+
else if (arg === "--worker")
|
|
91
|
+
parsed.worker = true;
|
|
92
|
+
else if (arg === "--source") {
|
|
93
|
+
const value = argv[++i];
|
|
94
|
+
if (value === undefined || value.trim() === "")
|
|
95
|
+
parsed.unknown.push("--source");
|
|
96
|
+
else
|
|
97
|
+
parsed.source = value;
|
|
98
|
+
}
|
|
99
|
+
else if (arg.startsWith("--source=")) {
|
|
100
|
+
const value = arg.slice("--source=".length);
|
|
101
|
+
if (value.trim() === "")
|
|
102
|
+
parsed.unknown.push("--source=");
|
|
103
|
+
else
|
|
104
|
+
parsed.source = value;
|
|
105
|
+
}
|
|
44
106
|
else if (arg === "--help" || arg === "-h")
|
|
45
107
|
parsed.help = true;
|
|
46
108
|
else if (arg === "--version" || arg === "-v")
|
|
47
109
|
parsed.version = true;
|
|
110
|
+
else if (arg === "--since") {
|
|
111
|
+
const value = argv[++i];
|
|
112
|
+
if (value === undefined)
|
|
113
|
+
parsed.unknown.push("--since");
|
|
114
|
+
else
|
|
115
|
+
parsed.since = value;
|
|
116
|
+
}
|
|
117
|
+
else if (arg.startsWith("--since="))
|
|
118
|
+
parsed.since = arg.slice("--since=".length);
|
|
119
|
+
else if (arg === "--type") {
|
|
120
|
+
const value = argv[++i];
|
|
121
|
+
if (value === undefined)
|
|
122
|
+
parsed.unknown.push("--type");
|
|
123
|
+
else
|
|
124
|
+
parsed.type = value;
|
|
125
|
+
}
|
|
126
|
+
else if (arg.startsWith("--type="))
|
|
127
|
+
parsed.type = arg.slice("--type=".length);
|
|
128
|
+
else if (arg === "--file") {
|
|
129
|
+
const value = argv[++i];
|
|
130
|
+
if (value === undefined)
|
|
131
|
+
parsed.unknown.push("--file");
|
|
132
|
+
else
|
|
133
|
+
parsed.file = value;
|
|
134
|
+
}
|
|
135
|
+
else if (arg.startsWith("--file="))
|
|
136
|
+
parsed.file = arg.slice("--file=".length);
|
|
137
|
+
else if (arg === "--client-request-id") {
|
|
138
|
+
const value = argv[++i];
|
|
139
|
+
if (value === undefined)
|
|
140
|
+
parsed.unknown.push("--client-request-id");
|
|
141
|
+
else
|
|
142
|
+
parsed.clientRequestId = value;
|
|
143
|
+
}
|
|
144
|
+
else if (arg.startsWith("--client-request-id=")) {
|
|
145
|
+
parsed.clientRequestId = arg.slice("--client-request-id=".length);
|
|
146
|
+
}
|
|
147
|
+
else if (arg === "--workspace-id") {
|
|
148
|
+
const value = argv[++i];
|
|
149
|
+
if (value === undefined)
|
|
150
|
+
parsed.unknown.push("--workspace-id");
|
|
151
|
+
else
|
|
152
|
+
parsed.workspaceId = value;
|
|
153
|
+
}
|
|
154
|
+
else if (arg.startsWith("--workspace-id=")) {
|
|
155
|
+
parsed.workspaceId = arg.slice("--workspace-id=".length);
|
|
156
|
+
}
|
|
48
157
|
else if (arg.startsWith("-"))
|
|
49
158
|
parsed.unknown.push(arg);
|
|
50
159
|
else
|
|
@@ -52,14 +161,17 @@ function parseArgs(argv) {
|
|
|
52
161
|
}
|
|
53
162
|
return parsed;
|
|
54
163
|
}
|
|
55
|
-
function
|
|
164
|
+
function commandStatusUnlocked(json) {
|
|
56
165
|
const installation = (0, store_1.loadOrCreateInstallation)();
|
|
57
166
|
const auth = (0, index_1.authStatusInfo)();
|
|
167
|
+
const activation = (0, launcher_1.readActivation)();
|
|
58
168
|
const status = {
|
|
59
169
|
installation_id: installation.installation_id,
|
|
60
170
|
installation_created_at: installation.created_at,
|
|
61
171
|
state: auth.authenticated ? "authenticated" : "unauthenticated",
|
|
62
172
|
cli_path: (0, managed_install_1.stableCliPath)(),
|
|
173
|
+
active_cli_version: activation?.active_version ?? null,
|
|
174
|
+
previous_cli_version: activation?.previous_version ?? null,
|
|
63
175
|
database_path: (0, db_1.dbFilePath)(),
|
|
64
176
|
skill_version: (0, skill_1.installedSkillVersion)(),
|
|
65
177
|
skill_path: (0, skill_1.skillFilePath)(),
|
|
@@ -72,7 +184,7 @@ function commandStatus(json) {
|
|
|
72
184
|
else {
|
|
73
185
|
const authLine = auth.authenticated
|
|
74
186
|
? `yes (via ${auth.source}, token ${auth.access_token ?? "pending refresh"}${auth.expires_at ? `, expires ${auth.expires_at}` : ""})`
|
|
75
|
-
: "no (run `dreams
|
|
187
|
+
: "no (run `dreams auth login`)";
|
|
76
188
|
console.log([
|
|
77
189
|
"Dreams status",
|
|
78
190
|
"",
|
|
@@ -80,6 +192,8 @@ function commandStatus(json) {
|
|
|
80
192
|
` Created at: ${status.installation_created_at}`,
|
|
81
193
|
` State: ${status.state}`,
|
|
82
194
|
` Durable CLI: ${status.cli_path}`,
|
|
195
|
+
` Active version: ${status.active_cli_version ?? "(not activated)"}`,
|
|
196
|
+
` Previous version:${status.previous_cli_version ? ` ${status.previous_cli_version}` : " (none)"}`,
|
|
83
197
|
` Skill installed: ${status.skill_version ? `v${status.skill_version} at ${status.skill_path}` : "no"}`,
|
|
84
198
|
` Authenticated: ${authLine}`,
|
|
85
199
|
` Database: ${status.database_path}`,
|
|
@@ -88,6 +202,23 @@ function commandStatus(json) {
|
|
|
88
202
|
}
|
|
89
203
|
return 0;
|
|
90
204
|
}
|
|
205
|
+
async function commandStatus(json) {
|
|
206
|
+
const lock = await (0, state_lock_1.waitForLocalStateLock)();
|
|
207
|
+
if (lock === null) {
|
|
208
|
+
const message = "Another Dreams command is using local state. Try again after it finishes.";
|
|
209
|
+
if (json)
|
|
210
|
+
console.log(JSON.stringify({ status: "error", code: "local_state_busy", message }));
|
|
211
|
+
else
|
|
212
|
+
process.stderr.write(message + "\n");
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
return commandStatusUnlocked(json);
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
(0, state_lock_1.releaseLocalStateLock)(lock);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
91
222
|
function commandAuth(subcommand, json) {
|
|
92
223
|
switch (subcommand) {
|
|
93
224
|
case "login":
|
|
@@ -101,6 +232,17 @@ function commandAuth(subcommand, json) {
|
|
|
101
232
|
return 1;
|
|
102
233
|
}
|
|
103
234
|
}
|
|
235
|
+
function commandDevelopment(positionals, confirmed, json) {
|
|
236
|
+
if (positionals[1] !== "reset") {
|
|
237
|
+
process.stderr.write(`Unknown dev subcommand: ${positionals[1] ?? "(none)"} — expected reset\n\n${USAGE}`);
|
|
238
|
+
return 1;
|
|
239
|
+
}
|
|
240
|
+
if (positionals.length > 3) {
|
|
241
|
+
process.stderr.write(`Unexpected argument(s): ${positionals.slice(3).join(", ")}\n\n${USAGE}`);
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
return (0, development_reset_1.commandDevelopmentReset)(positionals[2], confirmed, json);
|
|
245
|
+
}
|
|
104
246
|
async function runCli() {
|
|
105
247
|
const args = parseArgs(process.argv.slice(2));
|
|
106
248
|
const command = args.positionals[0];
|
|
@@ -113,6 +255,36 @@ async function runCli() {
|
|
|
113
255
|
process.stdout.write(USAGE);
|
|
114
256
|
return args.help ? 0 : 1;
|
|
115
257
|
}
|
|
258
|
+
if (args.confirm && (command !== "dev" || subcommand !== "reset")) {
|
|
259
|
+
process.stderr.write(`--confirm is only valid with \`dreams dev reset\`\n\n${USAGE}`);
|
|
260
|
+
return 1;
|
|
261
|
+
}
|
|
262
|
+
if ((args.type || args.file || args.clientRequestId || args.workspaceId) && command !== "snapshots") {
|
|
263
|
+
process.stderr.write(`Snapshot options are only valid with \`dreams snapshots create\`\n\n${USAGE}`);
|
|
264
|
+
return 1;
|
|
265
|
+
}
|
|
266
|
+
if ((args.force || args.worker) && command !== "sync") {
|
|
267
|
+
process.stderr.write(`--force/--worker are only valid with \`dreams sync\`\n\n${USAGE}`);
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
if (args.dryRun && command !== "hooks") {
|
|
271
|
+
process.stderr.write(`--dry-run is only valid with \`dreams hooks install\`\n\n${USAGE}`);
|
|
272
|
+
return 1;
|
|
273
|
+
}
|
|
274
|
+
if (args.source &&
|
|
275
|
+
command !== "sync" &&
|
|
276
|
+
command !== "wake" &&
|
|
277
|
+
command !== "upload" &&
|
|
278
|
+
command !== "hooks" &&
|
|
279
|
+
!(command === "source" && (subcommand === "detect" || subcommand === "scan")) &&
|
|
280
|
+
!(command === "backfill" && subcommand === "start")) {
|
|
281
|
+
process.stderr.write(`--source is only valid with \`dreams sync\`, \`dreams wake\`, \`dreams hooks install\`, \`dreams source detect\`, \`dreams source scan\`, \`dreams upload\`, or \`dreams backfill start\`\n\n${USAGE}`);
|
|
282
|
+
return 1;
|
|
283
|
+
}
|
|
284
|
+
if (args.since && command !== "backfill") {
|
|
285
|
+
process.stderr.write(`--since is only valid with \`dreams backfill start\`\n\n${USAGE}`);
|
|
286
|
+
return 1;
|
|
287
|
+
}
|
|
116
288
|
if (args.unknown.length > 0) {
|
|
117
289
|
process.stderr.write(`Unknown argument(s): ${args.unknown.join(", ")}\n\n${USAGE}`);
|
|
118
290
|
return 1;
|
|
@@ -122,23 +294,77 @@ async function runCli() {
|
|
|
122
294
|
process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(1).join(", ")}\n\n${USAGE}`);
|
|
123
295
|
return 1;
|
|
124
296
|
}
|
|
297
|
+
if (command === "snapshots" && args.positionals.length > 2) {
|
|
298
|
+
process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(2).join(", ")}\n\n${USAGE}`);
|
|
299
|
+
return 1;
|
|
300
|
+
}
|
|
301
|
+
if (command === "sync" && args.positionals.length > 2) {
|
|
302
|
+
process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(2).join(", ")}\n\n${USAGE}`);
|
|
303
|
+
return 1;
|
|
304
|
+
}
|
|
305
|
+
if (command === "backfill" && args.positionals.length > 2) {
|
|
306
|
+
process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(2).join(", ")}\n\n${USAGE}`);
|
|
307
|
+
return 1;
|
|
308
|
+
}
|
|
309
|
+
if (command === "hooks" && args.positionals.length > 2) {
|
|
310
|
+
process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(2).join(", ")}\n\n${USAGE}`);
|
|
311
|
+
return 1;
|
|
312
|
+
}
|
|
313
|
+
if (command === "wake" && args.positionals.length > 1) {
|
|
314
|
+
process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(1).join(", ")}\n\n${USAGE}`);
|
|
315
|
+
return 1;
|
|
316
|
+
}
|
|
125
317
|
switch (command) {
|
|
126
318
|
case "onboard":
|
|
127
319
|
return (0, onboard_1.commandOnboard)(args.json);
|
|
320
|
+
case "rollback":
|
|
321
|
+
return (0, launcher_1.commandRollback)(args.json);
|
|
128
322
|
case "status":
|
|
129
323
|
return commandStatus(args.json);
|
|
130
324
|
case "auth":
|
|
131
325
|
return commandAuth(subcommand, args.json);
|
|
326
|
+
case "dev":
|
|
327
|
+
return commandDevelopment(args.positionals, args.confirm, args.json);
|
|
132
328
|
case "source":
|
|
133
|
-
return (0, commands_2.commandSource)(subcommand, args.positionals[2], args.json);
|
|
329
|
+
return (0, commands_2.commandSource)(subcommand, args.positionals[2], args.json, args.source);
|
|
330
|
+
case "snapshots":
|
|
331
|
+
return (0, snapshots_1.commandSnapshots)(subcommand, {
|
|
332
|
+
type: args.type,
|
|
333
|
+
file: args.file,
|
|
334
|
+
clientRequestId: args.clientRequestId,
|
|
335
|
+
workspaceId: args.workspaceId,
|
|
336
|
+
json: args.json,
|
|
337
|
+
});
|
|
338
|
+
case "hooks":
|
|
339
|
+
if (subcommand !== "install") {
|
|
340
|
+
process.stderr.write(`Unknown hooks subcommand: ${subcommand ?? "(none)"} — expected install\n\n${USAGE}`);
|
|
341
|
+
return 1;
|
|
342
|
+
}
|
|
343
|
+
return (0, hooks_install_1.commandHooksInstall)({ json: args.json, dryRun: args.dryRun, sourceType: args.source });
|
|
344
|
+
case "wake":
|
|
345
|
+
return (0, sync_1.commandWake)({ json: args.json, sourceType: args.source });
|
|
346
|
+
case "sync":
|
|
347
|
+
return (0, sync_1.commandSync)(subcommand, {
|
|
348
|
+
json: args.json,
|
|
349
|
+
force: args.force,
|
|
350
|
+
worker: args.worker,
|
|
351
|
+
sourceType: args.source,
|
|
352
|
+
});
|
|
353
|
+
case "backfill":
|
|
354
|
+
if (subcommand !== "start") {
|
|
355
|
+
process.stderr.write(`Unknown backfill subcommand: ${subcommand ?? "(none)"} — expected start\n\n${USAGE}`);
|
|
356
|
+
return 1;
|
|
357
|
+
}
|
|
358
|
+
return (0, backfill_1.commandBackfillStart)({ since: args.since, json: args.json, sourceType: args.source });
|
|
134
359
|
case "upload":
|
|
135
|
-
return (0, commands_2.commandUpload)(args.json);
|
|
360
|
+
return (0, commands_2.commandUpload)(args.json, args.source);
|
|
136
361
|
default:
|
|
137
362
|
process.stderr.write(`Unknown command: ${command}\n\n${USAGE}`);
|
|
138
363
|
return 1;
|
|
139
364
|
}
|
|
140
365
|
}
|
|
141
366
|
function main() {
|
|
367
|
+
(0, launcher_1.signalLauncherReady)();
|
|
142
368
|
runCli().then((code) => process.exit(code), (error) => {
|
|
143
369
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
144
370
|
process.exit(1);
|
package/dist/cloud/client.js
CHANGED
|
@@ -42,8 +42,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
42
42
|
};
|
|
43
43
|
})();
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
-
exports.CloudError = exports.CloudAuthError = void 0;
|
|
45
|
+
exports.CLOUD_UPLOAD_MAX_CONTENT_CHARS = exports.CloudError = exports.CloudAuthError = void 0;
|
|
46
|
+
exports.setCloudUploadMaxContentCharsForTests = setCloudUploadMaxContentCharsForTests;
|
|
47
|
+
exports.encodeUploadContent = encodeUploadContent;
|
|
46
48
|
exports.registerSource = registerSource;
|
|
49
|
+
exports.createSnapshot = createSnapshot;
|
|
47
50
|
exports.uploadObject = uploadObject;
|
|
48
51
|
const zlib = __importStar(require("node:zlib"));
|
|
49
52
|
const index_1 = require("../auth/index");
|
|
@@ -71,6 +74,33 @@ exports.CloudError = CloudError;
|
|
|
71
74
|
// Comfortably below the source lease TTL (30s) so a stalled request cannot
|
|
72
75
|
// outlive the lease and let another process reclaim it mid-flight.
|
|
73
76
|
const REQUEST_TIMEOUT_MS = 20_000;
|
|
77
|
+
/**
|
|
78
|
+
* Cloud `createUploadBodySchema.content` max: base64 of gzip'd bytes (not the
|
|
79
|
+
* uncompressed transcript). Enforced locally so oversized payloads quarantine
|
|
80
|
+
* without a wasted round trip ([Amelia] two-limit model).
|
|
81
|
+
*/
|
|
82
|
+
exports.CLOUD_UPLOAD_MAX_CONTENT_CHARS = 8 * 1024 * 1024;
|
|
83
|
+
let cloudUploadMaxContentCharsForTests = null;
|
|
84
|
+
/** Test-only override for the encoded upload size gate. Pass null to clear. */
|
|
85
|
+
function setCloudUploadMaxContentCharsForTests(value) {
|
|
86
|
+
cloudUploadMaxContentCharsForTests = value;
|
|
87
|
+
}
|
|
88
|
+
function cloudUploadMaxContentChars() {
|
|
89
|
+
return cloudUploadMaxContentCharsForTests ?? exports.CLOUD_UPLOAD_MAX_CONTENT_CHARS;
|
|
90
|
+
}
|
|
91
|
+
/** Gzip + base64 encode transcript bytes and enforce the Cloud content char cap. */
|
|
92
|
+
function encodeUploadContent(bytes, maxContentChars = cloudUploadMaxContentChars()) {
|
|
93
|
+
const content = zlib.gzipSync(bytes).toString("base64");
|
|
94
|
+
if (content.length > maxContentChars) {
|
|
95
|
+
return {
|
|
96
|
+
ok: false,
|
|
97
|
+
code: "payload_too_large",
|
|
98
|
+
message: `gzip+base64 upload content is ${content.length} chars; Cloud maximum is ${maxContentChars}`,
|
|
99
|
+
encodedLength: content.length,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return { ok: true, content, encodedLength: content.length };
|
|
103
|
+
}
|
|
74
104
|
function errorDetail(error) {
|
|
75
105
|
const cause = error.cause;
|
|
76
106
|
return cause?.code || cause?.message || (error instanceof Error ? error.message : String(error));
|
|
@@ -144,7 +174,7 @@ function classifyAuthOrRetry(status, json, path) {
|
|
|
144
174
|
* id, so it is also how the CLI resolves the workspace.
|
|
145
175
|
*/
|
|
146
176
|
async function registerSource(input) {
|
|
147
|
-
const
|
|
177
|
+
const body = {
|
|
148
178
|
source_type: input.sourceType,
|
|
149
179
|
source_key: input.sourceKey,
|
|
150
180
|
display_name: input.displayName,
|
|
@@ -152,7 +182,16 @@ async function registerSource(input) {
|
|
|
152
182
|
adapter_version: input.adapterVersion,
|
|
153
183
|
privacy_config_hash: input.privacyConfigHash ?? null,
|
|
154
184
|
backfill_policy: input.backfillPolicy ?? null,
|
|
155
|
-
}
|
|
185
|
+
};
|
|
186
|
+
if (input.status != null)
|
|
187
|
+
body.status = input.status;
|
|
188
|
+
if (input.cliVersion != null)
|
|
189
|
+
body.cli_version = input.cliVersion;
|
|
190
|
+
if (input.skillVersion != null)
|
|
191
|
+
body.skill_version = input.skillVersion;
|
|
192
|
+
if (input.platform != null)
|
|
193
|
+
body.platform = input.platform;
|
|
194
|
+
const r = await request("POST", "/v1/dreams/sources", body);
|
|
156
195
|
if (r.status !== 200 && r.status !== 201)
|
|
157
196
|
classifyAuthOrRetry(r.status, r.json, "sources");
|
|
158
197
|
const id = r.json?.id;
|
|
@@ -164,14 +203,59 @@ async function registerSource(input) {
|
|
|
164
203
|
workspaceId: typeof r.json?.workspace_id === "string" ? r.json.workspace_id : null,
|
|
165
204
|
};
|
|
166
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Narrow transport for skill-authored snapshots ([11] / LET-9833).
|
|
208
|
+
* Validation beyond JSON-object + size belongs on the server.
|
|
209
|
+
*
|
|
210
|
+
* Cloud contract: POST /v1/dreams/workspaces/{workspaceId}/snapshots
|
|
211
|
+
* with body `{ type, data, client_request_id }`.
|
|
212
|
+
*/
|
|
213
|
+
async function createSnapshot(input) {
|
|
214
|
+
const path = `/v1/dreams/workspaces/${encodeURIComponent(input.workspaceId)}/snapshots`;
|
|
215
|
+
const r = await request("POST", path, {
|
|
216
|
+
type: input.type,
|
|
217
|
+
client_request_id: input.clientRequestId,
|
|
218
|
+
data: input.data,
|
|
219
|
+
});
|
|
220
|
+
if (r.status !== 200 && r.status !== 201)
|
|
221
|
+
classifyAuthOrRetry(r.status, r.json, "snapshots");
|
|
222
|
+
const id = r.json?.id;
|
|
223
|
+
if (typeof id !== "string")
|
|
224
|
+
throw new CloudError("snapshot response missing id", { retryable: true });
|
|
225
|
+
return {
|
|
226
|
+
id,
|
|
227
|
+
type: typeof r.json?.type === "string" ? r.json.type : input.type,
|
|
228
|
+
workspaceId: input.workspaceId,
|
|
229
|
+
clientRequestId: typeof r.json?.client_request_id === "string" ? r.json.client_request_id : input.clientRequestId,
|
|
230
|
+
status: typeof r.json?.status === "string" ? r.json.status : "stored",
|
|
231
|
+
createdAt: typeof r.json?.created_at === "string" ? r.json.created_at : null,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
167
234
|
async function uploadObject(input) {
|
|
168
|
-
|
|
235
|
+
// Prefer a preflight-encoded body; otherwise encode here (defense in depth).
|
|
236
|
+
const gated = input.encodedContent !== undefined
|
|
237
|
+
? input.encodedContent.length > cloudUploadMaxContentChars()
|
|
238
|
+
? {
|
|
239
|
+
ok: false,
|
|
240
|
+
code: "payload_too_large",
|
|
241
|
+
message: `gzip+base64 upload content is ${input.encodedContent.length} chars; Cloud maximum is ${cloudUploadMaxContentChars()}`,
|
|
242
|
+
encodedLength: input.encodedContent.length,
|
|
243
|
+
}
|
|
244
|
+
: {
|
|
245
|
+
ok: true,
|
|
246
|
+
content: input.encodedContent,
|
|
247
|
+
encodedLength: input.encodedContent.length,
|
|
248
|
+
}
|
|
249
|
+
: encodeUploadContent(input.bytes);
|
|
250
|
+
if (!gated.ok) {
|
|
251
|
+
return { kind: "quarantine", code: gated.code, message: gated.message };
|
|
252
|
+
}
|
|
169
253
|
const r = await request("POST", "/v1/dreams/uploads", {
|
|
170
254
|
source_id: input.sourceId,
|
|
171
255
|
client_upload_id: input.clientUploadId,
|
|
172
256
|
content_sha256: input.contentSha256,
|
|
173
257
|
content_encoding: "gzip+base64",
|
|
174
|
-
content,
|
|
258
|
+
content: gated.content,
|
|
175
259
|
source_schema: input.sourceSchema,
|
|
176
260
|
source_metadata: input.sourceMetadata,
|
|
177
261
|
priority: input.priority,
|