@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 CHANGED
@@ -15,38 +15,77 @@ npx @letta-ai/dreams@latest onboard --json
15
15
 
16
16
  1. verifies Node >= 22.13 through the bin preflight
17
17
  2. installs the exact CLI version under `~/.dreams/cli/<version>/`
18
- 3. creates or preserves the stable launcher at `~/.dreams/bin/dreams`
18
+ 3. activates it through the stable launcher at `~/.dreams/bin/dreams`
19
19
  4. creates or preserves the installation id in `~/.dreams/installation.json`
20
- 5. installs `~/.claude/skills/dreams-setup/SKILL.md`
20
+ 5. installs `~/.claude/skills/dreams-setup/` (skill + init snapshot template)
21
21
  6. initializes and migrates `~/.dreams/dreams.db`
22
- 7. returns the next agent action based on authentication state
22
+ 7. returns a skill handoff (`status: "ready"`) with absolute `cli_path` and skill path
23
23
 
24
- It does not start login or `orient` itself. An unauthenticated response directs
25
- the agent to run `auth login` and then rerun `onboard`; an authenticated
26
- response directs the agent to run `orient`. Repeated calls are safe and return
27
- the same action until authentication state changes.
24
+ It does **not** check authentication or choose login/orient next steps. The
25
+ installed `dreams-setup` skill owns sequencing (authenticate detect source
26
+ init snapshot activate test connection). Repeated `onboard` calls are safe
27
+ and repair local setup only.
28
28
 
29
29
  ### Commands
30
30
 
31
- - `dreams onboard [--json]` — install or repair local setup and return the next
32
- setup action.
33
- - `dreams status [--json]` — installation, durable CLI, SQLite, setup skill,
34
- and authentication state.
31
+ - `dreams onboard [--json]` — install or repair local setup and hand off to the
32
+ setup skill.
33
+ - `dreams rollback [--json]` — reactivate the previous completed CLI version.
34
+ - `dreams status [--json]` — installation, active and previous CLI versions,
35
+ SQLite, setup skill, and authentication state.
35
36
  - `dreams auth login [--json]` — OAuth device code sign-in (see Auth below).
36
37
  - `dreams auth status [--json]` — authentication state; tokens are masked
37
38
  (`at-let-…abcd`), never printed.
38
39
  - `dreams auth logout [--json]` — best-effort revoke of Dreams' refresh token,
39
40
  then clear stored credentials.
41
+ - `dreams dev reset local [--confirm] [--json]` — preview or remove only local
42
+ SQLite and blob state.
43
+ - `dreams dev reset identity [--confirm] [--json]` — preview or remove local
44
+ state, stored Dreams credentials, and the installation identity.
40
45
 
41
46
  Local state lives in `~/.dreams/`. Requires Node >= 22.13, where built-in
42
47
  `node:sqlite` no longer requires the `--experimental-sqlite` flag.
43
48
 
49
+ ### CLI upgrades and rollback
50
+
51
+ Installed CLI versions are immutable under `~/.dreams/cli/<version>/`. The
52
+ atomic `~/.dreams/cli/activation.json` record selects the active version and
53
+ retains one previous version. The stable launcher reads that record rather than
54
+ pointing directly at a version.
55
+
56
+ Run `dreams rollback` to reactivate the previous completed version. The launcher
57
+ also rolls back automatically when an active CLI cannot reach its startup
58
+ readiness marker, then retries the original command once. Normal command errors
59
+ after readiness, including invalid arguments, authentication failures, and
60
+ network failures, never trigger rollback.
61
+
62
+ ### Development reset
63
+
64
+ Development resets are preview-only until `--confirm` is provided. `local`
65
+ removes `dreams.db`, its SQLite sidecars, and `~/.dreams/blobs/`; it preserves
66
+ credentials and the installation identity. `identity` removes those local files
67
+ plus Dreams' stored credentials and `installation.json`, so the next `onboard`
68
+ mints a new device identity. Both scopes preserve installed CLI versions, the
69
+ stable launcher, the setup skill, and every Cloud resource. Confirmed resets
70
+ hold the same local-state lock as onboarding, auth, source, and upload commands,
71
+ so they fail instead of deleting files another process is using. If
72
+ `DREAMS_CREDENTIALS_BACKEND=file` is forced, identity reset clears the file and
73
+ explicitly reports that the OS keychain was not checked.
74
+
75
+ Test-workspace cleanup is intentionally not available through this local CLI.
76
+ It is a separate internal Cloud operation with different credentials and data
77
+ cleanup semantics. `dreams dev reset test-workspace` fails without deleting
78
+ anything.
79
+
44
80
  ## Local sources (Claude Code)
45
81
 
46
82
  The CLI maintains a crash-safe local SQLite store (`~/.dreams/dreams.db`) that
47
83
  discovers Claude Code transcripts and prepares immutable upload segments before
48
84
  they are sent to Letta Cloud.
49
85
 
86
+ - `dreams source detect [--json]` — detect Claude Code from harness filesystem
87
+ facts under `~/.claude` (no transcript reads) and register an installation-scoped
88
+ Cloud source in status `detected`. Idempotent; does not scan or upload.
50
89
  - `dreams source approve-root <path> [--json]` — approve a work root. Sources
51
90
  are **deny-by-default**: no transcript bytes are queued until their working
52
91
  directory is under an approved root.
@@ -57,8 +96,23 @@ they are sent to Letta Cloud.
57
96
  before anything is enqueued.
58
97
  - `dreams source list [--json]` — show the DB path, approved roots, sources, and
59
98
  the pending upload queue.
60
- - `dreams upload [--json]` authenticate, register the source, and upload due
61
- queue entries to Letta Cloud.
99
+ - `dreams snapshots create --type <type> --file <path> --client-request-id <id> [--workspace-id <id>] [--json]`
100
+ submit a skill-authored JSON snapshot (256 KiB UTF-8 object cap) to
101
+ `POST /v1/dreams/workspaces/{workspaceId}/snapshots`. Workspace defaults to the
102
+ local binding from `source detect`.
103
+ - `dreams upload [--json]` — authenticate, refresh source registration, and
104
+ upload due queue entries. Upload accepts `detected` sources; Cloud promotes to
105
+ `active` after durable acceptance.
106
+ - `dreams sync [--force] [--json]` — hook-safe scheduler: records a durable sync
107
+ trigger and runs a detached worker that live-scans tracked sessions and uploads
108
+ with a per-pass budget (~3h cadence unless `--force` or retry-due).
109
+ - `dreams sync pause|resume|status [--json]` — pause makes hook wakes no-ops;
110
+ resume clears pause and requests one immediate sync.
111
+ - `dreams backfill start --since <RFC3339> [--json]` — approve a frozen
112
+ `[since, until=now]` historical window and run one bounded local scan/queue
113
+ pass (session frontier, newest unfinished sessions first; no upload).
114
+ Re-running the same active `--since` is idempotent; a different active policy
115
+ fails closed. Sync drains live first, then backfill by newest session mtime.
62
116
 
63
117
  Local cursors track a `queued_through_offset` (advanced as segments are queued)
64
118
  separately from an `acked_offset` (advanced only after durable Cloud
@@ -127,20 +181,20 @@ npm test # build + node:test suite (uses a temp HOME)
127
181
  ## Testing the npx bootstrap via tarball (no publish)
128
182
 
129
183
  ```sh
130
- npm pack # produces letta-ai-dreams-0.0.1.tgz
184
+ npm pack # produces letta-ai-dreams-0.0.3.tgz
131
185
  cd "$(mktemp -d)"
132
- npx -y -p /absolute/path/to/dreams/letta-ai-dreams-0.0.1.tgz dreams onboard
186
+ npx -y -p /absolute/path/to/dreams/letta-ai-dreams-0.0.3.tgz dreams onboard
133
187
  ```
134
188
 
135
189
  (The `-p <tarball> dreams` form is required: a bare `npx <tarball>` treats the
136
190
  path as a script to execute, not a package to install.)
137
191
 
138
192
  `onboard` writes to `~/.dreams/` and installs
139
- `~/.claude/skills/dreams-setup/SKILL.md`. Set `HOME` to a temporary directory
140
- for a sandboxed run.
193
+ `~/.claude/skills/dreams-setup/` (including the init snapshot template). Set
194
+ `HOME` to a temporary directory for a sandboxed run.
141
195
 
142
196
  ## Deferred
143
197
 
144
- - `orient` and its Cloud-backed setup actions
145
- - Shared-memory generation and review
146
- - Automated source configuration and historical backfill
198
+ - Shared-memory repository generation and review
199
+ - Hook installation and default Dream schedule creation
200
+ - `dreams backfill start` (skill optional background history import)
@@ -7,8 +7,8 @@
7
7
  * {"status":"authenticated",…} object once the user approves in the browser.
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.commandAuthLogin = commandAuthLogin;
11
10
  exports.renderAuthStatus = renderAuthStatus;
11
+ exports.commandAuthLogin = commandAuthLogin;
12
12
  exports.commandAuthStatus = commandAuthStatus;
13
13
  exports.commandAuthLogout = commandAuthLogout;
14
14
  const browser_1 = require("./browser");
@@ -17,12 +17,13 @@ const index_1 = require("./index");
17
17
  const credentials_1 = require("./credentials");
18
18
  const oauth_refresh_1 = require("./oauth-refresh");
19
19
  const oauth_1 = require("./oauth");
20
+ const state_lock_1 = require("../local/state-lock");
20
21
  /** `auth login` short-circuits when the stored token is valid for >7 days. */
21
22
  const REAUTH_MARGIN_MS = 7 * 24 * 60 * 60 * 1000;
22
23
  function printJsonLine(value) {
23
24
  console.log(JSON.stringify(value));
24
25
  }
25
- async function commandAuthLogin(json) {
26
+ async function commandAuthLoginUnlocked(json) {
26
27
  const stored = (0, credentials_1.loadCredentials)();
27
28
  if (stored && stored.credentials.access_token && !(0, credentials_1.expiresWithin)(stored.credentials, REAUTH_MARGIN_MS)) {
28
29
  if (json) {
@@ -111,7 +112,7 @@ function renderAuthStatus(info) {
111
112
  ` Device id: ${info.device_id}`,
112
113
  ].join("\n");
113
114
  }
114
- function commandAuthStatus(json) {
115
+ function commandAuthStatusUnlocked(json) {
115
116
  const info = (0, index_1.authStatusInfo)();
116
117
  if (json) {
117
118
  console.log(JSON.stringify(info, null, 2));
@@ -121,7 +122,7 @@ function commandAuthStatus(json) {
121
122
  }
122
123
  return 0;
123
124
  }
124
- async function commandAuthLogout(json) {
125
+ async function commandAuthLogoutUnlocked(json) {
125
126
  const stored = (0, credentials_1.loadCredentials)();
126
127
  if (!stored) {
127
128
  if (json)
@@ -139,3 +140,44 @@ async function commandAuthLogout(json) {
139
140
  console.log("Logged out: refresh token revoked and local credentials cleared.");
140
141
  return 0;
141
142
  }
143
+ function stateBusy(json) {
144
+ const message = "Another Dreams command is using local state. Try again after it finishes.";
145
+ if (json)
146
+ printJsonLine({ status: "error", code: "local_state_busy", message });
147
+ else
148
+ process.stderr.write(message + "\n");
149
+ return 1;
150
+ }
151
+ async function commandAuthLogin(json) {
152
+ const lock = (0, state_lock_1.acquireLocalStateLock)();
153
+ if (lock === null)
154
+ return stateBusy(json);
155
+ try {
156
+ return await commandAuthLoginUnlocked(json);
157
+ }
158
+ finally {
159
+ (0, state_lock_1.releaseLocalStateLock)(lock);
160
+ }
161
+ }
162
+ function commandAuthStatus(json) {
163
+ const lock = (0, state_lock_1.acquireLocalStateLock)();
164
+ if (lock === null)
165
+ return stateBusy(json);
166
+ try {
167
+ return commandAuthStatusUnlocked(json);
168
+ }
169
+ finally {
170
+ (0, state_lock_1.releaseLocalStateLock)(lock);
171
+ }
172
+ }
173
+ async function commandAuthLogout(json) {
174
+ const lock = (0, state_lock_1.acquireLocalStateLock)();
175
+ if (lock === null)
176
+ return stateBusy(json);
177
+ try {
178
+ return await commandAuthLogoutUnlocked(json);
179
+ }
180
+ finally {
181
+ (0, state_lock_1.releaseLocalStateLock)(lock);
182
+ }
183
+ }
@@ -108,15 +108,26 @@ function keychainRead(account) {
108
108
  return null; // no entry, or keychain unusable — fall through to file
109
109
  }
110
110
  }
111
- function keychainDelete(account) {
111
+ function keychainDelete(account, strict) {
112
112
  const ctor = keyringCtor();
113
- if (!ctor)
114
- return;
113
+ if (!ctor) {
114
+ if (backendOverride() === "file")
115
+ return "not_checked_file_backend";
116
+ if (strict) {
117
+ throw new Error("Could not verify OS keychain cleanup because @napi-rs/keyring is unavailable. " +
118
+ "Rerun where keychain support is available.");
119
+ }
120
+ return "unavailable";
121
+ }
115
122
  try {
116
123
  new ctor(KEYCHAIN_SERVICE, account).deleteCredential();
124
+ return "cleared";
117
125
  }
118
- catch {
119
- // already gone or keychain unusable
126
+ catch (error) {
127
+ if (strict) {
128
+ throw new Error(`Could not clear Dreams credentials from the OS keychain: ${error instanceof Error ? error.message : String(error)}`);
129
+ }
130
+ return "unavailable";
120
131
  }
121
132
  }
122
133
  // --- file backend ------------------------------------------------------------
@@ -202,10 +213,19 @@ function saveCredentials(credentials) {
202
213
  fileWrite(credentials);
203
214
  }
204
215
  /** Clear both backends (login/logout/invalid_grant paths). */
205
- function clearCredentials() {
206
- keychainDelete(REFRESH_ACCOUNT);
207
- keychainDelete(ACCESS_ACCOUNT);
216
+ function clearCredentials(options = {}) {
217
+ const strict = options.strictKeychain === true;
218
+ const refresh = keychainDelete(REFRESH_ACCOUNT, strict);
219
+ const access = keychainDelete(ACCESS_ACCOUNT, strict);
208
220
  fs.rmSync(credentialsFilePath(), { force: true });
221
+ return {
222
+ file: "cleared",
223
+ keychain: refresh === "not_checked_file_backend" || access === "not_checked_file_backend"
224
+ ? "not_checked_file_backend"
225
+ : refresh === "cleared" && access === "cleared"
226
+ ? "cleared"
227
+ : "unavailable",
228
+ };
209
229
  }
210
230
  function expiresWithin(credentials, marginMs = exports.EXPIRY_MARGIN_MS) {
211
231
  const at = Date.parse(credentials.expires_at);
package/dist/cli.js CHANGED
@@ -4,47 +4,146 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.main = main;
5
5
  const index_1 = require("./auth/index");
6
6
  const commands_1 = require("./auth/commands");
7
+ const development_reset_1 = require("./development-reset");
7
8
  const commands_2 = require("./local/commands");
8
9
  const db_1 = require("./local/db");
10
+ const state_lock_1 = require("./local/state-lock");
11
+ const launcher_1 = require("./launcher");
9
12
  const managed_install_1 = require("./managed-install");
10
13
  const onboard_1 = require("./onboard");
14
+ const snapshots_1 = require("./snapshots");
11
15
  const skill_1 = require("./skill");
12
16
  const store_1 = require("./store");
17
+ const backfill_1 = require("./local/backfill");
18
+ const sync_1 = require("./sync");
13
19
  const version_1 = require("./version");
14
20
  const USAGE = `Usage: dreams <command> [options]
15
21
 
16
22
  Commands:
17
- onboard Install Dreams locally and return the next setup action
23
+ onboard Install Dreams locally and hand off to the setup skill
24
+ rollback Reactivate the previous installed CLI version
18
25
  status Show installation, skill, CLI, database, and auth state
19
26
  auth login Connect this device to Letta (OAuth device code flow)
20
27
  auth status Show authentication state (never prints tokens)
21
28
  auth logout Revoke Dreams' refresh token and clear stored credentials
29
+ dev reset local Preview/reset local SQLite and blob state
30
+ dev reset identity Preview/reset local state, credentials, and device identity
31
+ source detect Detect Claude Code and register a detected Cloud source
22
32
  source scan Discover Claude Code sessions and build the local upload queue
23
33
  source approve-root <path> Approve a work root so its transcripts become eligible
24
34
  source list Show local sources, approved roots, and the pending queue
35
+ snapshots create Submit a skill-authored snapshot JSON file to Cloud
36
+ sync Schedule a hook-safe sync worker (live then active backfill)
37
+ sync pause|resume|status Pause/resume hooks or show sync diagnostics
38
+ backfill start Queue a bounded historical backfill window (local only)
25
39
  upload Upload pending queued objects to Letta Cloud
26
40
 
27
41
  Options:
28
- --json Machine-readable JSON output
29
- --version Print the CLI version
30
- --help Show this help
42
+ --json Machine-readable JSON output
43
+ --confirm Apply a development reset preview
44
+ --force Bypass sync cadence (never bypasses pause/privacy)
45
+ --source <type> Sync source adapter (claude-code default; codex)
46
+ --since <RFC3339> Backfill window start (with backfill start)
47
+ --type <type> Snapshot type (with snapshots create)
48
+ --file <path> Snapshot JSON file (with snapshots create)
49
+ --client-request-id <id> Idempotency key (with snapshots create)
50
+ --workspace-id <id> Override workspace id (defaults to local binding)
51
+ --version Print the CLI version
52
+ --help Show this help
31
53
  `;
32
- const COMMANDS_WITH_SUBCOMMANDS = new Set(["auth", "source"]);
54
+ const COMMANDS_WITH_SUBCOMMANDS = new Set(["auth", "dev", "source", "snapshots", "sync", "backfill"]);
33
55
  function parseArgs(argv) {
34
56
  const parsed = {
35
57
  positionals: [],
36
58
  json: false,
59
+ confirm: false,
37
60
  help: false,
38
61
  version: false,
62
+ force: false,
63
+ worker: false,
64
+ source: undefined,
65
+ since: undefined,
66
+ type: undefined,
67
+ file: undefined,
68
+ clientRequestId: undefined,
69
+ workspaceId: undefined,
39
70
  unknown: [],
40
71
  };
41
- for (const arg of argv) {
72
+ for (let i = 0; i < argv.length; i++) {
73
+ const arg = argv[i];
42
74
  if (arg === "--json")
43
75
  parsed.json = true;
76
+ else if (arg === "--confirm")
77
+ parsed.confirm = true;
78
+ else if (arg === "--force")
79
+ parsed.force = true;
80
+ else if (arg === "--worker")
81
+ parsed.worker = true;
82
+ else if (arg === "--source") {
83
+ const value = argv[++i];
84
+ if (value === undefined || value.trim() === "")
85
+ parsed.unknown.push("--source");
86
+ else
87
+ parsed.source = value;
88
+ }
89
+ else if (arg.startsWith("--source=")) {
90
+ const value = arg.slice("--source=".length);
91
+ if (value.trim() === "")
92
+ parsed.unknown.push("--source=");
93
+ else
94
+ parsed.source = value;
95
+ }
44
96
  else if (arg === "--help" || arg === "-h")
45
97
  parsed.help = true;
46
98
  else if (arg === "--version" || arg === "-v")
47
99
  parsed.version = true;
100
+ else if (arg === "--since") {
101
+ const value = argv[++i];
102
+ if (value === undefined)
103
+ parsed.unknown.push("--since");
104
+ else
105
+ parsed.since = value;
106
+ }
107
+ else if (arg.startsWith("--since="))
108
+ parsed.since = arg.slice("--since=".length);
109
+ else if (arg === "--type") {
110
+ const value = argv[++i];
111
+ if (value === undefined)
112
+ parsed.unknown.push("--type");
113
+ else
114
+ parsed.type = value;
115
+ }
116
+ else if (arg.startsWith("--type="))
117
+ parsed.type = arg.slice("--type=".length);
118
+ else if (arg === "--file") {
119
+ const value = argv[++i];
120
+ if (value === undefined)
121
+ parsed.unknown.push("--file");
122
+ else
123
+ parsed.file = value;
124
+ }
125
+ else if (arg.startsWith("--file="))
126
+ parsed.file = arg.slice("--file=".length);
127
+ else if (arg === "--client-request-id") {
128
+ const value = argv[++i];
129
+ if (value === undefined)
130
+ parsed.unknown.push("--client-request-id");
131
+ else
132
+ parsed.clientRequestId = value;
133
+ }
134
+ else if (arg.startsWith("--client-request-id=")) {
135
+ parsed.clientRequestId = arg.slice("--client-request-id=".length);
136
+ }
137
+ else if (arg === "--workspace-id") {
138
+ const value = argv[++i];
139
+ if (value === undefined)
140
+ parsed.unknown.push("--workspace-id");
141
+ else
142
+ parsed.workspaceId = value;
143
+ }
144
+ else if (arg.startsWith("--workspace-id=")) {
145
+ parsed.workspaceId = arg.slice("--workspace-id=".length);
146
+ }
48
147
  else if (arg.startsWith("-"))
49
148
  parsed.unknown.push(arg);
50
149
  else
@@ -52,14 +151,17 @@ function parseArgs(argv) {
52
151
  }
53
152
  return parsed;
54
153
  }
55
- function commandStatus(json) {
154
+ function commandStatusUnlocked(json) {
56
155
  const installation = (0, store_1.loadOrCreateInstallation)();
57
156
  const auth = (0, index_1.authStatusInfo)();
157
+ const activation = (0, launcher_1.readActivation)();
58
158
  const status = {
59
159
  installation_id: installation.installation_id,
60
160
  installation_created_at: installation.created_at,
61
161
  state: auth.authenticated ? "authenticated" : "unauthenticated",
62
162
  cli_path: (0, managed_install_1.stableCliPath)(),
163
+ active_cli_version: activation?.active_version ?? null,
164
+ previous_cli_version: activation?.previous_version ?? null,
63
165
  database_path: (0, db_1.dbFilePath)(),
64
166
  skill_version: (0, skill_1.installedSkillVersion)(),
65
167
  skill_path: (0, skill_1.skillFilePath)(),
@@ -72,7 +174,7 @@ function commandStatus(json) {
72
174
  else {
73
175
  const authLine = auth.authenticated
74
176
  ? `yes (via ${auth.source}, token ${auth.access_token ?? "pending refresh"}${auth.expires_at ? `, expires ${auth.expires_at}` : ""})`
75
- : "no (run `dreams onboard`)";
177
+ : "no (run `dreams auth login`)";
76
178
  console.log([
77
179
  "Dreams status",
78
180
  "",
@@ -80,6 +182,8 @@ function commandStatus(json) {
80
182
  ` Created at: ${status.installation_created_at}`,
81
183
  ` State: ${status.state}`,
82
184
  ` Durable CLI: ${status.cli_path}`,
185
+ ` Active version: ${status.active_cli_version ?? "(not activated)"}`,
186
+ ` Previous version:${status.previous_cli_version ? ` ${status.previous_cli_version}` : " (none)"}`,
83
187
  ` Skill installed: ${status.skill_version ? `v${status.skill_version} at ${status.skill_path}` : "no"}`,
84
188
  ` Authenticated: ${authLine}`,
85
189
  ` Database: ${status.database_path}`,
@@ -88,6 +192,23 @@ function commandStatus(json) {
88
192
  }
89
193
  return 0;
90
194
  }
195
+ async function commandStatus(json) {
196
+ const lock = await (0, state_lock_1.waitForLocalStateLock)();
197
+ if (lock === null) {
198
+ const message = "Another Dreams command is using local state. Try again after it finishes.";
199
+ if (json)
200
+ console.log(JSON.stringify({ status: "error", code: "local_state_busy", message }));
201
+ else
202
+ process.stderr.write(message + "\n");
203
+ return 1;
204
+ }
205
+ try {
206
+ return commandStatusUnlocked(json);
207
+ }
208
+ finally {
209
+ (0, state_lock_1.releaseLocalStateLock)(lock);
210
+ }
211
+ }
91
212
  function commandAuth(subcommand, json) {
92
213
  switch (subcommand) {
93
214
  case "login":
@@ -101,6 +222,17 @@ function commandAuth(subcommand, json) {
101
222
  return 1;
102
223
  }
103
224
  }
225
+ function commandDevelopment(positionals, confirmed, json) {
226
+ if (positionals[1] !== "reset") {
227
+ process.stderr.write(`Unknown dev subcommand: ${positionals[1] ?? "(none)"} — expected reset\n\n${USAGE}`);
228
+ return 1;
229
+ }
230
+ if (positionals.length > 3) {
231
+ process.stderr.write(`Unexpected argument(s): ${positionals.slice(3).join(", ")}\n\n${USAGE}`);
232
+ return 1;
233
+ }
234
+ return (0, development_reset_1.commandDevelopmentReset)(positionals[2], confirmed, json);
235
+ }
104
236
  async function runCli() {
105
237
  const args = parseArgs(process.argv.slice(2));
106
238
  const command = args.positionals[0];
@@ -113,6 +245,26 @@ async function runCli() {
113
245
  process.stdout.write(USAGE);
114
246
  return args.help ? 0 : 1;
115
247
  }
248
+ if (args.confirm && (command !== "dev" || subcommand !== "reset")) {
249
+ process.stderr.write(`--confirm is only valid with \`dreams dev reset\`\n\n${USAGE}`);
250
+ return 1;
251
+ }
252
+ if ((args.type || args.file || args.clientRequestId || args.workspaceId) && command !== "snapshots") {
253
+ process.stderr.write(`Snapshot options are only valid with \`dreams snapshots create\`\n\n${USAGE}`);
254
+ return 1;
255
+ }
256
+ if ((args.force || args.worker) && command !== "sync") {
257
+ process.stderr.write(`--force/--worker are only valid with \`dreams sync\`\n\n${USAGE}`);
258
+ return 1;
259
+ }
260
+ if (args.source && command !== "sync") {
261
+ process.stderr.write(`--source is only valid with \`dreams sync\`\n\n${USAGE}`);
262
+ return 1;
263
+ }
264
+ if (args.since && command !== "backfill") {
265
+ process.stderr.write(`--since is only valid with \`dreams backfill start\`\n\n${USAGE}`);
266
+ return 1;
267
+ }
116
268
  if (args.unknown.length > 0) {
117
269
  process.stderr.write(`Unknown argument(s): ${args.unknown.join(", ")}\n\n${USAGE}`);
118
270
  return 1;
@@ -122,15 +274,52 @@ async function runCli() {
122
274
  process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(1).join(", ")}\n\n${USAGE}`);
123
275
  return 1;
124
276
  }
277
+ if (command === "snapshots" && args.positionals.length > 2) {
278
+ process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(2).join(", ")}\n\n${USAGE}`);
279
+ return 1;
280
+ }
281
+ if (command === "sync" && args.positionals.length > 2) {
282
+ process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(2).join(", ")}\n\n${USAGE}`);
283
+ return 1;
284
+ }
285
+ if (command === "backfill" && args.positionals.length > 2) {
286
+ process.stderr.write(`Unexpected argument(s): ${args.positionals.slice(2).join(", ")}\n\n${USAGE}`);
287
+ return 1;
288
+ }
125
289
  switch (command) {
126
290
  case "onboard":
127
291
  return (0, onboard_1.commandOnboard)(args.json);
292
+ case "rollback":
293
+ return (0, launcher_1.commandRollback)(args.json);
128
294
  case "status":
129
295
  return commandStatus(args.json);
130
296
  case "auth":
131
297
  return commandAuth(subcommand, args.json);
298
+ case "dev":
299
+ return commandDevelopment(args.positionals, args.confirm, args.json);
132
300
  case "source":
133
301
  return (0, commands_2.commandSource)(subcommand, args.positionals[2], args.json);
302
+ case "snapshots":
303
+ return (0, snapshots_1.commandSnapshots)(subcommand, {
304
+ type: args.type,
305
+ file: args.file,
306
+ clientRequestId: args.clientRequestId,
307
+ workspaceId: args.workspaceId,
308
+ json: args.json,
309
+ });
310
+ case "sync":
311
+ return (0, sync_1.commandSync)(subcommand, {
312
+ json: args.json,
313
+ force: args.force,
314
+ worker: args.worker,
315
+ sourceType: args.source,
316
+ });
317
+ case "backfill":
318
+ if (subcommand !== "start") {
319
+ process.stderr.write(`Unknown backfill subcommand: ${subcommand ?? "(none)"} — expected start\n\n${USAGE}`);
320
+ return 1;
321
+ }
322
+ return (0, backfill_1.commandBackfillStart)({ since: args.since, json: args.json });
134
323
  case "upload":
135
324
  return (0, commands_2.commandUpload)(args.json);
136
325
  default:
@@ -139,6 +328,7 @@ async function runCli() {
139
328
  }
140
329
  }
141
330
  function main() {
331
+ (0, launcher_1.signalLauncherReady)();
142
332
  runCli().then((code) => process.exit(code), (error) => {
143
333
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
144
334
  process.exit(1);