@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.
@@ -0,0 +1,359 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.commandDevelopmentReset = commandDevelopmentReset;
37
+ const node_sqlite_1 = require("node:sqlite");
38
+ const crypto = __importStar(require("node:crypto"));
39
+ const fs = __importStar(require("node:fs"));
40
+ const path = __importStar(require("node:path"));
41
+ const credentials_1 = require("./auth/credentials");
42
+ const blobs_1 = require("./local/blobs");
43
+ const db_1 = require("./local/db");
44
+ const leases_1 = require("./local/leases");
45
+ const state_lock_1 = require("./local/state-lock");
46
+ const store_1 = require("./store");
47
+ const PRESERVED_LOCAL = [
48
+ "stored credentials",
49
+ "installation identity",
50
+ "installed CLI versions and activation",
51
+ "stable launcher",
52
+ "setup skill",
53
+ "Cloud workspace and all Cloud data",
54
+ ];
55
+ const PRESERVED_IDENTITY = [
56
+ "installed CLI versions and activation",
57
+ "stable launcher",
58
+ "setup skill",
59
+ "Cloud workspace and all Cloud data",
60
+ ];
61
+ function localStatePaths() {
62
+ const database = (0, db_1.dbFilePath)();
63
+ return [database, `${database}-wal`, `${database}-shm`, (0, blobs_1.blobsDir)()];
64
+ }
65
+ function resetPaths(scope) {
66
+ return scope === "local"
67
+ ? localStatePaths()
68
+ : [...localStatePaths(), (0, credentials_1.credentialsFilePath)(), (0, store_1.installationFilePath)()];
69
+ }
70
+ function preserved(scope) {
71
+ return scope === "local" ? PRESERVED_LOCAL : PRESERVED_IDENTITY;
72
+ }
73
+ function assertPathIsLocal(file) {
74
+ const root = path.resolve((0, store_1.dreamsDir)());
75
+ const resolved = path.resolve(file);
76
+ if (resolved !== root && !resolved.startsWith(root + path.sep)) {
77
+ throw new Error(`Refusing to reset path outside ${root}: ${resolved}`);
78
+ }
79
+ }
80
+ function acquireLegacySourceLease() {
81
+ const database = (0, db_1.dbFilePath)();
82
+ if (!fs.existsSync(database) || fs.lstatSync(database).isSymbolicLink())
83
+ return null;
84
+ let db = null;
85
+ try {
86
+ db = new node_sqlite_1.DatabaseSync(database);
87
+ db.exec("PRAGMA busy_timeout = 0; BEGIN IMMEDIATE");
88
+ const table = db
89
+ .prepare("SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = 'leases'")
90
+ .get();
91
+ if (table) {
92
+ const active = db
93
+ .prepare("SELECT 1 AS active FROM leases WHERE name = ? AND expires_at > ?")
94
+ .get(leases_1.SOURCE_LEASE, Date.now());
95
+ if (active)
96
+ throw new Error("active source operation");
97
+ }
98
+ return { db, owner: `development-reset-${process.pid}`, canPublish: table !== undefined };
99
+ }
100
+ catch (error) {
101
+ try {
102
+ db?.exec("ROLLBACK");
103
+ }
104
+ catch {
105
+ // No transaction was opened.
106
+ }
107
+ try {
108
+ db?.close();
109
+ }
110
+ catch {
111
+ // Reset can still remove a corrupt database after the connection closes.
112
+ }
113
+ const message = error instanceof Error ? error.message : String(error);
114
+ if (/busy|locked|active source operation/i.test(message)) {
115
+ throw new Error("Local Dreams state is busy. Wait for the active source operation to finish.");
116
+ }
117
+ return null;
118
+ }
119
+ }
120
+ function releaseLegacySourceLease(lease) {
121
+ try {
122
+ lease.db.exec("ROLLBACK");
123
+ }
124
+ finally {
125
+ lease.db.close();
126
+ }
127
+ }
128
+ function publishLegacyResetLeaseAndClose(lease) {
129
+ if (!lease.canPublish) {
130
+ releaseLegacySourceLease(lease);
131
+ return;
132
+ }
133
+ const now = Date.now();
134
+ try {
135
+ lease.db.prepare("INSERT INTO leases (name, owner, acquired_at, expires_at) VALUES (?, ?, ?, ?) " +
136
+ "ON CONFLICT(name) DO UPDATE SET owner = excluded.owner, acquired_at = excluded.acquired_at, expires_at = excluded.expires_at").run(leases_1.SOURCE_LEASE, lease.owner, now, now + 60_000);
137
+ lease.db.exec("COMMIT");
138
+ const checkpoint = lease.db.prepare("PRAGMA wal_checkpoint(TRUNCATE)").get();
139
+ if (checkpoint.busy !== 0)
140
+ throw new Error("Could not checkpoint local state before reset");
141
+ }
142
+ finally {
143
+ lease.db.close();
144
+ }
145
+ }
146
+ function stagePath(file, stagingDirectory, staged) {
147
+ if (!fs.existsSync(file))
148
+ return;
149
+ const destination = path.join(stagingDirectory, `${String(staged.length).padStart(2, "0")}-${path.basename(file)}`);
150
+ fs.renameSync(file, destination);
151
+ staged.push({ source: file, destination });
152
+ }
153
+ function restoreStagedPaths(staged) {
154
+ for (const item of [...staged].reverse()) {
155
+ if (!fs.existsSync(item.destination) || fs.existsSync(item.source))
156
+ continue;
157
+ fs.renameSync(item.destination, item.source);
158
+ }
159
+ }
160
+ function recoverAbandonedResets() {
161
+ const root = (0, store_1.dreamsDir)();
162
+ const allowed = new Set([
163
+ path.basename((0, db_1.dbFilePath)()),
164
+ path.basename((0, db_1.dbFilePath)()) + "-wal",
165
+ path.basename((0, db_1.dbFilePath)()) + "-shm",
166
+ path.basename((0, blobs_1.blobsDir)()),
167
+ path.basename((0, credentials_1.credentialsFilePath)()),
168
+ path.basename((0, store_1.installationFilePath)()),
169
+ ]);
170
+ for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
171
+ if (!entry.isDirectory())
172
+ continue;
173
+ const directory = path.join(root, entry.name);
174
+ if (entry.name.startsWith(".reset-delete-")) {
175
+ fs.rmSync(directory, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
176
+ continue;
177
+ }
178
+ if (!entry.name.startsWith(".reset-staging-"))
179
+ continue;
180
+ const stagedNames = fs.readdirSync(directory).sort().reverse();
181
+ for (const stagedName of stagedNames) {
182
+ const basename = stagedName.replace(/^\d+-/, "");
183
+ if (!allowed.has(basename)) {
184
+ throw new Error(`Unknown abandoned reset entry at ${path.join(directory, stagedName)}`);
185
+ }
186
+ const source = path.join(root, basename);
187
+ const staged = path.join(directory, stagedName);
188
+ if (fs.existsSync(source)) {
189
+ throw new Error(`Cannot recover abandoned reset because both ${source} and ${staged} exist`);
190
+ }
191
+ fs.renameSync(staged, source);
192
+ }
193
+ fs.rmSync(directory, { recursive: true, force: true });
194
+ }
195
+ }
196
+ function preview(scope) {
197
+ return {
198
+ schema_version: 1,
199
+ status: "preview",
200
+ scope,
201
+ removes: resetPaths(scope),
202
+ preserves: preserved(scope),
203
+ stored_credentials: scope === "local"
204
+ ? "preserved"
205
+ : process.env.DREAMS_CREDENTIALS_BACKEND === "file"
206
+ ? "file_cleared_keychain_not_checked_on_confirm"
207
+ : "cleared_on_confirm",
208
+ cloud_workspace: "preserved",
209
+ confirmation_command: `dreams dev reset ${scope} --confirm --json`,
210
+ };
211
+ }
212
+ function executeLocked(scope, legacyLease) {
213
+ (0, db_1.closeDb)();
214
+ const paths = resetPaths(scope);
215
+ for (const file of paths)
216
+ assertPathIsLocal(file);
217
+ const existing = new Set(paths.filter((file) => fs.existsSync(file)));
218
+ const stagingDirectory = path.join((0, store_1.dreamsDir)(), `.reset-staging-${process.pid}-${crypto.randomUUID()}`);
219
+ fs.mkdirSync(stagingDirectory, { mode: 0o700 });
220
+ const staged = [];
221
+ let storedCredentials = "preserved";
222
+ let legacyLeaseOpen = legacyLease !== null;
223
+ let cutoverComplete = false;
224
+ try {
225
+ stagePath((0, blobs_1.blobsDir)(), stagingDirectory, staged);
226
+ if (scope === "identity") {
227
+ stagePath((0, credentials_1.credentialsFilePath)(), stagingDirectory, staged);
228
+ stagePath((0, store_1.installationFilePath)(), stagingDirectory, staged);
229
+ const credentials = (0, credentials_1.clearCredentials)({ strictKeychain: true });
230
+ storedCredentials =
231
+ credentials.keychain === "cleared" ? "cleared" : "file_cleared_keychain_not_checked";
232
+ }
233
+ if (legacyLease !== null) {
234
+ publishLegacyResetLeaseAndClose(legacyLease);
235
+ legacyLeaseOpen = false;
236
+ }
237
+ const database = (0, db_1.dbFilePath)();
238
+ stagePath(`${database}-wal`, stagingDirectory, staged);
239
+ stagePath(`${database}-shm`, stagingDirectory, staged);
240
+ stagePath(database, stagingDirectory, staged);
241
+ const deletionDirectory = stagingDirectory.replace(`${path.sep}.reset-staging-`, `${path.sep}.reset-delete-`);
242
+ fs.renameSync(stagingDirectory, deletionDirectory);
243
+ cutoverComplete = true;
244
+ fs.rmSync(deletionDirectory, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
245
+ }
246
+ catch (error) {
247
+ if (legacyLease !== null && legacyLeaseOpen) {
248
+ try {
249
+ releaseLegacySourceLease(legacyLease);
250
+ }
251
+ catch {
252
+ // Preserve the original reset failure.
253
+ }
254
+ }
255
+ if (!cutoverComplete) {
256
+ try {
257
+ restoreStagedPaths(staged);
258
+ if (!staged.some((item) => fs.existsSync(item.destination))) {
259
+ fs.rmSync(stagingDirectory, { recursive: true, force: true });
260
+ }
261
+ }
262
+ catch {
263
+ // Preserve the original reset failure and leave staged data for manual recovery.
264
+ }
265
+ }
266
+ throw error;
267
+ }
268
+ return {
269
+ schema_version: 1,
270
+ status: "completed",
271
+ scope,
272
+ removed: paths.filter((file) => existing.has(file)),
273
+ already_absent: paths.filter((file) => !existing.has(file)),
274
+ preserves: preserved(scope),
275
+ stored_credentials: storedCredentials,
276
+ cloud_workspace: "preserved",
277
+ environment_api_key_preserved: Boolean(process.env.LETTA_API_KEY),
278
+ };
279
+ }
280
+ async function execute(scope) {
281
+ const stateLock = (0, state_lock_1.acquireLocalStateLock)({ allowAbandonedReset: true });
282
+ if (stateLock === null) {
283
+ throw new Error("Another Dreams command is using local state. Try again after it finishes.");
284
+ }
285
+ try {
286
+ recoverAbandonedResets();
287
+ const legacyLease = acquireLegacySourceLease();
288
+ return executeLocked(scope, legacyLease);
289
+ }
290
+ finally {
291
+ (0, state_lock_1.releaseLocalStateLock)(stateLock);
292
+ }
293
+ }
294
+ function renderPlan(plan) {
295
+ return [
296
+ `Dreams development reset preview (${plan.scope})`,
297
+ "",
298
+ "Would remove:",
299
+ ...plan.removes.map((file) => ` ${file}`),
300
+ "",
301
+ "Preserves:",
302
+ ...plan.preserves.map((item) => ` ${item}`),
303
+ "",
304
+ `Stored credentials: ${plan.stored_credentials === "preserved"
305
+ ? "preserved"
306
+ : plan.stored_credentials === "cleared_on_confirm"
307
+ ? "will be cleared from file and OS keychain"
308
+ : "file credentials will be cleared; OS keychain will not be checked while the file backend is forced"}`,
309
+ `Run \`${plan.confirmation_command.replace(" --json", "")}\` to confirm.`,
310
+ ].join("\n");
311
+ }
312
+ function renderCompleted(result) {
313
+ return [
314
+ `Dreams development reset complete (${result.scope})`,
315
+ "",
316
+ ` Removed: ${result.removed.length}`,
317
+ ` Already absent: ${result.already_absent.length}`,
318
+ ` Credentials: ${result.stored_credentials === "preserved"
319
+ ? "preserved"
320
+ : result.stored_credentials === "cleared"
321
+ ? "cleared from file and OS keychain"
322
+ : "file cleared; OS keychain not checked (file backend forced)"}`,
323
+ " Cloud workspace: preserved",
324
+ ...(result.environment_api_key_preserved
325
+ ? ["", "LETTA_API_KEY remains set in the environment and was not cleared."]
326
+ : []),
327
+ ].join("\n");
328
+ }
329
+ async function commandDevelopmentReset(scope, confirmed, json) {
330
+ if (scope === "test-workspace") {
331
+ const message = "Test-workspace cleanup is a separate internal Cloud operation. This local CLI never deletes Cloud data.";
332
+ if (json)
333
+ console.log(JSON.stringify({ status: "error", code: "cloud_cleanup_separate", message }));
334
+ else
335
+ process.stderr.write(message + "\n");
336
+ return 1;
337
+ }
338
+ if (scope !== "local" && scope !== "identity") {
339
+ const message = `Unknown development reset scope: ${scope ?? "(none)"}; expected local or identity`;
340
+ if (json)
341
+ console.log(JSON.stringify({ status: "error", code: "invalid_scope", message }));
342
+ else
343
+ process.stderr.write(message + "\n");
344
+ return 1;
345
+ }
346
+ try {
347
+ const result = confirmed ? await execute(scope) : preview(scope);
348
+ console.log(json ? JSON.stringify(result, null, 2) : result.status === "preview" ? renderPlan(result) : renderCompleted(result));
349
+ return 0;
350
+ }
351
+ catch (error) {
352
+ const message = error instanceof Error ? error.message : String(error);
353
+ if (json)
354
+ console.log(JSON.stringify({ status: "error", code: "reset_failed", message }));
355
+ else
356
+ process.stderr.write(`Dreams development reset failed: ${message}\n`);
357
+ return 1;
358
+ }
359
+ }
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.installedBin = installedBin;
37
+ exports.installationComplete = installationComplete;
38
+ exports.activationFilePath = activationFilePath;
39
+ exports.stableLauncherControllerPath = stableLauncherControllerPath;
40
+ exports.stableCliPath = stableCliPath;
41
+ exports.readActivation = readActivation;
42
+ exports.withInstallLock = withInstallLock;
43
+ exports.activateVersionLocked = activateVersionLocked;
44
+ exports.rollbackActivation = rollbackActivation;
45
+ exports.commandRollback = commandRollback;
46
+ exports.signalLauncherReady = signalLauncherReady;
47
+ exports.runLauncher = runLauncher;
48
+ exports.main = main;
49
+ const node_child_process_1 = require("node:child_process");
50
+ const crypto = __importStar(require("node:crypto"));
51
+ const fs = __importStar(require("node:fs"));
52
+ const os = __importStar(require("node:os"));
53
+ const path = __importStar(require("node:path"));
54
+ const PACKAGE_NAME = "@letta-ai/dreams";
55
+ const READY_FILE_ENV = "DREAMS_LAUNCH_READY_FILE";
56
+ const ACTIVATION_SCHEMA_VERSION = 1;
57
+ function dreamsDir() {
58
+ return path.join(os.homedir(), ".dreams");
59
+ }
60
+ function versionDirectory(version) {
61
+ return path.join(dreamsDir(), "cli", version);
62
+ }
63
+ function installedBin(version) {
64
+ return path.join(versionDirectory(version), "package", "bin", "dreams.cjs");
65
+ }
66
+ function completeMarker(version) {
67
+ return path.join(versionDirectory(version), ".complete");
68
+ }
69
+ function installationComplete(version) {
70
+ return fs.existsSync(installedBin(version)) && fs.existsSync(completeMarker(version));
71
+ }
72
+ function activationFilePath() {
73
+ return path.join(dreamsDir(), "cli", "activation.json");
74
+ }
75
+ function stableLauncherControllerPath() {
76
+ return path.join(dreamsDir(), "bin", "launcher.cjs");
77
+ }
78
+ function stableCliPath(platform = process.platform) {
79
+ return path.join(dreamsDir(), "bin", platform === "win32" ? "dreams.cmd" : "dreams");
80
+ }
81
+ function readActivation() {
82
+ try {
83
+ const value = JSON.parse(fs.readFileSync(activationFilePath(), "utf8"));
84
+ if (value.schema_version !== ACTIVATION_SCHEMA_VERSION ||
85
+ typeof value.active_version !== "string" ||
86
+ (value.previous_version !== null && typeof value.previous_version !== "string") ||
87
+ typeof value.activated_at !== "string") {
88
+ return null;
89
+ }
90
+ return value;
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ }
96
+ function writeAtomic(file, contents, mode = 0o600) {
97
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
98
+ const temporary = `${file}.tmp-${process.pid}-${crypto.randomBytes(4).toString("hex")}`;
99
+ try {
100
+ fs.writeFileSync(temporary, contents, { mode });
101
+ fs.renameSync(temporary, file);
102
+ if (process.platform !== "win32")
103
+ fs.chmodSync(file, mode);
104
+ }
105
+ finally {
106
+ fs.rmSync(temporary, { force: true });
107
+ }
108
+ }
109
+ function writeActivation(state) {
110
+ writeAtomic(activationFilePath(), JSON.stringify(state, null, 2) + "\n");
111
+ }
112
+ function withInstallLock(fn) {
113
+ fs.mkdirSync(dreamsDir(), { recursive: true, mode: 0o700 });
114
+ const lock = path.join(dreamsDir(), "install.lock");
115
+ const sleeper = new Int32Array(new SharedArrayBuffer(4));
116
+ const deadline = Date.now() + 30_000;
117
+ let descriptor;
118
+ while (true) {
119
+ try {
120
+ descriptor = fs.openSync(lock, "wx", 0o600);
121
+ fs.writeFileSync(descriptor, `${process.pid}\n${new Date().toISOString()}\n`);
122
+ break;
123
+ }
124
+ catch (error) {
125
+ if (error.code !== "EEXIST")
126
+ throw error;
127
+ try {
128
+ if (Date.now() - fs.statSync(lock).mtimeMs > 5 * 60_000) {
129
+ fs.rmSync(lock, { force: true });
130
+ continue;
131
+ }
132
+ }
133
+ catch {
134
+ continue;
135
+ }
136
+ if (Date.now() >= deadline) {
137
+ throw new Error("Timed out waiting for another Dreams CLI installation to finish.");
138
+ }
139
+ Atomics.wait(sleeper, 0, 0, 100);
140
+ }
141
+ }
142
+ try {
143
+ return fn();
144
+ }
145
+ finally {
146
+ fs.closeSync(descriptor);
147
+ fs.rmSync(lock, { force: true });
148
+ }
149
+ }
150
+ function activateVersionLocked(version, previousHint = null) {
151
+ if (!installationComplete(version)) {
152
+ throw new Error(`Cannot activate incomplete Dreams CLI version ${version}.`);
153
+ }
154
+ const current = readActivation();
155
+ if (current?.active_version === version)
156
+ return current;
157
+ const previousCandidate = current?.active_version ?? previousHint;
158
+ const previousVersion = previousCandidate && previousCandidate !== version && installationComplete(previousCandidate)
159
+ ? previousCandidate
160
+ : null;
161
+ const state = {
162
+ schema_version: ACTIVATION_SCHEMA_VERSION,
163
+ active_version: version,
164
+ previous_version: previousVersion,
165
+ activated_at: new Date().toISOString(),
166
+ };
167
+ writeActivation(state);
168
+ return state;
169
+ }
170
+ function rollbackLocked(reason) {
171
+ const current = readActivation();
172
+ if (!current) {
173
+ return {
174
+ rolled_back: false,
175
+ from_version: null,
176
+ to_version: null,
177
+ cli_path: stableCliPath(),
178
+ error: "Dreams CLI activation state is missing or invalid.",
179
+ };
180
+ }
181
+ if (!current.previous_version) {
182
+ return {
183
+ rolled_back: false,
184
+ from_version: current.active_version,
185
+ to_version: null,
186
+ cli_path: stableCliPath(),
187
+ error: "No previous Dreams CLI version is available.",
188
+ };
189
+ }
190
+ if (!installationComplete(current.previous_version)) {
191
+ return {
192
+ rolled_back: false,
193
+ from_version: current.active_version,
194
+ to_version: current.previous_version,
195
+ cli_path: stableCliPath(),
196
+ error: `Previous Dreams CLI version ${current.previous_version} is incomplete.`,
197
+ };
198
+ }
199
+ const now = new Date().toISOString();
200
+ const result = {
201
+ rolled_back: true,
202
+ from_version: current.active_version,
203
+ to_version: current.previous_version,
204
+ cli_path: stableCliPath(),
205
+ };
206
+ writeActivation({
207
+ schema_version: ACTIVATION_SCHEMA_VERSION,
208
+ active_version: current.previous_version,
209
+ previous_version: null,
210
+ activated_at: now,
211
+ last_rollback: {
212
+ from_version: current.active_version,
213
+ to_version: current.previous_version,
214
+ reason,
215
+ rolled_back_at: now,
216
+ },
217
+ });
218
+ return result;
219
+ }
220
+ function rollbackActivation(reason = "explicit") {
221
+ return withInstallLock(() => rollbackLocked(reason));
222
+ }
223
+ function recoverStartupFailure(activeVersion, previousVersion) {
224
+ return withInstallLock(() => {
225
+ const current = readActivation();
226
+ if (current?.active_version === previousVersion && installationComplete(previousVersion)) {
227
+ return {
228
+ rolled_back: true,
229
+ from_version: activeVersion,
230
+ to_version: previousVersion,
231
+ cli_path: stableCliPath(),
232
+ };
233
+ }
234
+ if (current?.active_version !== activeVersion ||
235
+ current.previous_version !== previousVersion) {
236
+ return {
237
+ rolled_back: false,
238
+ from_version: current?.active_version ?? null,
239
+ to_version: current?.previous_version ?? null,
240
+ cli_path: stableCliPath(),
241
+ error: "Dreams CLI activation changed while startup recovery was running.",
242
+ };
243
+ }
244
+ return rollbackLocked("startup_failure");
245
+ });
246
+ }
247
+ function commandRollback(json) {
248
+ const result = rollbackActivation("explicit");
249
+ if (json) {
250
+ console.log(JSON.stringify(result, null, 2));
251
+ }
252
+ else if (result.rolled_back) {
253
+ console.log(`Rolled back Dreams CLI from ${result.from_version} to ${result.to_version}.`);
254
+ }
255
+ else {
256
+ process.stderr.write(`Dreams CLI rollback failed: ${result.error}\n`);
257
+ }
258
+ return result.rolled_back ? 0 : 1;
259
+ }
260
+ function signalLauncherReady() {
261
+ const readyFile = process.env[READY_FILE_ENV];
262
+ if (!readyFile)
263
+ return;
264
+ try {
265
+ fs.writeFileSync(readyFile, `${process.pid}\n`, { flag: "wx", mode: 0o600 });
266
+ }
267
+ catch {
268
+ // The launcher treats a missing marker as a startup failure. Never let a
269
+ // marker write mask the command the user asked to run.
270
+ }
271
+ }
272
+ function launchVersion(version, args) {
273
+ const readyFile = path.join(dreamsDir(), `.launch-ready-${process.pid}-${crypto.randomBytes(4).toString("hex")}`);
274
+ fs.rmSync(readyFile, { force: true });
275
+ const result = (0, node_child_process_1.spawnSync)(process.execPath, [installedBin(version), ...args], {
276
+ stdio: "inherit",
277
+ env: { ...process.env, [READY_FILE_ENV]: readyFile },
278
+ });
279
+ const ready = fs.existsSync(readyFile);
280
+ fs.rmSync(readyFile, { force: true });
281
+ return { status: result.status ?? 1, ready };
282
+ }
283
+ function runLauncher(args = process.argv.slice(2)) {
284
+ if (args[0] === "rollback") {
285
+ const unknown = args.slice(1).filter((arg) => arg !== "--json");
286
+ if (unknown.length > 0) {
287
+ process.stderr.write(`Unexpected argument(s): ${unknown.join(", ")}\n`);
288
+ return 1;
289
+ }
290
+ return commandRollback(args.includes("--json"));
291
+ }
292
+ const activation = readActivation();
293
+ if (!activation || !installationComplete(activation.active_version)) {
294
+ process.stderr.write(`Dreams CLI activation is missing or invalid. Repair it with \`npx ${PACKAGE_NAME}@latest onboard\`.\n`);
295
+ return 1;
296
+ }
297
+ const first = launchVersion(activation.active_version, args);
298
+ if (first.status === 0 || first.ready || !activation.previous_version)
299
+ return first.status;
300
+ const rollback = recoverStartupFailure(activation.active_version, activation.previous_version);
301
+ if (!rollback.rolled_back || !rollback.to_version)
302
+ return first.status;
303
+ process.stderr.write(`Dreams CLI ${rollback.from_version} failed to start; rolled back to ${rollback.to_version}.\n`);
304
+ return launchVersion(rollback.to_version, args).status;
305
+ }
306
+ function main() {
307
+ process.exit(runLauncher());
308
+ }
309
+ if (require.main === module) {
310
+ main();
311
+ }