create-snipara 2.1.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,16 +5,16 @@ import { Command, Option } from "commander";
5
5
  import prompts from "prompts";
6
6
  import chalk from "chalk";
7
7
  import ora from "ora";
8
- import { randomUUID } from "crypto";
9
- import fs from "fs";
10
- import path from "path";
11
- import { execSync, spawn } from "child_process";
12
- import { homedir } from "os";
8
+ import { randomUUID as randomUUID3 } from "crypto";
9
+ import fs4 from "fs";
10
+ import path4 from "path";
11
+ import { execFileSync as execFileSync3, execSync, spawn as spawn2 } from "child_process";
12
+ import { homedir as homedir2 } from "os";
13
13
 
14
14
  // package.json
15
15
  var package_default = {
16
16
  name: "create-snipara",
17
- version: "2.1.1",
17
+ version: "2.2.1",
18
18
  description: "Onboard Snipara Cloud or self-hosted MCP with an automatic project-grounded proof of value",
19
19
  main: "dist/index.js",
20
20
  type: "module",
@@ -22,8 +22,8 @@ var package_default = {
22
22
  "create-snipara": "dist/index.js"
23
23
  },
24
24
  scripts: {
25
- build: "tsup src/index.ts --format esm --dts --clean",
26
- dev: "tsup src/index.ts --format esm --watch",
25
+ build: "tsup --config tsup.config.ts",
26
+ dev: "tsup --config tsup.config.ts --watch",
27
27
  prepack: "npm run build",
28
28
  pretest: "pnpm build",
29
29
  test: "node --test test/*.test.cjs",
@@ -74,6 +74,7 @@ var package_default = {
74
74
  },
75
75
  devDependencies: {
76
76
  "@eslint/js": "^9.39.3",
77
+ "@snipara/project-intelligence-contracts": "workspace:*",
77
78
  "@types/node": "^20.17.12",
78
79
  "@types/prompts": "^2.4.9",
79
80
  "@typescript-eslint/eslint-plugin": "^8.56.0",
@@ -90,6 +91,781 @@ var package_default = {
90
91
  ]
91
92
  };
92
93
 
94
+ // src/managed-toolchain.ts
95
+ import fs from "fs";
96
+ import path from "path";
97
+ import { homedir } from "os";
98
+ import { createHash as createHash5, randomUUID as randomUUID2 } from "crypto";
99
+ import { spawn } from "child_process";
100
+
101
+ // ../project-intelligence-contracts/src/worker-profile.ts
102
+ import { createHash } from "crypto";
103
+
104
+ // ../project-intelligence-contracts/src/producer-loop.ts
105
+ import { createHash as createHash2 } from "crypto";
106
+
107
+ // ../project-intelligence-contracts/src/decision-request.ts
108
+ import { createHash as createHash3 } from "crypto";
109
+
110
+ // ../project-intelligence-contracts/src/why-capture-confidence-v1.json
111
+ var why_capture_confidence_v1_default = {
112
+ version: "why-capture-confidence-v1",
113
+ base: 0.72,
114
+ decisionRationaleBonus: 0.1,
115
+ nonManualSourceBonus: 0.04,
116
+ maximum: 0.9,
117
+ decimalPlaces: 2,
118
+ validatedExecutionOutcomes: ["completed"],
119
+ vectors: [
120
+ {
121
+ candidateType: "CONTEXT",
122
+ hasDecision: false,
123
+ hasRationale: false,
124
+ sourceKind: "manual",
125
+ executionOutcome: "completed",
126
+ expected: 0.72
127
+ },
128
+ {
129
+ candidateType: "DECISION",
130
+ hasDecision: true,
131
+ hasRationale: true,
132
+ sourceKind: "manual",
133
+ executionOutcome: "completed",
134
+ expected: 0.82
135
+ },
136
+ {
137
+ candidateType: "DECISION",
138
+ hasDecision: true,
139
+ hasRationale: true,
140
+ sourceKind: "phase_commit",
141
+ executionOutcome: "completed",
142
+ expected: 0.86
143
+ },
144
+ {
145
+ candidateType: "DECISION",
146
+ hasDecision: true,
147
+ hasRationale: true,
148
+ sourceKind: "phase_commit",
149
+ executionOutcome: "partial",
150
+ expected: 0.76
151
+ },
152
+ {
153
+ candidateType: "DECISION",
154
+ hasDecision: true,
155
+ hasRationale: true,
156
+ sourceKind: "phase_commit",
157
+ executionOutcome: "blocked",
158
+ expected: 0.76
159
+ },
160
+ {
161
+ candidateType: "DECISION",
162
+ hasDecision: true,
163
+ hasRationale: true,
164
+ sourceKind: "phase_commit",
165
+ executionOutcome: "abandoned",
166
+ expected: 0.76
167
+ },
168
+ {
169
+ candidateType: "DECISION",
170
+ hasDecision: true,
171
+ hasRationale: false,
172
+ sourceKind: "final_commit",
173
+ executionOutcome: "completed",
174
+ expected: 0.76
175
+ }
176
+ ]
177
+ };
178
+
179
+ // ../project-intelligence-contracts/src/why-capture-confidence.ts
180
+ var WHY_CAPTURE_CONFIDENCE_VERSION = why_capture_confidence_v1_default.version;
181
+ var WHY_CAPTURE_CONFIDENCE_TEST_VECTORS = why_capture_confidence_v1_default.vectors;
182
+
183
+ // ../project-intelligence-contracts/src/judgment-integrity.ts
184
+ import { randomUUID } from "crypto";
185
+
186
+ // ../project-intelligence-contracts/src/agent-memory-pilot.ts
187
+ import { createHash as createHash4 } from "crypto";
188
+
189
+ // ../project-intelligence-contracts/src/managed-toolchain.ts
190
+ var MANAGED_TOOLCHAIN_VERSION = "snipara.toolchain.v1";
191
+ var MANAGED_TOOLCHAIN_COMPONENTS = ["companion", "orchestrator", "sandbox"];
192
+ var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
193
+ var bounded = (value, max) => typeof value === "string" && value.length > 0 && value.length <= max && !Array.from(value).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127);
194
+ function canonicalAbsolute(value) {
195
+ return bounded(value, 4096) && value.startsWith("/") && value !== "/" && !value.endsWith("/") && !value.split("/").some((part, index) => index > 0 && ["", ".", ".."].includes(part));
196
+ }
197
+ function parseManagedToolchainManifest(value) {
198
+ if (!object(value) || value.schemaVersion !== MANAGED_TOOLCHAIN_VERSION || !bounded(value.bundleId, 128) || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value.bundleId) || !canonicalAbsolute(value.root) || !object(value.components) || !object(value.contracts) || value.contracts.taskReference !== 1 || value.contracts.validation !== 1 || !Array.isArray(value.capabilities) || value.capabilities.length > 32 || !value.capabilities.every((item) => bounded(item, 64) && /^[a-z][a-z0-9_-]*$/.test(item)) || new Set(value.capabilities).size !== value.capabilities.length) {
199
+ throw new Error("Unsupported or invalid managed toolchain manifest");
200
+ }
201
+ const root = value.root;
202
+ const components = {};
203
+ for (const name of MANAGED_TOOLCHAIN_COMPONENTS) {
204
+ const component = value.components[name];
205
+ if (!object(component) || !bounded(component.version, 64) || !/^\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/.test(component.version) || !canonicalAbsolute(component.command) || !component.command.startsWith(root + "/") || typeof component.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(component.sha256)) {
206
+ throw new Error("Invalid managed component identity");
207
+ }
208
+ components[name] = { version: component.version, command: component.command, sha256: component.sha256 };
209
+ }
210
+ return {
211
+ schemaVersion: MANAGED_TOOLCHAIN_VERSION,
212
+ bundleId: value.bundleId,
213
+ root,
214
+ components,
215
+ contracts: { taskReference: 1, validation: 1 },
216
+ capabilities: [...value.capabilities]
217
+ };
218
+ }
219
+
220
+ // src/managed-versions.json
221
+ var managed_versions_default = {
222
+ companion: "4.2.1",
223
+ orchestrator: "1.16.0",
224
+ sandbox: "3.0.0"
225
+ };
226
+
227
+ // src/managed-toolchain.ts
228
+ var MANAGED_VERSIONS = Object.freeze(managed_versions_default);
229
+ function assertManagedRuntimeVersions(nodeVersion, pythonVersion) {
230
+ const node = /^v?(\d+)\.\d+\.\d+(?:[-+].*)?$/.exec(nodeVersion);
231
+ const python = /^Python (\d+)\.(\d+)\.\d+(?:[a-z0-9.+-]*)?$/i.exec(pythonVersion.trim());
232
+ if (!node || Number(node[1]) < 18 || !python || Number(python[1]) !== 3 || Number(python[2]) < 10)
233
+ throw new Error("Local work requires existing Node.js 18+ and Python 3.10+. Update your system runtimes and retry; no operating-system runtime was installed");
234
+ }
235
+ var ManagedInstallerCleanupError = class extends Error {
236
+ };
237
+ var sha = (bytes2) => createHash5("sha256").update(bytes2).digest("hex");
238
+ var uid = () => {
239
+ if (!process.getuid || process.platform === "win32") throw new Error("Managed local work requires macOS or Linux");
240
+ return process.getuid();
241
+ };
242
+ function exists(file) {
243
+ try {
244
+ fs.lstatSync(file);
245
+ return true;
246
+ } catch (error) {
247
+ if (error.code === "ENOENT") return false;
248
+ throw error;
249
+ }
250
+ }
251
+ function owned(file, directory, privateMode = false) {
252
+ const info = fs.lstatSync(file);
253
+ if (info.uid !== uid() || info.isSymbolicLink() || (directory ? !info.isDirectory() : !info.isFile()) || !directory && info.nlink !== 1 || (info.mode & 18) !== 0 || privateMode && (info.mode & 63) !== 0)
254
+ throw new Error("Managed installation path is not owned and protected");
255
+ return info;
256
+ }
257
+ function privateDirectory(directory) {
258
+ if (!exists(directory)) {
259
+ privateDirectory(path.dirname(directory));
260
+ fs.mkdirSync(directory, { mode: 448 });
261
+ }
262
+ owned(directory, true);
263
+ }
264
+ function location(options) {
265
+ const home = fs.realpathSync(options.home ?? homedir());
266
+ owned(home, true);
267
+ return path.join(home, ".local/share/snipara/toolchains");
268
+ }
269
+ function bytes(file, limit = 65536) {
270
+ owned(file, false);
271
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
272
+ try {
273
+ const info = fs.fstatSync(fd);
274
+ if (!info.isFile() || info.uid !== uid() || info.size > limit || info.mode & 18)
275
+ throw new Error("Managed installation artifact is invalid");
276
+ const value = fs.readFileSync(fd);
277
+ if (value.length > limit) throw new Error("Managed installation artifact exceeds its bound");
278
+ return value;
279
+ } finally {
280
+ fs.closeSync(fd);
281
+ }
282
+ }
283
+ function writePrivate(file, value) {
284
+ fs.writeFileSync(file, JSON.stringify(value, null, 2) + "\n", { mode: 384, flag: "wx" });
285
+ }
286
+ function protectedCommand(root, command) {
287
+ const real = fs.realpathSync(command);
288
+ if (!real.startsWith(root + path.sep)) throw new Error("Managed command escapes its bundle");
289
+ for (let current = path.dirname(real); current !== root; current = path.dirname(current)) owned(current, true);
290
+ const info = owned(real, false);
291
+ if (!(info.mode & 73)) throw new Error("Managed command is not executable");
292
+ return real;
293
+ }
294
+ function readManagedManifest(file, options = {}) {
295
+ const base = location(options);
296
+ owned(base, true, true);
297
+ const link = fs.lstatSync(file);
298
+ let target = file;
299
+ if (link.isSymbolicLink()) {
300
+ if (link.uid !== uid()) throw new Error("Managed pointer has a foreign owner");
301
+ target = path.resolve(path.dirname(file), fs.readlinkSync(file));
302
+ }
303
+ if (path.basename(target) !== "toolchain.json" || path.dirname(path.dirname(target)) !== base)
304
+ throw new Error("Managed manifest is outside the private installation");
305
+ const root = path.dirname(target);
306
+ owned(root, true, true);
307
+ if (fs.realpathSync(root) !== root) throw new Error("Managed bundle cannot use linked directories");
308
+ owned(target, false, true);
309
+ const manifest = parseManagedToolchainManifest(JSON.parse(bytes(target).toString("utf8")));
310
+ if (manifest.root !== root || manifest.bundleId !== path.basename(root) || !manifest.capabilities.includes("validation") || !manifest.capabilities.includes("coordination"))
311
+ throw new Error("Managed bundle identity or required capabilities differ");
312
+ for (const component of Object.values(manifest.components)) {
313
+ if (protectedCommand(root, component.command) !== component.command || sha(bytes(component.command, 8e6)) !== component.sha256)
314
+ throw new Error("Managed command identity changed; prepare a replacement bundle");
315
+ }
316
+ return manifest;
317
+ }
318
+ function verifyRetainedCompanionBootstrap(command, active, options = {}) {
319
+ try {
320
+ const current = readManagedManifest(path.join(active.root, "toolchain.json"), options);
321
+ const base = location(options);
322
+ if (!path.isAbsolute(command) || !command.startsWith(base + path.sep)) return false;
323
+ const bundleId = command.slice(base.length + 1).split(path.sep)[0], root = path.join(base, bundleId);
324
+ owned(root, true, true);
325
+ if (fs.realpathSync(root) !== root || protectedCommand(root, command) !== command) return false;
326
+ const manifestFile = path.join(root, "toolchain.json");
327
+ owned(manifestFile, false, true);
328
+ const body = bytes(manifestFile), observedHash = sha(bytes(command, 8e6));
329
+ let previous;
330
+ try {
331
+ previous = parseManagedToolchainManifest(JSON.parse(body.toString("utf8")));
332
+ } catch {
333
+ return command === path.join(root, path.relative(current.root, current.components.companion.command)) && observedHash === current.components.companion.sha256;
334
+ }
335
+ return previous.root === root && previous.bundleId === bundleId && previous.components.companion.command === command && previous.components.companion.sha256 === observedHash;
336
+ } catch {
337
+ return false;
338
+ }
339
+ }
340
+ function managedToolchainStatus(options = {}) {
341
+ const active = path.join(location(options), "active.json");
342
+ if (!exists(active)) return { status: "absent", manifest: null, manifestPath: null };
343
+ return { status: "ready", manifest: readManagedManifest(active, options), manifestPath: fs.realpathSync(active) };
344
+ }
345
+ function program(name) {
346
+ for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
347
+ if (!directory || !path.isAbsolute(directory)) continue;
348
+ const candidate = path.join(directory, name);
349
+ try {
350
+ fs.accessSync(candidate, fs.constants.X_OK);
351
+ if (fs.statSync(candidate).isFile()) return fs.realpathSync(candidate);
352
+ } catch {
353
+ }
354
+ }
355
+ throw new Error(`${name} is required; no operating-system runtime will be installed`);
356
+ }
357
+ function cleanEnvironment(root) {
358
+ return {
359
+ PATH: [path.dirname(process.execPath), "/usr/local/bin", "/opt/homebrew/bin", "/usr/bin", "/bin"].join(path.delimiter),
360
+ HOME: path.join(root, "home"),
361
+ TMPDIR: path.join(root, "tmp"),
362
+ LANG: "C.UTF-8",
363
+ LC_ALL: "C.UTF-8",
364
+ PYTHONNOUSERSITE: "1",
365
+ PYTHONDONTWRITEBYTECODE: "1",
366
+ PIP_DISABLE_PIP_VERSION_CHECK: "1",
367
+ PIP_NO_INPUT: "1",
368
+ PIP_CONFIG_FILE: "/dev/null",
369
+ NPM_CONFIG_USERCONFIG: "/dev/null",
370
+ NPM_CONFIG_GLOBALCONFIG: "/dev/null",
371
+ NPM_CONFIG_IGNORE_SCRIPTS: "true",
372
+ NPM_CONFIG_AUDIT: "false",
373
+ NPM_CONFIG_FUND: "false",
374
+ NO_COLOR: "1"
375
+ };
376
+ }
377
+ var runManagedInstaller = async (input) => new Promise((resolve2, reject) => {
378
+ const child = spawn(input.command, input.args, {
379
+ cwd: input.cwd,
380
+ env: input.env,
381
+ shell: false,
382
+ stdio: ["ignore", "pipe", "pipe"],
383
+ detached: process.platform !== "win32"
384
+ });
385
+ let output = "", size = 0, stopped = false, stopReason = null;
386
+ let reapTimer;
387
+ const kill = () => {
388
+ try {
389
+ if (child.pid) process.kill(-child.pid, "SIGKILL");
390
+ } catch {
391
+ child.kill("SIGKILL");
392
+ }
393
+ };
394
+ const interrupted = () => stop("Managed preparation interrupted; the prior selection was preserved");
395
+ const exiting = () => kill();
396
+ const cleanup = () => {
397
+ clearTimeout(timer);
398
+ if (reapTimer) clearTimeout(reapTimer);
399
+ process.removeListener("SIGINT", interrupted);
400
+ process.removeListener("SIGTERM", interrupted);
401
+ process.removeListener("exit", exiting);
402
+ };
403
+ const stop = (message) => {
404
+ if (stopped || stopReason) return;
405
+ stopReason = message;
406
+ kill();
407
+ reapTimer = setTimeout(() => {
408
+ if (!stopped) {
409
+ stopped = true;
410
+ cleanup();
411
+ reject(new ManagedInstallerCleanupError("Managed installer cleanup could not be observed within its deadline; installation lock retained"));
412
+ }
413
+ }, 2e3);
414
+ };
415
+ const timer = setTimeout(() => stop("Managed preparation exceeded its bounded deadline"), input.timeoutMs);
416
+ process.on("SIGINT", interrupted);
417
+ process.on("SIGTERM", interrupted);
418
+ process.on("exit", exiting);
419
+ child.stdout.on("data", (value) => {
420
+ size += value.length;
421
+ if (size > 1e6) stop("Managed preparation output exceeded its bound");
422
+ else output += value.toString("utf8");
423
+ });
424
+ child.stderr.on("data", (value) => {
425
+ size += value.length;
426
+ if (size > 1e6) stop("Managed preparation output exceeded its bound");
427
+ });
428
+ child.on("error", () => {
429
+ cleanup();
430
+ if (!stopped) {
431
+ stopped = true;
432
+ reject(new Error("Managed preparation executable was unavailable"));
433
+ }
434
+ });
435
+ child.on("close", (code) => {
436
+ cleanup();
437
+ if (stopped) return;
438
+ stopped = true;
439
+ if (stopReason) reject(new Error(stopReason));
440
+ else if (code !== 0) reject(new Error("Managed preparation step failed; the previous active bundle was preserved"));
441
+ else resolve2({ stdout: output });
442
+ });
443
+ });
444
+ function switchPointer(base, name, target) {
445
+ const current = path.join(base, name);
446
+ if (exists(current)) {
447
+ const stat = fs.lstatSync(current);
448
+ if (!stat.isSymbolicLink() || stat.uid !== uid()) throw new Error("Managed active pointer must be an owned symlink");
449
+ }
450
+ const temporary = path.join(base, `.pointer-${randomUUID2()}`);
451
+ fs.symlinkSync(target, temporary);
452
+ try {
453
+ fs.renameSync(temporary, current);
454
+ } finally {
455
+ if (exists(temporary)) fs.unlinkSync(temporary);
456
+ }
457
+ }
458
+ async function locked(options, action) {
459
+ const base = location(options);
460
+ privateDirectory(base);
461
+ owned(base, true, true);
462
+ const lock = path.join(base, ".install.lock");
463
+ const fd = fs.openSync(lock, "wx", 384);
464
+ let release = true;
465
+ try {
466
+ fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }));
467
+ return await action(base);
468
+ } catch (error) {
469
+ if (error instanceof ManagedInstallerCleanupError) release = false;
470
+ throw error;
471
+ } finally {
472
+ fs.closeSync(fd);
473
+ if (release) fs.unlinkSync(lock);
474
+ }
475
+ }
476
+ async function prepareManagedToolchain(options = {}) {
477
+ if (Number(process.versions.node.split(".")[0]) < 18)
478
+ throw new Error("Local work requires Node.js 18+; update your system runtime and retry");
479
+ return locked(options, async (base) => {
480
+ const active = path.join(base, "active.json");
481
+ let prior = null;
482
+ let recovery = null;
483
+ const preserveInvalidSelection = () => {
484
+ const pointer = fs.lstatSync(active);
485
+ if (!pointer.isSymbolicLink() || pointer.uid !== uid()) throw new Error("Repair refuses a foreign or non-symlink active pointer");
486
+ const target = path.resolve(base, fs.readlinkSync(active));
487
+ if (path.basename(target) !== "toolchain.json" || path.dirname(path.dirname(target)) !== base)
488
+ throw new Error("Repair refuses an active pointer outside the owned installation");
489
+ const root2 = path.dirname(target);
490
+ owned(root2, true, true);
491
+ if (fs.realpathSync(root2) !== root2) throw new Error("Repair refuses a linked bundle directory");
492
+ let previousHash = null;
493
+ if (exists(target)) {
494
+ owned(target, false, true);
495
+ previousHash = sha(bytes(target));
496
+ }
497
+ return { priorManifestPath: target, priorManifestSha256: previousHash, priorSelection: "invalid_owned_bundle" };
498
+ };
499
+ if (exists(active)) {
500
+ try {
501
+ prior = readManagedManifest(active, options);
502
+ } catch (error) {
503
+ if (!options.repair) throw error;
504
+ recovery = preserveInvalidSelection();
505
+ prior = null;
506
+ }
507
+ }
508
+ const runner = options.runner ?? runManagedInstaller;
509
+ async function handshake(manifest) {
510
+ for (const [name, component] of Object.entries(manifest.components)) {
511
+ const command = name === "companion" ? process.execPath : component.command;
512
+ const args = name === "companion" ? [component.command, "--version"] : ["--version"];
513
+ const observed = await runner({ command, args, cwd: manifest.root, env: cleanEnvironment(manifest.root), timeoutMs: 15e3 });
514
+ if (!new RegExp(`^(?:snipara-${name}[ ,]+)?${component.version.replaceAll(".", "\\.")}$`, "i").test(observed.stdout.trim()))
515
+ throw new Error("Managed component version handshake did not match the pinned bundle");
516
+ }
517
+ }
518
+ const selectedPrior = prior;
519
+ if (selectedPrior && Object.entries(MANAGED_VERSIONS).every(([name, version]) => selectedPrior.components[name].version === version)) {
520
+ try {
521
+ await handshake(selectedPrior);
522
+ readManagedManifest(active, options);
523
+ return selectedPrior;
524
+ } catch (error) {
525
+ if (!options.repair) throw error;
526
+ recovery = preserveInvalidSelection();
527
+ prior = null;
528
+ }
529
+ }
530
+ const bundleId = `bundle-${MANAGED_VERSIONS.companion}-${MANAGED_VERSIONS.orchestrator}-${MANAGED_VERSIONS.sandbox}-${randomUUID2()}`;
531
+ const root = path.join(base, bundleId);
532
+ fs.mkdirSync(root, { mode: 448 });
533
+ for (const directory of ["home", "tmp"]) fs.mkdirSync(path.join(root, directory), { mode: 448 });
534
+ const env = cleanEnvironment(root);
535
+ const run = (command, args, timeoutMs = 3e5) => runner({ command, args, cwd: root, env, timeoutMs });
536
+ try {
537
+ if (recovery) writePrivate(path.join(root, "recovery.json"), { schemaVersion: "snipara.toolchain_recovery.v1", ...recovery, originalBundlePreserved: true });
538
+ const python = options.python ?? program("python3"), npm = options.npm ?? program("npm");
539
+ if (![python, npm].every((value) => path.isAbsolute(value))) throw new Error("Managed package managers need absolute executable paths");
540
+ const runtime = await run(python, ["--version"], 1e4);
541
+ assertManagedRuntimeVersions(process.versions.node, runtime.stdout);
542
+ const venv = path.join(root, "python"), prefix = path.join(root, "node");
543
+ await run(python, ["-I", "-m", "venv", venv], 6e4);
544
+ await run(path.join(venv, "bin/python"), ["-I", "-m", "pip", "install", "--no-input", "--disable-pip-version-check", "snipara-sandbox[validation,mcp]==" + MANAGED_VERSIONS.sandbox, "snipara-orchestrator==" + MANAGED_VERSIONS.orchestrator]);
545
+ await run(npm, ["install", "--prefix", prefix, "--ignore-scripts", "--no-audit", "--no-fund", "--save-exact", "snipara-companion@" + MANAGED_VERSIONS.companion]);
546
+ const manifest = {
547
+ schemaVersion: "snipara.toolchain.v1",
548
+ bundleId,
549
+ root,
550
+ components: {
551
+ companion: { version: MANAGED_VERSIONS.companion, command: fs.realpathSync(path.join(prefix, "node_modules/.bin/snipara-companion")), sha256: "" },
552
+ orchestrator: { version: MANAGED_VERSIONS.orchestrator, command: path.join(venv, "bin/snipara-orchestrator"), sha256: "" },
553
+ sandbox: { version: MANAGED_VERSIONS.sandbox, command: path.join(venv, "bin/snipara-sandbox"), sha256: "" }
554
+ },
555
+ contracts: { taskReference: 1, validation: 1 },
556
+ capabilities: ["validation", "coordination"]
557
+ };
558
+ for (const component of Object.values(manifest.components)) {
559
+ component.command = protectedCommand(root, component.command);
560
+ component.sha256 = sha(bytes(component.command, 8e6));
561
+ }
562
+ const manifestFile = path.join(root, "toolchain.json");
563
+ writePrivate(manifestFile, parseManagedToolchainManifest(manifest));
564
+ await handshake(manifest);
565
+ readManagedManifest(manifestFile, options);
566
+ writePrivate(path.join(root, "prepared.json"), { schemaVersion: "snipara.toolchain_preparation.v1", status: "prepared", manifestSha256: sha(bytes(manifestFile)), observedAt: (/* @__PURE__ */ new Date()).toISOString(), activationObserved: false });
567
+ if (prior) switchPointer(base, "previous.json", path.join(prior.root, "toolchain.json"));
568
+ switchPointer(base, "active.json", manifestFile);
569
+ return manifest;
570
+ } catch (error) {
571
+ writePrivate(path.join(root, "failed.json"), { schemaVersion: "snipara.toolchain_preparation.v1", status: "failed", observedAt: (/* @__PURE__ */ new Date()).toISOString(), reason: "preparation_or_identity_check_failed", priorActivePreserved: true });
572
+ throw error;
573
+ }
574
+ });
575
+ }
576
+ async function rollbackManagedToolchain(options = {}) {
577
+ return locked(options, async (base) => {
578
+ const previous = readManagedManifest(path.join(base, "previous.json"), options);
579
+ let active = null;
580
+ try {
581
+ active = readManagedManifest(path.join(base, "active.json"), options);
582
+ } catch {
583
+ }
584
+ switchPointer(base, "active.json", path.join(previous.root, "toolchain.json"));
585
+ if (active) switchPointer(base, "previous.json", path.join(active.root, "toolchain.json"));
586
+ return previous;
587
+ });
588
+ }
589
+
590
+ // src/managed-config.ts
591
+ import fs2 from "fs";
592
+ import path2 from "path";
593
+ import { execFileSync } from "child_process";
594
+ import { TextDecoder } from "util";
595
+ function mergeManagedMcpServers(existing, native, hosted, sourceBound = false) {
596
+ return { ...existing, ...sourceBound || existing["snipara-repository"] ? {} : { snipara: hosted }, "snipara-local": native };
597
+ }
598
+ var PARSE = "import json,sys\ntry:\n import tomllib\nexcept ImportError:\n import tomli as tomllib\nvalue=tomllib.loads(sys.stdin.read())\nservers=value.get('mcp_servers',{})\nif not isinstance(servers,dict): raise ValueError('invalid MCP table')\nprint(json.dumps(servers.get('snipara-local'),ensure_ascii=True))";
599
+ function parse(content, options) {
600
+ try {
601
+ return JSON.parse(execFileSync(options.python, ["-I", "-B", "-c", PARSE], {
602
+ input: content,
603
+ env: options.env,
604
+ encoding: "utf8",
605
+ timeout: 5e3,
606
+ maxBuffer: 1e6,
607
+ stdio: ["pipe", "pipe", "pipe"]
608
+ }));
609
+ } catch {
610
+ throw new Error("Existing Codex configuration is malformed or its parser is unavailable; file preserved");
611
+ }
612
+ }
613
+ function object2(value) {
614
+ return value !== null && typeof value === "object" && !Array.isArray(value);
615
+ }
616
+ function managedCodexSnippet(server) {
617
+ const env = server.env && Object.keys(server.env).length ? `
618
+ env = { ${Object.entries(server.env).map(([key, value]) => `${JSON.stringify(key)} = ${JSON.stringify(value)}`).join(", ")} }` : "";
619
+ return `[mcp_servers.snipara-local]
620
+ command = ${JSON.stringify(server.command)}
621
+ args = ${JSON.stringify(server.args)}${env}
622
+ `;
623
+ }
624
+ function mergeManagedCodexConfig(options) {
625
+ const directory = path2.dirname(options.file);
626
+ if (!fs2.existsSync(directory)) fs2.mkdirSync(directory, { mode: 448 });
627
+ const parent = fs2.lstatSync(directory);
628
+ if (!parent.isDirectory() || parent.isSymbolicLink() || parent.uid !== process.getuid?.() || parent.mode & 18)
629
+ throw new Error("Codex configuration directory is not safely owned; preserved");
630
+ let previous = null;
631
+ try {
632
+ const fd2 = fs2.openSync(options.file, fs2.constants.O_RDONLY | fs2.constants.O_NOFOLLOW | fs2.constants.O_NONBLOCK);
633
+ try {
634
+ const info = fs2.fstatSync(fd2);
635
+ if (!info.isFile() || info.uid !== process.getuid?.() || info.nlink !== 1 || info.size > 1e6 || info.mode & 18)
636
+ throw new Error("Existing Codex configuration is not safely owned; preserved");
637
+ previous = fs2.readFileSync(fd2);
638
+ if (previous.length > 1e6) throw new Error("Existing Codex configuration exceeds its bound; preserved");
639
+ } finally {
640
+ fs2.closeSync(fd2);
641
+ }
642
+ } catch (error) {
643
+ if (error.code !== "ENOENT") throw error;
644
+ }
645
+ const content = previous ? new TextDecoder("utf8", { fatal: true }).decode(previous) : options.initialContent ?? "";
646
+ const selected = parse(content, options);
647
+ if (selected !== null) {
648
+ if (!object2(selected) || Object.keys(selected).some((key) => !["command", "args", "env"].includes(key)) || typeof selected.command !== "string" || !Array.isArray(selected.args) || !selected.args.every((value) => typeof value === "string") || selected.env !== void 0 && (!object2(selected.env) || !Object.values(selected.env).every((value) => typeof value === "string")) || !options.validateExisting(selected))
649
+ throw new Error("Existing managed Codex connection conflicts with setup; file preserved and activation incomplete");
650
+ return "unchanged";
651
+ }
652
+ const result = content + (content ? content.endsWith("\n") ? "\n" : "\n\n" : "") + managedCodexSnippet(options.server);
653
+ parse(result, options);
654
+ if (previous && !fs2.readFileSync(options.file).equals(previous))
655
+ throw new Error("Codex configuration changed during setup; file preserved");
656
+ if (!previous) {
657
+ fs2.writeFileSync(options.file, result, { flag: "wx", mode: 384 });
658
+ return "created";
659
+ }
660
+ const fd = fs2.openSync(options.file, fs2.constants.O_WRONLY | fs2.constants.O_APPEND | fs2.constants.O_NOFOLLOW);
661
+ try {
662
+ const current = fs2.fstatSync(fd), named = fs2.lstatSync(options.file);
663
+ if (current.ino !== named.ino || current.dev !== named.dev || current.size !== previous.length || !current.isFile())
664
+ throw new Error("Codex configuration changed during setup; file preserved");
665
+ fs2.writeFileSync(fd, result.slice(content.length), "utf8");
666
+ } finally {
667
+ fs2.closeSync(fd);
668
+ }
669
+ return "appended";
670
+ }
671
+
672
+ // src/source-connection.ts
673
+ import fs3 from "fs";
674
+ import path3 from "path";
675
+ import { execFileSync as execFileSync2 } from "child_process";
676
+ import { TextDecoder as TextDecoder2 } from "util";
677
+
678
+ // src/source-binding.mjs
679
+ import { createHash as createHash6 } from "crypto";
680
+ import {
681
+ existsSync,
682
+ lstatSync,
683
+ mkdirSync,
684
+ readFileSync,
685
+ realpathSync,
686
+ writeFileSync
687
+ } from "fs";
688
+ import { spawnSync } from "child_process";
689
+ import { dirname, isAbsolute, join, resolve } from "path";
690
+ var fingerprint = (value) => createHash6("sha256").update(value).digest("hex");
691
+ var SourceBindingError = class extends Error {
692
+ constructor(code, message) {
693
+ super(message);
694
+ this.name = "SourceBindingError";
695
+ this.code = code;
696
+ }
697
+ };
698
+ function fail(code, message) {
699
+ throw new SourceBindingError(code, message);
700
+ }
701
+ function normalizeRepositoryRemote(remote) {
702
+ if (typeof remote !== "string" || !remote || remote.length > 2048 || /[\\%\s\x00-\x1f\x7f]/.test(remote) || /\/\.{1,2}(?:\/|$)/.test(remote))
703
+ fail("unsafe_remote", "Repository remote is unsupported or contains unsafe credentials.");
704
+ let hostname, port = "", repositoryPath;
705
+ try {
706
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(remote)) {
707
+ const url = new URL(remote);
708
+ if (!["https:", "http:", "ssh:"].includes(url.protocol) || url.password || url.search || url.hash || url.protocol !== "ssh:" && url.username) throw new Error();
709
+ if (url.username && !/^[A-Za-z0-9_.-]+$/.test(url.username)) throw new Error();
710
+ hostname = url.hostname.toLowerCase();
711
+ port = url.port && !(url.protocol === "ssh:" && url.port === "22") ? `:${url.port}` : "";
712
+ repositoryPath = url.pathname;
713
+ } else {
714
+ const match = remote.match(/^(?:([A-Za-z0-9_.-]+)@)?([A-Za-z0-9.-]+):([^:]+)$/);
715
+ if (!match) throw new Error();
716
+ hostname = match[2].toLowerCase();
717
+ repositoryPath = match[3];
718
+ }
719
+ repositoryPath = repositoryPath.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
720
+ if (!hostname || !/^[A-Za-z0-9.-]+$/.test(hostname) || !repositoryPath || !/^[A-Za-z0-9_./~-]+$/.test(repositoryPath) || repositoryPath.split("/").some((part) => !part || part === "." || part === "..")) throw new Error();
721
+ } catch {
722
+ fail("unsafe_remote", "Repository remote is unsupported or contains unsafe credentials.");
723
+ }
724
+ if (["github.com", "gitlab.com", "bitbucket.org"].includes(hostname)) repositoryPath = repositoryPath.toLowerCase();
725
+ return `${hostname}${port}/${repositoryPath}`;
726
+ }
727
+ function git(cwd, args) {
728
+ const names = ["PATH", "HOME", "USERPROFILE", "SystemRoot", "WINDIR", "TMPDIR", "TMP", "TEMP"];
729
+ const env = Object.fromEntries(names.filter((key) => typeof process.env[key] === "string").map((key) => [key, process.env[key]]));
730
+ const result = spawnSync("git", ["--no-optional-locks", "-C", cwd, ...args], {
731
+ encoding: "utf8",
732
+ timeout: 5e3,
733
+ maxBuffer: 16384,
734
+ shell: false,
735
+ env: { ...env, LC_ALL: "C", GIT_TERMINAL_PROMPT: "0" }
736
+ });
737
+ if (result.error || result.signal) fail("source_unavailable", "Cannot verify the current repository. Reconnect before using Snipara.");
738
+ return result;
739
+ }
740
+ function observeSource(workspacePath) {
741
+ if (typeof workspacePath !== "string" || !workspacePath || !isAbsolute(workspacePath))
742
+ fail("source_unavailable", "Choose an explicit absolute workspace folder.");
743
+ let folder;
744
+ try {
745
+ folder = realpathSync(workspacePath);
746
+ if (!lstatSync(folder).isDirectory()) throw new Error();
747
+ } catch {
748
+ fail("source_unavailable", "Workspace folder is unavailable.");
749
+ }
750
+ const rootResult = git(folder, ["rev-parse", "--show-toplevel"]);
751
+ if (rootResult.status !== 0) {
752
+ if (rootResult.status !== 128 || !rootResult.stderr.includes("not a git repository"))
753
+ fail("source_unavailable", "Cannot verify the current repository. Reconnect before using Snipara.");
754
+ return { root: folder, kind: "folder", fingerprint: fingerprint(`folder:${folder}`) };
755
+ }
756
+ let root;
757
+ try {
758
+ root = realpathSync(rootResult.stdout.trim());
759
+ } catch {
760
+ fail("source_unavailable", "Repository root is unavailable.");
761
+ }
762
+ const remote = git(root, ["config", "--includes", "--get", "remote.origin.url"]);
763
+ if (remote.status === 0) {
764
+ const repository = normalizeRepositoryRemote(remote.stdout.replace(/\r?\n$/, ""));
765
+ return { root, kind: "repository", fingerprint: fingerprint(`repository:${repository}`), repository };
766
+ }
767
+ if (remote.status !== 1) fail("source_unavailable", "Cannot verify the repository remote.");
768
+ const commonResult = git(root, ["rev-parse", "--git-common-dir"]);
769
+ if (commonResult.status !== 0) fail("source_unavailable", "Cannot verify the local repository identity.");
770
+ let common;
771
+ try {
772
+ common = realpathSync(resolve(root, commonResult.stdout.trim()));
773
+ } catch {
774
+ fail("source_unavailable", "Cannot verify the local repository identity.");
775
+ }
776
+ return { root, kind: "local-repository", fingerprint: fingerprint(`local-repository:${common}`) };
777
+ }
778
+
779
+ // src/source-connection.ts
780
+ var object3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
781
+ function boundedConfiguration(file) {
782
+ try {
783
+ const parent = fs3.lstatSync(path3.dirname(file));
784
+ if (!parent.isDirectory() || parent.isSymbolicLink() || parent.uid !== process.getuid?.())
785
+ throw new Error("Native configuration directory is not safely owned; preserve and repair it first");
786
+ } catch (error) {
787
+ if (error.code === "ENOENT") return null;
788
+ throw error;
789
+ }
790
+ let fd;
791
+ try {
792
+ fd = fs3.openSync(file, fs3.constants.O_RDONLY | fs3.constants.O_NOFOLLOW | fs3.constants.O_NONBLOCK);
793
+ } catch (error) {
794
+ if (error.code === "ENOENT") return null;
795
+ throw new Error("Existing native configuration is not a readable regular file; preserve and repair it first");
796
+ }
797
+ try {
798
+ const info = fs3.fstatSync(fd);
799
+ if (!info.isFile() || info.uid !== process.getuid?.() || info.size > 1e6 || info.mode & 18)
800
+ throw new Error("Existing native configuration is not safely owned; preserve and repair it first");
801
+ const value = fs3.readFileSync(fd);
802
+ if (value.length > 1e6) throw new Error("Native configuration exceeds its read bound");
803
+ return new TextDecoder2("utf8", { fatal: true }).decode(value);
804
+ } finally {
805
+ fs3.closeSync(fd);
806
+ }
807
+ }
808
+ function operatorPython() {
809
+ for (const directory of (process.env.PATH ?? "").split(path3.delimiter)) {
810
+ if (!directory || !path3.isAbsolute(directory)) continue;
811
+ const candidate = path3.join(directory, "python3");
812
+ try {
813
+ fs3.accessSync(candidate, fs3.constants.X_OK);
814
+ if (fs3.statSync(candidate).isFile()) return fs3.realpathSync(candidate);
815
+ } catch {
816
+ }
817
+ }
818
+ throw new Error("Reading existing Codex configuration requires Python 3.11+, or Python 3.10 with tomli; no configuration was changed");
819
+ }
820
+ function sourceServer(value, observed) {
821
+ if (!object3(value) || Object.keys(value).some((key) => !["command", "args"].includes(key)) || typeof value.command !== "string" || !path3.isAbsolute(value.command) || !Array.isArray(value.args) || value.args.length !== 5 || !value.args.every((item) => typeof item === "string") || value.args[1] !== "--source-fingerprint" || value.args[3] !== "--source-kind" || value.args[2] !== observed.fingerprint || value.args[4] !== observed.kind || !path3.isAbsolute(value.args[0]) || path3.basename(value.args[0]) !== "native-mcp.mjs")
822
+ throw new Error("Existing Snipara repository connection differs from this workspace; reconnect it explicitly before local-work setup");
823
+ try {
824
+ const script = value.args[0], info = fs3.lstatSync(script);
825
+ if (!info.isFile() || info.isSymbolicLink() || info.uid !== process.getuid?.() || info.mode & 18 || fs3.realpathSync(script) !== script || info.size > 256e3 || !fs3.statSync(value.command).isFile()) throw new Error();
826
+ } catch {
827
+ throw new Error("Existing Snipara repository launcher is unavailable or unsafe; preserve and repair its connection");
828
+ }
829
+ }
830
+ function detectSourceConnection(workspace, options = {}) {
831
+ const observed = observeSource(fs3.realpathSync(workspace));
832
+ const servers = [];
833
+ const json = boundedConfiguration(path3.join(observed.root, ".mcp.json"));
834
+ if (json !== null) {
835
+ let document;
836
+ try {
837
+ document = JSON.parse(json);
838
+ } catch {
839
+ throw new Error("Existing MCP JSON is malformed; preserve and repair it before setup");
840
+ }
841
+ if (!object3(document) || document.mcpServers !== void 0 && !object3(document.mcpServers))
842
+ throw new Error("Existing MCP server configuration is invalid; preserve and repair it before setup");
843
+ servers.push(document.mcpServers ?? {});
844
+ }
845
+ const toml = boundedConfiguration(path3.join(observed.root, ".codex/config.toml"));
846
+ if (toml !== null) {
847
+ const script = "import json,sys\ntry:\n import tomllib\nexcept ImportError:\n import tomli as tomllib\nvalue=tomllib.loads(sys.stdin.read()).get('mcp_servers',{})\nif not isinstance(value,dict): raise ValueError('invalid servers')\nprint(json.dumps({key:value[key] for key in ['snipara-repository','snipara','snipara-plugin'] if key in value},ensure_ascii=True))";
848
+ try {
849
+ servers.push(JSON.parse(execFileSync2(options.python ?? operatorPython(), ["-I", "-B", "-c", script], {
850
+ input: toml,
851
+ encoding: "utf8",
852
+ timeout: 5e3,
853
+ maxBuffer: 1e6,
854
+ stdio: ["pipe", "pipe", "pipe"],
855
+ env: { PATH: process.env.PATH, HOME: process.env.HOME, PYTHONNOUSERSITE: "1" }
856
+ })));
857
+ } catch {
858
+ throw new Error("Existing Codex configuration could not be parsed. Use Python 3.11+, or Python 3.10 with tomli, and repair malformed TOML before retrying; configuration was preserved");
859
+ }
860
+ }
861
+ const selected = servers.filter((entries) => entries["snipara-repository"] !== void 0);
862
+ if (!selected.length) return null;
863
+ if (servers.some((entries) => entries.snipara !== void 0 || entries["snipara-plugin"] !== void 0))
864
+ throw new Error("An existing direct Snipara connection conflicts with the repository connection; preserve and review it before setup");
865
+ for (const entries of selected) sourceServer(entries["snipara-repository"], observed);
866
+ return observed;
867
+ }
868
+
93
869
  // src/index.ts
94
870
  var VERSION = package_default.version;
95
871
  var BASE_URL = "https://www.snipara.com";
@@ -139,37 +915,37 @@ var DECISION_COMMIT_TERMS = [
139
915
  "switch"
140
916
  ];
141
917
  function buildActivationId() {
142
- return `act_${Date.now().toString(36)}_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
918
+ return `act_${Date.now().toString(36)}_${randomUUID3().replace(/-/g, "").slice(0, 12)}`;
143
919
  }
144
920
  function getActivationDir() {
145
- return path.join(process.cwd(), ".snipara", "activation");
921
+ return path4.join(process.cwd(), ".snipara", "activation");
146
922
  }
147
923
  function getActivationManifestPath() {
148
- return path.join(getActivationDir(), "manifest.json");
924
+ return path4.join(getActivationDir(), "manifest.json");
149
925
  }
150
926
  function getStableActivationManifestPath() {
151
- return path.join(getActivationDir(), "activation-manifest.json");
927
+ return path4.join(getActivationDir(), "activation-manifest.json");
152
928
  }
153
929
  function getActivationHandoffPath() {
154
- return path.join(getActivationDir(), "handoff.md");
930
+ return path4.join(getActivationDir(), "handoff.md");
155
931
  }
156
932
  function getFirstWorkBriefPath() {
157
- return path.join(getActivationDir(), "first-brief.md");
933
+ return path4.join(getActivationDir(), "first-brief.md");
158
934
  }
159
935
  function getMemoryCandidatesPath() {
160
- return path.join(getActivationDir(), "memory-candidates.json");
936
+ return path4.join(getActivationDir(), "memory-candidates.json");
161
937
  }
162
938
  function getProjectBootstrapJsonPath() {
163
- return path.join(getActivationDir(), "project-bootstrap.json");
939
+ return path4.join(getActivationDir(), "project-bootstrap.json");
164
940
  }
165
941
  function getProjectBootstrapMarkdownPath() {
166
- return path.join(getActivationDir(), "project-bootstrap.md");
942
+ return path4.join(getActivationDir(), "project-bootstrap.md");
167
943
  }
168
944
  function writeActivationManifest(manifest) {
169
- fs.mkdirSync(getActivationDir(), { recursive: true });
945
+ fs4.mkdirSync(getActivationDir(), { recursive: true });
170
946
  const payload = JSON.stringify(manifest, null, 2) + "\n";
171
- fs.writeFileSync(getActivationManifestPath(), payload, "utf8");
172
- fs.writeFileSync(getStableActivationManifestPath(), payload, "utf8");
947
+ fs4.writeFileSync(getActivationManifestPath(), payload, "utf8");
948
+ fs4.writeFileSync(getStableActivationManifestPath(), payload, "utf8");
173
949
  }
174
950
  function updateActivationManifest(manifest) {
175
951
  manifest.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -188,12 +964,12 @@ function failActivationLane(detail, command, error) {
188
964
  };
189
965
  }
190
966
  function readPackageJsonScripts() {
191
- const packageJsonPath = path.join(process.cwd(), "package.json");
192
- if (!fs.existsSync(packageJsonPath)) {
967
+ const packageJsonPath = path4.join(process.cwd(), "package.json");
968
+ if (!fs4.existsSync(packageJsonPath)) {
193
969
  return [];
194
970
  }
195
971
  try {
196
- const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
972
+ const parsed = JSON.parse(fs4.readFileSync(packageJsonPath, "utf8"));
197
973
  return Object.keys(parsed.scripts ?? {}).sort();
198
974
  } catch {
199
975
  return [];
@@ -254,11 +1030,11 @@ function readDecisionCommitSubjectLines() {
254
1030
  }
255
1031
  }
256
1032
  function readMemoryCandidateLines(relativePath) {
257
- const absolutePath = path.join(process.cwd(), relativePath);
258
- if (!fs.existsSync(absolutePath) || fs.statSync(absolutePath).isDirectory()) {
1033
+ const absolutePath = path4.join(process.cwd(), relativePath);
1034
+ if (!fs4.existsSync(absolutePath) || fs4.statSync(absolutePath).isDirectory()) {
259
1035
  return [];
260
1036
  }
261
- const content = fs.readFileSync(absolutePath, "utf8").slice(0, 24e3);
1037
+ const content = fs4.readFileSync(absolutePath, "utf8").slice(0, 24e3);
262
1038
  return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => /decision|decided|must|should|always|never|convention|rule/i.test(line)).slice(0, 4).map((line) => ({
263
1039
  source: relativePath,
264
1040
  text: line.slice(0, 280),
@@ -268,12 +1044,12 @@ function readMemoryCandidateLines(relativePath) {
268
1044
  }));
269
1045
  }
270
1046
  function estimateFileTokens(relativePath) {
271
- const absolutePath = path.join(process.cwd(), relativePath);
272
- if (!fs.existsSync(absolutePath) || fs.statSync(absolutePath).isDirectory()) {
1047
+ const absolutePath = path4.join(process.cwd(), relativePath);
1048
+ if (!fs4.existsSync(absolutePath) || fs4.statSync(absolutePath).isDirectory()) {
273
1049
  return 0;
274
1050
  }
275
1051
  try {
276
- const content = fs.readFileSync(absolutePath, "utf8");
1052
+ const content = fs4.readFileSync(absolutePath, "utf8");
277
1053
  return Math.ceil(content.length / 4);
278
1054
  } catch {
279
1055
  return 0;
@@ -302,12 +1078,12 @@ function buildMemoryCandidatePreviews(scan) {
302
1078
  }).slice(0, 20);
303
1079
  }
304
1080
  function readTextFileSnippet(relativePath, maxChars = 24e3) {
305
- const absolutePath = path.join(process.cwd(), relativePath);
306
- if (!fs.existsSync(absolutePath) || fs.statSync(absolutePath).isDirectory()) {
1081
+ const absolutePath = path4.join(process.cwd(), relativePath);
1082
+ if (!fs4.existsSync(absolutePath) || fs4.statSync(absolutePath).isDirectory()) {
307
1083
  return "";
308
1084
  }
309
1085
  try {
310
- return fs.readFileSync(absolutePath, "utf8").slice(0, maxChars);
1086
+ return fs4.readFileSync(absolutePath, "utf8").slice(0, maxChars);
311
1087
  } catch {
312
1088
  return "";
313
1089
  }
@@ -316,7 +1092,7 @@ function collectDayZeroCorpusDocuments(scan) {
316
1092
  const documents = [];
317
1093
  let totalChars = 0;
318
1094
  const cwd = process.cwd();
319
- const rootReadmes = fs.existsSync(cwd) ? fs.readdirSync(cwd).filter((entry) => /^readme(?:\.[a-z0-9_-]+)?\.mdx?$/i.test(entry)).sort() : [];
1095
+ const rootReadmes = fs4.existsSync(cwd) ? fs4.readdirSync(cwd).filter((entry) => /^readme(?:\.[a-z0-9_-]+)?\.mdx?$/i.test(entry)).sort() : [];
320
1096
  const sources = [
321
1097
  ...rootReadmes,
322
1098
  ...listFilesUnder("docs", DAY_ZERO_CORPUS_MAX_FILES * 8),
@@ -429,15 +1205,15 @@ function buildAgentReadinessReport(scan) {
429
1205
  };
430
1206
  }
431
1207
  function writeMemoryCandidates(candidates) {
432
- fs.mkdirSync(getActivationDir(), { recursive: true });
433
- fs.writeFileSync(
1208
+ fs4.mkdirSync(getActivationDir(), { recursive: true });
1209
+ fs4.writeFileSync(
434
1210
  getMemoryCandidatesPath(),
435
1211
  JSON.stringify({ candidates, note: "Review required before durable memory import." }, null, 2) + "\n",
436
1212
  "utf8"
437
1213
  );
438
1214
  }
439
1215
  function writeInitialHandoff(answers, manifest) {
440
- fs.mkdirSync(getActivationDir(), { recursive: true });
1216
+ fs4.mkdirSync(getActivationDir(), { recursive: true });
441
1217
  const statusLines = readGitStatusPorcelain();
442
1218
  const recentCommits = readRecentCommitSubjects();
443
1219
  const lines = [
@@ -470,10 +1246,10 @@ function writeInitialHandoff(answers, manifest) {
470
1246
  "- Connect GitHub later for PRs, reviews, team sync, and continuous sync.",
471
1247
  ""
472
1248
  ];
473
- fs.writeFileSync(getActivationHandoffPath(), lines.join("\n"), "utf8");
1249
+ fs4.writeFileSync(getActivationHandoffPath(), lines.join("\n"), "utf8");
474
1250
  }
475
1251
  function writeFirstWorkBrief(manifest) {
476
- fs.mkdirSync(getActivationDir(), { recursive: true });
1252
+ fs4.mkdirSync(getActivationDir(), { recursive: true });
477
1253
  const lines = [
478
1254
  `# ${manifest.firstBrief.title}`,
479
1255
  "",
@@ -488,40 +1264,40 @@ function writeFirstWorkBrief(manifest) {
488
1264
  ""
489
1265
  ])
490
1266
  ];
491
- fs.writeFileSync(getFirstWorkBriefPath(), lines.join("\n"), "utf8");
1267
+ fs4.writeFileSync(getFirstWorkBriefPath(), lines.join("\n"), "utf8");
492
1268
  }
493
1269
  function detectLocalActivationScan(detection) {
494
1270
  const cwd = process.cwd();
495
- const exists = (relativePath) => fs.existsSync(path.join(cwd, relativePath));
1271
+ const exists2 = (relativePath) => fs4.existsSync(path4.join(cwd, relativePath));
496
1272
  const candidateDocs = findDecisionArchaeologySources(cwd);
497
1273
  const lockfiles = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"].filter(
498
- exists
1274
+ exists2
499
1275
  );
500
1276
  const packageManagers = [
501
- exists("pnpm-lock.yaml") ? "pnpm" : null,
502
- exists("package-lock.json") ? "npm" : null,
503
- exists("yarn.lock") ? "yarn" : null,
504
- exists("bun.lockb") ? "bun" : null
1277
+ exists2("pnpm-lock.yaml") ? "pnpm" : null,
1278
+ exists2("package-lock.json") ? "npm" : null,
1279
+ exists2("yarn.lock") ? "yarn" : null,
1280
+ exists2("bun.lockb") ? "bun" : null
505
1281
  ].filter((value) => Boolean(value));
506
- const rootEntries = fs.existsSync(cwd) ? fs.readdirSync(cwd).slice(0, 250) : [];
1282
+ const rootEntries = fs4.existsSync(cwd) ? fs4.readdirSync(cwd).slice(0, 250) : [];
507
1283
  const rootExtensions = new Set(
508
- rootEntries.map((entry) => path.extname(entry).replace(/^\./, "")).filter(Boolean)
1284
+ rootEntries.map((entry) => path4.extname(entry).replace(/^\./, "")).filter(Boolean)
509
1285
  );
510
1286
  const languages = [
511
- exists("package.json") || rootExtensions.has("ts") || rootExtensions.has("tsx") ? "typescript" : null,
512
- exists("package.json") || rootExtensions.has("js") || rootExtensions.has("jsx") ? "javascript" : null,
513
- exists("pyproject.toml") || exists("requirements.txt") || rootExtensions.has("py") ? "python" : null,
514
- exists("go.mod") || rootExtensions.has("go") ? "go" : null,
515
- exists("Cargo.toml") || rootExtensions.has("rs") ? "rust" : null
1287
+ exists2("package.json") || rootExtensions.has("ts") || rootExtensions.has("tsx") ? "typescript" : null,
1288
+ exists2("package.json") || rootExtensions.has("js") || rootExtensions.has("jsx") ? "javascript" : null,
1289
+ exists2("pyproject.toml") || exists2("requirements.txt") || rootExtensions.has("py") ? "python" : null,
1290
+ exists2("go.mod") || rootExtensions.has("go") ? "go" : null,
1291
+ exists2("Cargo.toml") || rootExtensions.has("rs") ? "rust" : null
516
1292
  ].filter((value) => Boolean(value));
517
1293
  return {
518
1294
  files: {
519
1295
  candidateDocs,
520
1296
  lockfiles,
521
- hasPackageJson: exists("package.json"),
522
- hasPyproject: exists("pyproject.toml"),
523
- hasRequirements: exists("requirements.txt"),
524
- hasDockerfile: exists("Dockerfile")
1297
+ hasPackageJson: exists2("package.json"),
1298
+ hasPyproject: exists2("pyproject.toml"),
1299
+ hasRequirements: exists2("requirements.txt"),
1300
+ hasDockerfile: exists2("Dockerfile")
525
1301
  },
526
1302
  packageManagers,
527
1303
  languages: [...new Set(languages)],
@@ -547,8 +1323,8 @@ function classifyDecisionSource(relativePath) {
547
1323
  return "project_document";
548
1324
  }
549
1325
  function listFilesUnder(relativeDir, maxFiles) {
550
- const root = path.join(process.cwd(), relativeDir);
551
- if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
1326
+ const root = path4.join(process.cwd(), relativeDir);
1327
+ if (!fs4.existsSync(root) || !fs4.statSync(root).isDirectory()) {
552
1328
  return [];
553
1329
  }
554
1330
  const results = [];
@@ -556,13 +1332,13 @@ function listFilesUnder(relativeDir, maxFiles) {
556
1332
  if (results.length >= maxFiles) {
557
1333
  return;
558
1334
  }
559
- for (const entry of fs.readdirSync(dir).sort()) {
560
- const absolutePath = path.join(dir, entry);
561
- const stat = fs.statSync(absolutePath);
1335
+ for (const entry of fs4.readdirSync(dir).sort()) {
1336
+ const absolutePath = path4.join(dir, entry);
1337
+ const stat = fs4.statSync(absolutePath);
562
1338
  if (stat.isDirectory()) {
563
1339
  visit(absolutePath);
564
1340
  } else if (/\.(md|mdx|txt|mdc)$/i.test(entry)) {
565
- results.push(path.relative(process.cwd(), absolutePath));
1341
+ results.push(path4.relative(process.cwd(), absolutePath));
566
1342
  }
567
1343
  if (results.length >= maxFiles) {
568
1344
  return;
@@ -577,7 +1353,7 @@ function isCreateSniparaGeneratedInstructionSource(relativePath) {
577
1353
  return false;
578
1354
  }
579
1355
  try {
580
- const prefix = fs.readFileSync(path.join(process.cwd(), relativePath), "utf8").slice(0, 4096);
1356
+ const prefix = fs4.readFileSync(path4.join(process.cwd(), relativePath), "utf8").slice(0, 4096);
581
1357
  return prefix.includes(
582
1358
  "## Snipara Context Workflow\n\nThis project uses Snipara Hosted MCP for project context and reviewed memory."
583
1359
  ) || prefix.includes(
@@ -590,15 +1366,15 @@ function isCreateSniparaGeneratedInstructionSource(relativePath) {
590
1366
  }
591
1367
  }
592
1368
  function findDecisionArchaeologySources(cwd) {
593
- const exists = (relativePath) => fs.existsSync(path.join(cwd, relativePath));
1369
+ const exists2 = (relativePath) => fs4.existsSync(path4.join(cwd, relativePath));
594
1370
  const directSources = [
595
1371
  "README.md",
596
1372
  "CHANGELOG.md",
597
1373
  "MEMORY.md",
598
1374
  ...["AGENTS.md", "CLAUDE.md", ".github/copilot-instructions.md"].filter(
599
- (source) => exists(source)
1375
+ (source) => exists2(source)
600
1376
  )
601
- ].filter(exists);
1377
+ ].filter(exists2);
602
1378
  const archaeologySources = [
603
1379
  ...listFilesUnder("docs/adr", 20),
604
1380
  ...listFilesUnder("docs/adrs", 20),
@@ -630,7 +1406,7 @@ function findBoundedProjectFiles(predicate, maxMatches = PROJECT_BOOTSTRAP_MAX_M
630
1406
  }
631
1407
  let entries;
632
1408
  try {
633
- entries = fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
1409
+ entries = fs4.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
634
1410
  } catch {
635
1411
  return;
636
1412
  }
@@ -638,14 +1414,14 @@ function findBoundedProjectFiles(predicate, maxMatches = PROJECT_BOOTSTRAP_MAX_M
638
1414
  if (entry.isSymbolicLink()) {
639
1415
  continue;
640
1416
  }
641
- const absolutePath = path.join(directory, entry.name);
1417
+ const absolutePath = path4.join(directory, entry.name);
642
1418
  if (entry.isDirectory()) {
643
1419
  if (!ignoredDirectories.has(entry.name)) {
644
1420
  visit(absolutePath);
645
1421
  }
646
1422
  } else if (entry.isFile()) {
647
1423
  walkedFiles += 1;
648
- const relativePath = path.relative(process.cwd(), absolutePath).replace(/\\/g, "/");
1424
+ const relativePath = path4.relative(process.cwd(), absolutePath).replace(/\\/g, "/");
649
1425
  if (predicate(relativePath)) {
650
1426
  results.push(relativePath);
651
1427
  }
@@ -659,12 +1435,12 @@ function findBoundedProjectFiles(predicate, maxMatches = PROJECT_BOOTSTRAP_MAX_M
659
1435
  return results;
660
1436
  }
661
1437
  function readProjectPackageMetadata() {
662
- const packageJsonPath = path.join(process.cwd(), "package.json");
663
- if (!fs.existsSync(packageJsonPath)) {
1438
+ const packageJsonPath = path4.join(process.cwd(), "package.json");
1439
+ if (!fs4.existsSync(packageJsonPath)) {
664
1440
  return { scripts: {}, dependencies: [], workspaces: false };
665
1441
  }
666
1442
  try {
667
- const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
1443
+ const parsed = JSON.parse(fs4.readFileSync(packageJsonPath, "utf8"));
668
1444
  const scripts = Object.fromEntries(
669
1445
  Object.entries(parsed.scripts ?? {}).filter(
670
1446
  (entry) => typeof entry[1] === "string"
@@ -722,7 +1498,7 @@ function createBootstrapItem(section, title, status, detail, evidence) {
722
1498
  }
723
1499
  function buildProjectBootstrap(scan) {
724
1500
  const cwd = process.cwd();
725
- const exists = (relativePath) => fs.existsSync(path.join(cwd, relativePath));
1501
+ const exists2 = (relativePath) => fs4.existsSync(path4.join(cwd, relativePath));
726
1502
  const packageMetadata = readProjectPackageMetadata();
727
1503
  const allDetectedFiles = findBoundedProjectFiles(() => true, PROJECT_BOOTSTRAP_MAX_WALKED_FILES);
728
1504
  const testFiles = allDetectedFiles.filter(
@@ -738,14 +1514,14 @@ function buildProjectBootstrap(scan) {
738
1514
  (relativePath) => /^\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath)
739
1515
  ),
740
1516
  ...[".gitlab-ci.yml", "azure-pipelines.yml", "Jenkinsfile", ".circleci/config.yml"].filter(
741
- exists
1517
+ exists2
742
1518
  )
743
1519
  ].filter((value, index, values) => values.indexOf(value) === index);
744
1520
  const instructionSources = scan.files.candidateDocs.filter(
745
1521
  (source) => classifyDecisionSource(source) === "agent_instruction"
746
1522
  );
747
1523
  const documentationSources = [
748
- ...["README.md", "CHANGELOG.md", "MEMORY.md"].filter(exists),
1524
+ ...["README.md", "CHANGELOG.md", "MEMORY.md"].filter(exists2),
749
1525
  ...listFilesUnder("docs", 40)
750
1526
  ].filter((value, index, values) => values.indexOf(value) === index);
751
1527
  const releaseTags = scan.git.detected ? readBootstrapReleaseTags() : [];
@@ -774,7 +1550,7 @@ function buildProjectBootstrap(scan) {
774
1550
  );
775
1551
  }
776
1552
  const workspaceEvidence = ["pnpm-workspace.yaml", "turbo.json", "nx.json", "lerna.json"].filter(
777
- exists
1553
+ exists2
778
1554
  );
779
1555
  if (packageMetadata.workspaces || workspaceEvidence.length > 0) {
780
1556
  architecture.push(
@@ -790,7 +1566,7 @@ function buildProjectBootstrap(scan) {
790
1566
  )
791
1567
  );
792
1568
  }
793
- for (const directory of ["apps", "packages", "services", "libs"].filter(exists)) {
1569
+ for (const directory of ["apps", "packages", "services", "libs"].filter(exists2)) {
794
1570
  architecture.push(
795
1571
  createBootstrapItem(
796
1572
  "architecture",
@@ -871,7 +1647,7 @@ function buildProjectBootstrap(scan) {
871
1647
  procedures.push(
872
1648
  createBootstrapItem(
873
1649
  "procedures",
874
- `CI workflow: ${path.basename(source)}`,
1650
+ `CI workflow: ${path4.basename(source)}`,
875
1651
  "observed",
876
1652
  "Automation configuration detected; individual jobs were not interpreted during the bounded scan.",
877
1653
  [{ source, kind: "ci_workflow" }]
@@ -881,7 +1657,7 @@ function buildProjectBootstrap(scan) {
881
1657
  const skills = skillFiles.slice(0, 12).map(
882
1658
  (source) => createBootstrapItem(
883
1659
  "skills",
884
- `Existing skill: ${path.basename(path.dirname(source)) || "root"}`,
1660
+ `Existing skill: ${path4.basename(path4.dirname(source)) || "root"}`,
885
1661
  "observed",
886
1662
  "An explicit SKILL.md workflow was found.",
887
1663
  [{ source, kind: "skill_definition" }]
@@ -1048,11 +1824,11 @@ function buildProjectBootstrap(scan) {
1048
1824
  testFiles.slice(0, 20)
1049
1825
  ),
1050
1826
  releaseHistory: createBootstrapCoverage(
1051
- releaseTags.length > 0 || exists("CHANGELOG.md") ? "partial" : "unavailable",
1052
- releaseTags.length > 0 || exists("CHANGELOG.md") ? `Detected ${releaseTags.length} recent tag(s)${exists("CHANGELOG.md") ? " and CHANGELOG.md" : ""}.` : "No Git tags or root CHANGELOG.md were detected.",
1827
+ releaseTags.length > 0 || exists2("CHANGELOG.md") ? "partial" : "unavailable",
1828
+ releaseTags.length > 0 || exists2("CHANGELOG.md") ? `Detected ${releaseTags.length} recent tag(s)${exists2("CHANGELOG.md") ? " and CHANGELOG.md" : ""}.` : "No Git tags or root CHANGELOG.md were detected.",
1053
1829
  [
1054
1830
  ...releaseTags.map((tag) => `git tag ${tag}`),
1055
- ...exists("CHANGELOG.md") ? ["CHANGELOG.md"] : []
1831
+ ...exists2("CHANGELOG.md") ? ["CHANGELOG.md"] : []
1056
1832
  ]
1057
1833
  ),
1058
1834
  codeGraph: createBootstrapCoverage(
@@ -1079,8 +1855,8 @@ function createBootstrapCoverage(status, detail, sources) {
1079
1855
  return { status, detail, sources };
1080
1856
  }
1081
1857
  function writeProjectBootstrap(report) {
1082
- fs.mkdirSync(getActivationDir(), { recursive: true });
1083
- fs.writeFileSync(getProjectBootstrapJsonPath(), JSON.stringify(report, null, 2) + "\n", "utf8");
1858
+ fs4.mkdirSync(getActivationDir(), { recursive: true });
1859
+ fs4.writeFileSync(getProjectBootstrapJsonPath(), JSON.stringify(report, null, 2) + "\n", "utf8");
1084
1860
  const lines = [
1085
1861
  "# PROJECT BOOTSTRAP",
1086
1862
  "",
@@ -1110,7 +1886,7 @@ function writeProjectBootstrap(report) {
1110
1886
  `**${report.reviewPrompt}**`,
1111
1887
  ""
1112
1888
  ];
1113
- fs.writeFileSync(getProjectBootstrapMarkdownPath(), lines.join("\n"), "utf8");
1889
+ fs4.writeFileSync(getProjectBootstrapMarkdownPath(), lines.join("\n"), "utf8");
1114
1890
  }
1115
1891
  function addProjectBootstrapToFirstWorkBrief(manifest, report) {
1116
1892
  manifest.firstBrief.sections.splice(1, 0, {
@@ -1200,6 +1976,7 @@ function createActivationManifest(answers, detection, mode = "default", scan) {
1200
1976
  const surfaceContract = buildActivationSurfaceContract(answers.aiClient);
1201
1977
  return {
1202
1978
  activationId: buildActivationId(),
1979
+ profile: answers.profile,
1203
1980
  mode,
1204
1981
  deployment: answers.deployment,
1205
1982
  serverUrl: answers.serverUrl,
@@ -1283,8 +2060,8 @@ function createActivationManifest(answers, detection, mode = "default", scan) {
1283
2060
  )
1284
2061
  },
1285
2062
  artifacts: {
1286
- activationManifestPath: path.relative(process.cwd(), getStableActivationManifestPath()),
1287
- legacyManifestPath: path.relative(process.cwd(), getActivationManifestPath()),
2063
+ activationManifestPath: path4.relative(process.cwd(), getStableActivationManifestPath()),
2064
+ legacyManifestPath: path4.relative(process.cwd(), getActivationManifestPath()),
1288
2065
  firstBriefPath: null,
1289
2066
  handoffPath: null,
1290
2067
  memoryCandidatesPath: null,
@@ -1302,7 +2079,8 @@ function runLocalSourceActivation(answers, manifest) {
1302
2079
  updateActivationManifest(manifest);
1303
2080
  return;
1304
2081
  }
1305
- const companionCommand = detectCompanionCommand();
2082
+ const managed = answers.profile === "local-work" ? answers.managedToolchain ?? managedToolchainStatus().manifest : null;
2083
+ const companionCommand = managed?.components.companion.command ?? detectCompanionCommand();
1306
2084
  if (!companionCommand) {
1307
2085
  const detail = `${COMPANION_COMMAND_NAME} ${MIN_COMPANION_VERSION}+ is not on PATH after setup.`;
1308
2086
  manifest.lanes.localSnapshot = createActivationLane("skipped", detail);
@@ -1315,12 +2093,14 @@ function runLocalSourceActivation(answers, manifest) {
1315
2093
  const syncCommand = `${companionCommand} source sync --json`;
1316
2094
  const spinner = ora("Activating local Project Intelligence").start();
1317
2095
  try {
1318
- execSync(initCommand, {
2096
+ const runOptions = {
1319
2097
  cwd: process.cwd(),
1320
- encoding: "utf-8",
2098
+ encoding: "utf8",
1321
2099
  stdio: ["ignore", "pipe", "pipe"],
1322
2100
  timeout: 3e4
1323
- });
2101
+ };
2102
+ if (managed) execFileSync3(process.execPath, [companionCommand, "source", "init", "."], runOptions);
2103
+ else execSync(initCommand, runOptions);
1324
2104
  manifest.lanes.localSnapshot = createActivationLane(
1325
2105
  "ready",
1326
2106
  "Local source snapshot created.",
@@ -1337,12 +2117,14 @@ function runLocalSourceActivation(answers, manifest) {
1337
2117
  return;
1338
2118
  }
1339
2119
  try {
1340
- execSync(syncCommand, {
2120
+ const runOptions = {
1341
2121
  cwd: process.cwd(),
1342
- encoding: "utf-8",
2122
+ encoding: "utf8",
1343
2123
  stdio: ["ignore", "pipe", "pipe"],
1344
2124
  timeout: 45e3
1345
- });
2125
+ };
2126
+ if (managed) execFileSync3(process.execPath, [companionCommand, "source", "sync", "--json"], runOptions);
2127
+ else execSync(syncCommand, runOptions);
1346
2128
  manifest.lanes.contextIndex = createActivationLane(
1347
2129
  "indexing",
1348
2130
  "Local documents submitted for context indexing.",
@@ -1564,11 +2346,11 @@ function extractMcpText(data) {
1564
2346
  function createActivationArtifacts(answers, manifest) {
1565
2347
  const projectBootstrap = buildProjectBootstrap(manifest.localScan);
1566
2348
  writeProjectBootstrap(projectBootstrap);
1567
- manifest.artifacts.projectBootstrapJsonPath = path.relative(
2349
+ manifest.artifacts.projectBootstrapJsonPath = path4.relative(
1568
2350
  process.cwd(),
1569
2351
  getProjectBootstrapJsonPath()
1570
2352
  );
1571
- manifest.artifacts.projectBootstrapMarkdownPath = path.relative(
2353
+ manifest.artifacts.projectBootstrapMarkdownPath = path4.relative(
1572
2354
  process.cwd(),
1573
2355
  getProjectBootstrapMarkdownPath()
1574
2356
  );
@@ -1580,21 +2362,21 @@ function createActivationArtifacts(answers, manifest) {
1580
2362
  addProjectBootstrapToFirstWorkBrief(manifest, projectBootstrap);
1581
2363
  const candidates = buildMemoryCandidatePreviews(manifest.localScan);
1582
2364
  writeMemoryCandidates(candidates);
1583
- manifest.artifacts.memoryCandidatesPath = path.relative(process.cwd(), getMemoryCandidatesPath());
2365
+ manifest.artifacts.memoryCandidatesPath = path4.relative(process.cwd(), getMemoryCandidatesPath());
1584
2366
  manifest.lanes.memoryCandidates = createActivationLane(
1585
2367
  candidates.length > 0 ? "needs_review" : "skipped",
1586
2368
  candidates.length > 0 ? `${candidates.length} memory candidates prepared for review; none were imported.` : "No memory candidates found in local docs.",
1587
2369
  manifest.artifacts.memoryCandidatesPath
1588
2370
  );
1589
2371
  writeInitialHandoff(answers, manifest);
1590
- manifest.artifacts.handoffPath = path.relative(process.cwd(), getActivationHandoffPath());
2372
+ manifest.artifacts.handoffPath = path4.relative(process.cwd(), getActivationHandoffPath());
1591
2373
  manifest.lanes.handoff = createActivationLane(
1592
2374
  "ready",
1593
2375
  "Initial local handoff created.",
1594
2376
  manifest.artifacts.handoffPath
1595
2377
  );
1596
2378
  writeFirstWorkBrief(manifest);
1597
- manifest.artifacts.firstBriefPath = path.relative(process.cwd(), getFirstWorkBriefPath());
2379
+ manifest.artifacts.firstBriefPath = path4.relative(process.cwd(), getFirstWorkBriefPath());
1598
2380
  manifest.lanes.firstBrief = createActivationLane(
1599
2381
  "ready",
1600
2382
  "First Work Brief generated from local setup and scan signals.",
@@ -1683,61 +2465,71 @@ function shouldInstallDefaultHooks(aiClient) {
1683
2465
  return aiClient === "claude-code" || aiClient === "codex";
1684
2466
  }
1685
2467
  function buildProgram() {
1686
- const program = new Command();
1687
- program.name("create-snipara").description("Connect Snipara Cloud or a self-hosted Snipara Server with a fast proof of value").version(VERSION);
1688
- program.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
2468
+ const program2 = new Command();
2469
+ program2.name("create-snipara").description("Connect Snipara Cloud or a self-hosted Snipara Server with a fast proof of value").version(VERSION);
2470
+ const toolchain = program2.command("toolchain").description("Prepare or inspect private local-work components");
2471
+ toolchain.command("prepare").description("Prepare the compatible local-work bundle and switch only after checks").action(async () => {
2472
+ console.log(JSON.stringify(await prepareManagedToolchain(), null, 2));
2473
+ });
2474
+ toolchain.command("status").description("Inspect the active bundle without installing or activating").action(() => {
2475
+ console.log(JSON.stringify(managedToolchainStatus(), null, 2));
2476
+ });
2477
+ toolchain.command("rollback").description("Select the previous valid bundle; retain task state and evidence").action(async () => {
2478
+ console.log(JSON.stringify(await rollbackManagedToolchain(), null, 2));
2479
+ });
2480
+ program2.command("init").description("Install and configure Snipara for this project").option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
1689
2481
  "--client <client>",
1690
2482
  "AI client: claude-code, cursor, kimi, codex, gemini, mistral, chatgpt, vscode, continue, claude-desktop, other"
1691
- ).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
2483
+ ).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option("--connect-context", "Optionally connect project context during local-work setup").option(
1692
2484
  "--profile <profile>",
1693
- "Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
2485
+ "Install profile: hosted-only (Context), local-work, hosted-companion, full-stack, runtime-only"
1694
2486
  ).option("--deployment <deployment>", "Deployment: hosted or self-hosted").option("--self-hosted", "Use an existing self-hosted Snipara MCP server").option("--server-url <url>", "Self-hosted MCP endpoint URL").option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip snipara-sandbox installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip MCP connection and value-proof checks").option("--with-orchestrator", "Install optional snipara-orchestrator package").option("--github", "Open GitHub repository automation setup when a git remote is detected").option("--skip-github", "Skip the GitHub automation prompt").option("--github-path <path>", "Optional GitHub path filter for the initial repository sync").option("--starter", "Mark this run as a starter activation and print dashboard-first guidance").option("--json", "Print a machine-readable activation summary after setup").option("--runtime-only", "Install only Snipara Sandbox").addOption(new Option("--full", "Legacy alias for --profile full-stack").hideHelp()).option("-y, --yes", "Accept defaults without prompting").action(async (options) => {
1695
2487
  printBanner();
1696
2488
  await runInit(options);
1697
2489
  });
1698
- program.command("doctor").description("Inspect the current project and write a validation report").option(
2490
+ program2.command("doctor").description("Inspect the current project and write a validation report").option(
1699
2491
  "--client <client>",
1700
2492
  "Run client-specific diagnostics: claude, claude-code, claude-desktop, cursor, codex, etc."
1701
2493
  ).option("--skip-npm-check", "Skip npm latest-version freshness check").option("--json", "Print the report as JSON").action(async (options) => {
1702
2494
  printBanner();
1703
2495
  await runDoctor(options);
1704
2496
  });
1705
- program.command("repair").description("Rebuild local Snipara config and re-run validation").option("--api-key <key>", "Snipara API key override").option("--slug <slug>", "Project slug override").option("--project-id <id>", "Project ID override").option("--profile <profile>", "Target install profile").option("--deployment <deployment>", "Deployment: hosted or self-hosted").option("--self-hosted", "Use an existing self-hosted Snipara MCP server").option("--server-url <url>", "Self-hosted MCP endpoint URL").option("--with-hooks", "Force hook generation").option("--with-runtime", "Force Snipara Sandbox server/config").option("--with-mcp", "Force MCP config").option("--with-companion", "Force companion pack generation").option("--with-orchestrator", "Install optional snipara-orchestrator package").action(async (options) => {
2497
+ program2.command("repair").description("Rebuild local Snipara config and re-run validation").option("--api-key <key>", "Snipara API key override").option("--slug <slug>", "Project slug override").option("--project-id <id>", "Project ID override").option("--profile <profile>", "Target install profile").option("--deployment <deployment>", "Deployment: hosted or self-hosted").option("--self-hosted", "Use an existing self-hosted Snipara MCP server").option("--server-url <url>", "Self-hosted MCP endpoint URL").option("--with-hooks", "Force hook generation").option("--with-runtime", "Force Snipara Sandbox server/config").option("--with-mcp", "Force MCP config").option("--with-companion", "Force companion pack generation").option("--with-orchestrator", "Install optional snipara-orchestrator package").action(async (options) => {
1706
2498
  printBanner();
1707
2499
  await runRepair(options);
1708
2500
  });
1709
- program.command("demo").description("Open the guided Snipara activation demo without changing this repository").option("--no-open", "Print demo links without opening a browser").action((options) => {
2501
+ program2.command("demo").description("Open the guided Snipara activation demo without changing this repository").option("--no-open", "Print demo links without opening a browser").action((options) => {
1710
2502
  printBanner();
1711
2503
  runDemo(options);
1712
2504
  });
1713
- program.command("upgrade").description("Upgrade installed Snipara local components").option("--slug <slug>", "Project slug override").option("--with-hooks", "Refresh hooks after upgrade").option("--with-runtime", "Upgrade Snipara Sandbox even if not detected").option("--with-mcp", "Upgrade snipara-mcp even if not detected").option("--with-companion", "Upgrade companion even if not detected").option("--with-orchestrator", "Upgrade snipara-orchestrator even if not detected").action(async (options) => {
2505
+ program2.command("upgrade").description("Upgrade installed Snipara local components").option("--slug <slug>", "Project slug override").option("--with-hooks", "Refresh hooks after upgrade").option("--with-runtime", "Upgrade Snipara Sandbox even if not detected").option("--with-mcp", "Upgrade snipara-mcp even if not detected").option("--with-companion", "Upgrade companion even if not detected").option("--with-orchestrator", "Upgrade snipara-orchestrator even if not detected").action(async (options) => {
1714
2506
  printBanner();
1715
2507
  await runUpgrade(options);
1716
2508
  });
1717
- program.command("print-config").description("Print detected local setup and inferred install profile").option("--json", "Print JSON instead of a formatted summary").action(async (options) => {
2509
+ program2.command("print-config").description("Print detected local setup and inferred install profile").option("--json", "Print JSON instead of a formatted summary").action(async (options) => {
1718
2510
  printBanner();
1719
2511
  await runPrintConfig(options);
1720
2512
  });
1721
- addSharedRootOptions(program);
1722
- program.action(async (options) => {
2513
+ addSharedRootOptions(program2);
2514
+ program2.action(async (options) => {
1723
2515
  printBanner();
1724
2516
  await runInit(options);
1725
2517
  });
1726
- return program;
2518
+ return program2;
1727
2519
  }
1728
- function addSharedRootOptions(program) {
1729
- program.option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
2520
+ function addSharedRootOptions(program2) {
2521
+ program2.option("--api-key <key>", "Snipara API key").option("--slug <slug>", "Project slug").option("--project-id <id>", "Project ID").option(
1730
2522
  "--client <client>",
1731
2523
  "AI client: claude-code, cursor, kimi, codex, gemini, mistral, chatgpt, vscode, continue, claude-desktop, other"
1732
- ).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option(
2524
+ ).option("--advanced", "Show advanced profile, project id, hooks, GitHub, and sandbox prompts").option("--connect-context", "Optionally connect project context during local-work setup").option(
1733
2525
  "--profile <profile>",
1734
- "Install profile: hosted-only, hosted-companion, full-stack, runtime-only"
2526
+ "Install profile: hosted-only (Context), local-work, hosted-companion, full-stack, runtime-only"
1735
2527
  ).option("--deployment <deployment>", "Deployment: hosted or self-hosted").option("--self-hosted", "Use an existing self-hosted Snipara MCP server").option("--server-url <url>", "Self-hosted MCP endpoint URL").option("--skip-companion", "Skip local companion CLI installation").option("--skip-mcp", "Skip local snipara-mcp package installation").option("--skip-runtime", "Skip snipara-sandbox installation").option("--skip-hooks", "Skip hook generation").option("--skip-test", "Skip MCP connection and value-proof checks").option("--with-orchestrator", "Install optional snipara-orchestrator package").option("--github", "Open GitHub repository automation setup when a git remote is detected").option("--skip-github", "Skip the GitHub automation prompt").option("--github-path <path>", "Optional GitHub path filter for the initial repository sync").option("--starter", "Mark this run as a starter activation and print dashboard-first guidance").option("--json", "Print a machine-readable activation summary after setup").option("--runtime-only", "Install only Snipara Sandbox").addOption(new Option("--full", "Legacy alias for --profile full-stack").hideHelp()).option("-y, --yes", "Accept defaults without prompting");
1736
2528
  }
1737
2529
  async function main() {
1738
- const program = buildProgram();
2530
+ const program2 = buildProgram();
1739
2531
  normalizeLegacyArgAliases(process.argv);
1740
- await program.parseAsync(process.argv);
2532
+ await program2.parseAsync(process.argv);
1741
2533
  }
1742
2534
  function normalizeLegacyArgAliases(argv) {
1743
2535
  for (let index = 2; index < argv.length; index += 1) {
@@ -1822,6 +2614,7 @@ function parseInitOptionsFromArgv(args) {
1822
2614
  };
1823
2615
  const booleanFlags = {
1824
2616
  "--advanced": "advanced",
2617
+ "--connect-context": "connectContext",
1825
2618
  "--skip-companion": "skipCompanion",
1826
2619
  "--skip-mcp": "skipMcp",
1827
2620
  "--skip-runtime": "skipRuntime",
@@ -1864,12 +2657,15 @@ function parseInitOptionsFromArgv(args) {
1864
2657
  async function runInit(options) {
1865
2658
  const normalizedOptions = normalizeInitOptions(options);
1866
2659
  const answers = await gatherConfiguration(normalizedOptions);
1867
- const activationDetection = detectEnvironment();
1868
- const activationScan = detectLocalActivationScan(activationDetection);
1869
- if (answers.installMcp || answers.installRuntime || answers.installOrchestrator) {
2660
+ const activationDetection = detectEnvironment(answers.profile === "local-work");
2661
+ const activationScan = managedSetupOnly(answers) ? void 0 : detectLocalActivationScan(activationDetection);
2662
+ if (answers.profile === "local-work") {
2663
+ assertManagedMcpConfig();
2664
+ answers.managedToolchain = await prepareManagedToolchain();
2665
+ } else if (answers.installMcp || answers.installRuntime || answers.installOrchestrator) {
1870
2666
  await installPythonPackages(answers, false);
1871
2667
  }
1872
- if (answers.installCompanion) {
2668
+ if (answers.installCompanion && answers.profile !== "local-work") {
1873
2669
  await installNodeCompanion(false);
1874
2670
  }
1875
2671
  if (answers.profile !== "runtime-only" || answers.installRuntime) {
@@ -1878,6 +2674,10 @@ async function runInit(options) {
1878
2674
  if (answers.aiClient === "claude-code" && answers.profile !== "runtime-only") {
1879
2675
  await createClaudeCodeConfig(answers);
1880
2676
  }
2677
+ if (managedSetupOnly(answers)) {
2678
+ await finishManagedLocalWork(answers, normalizedOptions.json);
2679
+ return;
2680
+ }
1881
2681
  await createProjectBinding(answers);
1882
2682
  await createAgentTemplates(answers);
1883
2683
  if (answers.installHooks && answers.aiClient === "claude-code") {
@@ -1965,16 +2765,23 @@ async function runDoctor(options) {
1965
2765
  async function runRepair(options) {
1966
2766
  const answers = loadExistingAnswers(options);
1967
2767
  console.log(chalk.bold("\n\u{1F6E0}\uFE0F Repairing Local Setup\n"));
1968
- if (answers.installMcp || answers.installRuntime || answers.installOrchestrator) {
2768
+ if (answers.profile === "local-work") {
2769
+ assertManagedMcpConfig();
2770
+ answers.managedToolchain = await prepareManagedToolchain({ repair: true });
2771
+ } else if (answers.installMcp || answers.installRuntime || answers.installOrchestrator) {
1969
2772
  await installPythonPackages(answers, true);
1970
2773
  }
1971
- if (answers.installCompanion) {
2774
+ if (answers.installCompanion && answers.profile !== "local-work") {
1972
2775
  await installNodeCompanion(true);
1973
2776
  }
1974
2777
  await createMcpConfig(answers);
1975
2778
  if (answers.aiClient === "claude-code" && answers.profile !== "runtime-only") {
1976
2779
  await createClaudeCodeConfig(answers);
1977
2780
  }
2781
+ if (managedSetupOnly(answers)) {
2782
+ await finishManagedLocalWork(answers);
2783
+ return;
2784
+ }
1978
2785
  await createProjectBinding(answers);
1979
2786
  await createAgentTemplates(answers);
1980
2787
  await updateEnvFiles(answers);
@@ -1989,7 +2796,7 @@ async function runRepair(options) {
1989
2796
  printDoctorReport(detectEnvironment(), report);
1990
2797
  }
1991
2798
  async function runUpgrade(options) {
1992
- const detection = detectEnvironment();
2799
+ const detection = detectEnvironment(hasLocalWorkSelection());
1993
2800
  const answers = loadExistingAnswers(options);
1994
2801
  const upgradedAnswers = {
1995
2802
  ...answers,
@@ -1998,18 +2805,25 @@ async function runUpgrade(options) {
1998
2805
  installOrchestrator: options.withOrchestrator || answers.installOrchestrator
1999
2806
  };
2000
2807
  console.log(chalk.bold("\n\u2B06\uFE0F Upgrading Local Components\n"));
2001
- if (options.withMcp || options.withRuntime || options.withOrchestrator || detection.existingConfig.hasRuntimeServer || detection.tooling.orchestratorCli) {
2808
+ if (answers.profile === "local-work") {
2809
+ assertManagedMcpConfig();
2810
+ upgradedAnswers.managedToolchain = await prepareManagedToolchain();
2811
+ } else if (options.withMcp || options.withRuntime || options.withOrchestrator || detection.existingConfig.hasRuntimeServer || detection.tooling.orchestratorCli) {
2002
2812
  await installPythonPackages(upgradedAnswers, true);
2003
2813
  }
2004
- if (options.withCompanion || detection.tooling.companionCli || answers.installCompanion) {
2814
+ if (answers.profile !== "local-work" && (options.withCompanion || detection.tooling.companionCli || answers.installCompanion)) {
2005
2815
  await installNodeCompanion(true);
2006
2816
  }
2007
- if (options.withMcp || options.withRuntime || detection.files.mcpConfig) {
2817
+ if (answers.profile === "local-work" || options.withMcp || options.withRuntime || detection.files.mcpConfig) {
2008
2818
  await createMcpConfig(upgradedAnswers);
2009
2819
  }
2010
2820
  if (upgradedAnswers.aiClient === "claude-code" && upgradedAnswers.profile !== "runtime-only") {
2011
2821
  await createClaudeCodeConfig(upgradedAnswers);
2012
2822
  }
2823
+ if (managedSetupOnly(upgradedAnswers)) {
2824
+ await finishManagedLocalWork(upgradedAnswers);
2825
+ return;
2826
+ }
2013
2827
  await createProjectBinding(upgradedAnswers);
2014
2828
  await createAgentTemplates(upgradedAnswers);
2015
2829
  await createCompanionConfig(upgradedAnswers);
@@ -2119,13 +2933,13 @@ async function gatherConfiguration(options) {
2119
2933
  }
2120
2934
  const initialProfile = resolveRequestedProfile(options);
2121
2935
  const initialDeployment = resolveRequestedDeployment(options);
2122
- const initialServerUrl = normalizeMcpEndpoint(
2936
+ const initialServerUrl = initialProfile === "local-work" && (!options.connectContext || hasBoundLocalWorkSource("local-work")) ? "http://localhost:8000/mcp/local" : normalizeMcpEndpoint(
2123
2937
  options.serverUrl || process.env.SNIPARA_MCP_URL || getExistingConfig()?.serverUrl || "http://localhost:8000/mcp/local"
2124
2938
  );
2125
2939
  const defaultProjectSlug = options.slug || detectedSlug || (initialDeployment === "self-hosted" ? extractSlugFromMcpEndpoint(initialServerUrl) || "local" : "");
2126
2940
  const optionApiKey = options.apiKey?.trim();
2127
2941
  const environmentApiKey = options.yes ? initialDeployment === "self-hosted" ? process.env.SNIPARA_LOCAL_API_KEY?.trim() || process.env.SNIPARA_API_KEY?.trim() : process.env.SNIPARA_API_KEY?.trim() : void 0;
2128
- const providedApiKey = optionApiKey || environmentApiKey;
2942
+ const providedApiKey = initialProfile === "local-work" && (!options.connectContext || hasBoundLocalWorkSource("local-work")) ? void 0 : optionApiKey || environmentApiKey;
2129
2943
  if (providedApiKey && !isValidApiKey(providedApiKey, initialDeployment)) {
2130
2944
  throw new Error(manualApiKeyValidationMessage(initialDeployment));
2131
2945
  }
@@ -2141,6 +2955,7 @@ async function gatherConfiguration(options) {
2141
2955
  apiKey: providedApiKey || "YOUR_API_KEY_HERE",
2142
2956
  apiKeyType: providedApiKey ? "api" : "none",
2143
2957
  profile: initialProfile,
2958
+ connectContext: options.connectContext,
2144
2959
  aiClient: requestedClient || detectedClient,
2145
2960
  skipCompanion: !!options.skipCompanion,
2146
2961
  skipMcp: !!options.skipMcp,
@@ -2188,27 +3003,27 @@ async function gatherConfiguration(options) {
2188
3003
  }
2189
3004
  },
2190
3005
  {
2191
- type: showAdvancedPrompts ? "select" : null,
3006
+ type: options.profile ? null : "select",
2192
3007
  name: "profile",
2193
- message: "Choose an install profile:",
2194
- initial: installProfileChoices().findIndex((choice) => choice.value === initialProfile),
2195
- choices: installProfileChoices()
3008
+ message: "How will you use Snipara?",
3009
+ initial: Math.max(0, installProfileChoices(showAdvancedPrompts).findIndex((choice) => choice.value === initialProfile)),
3010
+ choices: installProfileChoices(showAdvancedPrompts)
2196
3011
  },
2197
3012
  {
2198
- type: "text",
3013
+ type: (_, values) => (values.profile || initialProfile) === "local-work" && (!options.connectContext || hasBoundLocalWorkSource("local-work")) ? null : "text",
2199
3014
  name: "projectSlug",
2200
3015
  message: "Project slug:",
2201
3016
  initial: defaultProjectSlug,
2202
3017
  validate: (value) => value.length > 0 ? true : "Project slug is required (for example my-project)"
2203
3018
  },
2204
3019
  {
2205
- type: showAdvancedPrompts ? "text" : null,
3020
+ type: showAdvancedPrompts && (initialProfile !== "local-work" || options.connectContext) ? "text" : null,
2206
3021
  name: "projectId",
2207
3022
  message: "Project ID (optional, press Enter to skip):",
2208
3023
  initial: options.projectId || detectedProjectId || ""
2209
3024
  },
2210
3025
  {
2211
- type: (_, values) => values.profile === "runtime-only" || providedApiKey ? null : "select",
3026
+ type: (_, values) => values.profile === "runtime-only" || providedApiKey || (values.profile || initialProfile) === "local-work" && (!options.connectContext || hasBoundLocalWorkSource("local-work")) ? null : "select",
2212
3027
  name: "apiKeyType",
2213
3028
  message: (_, values) => values.deployment === "self-hosted" || initialDeployment === "self-hosted" ? "How do you want to add the self-hosted operator API key?" : "How do you want to add your Snipara API key?",
2214
3029
  choices: (_, values) => values.deployment === "self-hosted" || initialDeployment === "self-hosted" ? [
@@ -2308,13 +3123,14 @@ async function gatherConfiguration(options) {
2308
3123
  }
2309
3124
  }
2310
3125
  const answers = buildAnswersFromProfile({
2311
- projectSlug: signupResult?.projectSlug || response.projectSlug,
3126
+ projectSlug: signupResult?.projectSlug || response.projectSlug || defaultProjectSlug || "existing-source",
2312
3127
  projectId: signupResult?.projectId || response.projectId || options.projectId || "",
2313
3128
  deployment: selectedDeployment,
2314
3129
  serverUrl: selectedServerUrl,
2315
3130
  apiKey: apiKey || "",
2316
3131
  apiKeyType,
2317
3132
  profile: response.profile || initialProfile,
3133
+ connectContext: options.connectContext,
2318
3134
  aiClient: requestedClient || response.aiClient || detectedClient,
2319
3135
  skipCompanion: !!options.skipCompanion,
2320
3136
  skipMcp: !!options.skipMcp,
@@ -2327,7 +3143,7 @@ async function gatherConfiguration(options) {
2327
3143
  githubRepo: detectedGitRepo || void 0,
2328
3144
  githubPath: response.githubPath || options.githubPath
2329
3145
  });
2330
- if (answers.installRuntime) {
3146
+ if (answers.installRuntime && answers.profile !== "local-work") {
2331
3147
  const runtimeResponse = await prompts([
2332
3148
  {
2333
3149
  type: "select",
@@ -2418,7 +3234,7 @@ function getDeploymentLabel(deployment) {
2418
3234
  return deployment === "self-hosted" ? "self-hosted Snipara Server" : "Snipara Cloud";
2419
3235
  }
2420
3236
  function isInstallProfile(value) {
2421
- return ["hosted-only", "hosted-companion", "full-stack", "runtime-only"].includes(value);
3237
+ return ["hosted-only", "local-work", "hosted-companion", "full-stack", "runtime-only"].includes(value);
2422
3238
  }
2423
3239
  function normalizeClientOption(value) {
2424
3240
  if (!value) {
@@ -2457,13 +3273,21 @@ function normalizeClientOption(value) {
2457
3273
  }
2458
3274
  return client;
2459
3275
  }
2460
- function installProfileChoices() {
2461
- return [
3276
+ function installProfileChoices(advanced = true) {
3277
+ const normal = [
2462
3278
  {
2463
- title: "Hosted only",
3279
+ title: "Context",
2464
3280
  value: "hosted-only",
2465
- description: "Default: hosted MCP config, no local companion, hooks, or sandbox"
3281
+ description: "Connect hosted project knowledge to your agent"
2466
3282
  },
3283
+ {
3284
+ title: "Local work",
3285
+ value: "local-work",
3286
+ description: "Work locally; automatically reuse a connected repository's optional context"
3287
+ }
3288
+ ];
3289
+ return advanced ? [
3290
+ ...normal,
2467
3291
  {
2468
3292
  title: "Hosted + Companion",
2469
3293
  value: "hosted-companion",
@@ -2479,9 +3303,11 @@ function installProfileChoices() {
2479
3303
  value: "runtime-only",
2480
3304
  description: "Snipara Sandbox execution only, no hosted API key required"
2481
3305
  }
2482
- ];
3306
+ ] : normal;
2483
3307
  }
2484
3308
  function buildAnswersFromProfile(input) {
3309
+ if (input.profile === "local-work" && (input.skipCompanion || input.skipRuntime))
3310
+ throw new Error("local-work requires the complete managed bundle; choose hosted-only to skip local components");
2485
3311
  const defaults = {
2486
3312
  "hosted-only": {
2487
3313
  installCompanion: false,
@@ -2489,6 +3315,12 @@ function buildAnswersFromProfile(input) {
2489
3315
  installRuntime: false,
2490
3316
  installHooks: false
2491
3317
  },
3318
+ "local-work": {
3319
+ installCompanion: true,
3320
+ installMcp: false,
3321
+ installRuntime: true,
3322
+ installHooks: false
3323
+ },
2492
3324
  "hosted-companion": {
2493
3325
  installCompanion: true,
2494
3326
  installMcp: false,
@@ -2513,14 +3345,15 @@ function buildAnswersFromProfile(input) {
2513
3345
  projectSlug: input.projectSlug,
2514
3346
  projectId: input.projectId,
2515
3347
  deployment: input.deployment,
2516
- serverUrl: input.deployment === "hosted" ? `${API_URL}/mcp/${encodeURIComponent(input.projectSlug)}` : normalizeMcpEndpoint(input.serverUrl),
3348
+ serverUrl: input.profile === "local-work" && (!input.connectContext || hasBoundLocalWorkSource("local-work")) ? "" : input.deployment === "hosted" ? `${API_URL}/mcp/${encodeURIComponent(input.projectSlug)}` : normalizeMcpEndpoint(input.serverUrl),
2517
3349
  apiKey: input.apiKey || "YOUR_API_KEY_HERE",
2518
3350
  apiKeyType: input.profile === "runtime-only" ? "none" : input.apiKeyType,
2519
3351
  profile: input.profile,
3352
+ connectContext: input.profile === "local-work" ? Boolean(input.connectContext) : true,
2520
3353
  installCompanion: selected.installCompanion && !input.skipCompanion,
2521
3354
  installMcp: selected.installMcp && !input.skipMcp,
2522
3355
  installRuntime: selected.installRuntime && !input.skipRuntime,
2523
- installOrchestrator: input.withOrchestrator,
3356
+ installOrchestrator: input.profile === "local-work" || input.withOrchestrator,
2524
3357
  installHooks: selected.installHooks && !input.skipHooks,
2525
3358
  aiClient: input.aiClient,
2526
3359
  configureGitHub: Boolean(input.configureGitHub && input.githubRepo),
@@ -2686,7 +3519,7 @@ function runDemo(options) {
2686
3519
  }
2687
3520
  }
2688
3521
  function sleep(ms) {
2689
- return new Promise((resolve) => setTimeout(resolve, ms));
3522
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
2690
3523
  }
2691
3524
  function isValidApiKey(value, deployment) {
2692
3525
  const trimmed = value.trim();
@@ -2890,36 +3723,126 @@ async function installNodeCompanion(upgrade) {
2890
3723
  console.log(chalk.gray(` npm install -g ${COMPANION_PACKAGE_NAME}@latest
2891
3724
  `));
2892
3725
  }
3726
+ function assertManagedMcpConfig(file = path4.join(process.cwd(), ".mcp.json")) {
3727
+ sourceConnectionObservation = { workspace: fs4.realpathSync(process.cwd()), source: detectSourceConnection(process.cwd()) };
3728
+ if (!fs4.existsSync(file)) {
3729
+ try {
3730
+ fs4.lstatSync(file);
3731
+ throw new Error("Managed configuration cannot replace a linked path");
3732
+ } catch (error) {
3733
+ if (error.code !== "ENOENT") throw error;
3734
+ }
3735
+ return;
3736
+ }
3737
+ const info = fs4.lstatSync(file);
3738
+ if (!info.isFile() || info.isSymbolicLink() || info.size > 1e6)
3739
+ throw new Error("Existing .mcp.json is not a bounded regular configuration; preserve and repair it first");
3740
+ const value = JSON.parse(fs4.readFileSync(file, "utf8"));
3741
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Existing .mcp.json is invalid");
3742
+ const servers = value.mcpServers;
3743
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) throw new Error("Existing MCP servers are invalid");
3744
+ const native = servers["snipara-local"];
3745
+ if (native !== void 0 && !isManagedNativeServer(native))
3746
+ throw new Error("Existing snipara-local connection was preserved; choose another name before managed setup");
3747
+ }
3748
+ function isManagedNativeServer(value) {
3749
+ const server = asObjectRecord(value);
3750
+ return typeof server.command === "string" && Array.isArray(server.args) && server.args[1] === "toolchain" && server.args[2] === "mcp" && server.args[3] === "--workspace-root" && server.args.length === 5 && typeof server.args[0] === "string" && typeof server.args[4] === "string";
3751
+ }
3752
+ function validManagedBootstrap(server, manifest) {
3753
+ if (!isManagedNativeServer(server) || server.command !== process.execPath || server.args[4] !== observeSource(fs4.realpathSync(process.cwd())).root || Object.keys(server.env ?? {}).length > 0)
3754
+ return false;
3755
+ const command = server.args[0], base = path4.dirname(manifest.root);
3756
+ if (!path4.isAbsolute(command) || !command.startsWith(base + path4.sep)) return false;
3757
+ return verifyRetainedCompanionBootstrap(command, manifest);
3758
+ }
3759
+ function managedNativeServer(answers) {
3760
+ const manifest = answers.managedToolchain ?? managedToolchainStatus().manifest;
3761
+ if (!manifest) throw new Error("Prepare the local-work toolchain before configuring this workspace");
3762
+ const existing = asObjectRecord(asObjectRecord(readJsonFile(path4.join(process.cwd(), ".mcp.json"))).mcpServers)["snipara-local"];
3763
+ if (isManagedNativeServer(existing)) {
3764
+ const selected = existing;
3765
+ if (!validManagedBootstrap(selected, manifest)) throw new Error("Existing managed bootstrap differs from its retained bundle; configuration was preserved");
3766
+ return selected;
3767
+ }
3768
+ return {
3769
+ type: "stdio",
3770
+ command: process.execPath,
3771
+ args: [manifest.components.companion.command, "toolchain", "mcp", "--workspace-root", observeSource(fs4.realpathSync(process.cwd())).root]
3772
+ };
3773
+ }
3774
+ function hasLocalWorkSelection() {
3775
+ const native = asObjectRecord(asObjectRecord(readJsonFile(path4.join(process.cwd(), ".mcp.json"))).mcpServers)["snipara-local"];
3776
+ const activation = asObjectRecord(readJsonFile(getStableActivationManifestPath()));
3777
+ return isManagedNativeServer(native) || activation.profile === "local-work";
3778
+ }
3779
+ function hasBoundLocalWorkSource(profile) {
3780
+ if (profile !== "local-work") return false;
3781
+ const workspace = fs4.realpathSync(process.cwd());
3782
+ if (!sourceConnectionObservation || sourceConnectionObservation.workspace !== workspace)
3783
+ sourceConnectionObservation = { workspace, source: detectSourceConnection(workspace) };
3784
+ return sourceConnectionObservation.source !== null;
3785
+ }
3786
+ var sourceConnectionObservation = null;
3787
+ function managedSetupOnly(answers) {
3788
+ return answers.profile === "local-work" && (!answers.connectContext || hasBoundLocalWorkSource(answers.profile));
3789
+ }
3790
+ async function finishManagedLocalWork(answers, json = false) {
3791
+ sourceConnectionObservation = { workspace: fs4.realpathSync(process.cwd()), source: detectSourceConnection(process.cwd()) };
3792
+ await createAgentTemplates(answers);
3793
+ const report = await runValidation({ ...answers, apiKeyType: "none" }, false);
3794
+ writeDoctorReport(report);
3795
+ printDoctorReport(detectEnvironment(), report);
3796
+ const existingSource = hasBoundLocalWorkSource(answers.profile);
3797
+ console.log(chalk.green(existingSource ? "Local work prepared. Existing repository context is reused automatically when its connection is authorized." : "Local work prepared. An account or hosted context connection is optional."));
3798
+ if (json) console.log(JSON.stringify({
3799
+ ok: true,
3800
+ profile: "local-work",
3801
+ localWork: { prepared: true },
3802
+ context: { configured: existingSource, authorized: null, mode: existingSource ? "existing_source" : "local_only" },
3803
+ activationObserved: false,
3804
+ validation: report
3805
+ }, null, 2));
3806
+ }
2893
3807
  async function createMcpConfig(answers) {
2894
3808
  console.log(chalk.bold("\n\u2699\uFE0F Creating Configuration\n"));
2895
- const mcpConfigPath = path.join(process.cwd(), ".mcp.json");
3809
+ const mcpConfigPath = path4.join(process.cwd(), ".mcp.json");
2896
3810
  let mcpConfig = { mcpServers: {} };
2897
- if (fs.existsSync(mcpConfigPath)) {
3811
+ if (answers.profile === "local-work") assertManagedMcpConfig();
3812
+ if (fs4.existsSync(mcpConfigPath)) {
2898
3813
  try {
2899
- mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
3814
+ mcpConfig = JSON.parse(fs4.readFileSync(mcpConfigPath, "utf-8"));
2900
3815
  console.log(chalk.dim(" Found existing .mcp.json, merging..."));
2901
3816
  } catch {
3817
+ if (answers.profile === "local-work") throw new Error("Existing .mcp.json changed or became invalid; configuration preserved");
2902
3818
  console.log(chalk.yellow(" Could not parse existing .mcp.json, creating new..."));
2903
3819
  }
2904
3820
  }
2905
- if (answers.profile !== "runtime-only") {
3821
+ if (answers.profile !== "runtime-only" && answers.profile !== "local-work") {
2906
3822
  mcpConfig.mcpServers.snipara = buildHostedMcpServer(answers);
2907
3823
  }
2908
- if (answers.installRuntime) {
3824
+ if (answers.profile === "local-work") {
3825
+ mcpConfig.mcpServers = mergeManagedMcpServers(
3826
+ mcpConfig.mcpServers,
3827
+ managedNativeServer(answers),
3828
+ managedSetupOnly(answers) ? { type: "http" } : buildHostedMcpServer(answers),
3829
+ managedSetupOnly(answers)
3830
+ );
3831
+ } else if (answers.installRuntime) {
2909
3832
  mcpConfig.mcpServers[SANDBOX_SERVER_NAME] = {
2910
3833
  type: "http",
2911
3834
  url: "http://localhost:8765/mcp",
2912
3835
  headers: {}
2913
3836
  };
2914
3837
  }
2915
- fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n", { mode: 384 });
3838
+ fs4.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n", { mode: 384 });
2916
3839
  try {
2917
- fs.chmodSync(mcpConfigPath, 384);
3840
+ fs4.chmodSync(mcpConfigPath, 384);
2918
3841
  } catch {
2919
3842
  }
2920
3843
  console.log(chalk.green(" \u2713 Created .mcp.json"));
2921
3844
  ensureGitignoreEntry(".mcp.json", "# Snipara MCP config (contains API key)");
2922
- if (answers.aiClient === "kimi" && answers.profile !== "runtime-only") {
3845
+ if (answers.aiClient === "kimi" && answers.profile !== "runtime-only" && !managedSetupOnly(answers)) {
2923
3846
  createKimiMcpConfig(answers);
2924
3847
  }
2925
3848
  if (answers.aiClient === "claude-desktop") {
@@ -2927,8 +3850,8 @@ async function createMcpConfig(answers) {
2927
3850
  }
2928
3851
  }
2929
3852
  function createKimiMcpConfig(answers) {
2930
- const kimiDir = path.join(process.cwd(), ".kimi-code");
2931
- const configPath = path.join(kimiDir, "mcp.json");
3853
+ const kimiDir = path4.join(process.cwd(), ".kimi-code");
3854
+ const configPath = path4.join(kimiDir, "mcp.json");
2932
3855
  const existing = asObjectRecord(readJsonFile(configPath));
2933
3856
  const existingServers = asObjectRecord(existing.mcpServers);
2934
3857
  const config = {
@@ -2944,8 +3867,8 @@ function createKimiMcpConfig(answers) {
2944
3867
  }
2945
3868
  }
2946
3869
  };
2947
- fs.mkdirSync(kimiDir, { recursive: true });
2948
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
3870
+ fs4.mkdirSync(kimiDir, { recursive: true });
3871
+ fs4.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
2949
3872
  console.log(
2950
3873
  chalk.green(" \u2713 Created .kimi-code/mcp.json") + chalk.dim(" - SNIPARA_API_KEY stays in the launch environment")
2951
3874
  );
@@ -2970,13 +3893,13 @@ function normalizeCorrelationSessionId(value) {
2970
3893
  }
2971
3894
  function readExistingCorrelationSessionId() {
2972
3895
  const companionConfig = readCompanionConfigFile(
2973
- path.join(process.cwd(), ".snipara", "companion", "config.json")
3896
+ path4.join(process.cwd(), ".snipara", "companion", "config.json")
2974
3897
  );
2975
- const projectMcpConfig = asObjectRecord(readJsonFile(path.join(process.cwd(), ".mcp.json")));
3898
+ const projectMcpConfig = asObjectRecord(readJsonFile(path4.join(process.cwd(), ".mcp.json")));
2976
3899
  const projectMcpServer = asObjectRecord(asObjectRecord(projectMcpConfig.mcpServers).snipara);
2977
3900
  const projectMcpHeaders = asObjectRecord(projectMcpServer.headers);
2978
3901
  const kimiConfig = asObjectRecord(
2979
- readJsonFile(path.join(process.cwd(), ".kimi-code", "mcp.json"))
3902
+ readJsonFile(path4.join(process.cwd(), ".kimi-code", "mcp.json"))
2980
3903
  );
2981
3904
  const kimiServer = asObjectRecord(asObjectRecord(kimiConfig.mcpServers).snipara);
2982
3905
  const kimiHeaders = asObjectRecord(kimiServer.headers);
@@ -2984,7 +3907,7 @@ function readExistingCorrelationSessionId() {
2984
3907
  }
2985
3908
  function getCorrelationSessionId(answers) {
2986
3909
  const existing = normalizeCorrelationSessionId(answers.correlationSessionId) || readExistingCorrelationSessionId();
2987
- const sessionId = existing || `sess_${Date.now()}_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
3910
+ const sessionId = existing || `sess_${Date.now()}_${randomUUID3().replace(/-/g, "").slice(0, 12)}`;
2988
3911
  answers.correlationSessionId = sessionId;
2989
3912
  return sessionId;
2990
3913
  }
@@ -2995,6 +3918,10 @@ async function createClaudeCodeConfig(answers) {
2995
3918
  if (answers.profile === "runtime-only") {
2996
3919
  return;
2997
3920
  }
3921
+ if (managedSetupOnly(answers)) {
3922
+ console.log(chalk.green(" Local connection prepared in .mcp.json; reload and approve it in Claude Code."));
3923
+ return;
3924
+ }
2998
3925
  console.log(chalk.bold("\n\u{1F916} Claude Code MCP\n"));
2999
3926
  console.log(
3000
3927
  chalk.green(
@@ -3013,112 +3940,163 @@ async function createProjectBinding(answers) {
3013
3940
  if (answers.profile === "runtime-only") {
3014
3941
  return;
3015
3942
  }
3016
- const sniparaDir = path.join(process.cwd(), ".snipara");
3017
- const projectBindingPath = path.join(sniparaDir, "project");
3943
+ const sniparaDir = path4.join(process.cwd(), ".snipara");
3944
+ const projectBindingPath = path4.join(sniparaDir, "project");
3018
3945
  const projectBinding = `${answers.projectSlug}
3019
3946
  `;
3020
- const action = fs.existsSync(projectBindingPath) ? "Updated" : "Created";
3021
- fs.mkdirSync(sniparaDir, { recursive: true });
3022
- fs.writeFileSync(projectBindingPath, projectBinding);
3947
+ const action = fs4.existsSync(projectBindingPath) ? "Updated" : "Created";
3948
+ fs4.mkdirSync(sniparaDir, { recursive: true });
3949
+ fs4.writeFileSync(projectBindingPath, projectBinding);
3023
3950
  console.log(chalk.green(` \u2713 ${action} .snipara/project`));
3024
3951
  }
3025
3952
  async function createAgentTemplates(answers) {
3026
3953
  if (answers.profile === "runtime-only") {
3027
3954
  return;
3028
3955
  }
3956
+ if (managedSetupOnly(answers)) {
3957
+ createManagedLocalTemplates(answers);
3958
+ return;
3959
+ }
3029
3960
  console.log(chalk.bold("\nGenerating Agent Templates\n"));
3030
- const templatesDir = path.join(process.cwd(), ".snipara", "templates");
3031
- fs.mkdirSync(templatesDir, { recursive: true });
3961
+ const templatesDir = path4.join(process.cwd(), ".snipara", "templates");
3962
+ fs4.mkdirSync(templatesDir, { recursive: true });
3032
3963
  const templates = [
3033
- { path: path.join(templatesDir, "AGENTS.md"), content: buildAgentsMdTemplate(answers) },
3034
- { path: path.join(templatesDir, "CLAUDE.md"), content: buildClaudeMdTemplate(answers) },
3964
+ { path: path4.join(templatesDir, "AGENTS.md"), content: buildAgentsMdTemplate(answers) },
3965
+ { path: path4.join(templatesDir, "CLAUDE.md"), content: buildClaudeMdTemplate(answers) },
3035
3966
  {
3036
- path: path.join(templatesDir, "cursor-snipara.mdc"),
3967
+ path: path4.join(templatesDir, "cursor-snipara.mdc"),
3037
3968
  content: buildCursorRuleTemplate(answers)
3038
3969
  },
3039
3970
  {
3040
- path: path.join(templatesDir, "codex-config.toml"),
3971
+ path: path4.join(templatesDir, "codex-config.toml"),
3041
3972
  content: buildCodexConfigTemplate(answers)
3042
3973
  },
3043
3974
  {
3044
- path: path.join(templatesDir, "chatgpt-openai-mcp.json"),
3975
+ path: path4.join(templatesDir, "chatgpt-openai-mcp.json"),
3045
3976
  content: buildChatGptMcpTemplate(answers)
3046
3977
  },
3047
3978
  {
3048
- path: path.join(templatesDir, "gemini-mcp-reference.json"),
3979
+ path: path4.join(templatesDir, "gemini-mcp-reference.json"),
3049
3980
  content: buildGenericMcpReferenceTemplate(answers, "gemini")
3050
3981
  },
3051
3982
  {
3052
- path: path.join(templatesDir, "mistral-vibe-config.toml"),
3983
+ path: path4.join(templatesDir, "mistral-vibe-config.toml"),
3053
3984
  content: buildMistralVibeConfigTemplate(answers)
3054
3985
  },
3055
3986
  {
3056
- path: path.join(templatesDir, "mistral-le-chat-mcp.json"),
3987
+ path: path4.join(templatesDir, "mistral-le-chat-mcp.json"),
3057
3988
  content: buildMistralLeChatTemplate(answers)
3058
3989
  },
3059
3990
  {
3060
- path: path.join(templatesDir, "mistral-langchain-tools.ts"),
3991
+ path: path4.join(templatesDir, "mistral-langchain-tools.ts"),
3061
3992
  content: buildMistralLangChainToolsTemplate(answers)
3062
3993
  },
3063
3994
  {
3064
- path: path.join(templatesDir, "vscode-mcp-reference.json"),
3995
+ path: path4.join(templatesDir, "vscode-mcp-reference.json"),
3065
3996
  content: buildGenericMcpReferenceTemplate(answers, "vscode")
3066
3997
  },
3067
3998
  {
3068
- path: path.join(templatesDir, "continue-mcp-reference.json"),
3999
+ path: path4.join(templatesDir, "continue-mcp-reference.json"),
3069
4000
  content: buildGenericMcpReferenceTemplate(answers, "continue")
3070
4001
  },
3071
4002
  {
3072
- path: path.join(templatesDir, "custom-mcp-reference.json"),
4003
+ path: path4.join(templatesDir, "custom-mcp-reference.json"),
3073
4004
  content: buildGenericMcpReferenceTemplate(answers, "custom")
3074
4005
  }
3075
4006
  ];
3076
4007
  for (const template of templates) {
3077
- fs.writeFileSync(template.path, template.content);
4008
+ fs4.writeFileSync(template.path, template.content);
3078
4009
  }
3079
4010
  console.log(chalk.green(" Created .snipara/templates agent snippets"));
3080
- writeAgentSkillPackTemplates(path.join(templatesDir, "skills"), answers);
4011
+ writeAgentSkillPackTemplates(path4.join(templatesDir, "skills"), answers);
3081
4012
  writeIfMissing(
3082
- path.join(process.cwd(), "AGENTS.md"),
4013
+ path4.join(process.cwd(), "AGENTS.md"),
3083
4014
  buildAgentsMdTemplate(answers),
3084
4015
  "AGENTS.md"
3085
4016
  );
3086
4017
  writeIfMissing(
3087
- path.join(process.cwd(), "CLAUDE.md"),
4018
+ path4.join(process.cwd(), "CLAUDE.md"),
3088
4019
  buildClaudeMdTemplate(answers),
3089
4020
  "CLAUDE.md"
3090
4021
  );
3091
- const cursorRulesDir = path.join(process.cwd(), ".cursor", "rules");
3092
- fs.mkdirSync(cursorRulesDir, { recursive: true });
4022
+ const cursorRulesDir = path4.join(process.cwd(), ".cursor", "rules");
4023
+ fs4.mkdirSync(cursorRulesDir, { recursive: true });
3093
4024
  writeIfMissing(
3094
- path.join(cursorRulesDir, "snipara.mdc"),
4025
+ path4.join(cursorRulesDir, "snipara.mdc"),
3095
4026
  buildCursorRuleTemplate(answers),
3096
4027
  ".cursor/rules/snipara.mdc"
3097
4028
  );
3098
- const codexDir = path.join(process.cwd(), ".codex");
3099
- fs.mkdirSync(codexDir, { recursive: true });
3100
- writeIfMissing(
3101
- path.join(codexDir, "config.toml"),
3102
- buildCodexConfigTemplate(answers),
3103
- ".codex/config.toml"
3104
- );
4029
+ const codexDir = path4.join(process.cwd(), ".codex");
4030
+ if (answers.profile === "local-work") {
4031
+ configureManagedCodex(answers);
4032
+ } else {
4033
+ fs4.mkdirSync(codexDir, { recursive: true });
4034
+ writeIfMissing(path4.join(codexDir, "config.toml"), buildCodexConfigTemplate(answers), ".codex/config.toml");
4035
+ }
3105
4036
  if (answers.aiClient === "mistral") {
3106
- const vibeDir = path.join(process.cwd(), ".vibe");
3107
- fs.mkdirSync(vibeDir, { recursive: true });
4037
+ const vibeDir = path4.join(process.cwd(), ".vibe");
4038
+ fs4.mkdirSync(vibeDir, { recursive: true });
3108
4039
  writeIfMissing(
3109
- path.join(vibeDir, "config.toml"),
4040
+ path4.join(vibeDir, "config.toml"),
3110
4041
  buildMistralVibeConfigTemplate(answers),
3111
4042
  ".vibe/config.toml"
3112
4043
  );
3113
4044
  }
3114
4045
  ensureGitignoreEntry(".snipara/companion/doctor.json", "# Snipara generated validation output");
3115
4046
  }
4047
+ function configureManagedCodex(answers) {
4048
+ const manifest = answers.managedToolchain ?? managedToolchainStatus().manifest;
4049
+ if (!manifest) throw new Error("The managed local-work bundle must be prepared before configuring Codex");
4050
+ const native = managedNativeServer(answers);
4051
+ mergeManagedCodexConfig({
4052
+ file: path4.join(process.cwd(), ".codex/config.toml"),
4053
+ python: path4.join(manifest.root, "python/bin/python"),
4054
+ server: { command: native.command, args: native.args, ...native.env ? { env: native.env } : {} },
4055
+ initialContent: buildCodexHostedConfigTemplate(answers),
4056
+ env: { PATH: path4.dirname(process.execPath) + path4.delimiter + "/usr/bin:/bin", HOME: path4.join(manifest.root, "home"), PYTHONNOUSERSITE: "1" },
4057
+ validateExisting: (server) => validManagedBootstrap({ type: "stdio", ...server }, manifest)
4058
+ });
4059
+ }
4060
+ function createManagedLocalTemplates(answers) {
4061
+ const manifest = answers.managedToolchain ?? managedToolchainStatus().manifest;
4062
+ if (!manifest) throw new Error("Prepare local work before generating its agent instructions");
4063
+ const guide = `# Snipara local work
4064
+
4065
+ Use the user's existing agent and the local Snipara task workflow. No account or
4066
+ cloud subscription is required for local tasks and deterministic validation.
4067
+ Local CLI argv prefix: ${JSON.stringify([process.execPath, manifest.components.companion.command])}.
4068
+
4069
+ Bind each task to the current repository/worktree, explicit acceptance criteria
4070
+ and scope. Reuse its existing task and recorded progress rather than creating
4071
+ duplicate work. Run relevant authorized checks through the native local
4072
+ validation connection. Saved running markers, declarations and historical
4073
+ closure do not establish observed execution or verified completion.
4074
+
4075
+ Context is optional. If an existing repository connection reports connected,
4076
+ automatically use its authorized project context. A configured or pending
4077
+ connection alone does not establish authorization. Otherwise continue with local
4078
+ files and local work. Offer context setup when requested; never infer a hosted
4079
+ project from a directory name or a global API key. Do not read credential stores,
4080
+ copy provider/API secrets or automatically upload repository documents during setup.
4081
+ `;
4082
+ const templates = path4.join(process.cwd(), ".snipara/templates");
4083
+ fs4.mkdirSync(templates, { recursive: true });
4084
+ for (const name of ["AGENTS.md", "CLAUDE.md"]) {
4085
+ fs4.writeFileSync(path4.join(templates, name), guide);
4086
+ writeIfMissing(path4.join(process.cwd(), name), guide, name);
4087
+ }
4088
+ const cursor = path4.join(process.cwd(), ".cursor/rules");
4089
+ fs4.mkdirSync(cursor, { recursive: true });
4090
+ writeIfMissing(path4.join(cursor, "snipara.mdc"), guide, ".cursor/rules/snipara.mdc");
4091
+ fs4.writeFileSync(path4.join(templates, "codex-config.toml"), buildCodexConfigTemplate(answers));
4092
+ configureManagedCodex(answers);
4093
+ }
3116
4094
  function writeIfMissing(filePath, content, label) {
3117
- if (fs.existsSync(filePath)) {
4095
+ if (fs4.existsSync(filePath)) {
3118
4096
  console.log(chalk.dim(` ${label} already exists; merge from .snipara/templates if needed`));
3119
4097
  return;
3120
4098
  }
3121
- fs.writeFileSync(filePath, content);
4099
+ fs4.writeFileSync(filePath, content);
3122
4100
  console.log(chalk.green(` Created ${label}`));
3123
4101
  }
3124
4102
  function buildMcpEndpoint(answers) {
@@ -3133,11 +4111,11 @@ function buildHostedMcpEndpoint(answers) {
3133
4111
  function writeAgentSkillPackTemplates(skillsDir, answers) {
3134
4112
  const skills = buildSniparaSkillPackTemplates(answers);
3135
4113
  for (const skill of skills) {
3136
- const skillDir = path.join(skillsDir, skill.name);
3137
- const agentsDir = path.join(skillDir, "agents");
3138
- fs.mkdirSync(agentsDir, { recursive: true });
3139
- fs.writeFileSync(path.join(skillDir, "SKILL.md"), skill.skillMd);
3140
- fs.writeFileSync(path.join(agentsDir, "openai.yaml"), buildSkillOpenAiYaml(skill, answers));
4114
+ const skillDir = path4.join(skillsDir, skill.name);
4115
+ const agentsDir = path4.join(skillDir, "agents");
4116
+ fs4.mkdirSync(agentsDir, { recursive: true });
4117
+ fs4.writeFileSync(path4.join(skillDir, "SKILL.md"), skill.skillMd);
4118
+ fs4.writeFileSync(path4.join(agentsDir, "openai.yaml"), buildSkillOpenAiYaml(skill, answers));
3141
4119
  }
3142
4120
  console.log(chalk.green(" Created .snipara/templates/skills Snipara skill pack"));
3143
4121
  }
@@ -3606,14 +4584,20 @@ ${EXPERIENCE_GOAL_SECTION}
3606
4584
  - End substantial work with \`snipara_end_of_task_commit\` when available; for managed workflows, end every phase with \`snipara-companion workflow phase-commit\` and the task with \`snipara-companion final-commit\`. Use \`snipara_remember_if_novel\` or \`snipara_remember\` only for narrow durable memories. When remembered knowledge is obsolete or replaced, use \`snipara-companion memory invalidate <memory-id>\` or \`snipara-companion memory supersede <old-memory-id> <new-memory-id>\` when companion is installed; otherwise use hosted \`snipara_memory_invalidate\` or \`snipara_memory_supersede\`. Never store secrets.
3607
4585
  `;
3608
4586
  }
3609
- function buildCodexConfigTemplate(answers) {
3610
- return `[mcp_servers.snipara]
4587
+ function buildCodexHostedConfigTemplate(answers) {
4588
+ return managedSetupOnly(answers) ? "" : `[mcp_servers.snipara]
3611
4589
  type = "streamable_http"
3612
4590
  url = "${buildHostedMcpEndpoint(answers)}"
3613
4591
  bearer_token_env_var = "SNIPARA_API_KEY"
3614
4592
  http_headers = { "X-Snipara-Session-Id" = "${getCorrelationSessionId(answers)}" }
3615
4593
  `;
3616
4594
  }
4595
+ function buildCodexConfigTemplate(answers) {
4596
+ const hosted = buildCodexHostedConfigTemplate(answers);
4597
+ if (answers.profile !== "local-work") return hosted;
4598
+ const native = managedNativeServer(answers);
4599
+ return hosted + "\n" + managedCodexSnippet({ command: native.command, args: native.args });
4600
+ }
3617
4601
  function buildChatGptMcpTemplate(answers) {
3618
4602
  return `${JSON.stringify(
3619
4603
  {
@@ -3781,11 +4765,11 @@ function buildGenericMcpReferenceTemplate(answers, client) {
3781
4765
  `;
3782
4766
  }
3783
4767
  function ensureGitignoreEntry(entry, comment) {
3784
- const gitignorePath = path.join(process.cwd(), ".gitignore");
3785
- if (fs.existsSync(gitignorePath)) {
3786
- const gitignore = fs.readFileSync(gitignorePath, "utf-8");
4768
+ const gitignorePath = path4.join(process.cwd(), ".gitignore");
4769
+ if (fs4.existsSync(gitignorePath)) {
4770
+ const gitignore = fs4.readFileSync(gitignorePath, "utf-8");
3787
4771
  if (!gitignore.includes(entry)) {
3788
- fs.appendFileSync(gitignorePath, `
4772
+ fs4.appendFileSync(gitignorePath, `
3789
4773
  ${comment}
3790
4774
  ${entry}
3791
4775
  `);
@@ -3793,27 +4777,31 @@ ${entry}
3793
4777
  }
3794
4778
  return;
3795
4779
  }
3796
- fs.writeFileSync(gitignorePath, `${comment}
4780
+ fs4.writeFileSync(gitignorePath, `${comment}
3797
4781
  ${entry}
3798
4782
  `);
3799
4783
  console.log(chalk.green(` \u2713 Created .gitignore with ${entry}`));
3800
4784
  }
3801
4785
  async function createClaudeDesktopConfig(answers) {
3802
- const configDir = process.platform === "darwin" ? path.join(homedir(), "Library", "Application Support", "Claude") : process.platform === "win32" ? path.join(homedir(), "AppData", "Roaming", "Claude") : path.join(homedir(), ".config", "claude");
3803
- const configPath = path.join(configDir, "claude_desktop_config.json");
4786
+ const configDir = process.platform === "darwin" ? path4.join(homedir2(), "Library", "Application Support", "Claude") : process.platform === "win32" ? path4.join(homedir2(), "AppData", "Roaming", "Claude") : path4.join(homedir2(), ".config", "claude");
4787
+ const configPath = path4.join(configDir, "claude_desktop_config.json");
3804
4788
  console.log(chalk.dim(`
3805
4789
  Claude Desktop config: ${configPath}`));
3806
4790
  try {
3807
- if (!fs.existsSync(configDir)) {
3808
- fs.mkdirSync(configDir, { recursive: true });
4791
+ if (answers.profile === "local-work") assertManagedMcpConfig(configPath);
4792
+ if (!fs4.existsSync(configDir)) {
4793
+ fs4.mkdirSync(configDir, { recursive: true });
3809
4794
  }
3810
4795
  let config = {};
3811
- if (fs.existsSync(configPath)) {
3812
- config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
4796
+ if (fs4.existsSync(configPath)) {
4797
+ config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
3813
4798
  }
3814
4799
  config.mcpServers = config.mcpServers || {};
3815
- config.mcpServers.snipara = buildHostedMcpServer(answers);
3816
- if (answers.installRuntime) {
4800
+ if (!(answers.profile === "local-work" && (managedSetupOnly(answers) || config.mcpServers["snipara-repository"] || asObjectRecord(asObjectRecord(readJsonFile(path4.join(process.cwd(), ".mcp.json"))).mcpServers)["snipara-repository"])))
4801
+ config.mcpServers.snipara = buildHostedMcpServer(answers);
4802
+ if (answers.profile === "local-work") {
4803
+ config.mcpServers["snipara-local"] = managedNativeServer(answers);
4804
+ } else if (answers.installRuntime) {
3817
4805
  config.mcpServers[SANDBOX_SERVER_NAME] = {
3818
4806
  command: detectSandboxCommand() || SANDBOX_COMMAND_NAME,
3819
4807
  args: ["mcp-serve"],
@@ -3823,18 +4811,19 @@ async function createClaudeDesktopConfig(answers) {
3823
4811
  }
3824
4812
  };
3825
4813
  }
3826
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
4814
+ fs4.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
3827
4815
  console.log(chalk.green(" \u2713 Updated Claude Desktop config"));
3828
4816
  } catch (error) {
4817
+ if (answers.profile === "local-work") throw error;
3829
4818
  console.log(chalk.yellow(` \u26A0\uFE0F Could not update Claude Desktop config: ${error}`));
3830
4819
  }
3831
4820
  }
3832
4821
  async function installClaudeCodeHooks(answers) {
3833
4822
  console.log(chalk.bold("\n\u{1FA9D} Installing Claude Code Hooks\n"));
3834
- const claudeDir = path.join(process.cwd(), ".claude");
3835
- const hooksDir = path.join(claudeDir, "hooks");
3836
- if (!fs.existsSync(hooksDir)) {
3837
- fs.mkdirSync(hooksDir, { recursive: true });
4823
+ const claudeDir = path4.join(process.cwd(), ".claude");
4824
+ const hooksDir = path4.join(claudeDir, "hooks");
4825
+ if (!fs4.existsSync(hooksDir)) {
4826
+ fs4.mkdirSync(hooksDir, { recursive: true });
3838
4827
  }
3839
4828
  const hooks = [
3840
4829
  {
@@ -3855,16 +4844,16 @@ async function installClaudeCodeHooks(answers) {
3855
4844
  }
3856
4845
  ];
3857
4846
  for (const hook of hooks) {
3858
- const hookPath = path.join(hooksDir, hook.name);
3859
- fs.writeFileSync(hookPath, hook.content);
3860
- fs.chmodSync(hookPath, "755");
4847
+ const hookPath = path4.join(hooksDir, hook.name);
4848
+ fs4.writeFileSync(hookPath, hook.content);
4849
+ fs4.chmodSync(hookPath, "755");
3861
4850
  console.log(chalk.green(` \u2713 Created ${hook.name}`));
3862
4851
  }
3863
- const settingsPath = path.join(claudeDir, "settings.json");
4852
+ const settingsPath = path4.join(claudeDir, "settings.json");
3864
4853
  let settings = {};
3865
- if (fs.existsSync(settingsPath)) {
4854
+ if (fs4.existsSync(settingsPath)) {
3866
4855
  try {
3867
- settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
4856
+ settings = JSON.parse(fs4.readFileSync(settingsPath, "utf-8"));
3868
4857
  } catch {
3869
4858
  settings = {};
3870
4859
  }
@@ -3928,13 +4917,13 @@ async function installClaudeCodeHooks(answers) {
3928
4917
  for (const { event, entry } of hookEntries) {
3929
4918
  const eventHooks = Array.isArray(hooksConfig[event]) ? hooksConfig[event] : [];
3930
4919
  const expectedCommand = entry.hooks[0].command;
3931
- const exists = eventHooks.some((hook) => hookConfigEntryHasCommand(hook, expectedCommand));
3932
- if (!exists) {
4920
+ const exists2 = eventHooks.some((hook) => hookConfigEntryHasCommand(hook, expectedCommand));
4921
+ if (!exists2) {
3933
4922
  eventHooks.push(entry);
3934
4923
  hooksConfig[event] = eventHooks;
3935
4924
  }
3936
4925
  }
3937
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4926
+ fs4.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
3938
4927
  console.log(chalk.green(" \u2713 Updated .claude/settings.json hooks"));
3939
4928
  }
3940
4929
  function createClaudeProjectHookCommand(scriptName) {
@@ -4074,9 +5063,9 @@ fi
4074
5063
  }
4075
5064
  async function updateEnvFiles(answers) {
4076
5065
  console.log(chalk.bold("\n\u{1F4DD} Updating Environment Files\n"));
4077
- const envExamplePath = path.join(process.cwd(), ".env.example");
4078
- const envLocalPath = path.join(process.cwd(), ".env.local");
4079
- const envPath = path.join(process.cwd(), ".env");
5066
+ const envExamplePath = path4.join(process.cwd(), ".env.example");
5067
+ const envLocalPath = path4.join(process.cwd(), ".env.local");
5068
+ const envPath = path4.join(process.cwd(), ".env");
4080
5069
  let envVars = "";
4081
5070
  let envExampleVars = "";
4082
5071
  if (answers.profile !== "runtime-only") {
@@ -4114,26 +5103,26 @@ ${llmVarName}=your_${answers.llmProvider}_api_key_here`;
4114
5103
  console.log(chalk.dim(" No environment variables to save"));
4115
5104
  return;
4116
5105
  }
4117
- if (envExampleVars && fs.existsSync(envExamplePath)) {
4118
- const content = fs.readFileSync(envExamplePath, "utf-8");
5106
+ if (envExampleVars && fs4.existsSync(envExamplePath)) {
5107
+ const content = fs4.readFileSync(envExamplePath, "utf-8");
4119
5108
  const missingLines = envExampleVars.split("\n").filter((line) => {
4120
5109
  const match = line.match(/^([A-Z0-9_]+)=/);
4121
5110
  return Boolean(match && !content.includes(`${match[1]}=`));
4122
5111
  });
4123
5112
  if (missingLines.length > 0) {
4124
- fs.appendFileSync(envExamplePath, `
5113
+ fs4.appendFileSync(envExamplePath, `
4125
5114
 
4126
5115
  ${missingLines.join("\n")}
4127
5116
  `);
4128
5117
  console.log(chalk.green(" \u2713 Updated .env.example"));
4129
5118
  }
4130
5119
  } else if (envExampleVars) {
4131
- fs.writeFileSync(envExamplePath, envExampleVars + "\n");
5120
+ fs4.writeFileSync(envExamplePath, envExampleVars + "\n");
4132
5121
  console.log(chalk.green(" \u2713 Created .env.example"));
4133
5122
  }
4134
- const targetEnvPath = fs.existsSync(envLocalPath) ? envLocalPath : envPath;
4135
- if (fs.existsSync(targetEnvPath)) {
4136
- const content = fs.readFileSync(targetEnvPath, "utf-8");
5123
+ const targetEnvPath = fs4.existsSync(envLocalPath) ? envLocalPath : envPath;
5124
+ if (fs4.existsSync(targetEnvPath)) {
5125
+ const content = fs4.readFileSync(targetEnvPath, "utf-8");
4137
5126
  const missingLines = envVars.split("\n").filter((line) => {
4138
5127
  const match = line.match(/^([A-Z0-9_]+)=/);
4139
5128
  if (!match) {
@@ -4145,26 +5134,26 @@ ${missingLines.join("\n")}
4145
5134
  return !content.includes(`${match[1]}=`);
4146
5135
  });
4147
5136
  if (missingLines.length > 0) {
4148
- fs.appendFileSync(targetEnvPath, `
5137
+ fs4.appendFileSync(targetEnvPath, `
4149
5138
 
4150
5139
  ${missingLines.join("\n")}
4151
5140
  `);
4152
- console.log(chalk.green(` \u2713 Updated ${path.basename(targetEnvPath)}`));
5141
+ console.log(chalk.green(` \u2713 Updated ${path4.basename(targetEnvPath)}`));
4153
5142
  }
4154
5143
  return;
4155
5144
  }
4156
5145
  if (!answers.apiKey.includes("YOUR_") && answers.profile !== "runtime-only" || answers.llmApiKey) {
4157
- fs.writeFileSync(targetEnvPath, envVars + "\n");
4158
- console.log(chalk.green(` \u2713 Created ${path.basename(targetEnvPath)}`));
5146
+ fs4.writeFileSync(targetEnvPath, envVars + "\n");
5147
+ console.log(chalk.green(` \u2713 Created ${path4.basename(targetEnvPath)}`));
4159
5148
  }
4160
5149
  }
4161
5150
  async function createCompanionConfig(answers) {
4162
5151
  if (!answers.installCompanion || answers.apiKeyType === "none" || !answers.apiKey || answers.apiKey.includes("YOUR_")) {
4163
5152
  return;
4164
5153
  }
4165
- const configDir = path.join(process.cwd(), ".snipara", "companion");
4166
- const configPath = path.join(configDir, "config.json");
4167
- fs.mkdirSync(configDir, { recursive: true });
5154
+ const configDir = path4.join(process.cwd(), ".snipara", "companion");
5155
+ const configPath = path4.join(configDir, "config.json");
5156
+ fs4.mkdirSync(configDir, { recursive: true });
4168
5157
  const existingWorkspaceConfig = readCompanionConfigFile(configPath);
4169
5158
  const workspaceConfig = {
4170
5159
  ...existingWorkspaceConfig,
@@ -4174,7 +5163,7 @@ async function createCompanionConfig(answers) {
4174
5163
  sessionId: getCorrelationSessionId(answers),
4175
5164
  client: getAutomationClientId(answers.aiClient)
4176
5165
  };
4177
- fs.writeFileSync(configPath, JSON.stringify(workspaceConfig, null, 2) + "\n");
5166
+ fs4.writeFileSync(configPath, JSON.stringify(workspaceConfig, null, 2) + "\n");
4178
5167
  ensureGitignoreEntry(".snipara/companion/config.json", "# Snipara local companion config");
4179
5168
  console.log(chalk.green(" \u2713 Configured local companion config"));
4180
5169
  }
@@ -4244,11 +5233,11 @@ async function syncRemoteAutomationClient(answers, options) {
4244
5233
  }
4245
5234
  }
4246
5235
  function readCompanionConfigFile(filePath) {
4247
- if (!fs.existsSync(filePath)) {
5236
+ if (!fs4.existsSync(filePath)) {
4248
5237
  return {};
4249
5238
  }
4250
5239
  try {
4251
- const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
5240
+ const parsed = JSON.parse(fs4.readFileSync(filePath, "utf-8"));
4252
5241
  return typeof parsed === "object" && parsed !== null ? parsed : {};
4253
5242
  } catch {
4254
5243
  return {};
@@ -4762,13 +5751,13 @@ ${guidance.map((line) => `- ${line}`).join("\n")}
4762
5751
  }
4763
5752
  async function createCompanionPack(answers) {
4764
5753
  console.log(chalk.bold("\n\u{1F5C2}\uFE0F Generating Companion Pack\n"));
4765
- const packDir = path.join(process.cwd(), ".snipara", "companion");
5754
+ const packDir = path4.join(process.cwd(), ".snipara", "companion");
4766
5755
  const commands = getCompanionCommands(answers);
4767
5756
  const guidance = [UNIVERSAL_CLIENT_GUIDANCE, ...getClientGuidance(answers.aiClient)];
4768
5757
  const detection = detectEnvironment();
4769
5758
  const clientPackFilename = getClientPackFilename(answers.aiClient);
4770
5759
  const deploymentLabel = getDeploymentLabel(answers.deployment);
4771
- fs.mkdirSync(packDir, { recursive: true });
5760
+ fs4.mkdirSync(packDir, { recursive: true });
4772
5761
  const readme = `# Snipara Companion Pack
4773
5762
 
4774
5763
  This project uses ${deploymentLabel} as a project intelligence layer with source-backed context, reviewed memory, and local companion workflow helpers.
@@ -4915,12 +5904,12 @@ ${item.purpose}`
4915
5904
  guidance,
4916
5905
  detection
4917
5906
  };
4918
- fs.writeFileSync(path.join(packDir, "README.md"), readme + "\n");
4919
- fs.writeFileSync(
4920
- path.join(packDir, clientPackFilename),
5907
+ fs4.writeFileSync(path4.join(packDir, "README.md"), readme + "\n");
5908
+ fs4.writeFileSync(
5909
+ path4.join(packDir, clientPackFilename),
4921
5910
  buildClientPackMarkdown(answers, commands, guidance) + "\n"
4922
5911
  );
4923
- fs.writeFileSync(path.join(packDir, "commands.json"), JSON.stringify(pack, null, 2) + "\n");
5912
+ fs4.writeFileSync(path4.join(packDir, "commands.json"), JSON.stringify(pack, null, 2) + "\n");
4924
5913
  console.log(chalk.green(" \u2713 Created .snipara/companion/README.md"));
4925
5914
  console.log(chalk.green(` \u2713 Created .snipara/companion/${clientPackFilename}`));
4926
5915
  console.log(chalk.green(" \u2713 Created .snipara/companion/commands.json"));
@@ -4934,8 +5923,8 @@ async function runSandboxInit() {
4934
5923
  `
4935
5924
  )
4936
5925
  );
4937
- return new Promise((resolve) => {
4938
- const child = spawn(sandboxCommand, ["init"], {
5926
+ return new Promise((resolve2) => {
5927
+ const child = spawn2(sandboxCommand, ["init"], {
4939
5928
  stdio: "inherit",
4940
5929
  shell: true
4941
5930
  });
@@ -4947,7 +5936,7 @@ async function runSandboxInit() {
4947
5936
  \u26A0\uFE0F ${sandboxCommand} init exited with code ${code}`));
4948
5937
  console.log(chalk.dim(` You can run '${sandboxCommand} init' manually later.`));
4949
5938
  }
4950
- resolve();
5939
+ resolve2();
4951
5940
  });
4952
5941
  child.on("error", (error) => {
4953
5942
  console.log(chalk.yellow(`
@@ -4955,7 +5944,7 @@ async function runSandboxInit() {
4955
5944
  console.log(
4956
5945
  chalk.dim(` Run '${SANDBOX_COMMAND_NAME} init' manually after installation completes.`)
4957
5946
  );
4958
- resolve();
5947
+ resolve2();
4959
5948
  });
4960
5949
  });
4961
5950
  }
@@ -4992,8 +5981,8 @@ function buildGitHubAutomationUrl(answers) {
4992
5981
  function getClaudeClientChecks(answers, detection) {
4993
5982
  const checks = [];
4994
5983
  const claudePaths = getClaudeConfigPaths();
4995
- const projectConfig = readJsonFile(path.join(process.cwd(), ".mcp.json"));
4996
- const projectSettings = readJsonFile(path.join(process.cwd(), ".claude", "settings.json"));
5984
+ const projectConfig = readJsonFile(path4.join(process.cwd(), ".mcp.json"));
5985
+ const projectSettings = readJsonFile(path4.join(process.cwd(), ".claude", "settings.json"));
4997
5986
  const codeSettings = readJsonFile(claudePaths.codeSettingsPath);
4998
5987
  const desktopConfig = readJsonFile(claudePaths.desktopConfigPath);
4999
5988
  const projectHasSnipara = containsSniparaMcpReference(projectConfig);
@@ -5047,11 +6036,11 @@ function getClaudeClientChecks(answers, detection) {
5047
6036
  return checks;
5048
6037
  }
5049
6038
  function readJsonFile(filePath) {
5050
- if (!fs.existsSync(filePath)) {
6039
+ if (!fs4.existsSync(filePath)) {
5051
6040
  return null;
5052
6041
  }
5053
6042
  try {
5054
- return JSON.parse(fs.readFileSync(filePath, "utf-8"));
6043
+ return JSON.parse(fs4.readFileSync(filePath, "utf-8"));
5055
6044
  } catch {
5056
6045
  return null;
5057
6046
  }
@@ -5144,13 +6133,35 @@ function compareSemver(left, right) {
5144
6133
  async function runValidation(answers, includeHostedCheck, options = {}) {
5145
6134
  console.log(chalk.bold("\n\u{1FA7A} Running Validation\n"));
5146
6135
  const checks = [];
5147
- const detection = detectEnvironment();
6136
+ const detection = detectEnvironment(answers.profile === "local-work");
6137
+ if (answers.profile === "local-work") {
6138
+ try {
6139
+ const managed = managedToolchainStatus();
6140
+ if (!managed.manifest) throw new Error("Managed local-work bundle is absent");
6141
+ answers.managedToolchain = managed.manifest;
6142
+ detection.tooling.companionCli = true;
6143
+ detection.tooling.runtimeCli = true;
6144
+ detection.tooling.orchestratorCli = true;
6145
+ checks.push({ name: "managed_toolchain", status: "pass", detail: `Private compatible bundle ${managed.manifest.bundleId}; installation is not project activation` });
6146
+ const native = asObjectRecord(asObjectRecord(readJsonFile(path4.join(process.cwd(), ".mcp.json"))).mcpServers)["snipara-local"];
6147
+ checks.push({
6148
+ name: "managed_native_config",
6149
+ status: isManagedNativeServer(native) && validManagedBootstrap(native, managed.manifest) ? "pass" : "fail",
6150
+ detail: "Native launcher configuration is checked against retained owned bundles; live client connectivity is separate"
6151
+ });
6152
+ } catch {
6153
+ detection.tooling.companionCli = false;
6154
+ detection.tooling.runtimeCli = false;
6155
+ detection.tooling.orchestratorCli = false;
6156
+ checks.push({ name: "managed_toolchain", status: "fail", detail: "Managed bundle is absent, changed or incompatible; existing state was preserved" });
6157
+ }
6158
+ }
5148
6159
  const targetClient = options.client || answers.aiClient;
5149
6160
  const effectiveAnswers = { ...answers, aiClient: targetClient };
5150
6161
  checks.push({
5151
6162
  name: "project_slug",
5152
- status: answers.projectSlug ? "pass" : "fail",
5153
- detail: answers.projectSlug || "Project slug missing"
6163
+ status: managedSetupOnly(answers) || answers.projectSlug ? "pass" : "fail",
6164
+ detail: managedSetupOnly(answers) ? "A hosted project slug is optional for local work" : answers.projectSlug || "Project slug missing"
5154
6165
  });
5155
6166
  const configuredSlugSignals = collectConfiguredProjectSlugSignals(detection.existingConfig);
5156
6167
  const presentSlugSignals = configuredSlugSignals.filter((signal) => signal.slug);
@@ -5175,7 +6186,7 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
5175
6186
  checks.push({
5176
6187
  name: "companion_cli",
5177
6188
  status: answers.installCompanion ? detection.tooling.companionCli ? "pass" : "fail" : "pass",
5178
- detail: answers.installCompanion && !detection.tooling.companionCli ? getCompanionRequirementMessage() : "Companion CLI ready or not requested"
6189
+ detail: answers.installCompanion && !detection.tooling.companionCli ? answers.profile === "local-work" ? "Managed Companion unavailable; repair local-work" : getCompanionRequirementMessage() : "Companion CLI ready or not requested"
5179
6190
  });
5180
6191
  checks.push({
5181
6192
  name: "sandbox_cli",
@@ -5218,10 +6229,12 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
5218
6229
  status: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeProjectSettings ? "pass" : "warn" : "pass",
5219
6230
  detail: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeProjectSettings ? "Project .claude/settings.json hooks present" : "Project .claude/settings.json hooks missing" : "Hooks not requested"
5220
6231
  });
5221
- if (targetClient === "claude-code" || targetClient === "claude-desktop") {
6232
+ if (managedSetupOnly(answers)) {
6233
+ checks.push({ name: "repository_context", status: "pass", detail: hasBoundLocalWorkSource(answers.profile) ? "Existing source connection preserved; authorization is observed by its native broker" : "Optional project context is not configured; local work needs no account" });
6234
+ } else if (targetClient === "claude-code" || targetClient === "claude-desktop") {
5222
6235
  checks.push(...getClaudeClientChecks(effectiveAnswers, detection));
5223
6236
  }
5224
- if (options.includeNpmFreshness) {
6237
+ if (options.includeNpmFreshness && !managedSetupOnly(answers)) {
5225
6238
  checks.push(getCreateSniparaPackageFreshnessCheck());
5226
6239
  }
5227
6240
  const githubRepo = answers.githubRepo || detectGitRepository();
@@ -5230,7 +6243,9 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
5230
6243
  status: answers.configureGitHub ? "pass" : githubRepo ? "warn" : "pass",
5231
6244
  detail: answers.configureGitHub ? `GitHub App setup link generated for ${githubRepo?.fullName || "detected repo"}; PR Answer Packs are enabled after browser approval` : githubRepo ? `GitHub repo ${githubRepo.fullName} detected; run ${CREATE_SNIPARA_NPX_COMMAND} --github to connect PR Answer Packs` : "No GitHub repo detected; PR Answer Packs require the GitHub App setup path"
5232
6245
  });
5233
- if (includeHostedCheck && answers.apiKeyType !== "none" && !answers.apiKey.includes("YOUR_")) {
6246
+ if (includeHostedCheck && managedSetupOnly(answers)) {
6247
+ checks.push({ name: "hosted_connection", status: "pass", detail: hasBoundLocalWorkSource(answers.profile) ? "Existing native broker automatically reuses authorized project context; authorization was not probed by setup" : "No hosted connection is required for local work" });
6248
+ } else if (includeHostedCheck && answers.apiKeyType !== "none" && !answers.apiKey.includes("YOUR_")) {
5234
6249
  checks.push(...await testHostedConnection(answers));
5235
6250
  } else if (includeHostedCheck) {
5236
6251
  checks.push({
@@ -5250,29 +6265,34 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
5250
6265
  };
5251
6266
  }
5252
6267
  function writeDoctorReport(report) {
5253
- const reportDir = path.join(process.cwd(), ".snipara", "companion");
5254
- fs.mkdirSync(reportDir, { recursive: true });
5255
- fs.writeFileSync(path.join(reportDir, "doctor.json"), JSON.stringify(report, null, 2) + "\n");
6268
+ const reportDir = path4.join(process.cwd(), ".snipara", "companion");
6269
+ fs4.mkdirSync(reportDir, { recursive: true });
6270
+ fs4.writeFileSync(path4.join(reportDir, "doctor.json"), JSON.stringify(report, null, 2) + "\n");
5256
6271
  console.log(chalk.green(" \u2713 Wrote .snipara/companion/doctor.json"));
5257
6272
  }
5258
6273
  function getCodexConfigCheck(answers) {
6274
+ if (managedSetupOnly(answers)) return {
6275
+ name: "codex_config",
6276
+ status: "warn",
6277
+ detail: "Managed local launcher is configured; restart Codex and observe connection status. Optional context authorization remains separate"
6278
+ };
5259
6279
  const candidates = [
5260
6280
  {
5261
6281
  label: ".codex/config.toml",
5262
- filePath: path.join(process.cwd(), ".codex", "config.toml")
6282
+ filePath: path4.join(process.cwd(), ".codex", "config.toml")
5263
6283
  },
5264
6284
  {
5265
6285
  label: ".snipara/templates/codex-config.toml",
5266
- filePath: path.join(process.cwd(), ".snipara", "templates", "codex-config.toml")
6286
+ filePath: path4.join(process.cwd(), ".snipara", "templates", "codex-config.toml")
5267
6287
  },
5268
6288
  {
5269
6289
  label: "~/.codex/config.toml",
5270
- filePath: path.join(homedir(), ".codex", "config.toml")
6290
+ filePath: path4.join(homedir2(), ".codex", "config.toml")
5271
6291
  }
5272
6292
  ];
5273
- const present = candidates.filter((candidate) => fs.existsSync(candidate.filePath)).map((candidate) => ({
6293
+ const present = candidates.filter((candidate) => fs4.existsSync(candidate.filePath)).map((candidate) => ({
5274
6294
  label: candidate.label,
5275
- content: fs.readFileSync(candidate.filePath, "utf-8")
6295
+ content: fs4.readFileSync(candidate.filePath, "utf-8")
5276
6296
  }));
5277
6297
  if (present.length === 0) {
5278
6298
  return {
@@ -5324,20 +6344,20 @@ function getMistralConfigCheck(answers) {
5324
6344
  const candidates = [
5325
6345
  {
5326
6346
  label: ".vibe/config.toml",
5327
- filePath: path.join(process.cwd(), ".vibe", "config.toml")
6347
+ filePath: path4.join(process.cwd(), ".vibe", "config.toml")
5328
6348
  },
5329
6349
  {
5330
6350
  label: ".snipara/templates/mistral-vibe-config.toml",
5331
- filePath: path.join(process.cwd(), ".snipara", "templates", "mistral-vibe-config.toml")
6351
+ filePath: path4.join(process.cwd(), ".snipara", "templates", "mistral-vibe-config.toml")
5332
6352
  },
5333
6353
  {
5334
6354
  label: "~/.vibe/config.toml",
5335
- filePath: path.join(homedir(), ".vibe", "config.toml")
6355
+ filePath: path4.join(homedir2(), ".vibe", "config.toml")
5336
6356
  }
5337
6357
  ];
5338
- const present = candidates.filter((candidate) => fs.existsSync(candidate.filePath)).map((candidate) => ({
6358
+ const present = candidates.filter((candidate) => fs4.existsSync(candidate.filePath)).map((candidate) => ({
5339
6359
  label: candidate.label,
5340
- content: fs.readFileSync(candidate.filePath, "utf-8")
6360
+ content: fs4.readFileSync(candidate.filePath, "utf-8")
5341
6361
  }));
5342
6362
  if (present.length === 0) {
5343
6363
  return {
@@ -5601,7 +6621,7 @@ function printActivationSummary(manifest) {
5601
6621
  console.log(
5602
6622
  chalk.dim(
5603
6623
  `
5604
- Activation manifest: ${path.relative(process.cwd(), getActivationManifestPath())}`
6624
+ Activation manifest: ${path4.relative(process.cwd(), getActivationManifestPath())}`
5605
6625
  )
5606
6626
  );
5607
6627
  if (manifest.artifacts.handoffPath) {
@@ -5667,13 +6687,25 @@ function commandExists(command) {
5667
6687
  }
5668
6688
  }
5669
6689
  function getClaudeConfigPaths() {
5670
- const desktopDir = process.platform === "darwin" ? path.join(homedir(), "Library", "Application Support", "Claude") : process.platform === "win32" ? path.join(homedir(), "AppData", "Roaming", "Claude") : path.join(homedir(), ".config", "claude");
6690
+ const desktopDir = process.platform === "darwin" ? path4.join(homedir2(), "Library", "Application Support", "Claude") : process.platform === "win32" ? path4.join(homedir2(), "AppData", "Roaming", "Claude") : path4.join(homedir2(), ".config", "claude");
5671
6691
  return {
5672
- codeSettingsPath: path.join(homedir(), ".claude", "settings.json"),
5673
- desktopConfigPath: path.join(desktopDir, "claude_desktop_config.json")
6692
+ codeSettingsPath: path4.join(homedir2(), ".claude", "settings.json"),
6693
+ desktopConfigPath: path4.join(desktopDir, "claude_desktop_config.json")
5674
6694
  };
5675
6695
  }
5676
- function detectEnvironment() {
6696
+ function executableOnPath(command) {
6697
+ return (process.env.PATH ?? "").split(path4.delimiter).some((directory) => {
6698
+ if (!directory || !path4.isAbsolute(directory)) return false;
6699
+ try {
6700
+ const file = path4.join(directory, command);
6701
+ fs4.accessSync(file, fs4.constants.X_OK);
6702
+ return fs4.statSync(file).isFile();
6703
+ } catch {
6704
+ return false;
6705
+ }
6706
+ });
6707
+ }
6708
+ function detectEnvironment(managed = hasLocalWorkSelection()) {
5677
6709
  const existingConfig = getExistingConfig();
5678
6710
  const projectSlugResolution = resolveProjectSlug(existingConfig);
5679
6711
  const claudePaths = getClaudeConfigPaths();
@@ -5685,38 +6717,38 @@ function detectEnvironment() {
5685
6717
  aiClient: detectAiClient(),
5686
6718
  inGitRepo: isGitRepo(),
5687
6719
  files: {
5688
- mcpConfig: fs.existsSync(path.join(process.cwd(), ".mcp.json")),
5689
- env: fs.existsSync(path.join(process.cwd(), ".env")),
5690
- envLocal: fs.existsSync(path.join(process.cwd(), ".env.local")),
5691
- envExample: fs.existsSync(path.join(process.cwd(), ".env.example")),
5692
- agentsMd: fs.existsSync(path.join(process.cwd(), "AGENTS.md")),
5693
- claudeMd: fs.existsSync(path.join(process.cwd(), "CLAUDE.md")),
5694
- codexConfig: fs.existsSync(path.join(process.cwd(), ".codex", "config.toml")),
5695
- cursorRule: fs.existsSync(path.join(process.cwd(), ".cursor", "rules", "snipara.mdc")),
5696
- sniparaProject: fs.existsSync(path.join(process.cwd(), ".snipara", "project")),
5697
- claudeDir: fs.existsSync(path.join(homedir(), ".claude")),
5698
- claudeSettings: fs.existsSync(claudePaths.codeSettingsPath),
5699
- claudeProjectDir: fs.existsSync(path.join(process.cwd(), ".claude")),
5700
- claudeProjectSettings: fs.existsSync(path.join(process.cwd(), ".claude", "settings.json")),
5701
- claudeDesktopConfig: fs.existsSync(claudePaths.desktopConfigPath),
5702
- cursorDir: fs.existsSync(path.join(homedir(), ".cursor")),
5703
- mistralVibeConfig: fs.existsSync(path.join(process.cwd(), ".vibe", "config.toml")) || fs.existsSync(path.join(homedir(), ".vibe", "config.toml")),
5704
- sniparaCompanionPack: fs.existsSync(path.join(process.cwd(), ".snipara", "companion")),
5705
- sniparaTemplates: fs.existsSync(path.join(process.cwd(), ".snipara", "templates")),
5706
- sniparaSkillPack: fs.existsSync(path.join(process.cwd(), ".snipara", "templates", "skills"))
6720
+ mcpConfig: fs4.existsSync(path4.join(process.cwd(), ".mcp.json")),
6721
+ env: fs4.existsSync(path4.join(process.cwd(), ".env")),
6722
+ envLocal: fs4.existsSync(path4.join(process.cwd(), ".env.local")),
6723
+ envExample: fs4.existsSync(path4.join(process.cwd(), ".env.example")),
6724
+ agentsMd: fs4.existsSync(path4.join(process.cwd(), "AGENTS.md")),
6725
+ claudeMd: fs4.existsSync(path4.join(process.cwd(), "CLAUDE.md")),
6726
+ codexConfig: fs4.existsSync(path4.join(process.cwd(), ".codex", "config.toml")),
6727
+ cursorRule: fs4.existsSync(path4.join(process.cwd(), ".cursor", "rules", "snipara.mdc")),
6728
+ sniparaProject: fs4.existsSync(path4.join(process.cwd(), ".snipara", "project")),
6729
+ claudeDir: fs4.existsSync(path4.join(homedir2(), ".claude")),
6730
+ claudeSettings: fs4.existsSync(claudePaths.codeSettingsPath),
6731
+ claudeProjectDir: fs4.existsSync(path4.join(process.cwd(), ".claude")),
6732
+ claudeProjectSettings: fs4.existsSync(path4.join(process.cwd(), ".claude", "settings.json")),
6733
+ claudeDesktopConfig: fs4.existsSync(claudePaths.desktopConfigPath),
6734
+ cursorDir: fs4.existsSync(path4.join(homedir2(), ".cursor")),
6735
+ mistralVibeConfig: fs4.existsSync(path4.join(process.cwd(), ".vibe", "config.toml")) || fs4.existsSync(path4.join(homedir2(), ".vibe", "config.toml")),
6736
+ sniparaCompanionPack: fs4.existsSync(path4.join(process.cwd(), ".snipara", "companion")),
6737
+ sniparaTemplates: fs4.existsSync(path4.join(process.cwd(), ".snipara", "templates")),
6738
+ sniparaSkillPack: fs4.existsSync(path4.join(process.cwd(), ".snipara", "templates", "skills"))
5707
6739
  },
5708
6740
  tooling: {
5709
- node: commandExists("node"),
5710
- npm: commandExists("npm"),
5711
- pnpm: commandExists("pnpm"),
5712
- python: commandExists("python3") || commandExists("python"),
5713
- pip: commandExists("pip3") || commandExists("pip"),
5714
- uv: commandExists("uv"),
5715
- docker: commandExists("docker"),
5716
- git: commandExists("git"),
5717
- companionCli: Boolean(detectCompanionCommand()),
5718
- runtimeCli: Boolean(detectSandboxCommand()),
5719
- orchestratorCli: Boolean(detectOrchestratorCommand())
6741
+ node: managed ? true : commandExists("node"),
6742
+ npm: (managed ? executableOnPath : commandExists)("npm"),
6743
+ pnpm: (managed ? executableOnPath : commandExists)("pnpm"),
6744
+ python: (managed ? executableOnPath : commandExists)("python3") || (managed ? executableOnPath : commandExists)("python"),
6745
+ pip: (managed ? executableOnPath : commandExists)("pip3") || (managed ? executableOnPath : commandExists)("pip"),
6746
+ uv: (managed ? executableOnPath : commandExists)("uv"),
6747
+ docker: (managed ? executableOnPath : commandExists)("docker"),
6748
+ git: (managed ? executableOnPath : commandExists)("git"),
6749
+ companionCli: managed ? false : Boolean(detectCompanionCommand()),
6750
+ runtimeCli: managed ? false : Boolean(detectSandboxCommand()),
6751
+ orchestratorCli: managed ? false : Boolean(detectOrchestratorCommand())
5720
6752
  },
5721
6753
  existingConfig: {
5722
6754
  slug: existingConfig?.slug,
@@ -5728,7 +6760,7 @@ function detectEnvironment() {
5728
6760
  };
5729
6761
  }
5730
6762
  function loadExistingAnswers(options) {
5731
- const detection = detectEnvironment();
6763
+ const detection = detectEnvironment(options.profile === "local-work" || hasLocalWorkSelection());
5732
6764
  const effectiveProfile = resolveMaintenanceProfile(detection, options.profile);
5733
6765
  const deployment = resolveRequestedDeployment(options);
5734
6766
  const providedApiKey = options.apiKey?.trim();
@@ -5750,9 +6782,9 @@ function loadExistingAnswers(options) {
5750
6782
  apiKeyType,
5751
6783
  profile: effectiveProfile,
5752
6784
  aiClient: detection.aiClient,
5753
- skipCompanion: options.withCompanion === true ? false : !detection.tooling.companionCli && effectiveProfile !== "hosted-companion" && effectiveProfile !== "full-stack",
6785
+ skipCompanion: effectiveProfile === "local-work" ? false : options.withCompanion === true ? false : !detection.tooling.companionCli && effectiveProfile !== "hosted-companion" && effectiveProfile !== "full-stack",
5754
6786
  skipMcp: options.withMcp === true ? false : !detection.existingConfig.hasSniparaServer && effectiveProfile === "runtime-only",
5755
- skipRuntime: options.withRuntime === true ? false : !detection.existingConfig.hasRuntimeServer && effectiveProfile !== "full-stack" && effectiveProfile !== "runtime-only",
6787
+ skipRuntime: effectiveProfile === "local-work" ? false : options.withRuntime === true ? false : !detection.existingConfig.hasRuntimeServer && effectiveProfile !== "full-stack" && effectiveProfile !== "runtime-only",
5756
6788
  withOrchestrator: Boolean(options.withOrchestrator || detection.tooling.orchestratorCli),
5757
6789
  skipHooks: options.withHooks ? false : !shouldInstallDefaultHooks(detection.aiClient)
5758
6790
  });
@@ -5761,6 +6793,7 @@ function resolveMaintenanceProfile(detection, explicitProfile) {
5761
6793
  if (explicitProfile && isInstallProfile(explicitProfile)) {
5762
6794
  return explicitProfile;
5763
6795
  }
6796
+ if (hasLocalWorkSelection()) return "local-work";
5764
6797
  if (detection.existingConfig.hasRuntimeServer && detection.existingConfig.hasSniparaServer) {
5765
6798
  return detection.tooling.companionCli ? "full-stack" : "full-stack";
5766
6799
  }
@@ -5805,22 +6838,22 @@ function collectConfiguredProjectSlugSignals(existingConfig = getExistingConfig(
5805
6838
  { source: ".snipara/project", slug: readProjectSlugFromProjectBinding() },
5806
6839
  {
5807
6840
  source: ".codex/config.toml",
5808
- slug: readProjectSlugFromTextFile(path.join(process.cwd(), ".codex", "config.toml"))
6841
+ slug: readProjectSlugFromTextFile(path4.join(process.cwd(), ".codex", "config.toml"))
5809
6842
  },
5810
6843
  {
5811
6844
  source: ".snipara/templates/codex-config.toml",
5812
6845
  slug: readProjectSlugFromTextFile(
5813
- path.join(process.cwd(), ".snipara", "templates", "codex-config.toml")
6846
+ path4.join(process.cwd(), ".snipara", "templates", "codex-config.toml")
5814
6847
  )
5815
6848
  },
5816
6849
  {
5817
6850
  source: ".vibe/config.toml",
5818
- slug: readProjectSlugFromTextFile(path.join(process.cwd(), ".vibe", "config.toml"))
6851
+ slug: readProjectSlugFromTextFile(path4.join(process.cwd(), ".vibe", "config.toml"))
5819
6852
  },
5820
6853
  {
5821
6854
  source: ".snipara/templates/mistral-vibe-config.toml",
5822
6855
  slug: readProjectSlugFromTextFile(
5823
- path.join(process.cwd(), ".snipara", "templates", "mistral-vibe-config.toml")
6856
+ path4.join(process.cwd(), ".snipara", "templates", "mistral-vibe-config.toml")
5824
6857
  )
5825
6858
  },
5826
6859
  { source: ".env.local", slug: readProjectSlugFromEnvFile(".env.local") },
@@ -5858,22 +6891,22 @@ function extractSlugFromText(content) {
5858
6891
  return normalizeSlugCandidate(match?.[1] ? decodeURIComponent(match[1]) : null);
5859
6892
  }
5860
6893
  function readProjectSlugFromTextFile(filePath) {
5861
- if (!fs.existsSync(filePath)) {
6894
+ if (!fs4.existsSync(filePath)) {
5862
6895
  return null;
5863
6896
  }
5864
6897
  try {
5865
- return extractSlugFromText(fs.readFileSync(filePath, "utf-8"));
6898
+ return extractSlugFromText(fs4.readFileSync(filePath, "utf-8"));
5866
6899
  } catch {
5867
6900
  return null;
5868
6901
  }
5869
6902
  }
5870
6903
  function readProjectSlugFromProjectBinding() {
5871
- const filePath = path.join(process.cwd(), ".snipara", "project");
5872
- if (!fs.existsSync(filePath)) {
6904
+ const filePath = path4.join(process.cwd(), ".snipara", "project");
6905
+ if (!fs4.existsSync(filePath)) {
5873
6906
  return null;
5874
6907
  }
5875
6908
  try {
5876
- const content = fs.readFileSync(filePath, "utf-8").trim();
6909
+ const content = fs4.readFileSync(filePath, "utf-8").trim();
5877
6910
  if (!content) {
5878
6911
  return null;
5879
6912
  }
@@ -5909,12 +6942,12 @@ function readProjectSlugFromEnvFile(file) {
5909
6942
  return null;
5910
6943
  }
5911
6944
  function readEnvValueFromFile(file, names) {
5912
- const filePath = path.join(process.cwd(), file);
5913
- if (!fs.existsSync(filePath)) {
6945
+ const filePath = path4.join(process.cwd(), file);
6946
+ if (!fs4.existsSync(filePath)) {
5914
6947
  return null;
5915
6948
  }
5916
6949
  try {
5917
- const content = fs.readFileSync(filePath, "utf-8");
6950
+ const content = fs4.readFileSync(filePath, "utf-8");
5918
6951
  for (const line of content.split(/\r?\n/)) {
5919
6952
  const match = line.trim().match(/^(?:export\s+)?([A-Z0-9_]+)\s*=\s*(.*)$/);
5920
6953
  if (!match || !names.includes(match[1])) {
@@ -5954,7 +6987,7 @@ function detectRepoProjectSlug() {
5954
6987
  return null;
5955
6988
  }
5956
6989
  function detectDirectoryProjectSlug() {
5957
- return slugifyFallback(path.basename(process.cwd()));
6990
+ return slugifyFallback(path4.basename(process.cwd()));
5958
6991
  }
5959
6992
  function detectGitRepository() {
5960
6993
  try {
@@ -6016,50 +7049,50 @@ function isGitRepo() {
6016
7049
  }
6017
7050
  }
6018
7051
  function detectAiClient() {
6019
- if (fs.existsSync(path.join(process.cwd(), ".codex"))) {
7052
+ if (fs4.existsSync(path4.join(process.cwd(), ".codex"))) {
6020
7053
  return "codex";
6021
7054
  }
6022
- if (fs.existsSync(path.join(process.cwd(), ".cursor"))) {
7055
+ if (fs4.existsSync(path4.join(process.cwd(), ".cursor"))) {
6023
7056
  return "cursor";
6024
7057
  }
6025
- if (fs.existsSync(path.join(process.cwd(), ".kimi-code"))) {
7058
+ if (fs4.existsSync(path4.join(process.cwd(), ".kimi-code"))) {
6026
7059
  return "kimi";
6027
7060
  }
6028
- if (fs.existsSync(path.join(process.cwd(), ".gemini"))) {
7061
+ if (fs4.existsSync(path4.join(process.cwd(), ".gemini"))) {
6029
7062
  return "gemini";
6030
7063
  }
6031
- if (fs.existsSync(path.join(process.cwd(), ".vibe"))) {
7064
+ if (fs4.existsSync(path4.join(process.cwd(), ".vibe"))) {
6032
7065
  return "mistral";
6033
7066
  }
6034
- if (fs.existsSync(path.join(process.cwd(), ".continue"))) {
7067
+ if (fs4.existsSync(path4.join(process.cwd(), ".continue"))) {
6035
7068
  return "continue";
6036
7069
  }
6037
- if (fs.existsSync(path.join(process.cwd(), ".vscode"))) {
7070
+ if (fs4.existsSync(path4.join(process.cwd(), ".vscode"))) {
6038
7071
  return "vscode";
6039
7072
  }
6040
- if (fs.existsSync(path.join(homedir(), ".claude")) || process.env.CLAUDE_CODE) {
7073
+ if (fs4.existsSync(path4.join(homedir2(), ".claude")) || process.env.CLAUDE_CODE) {
6041
7074
  return "claude-code";
6042
7075
  }
6043
- if (fs.existsSync(path.join(homedir(), ".cursor")) || process.env.CURSOR_EDITOR) {
7076
+ if (fs4.existsSync(path4.join(homedir2(), ".cursor")) || process.env.CURSOR_EDITOR) {
6044
7077
  return "cursor";
6045
7078
  }
6046
- if (fs.existsSync(path.join(homedir(), ".kimi-code")) || process.env.KIMI_CODE_HOME) {
7079
+ if (fs4.existsSync(path4.join(homedir2(), ".kimi-code")) || process.env.KIMI_CODE_HOME) {
6047
7080
  return "kimi";
6048
7081
  }
6049
- if (fs.existsSync(path.join(homedir(), ".gemini")) || process.env.GEMINI_CLI) {
7082
+ if (fs4.existsSync(path4.join(homedir2(), ".gemini")) || process.env.GEMINI_CLI) {
6050
7083
  return "gemini";
6051
7084
  }
6052
- if (fs.existsSync(path.join(homedir(), ".vibe")) || process.env.MISTRAL_VIBE) {
7085
+ if (fs4.existsSync(path4.join(homedir2(), ".vibe")) || process.env.MISTRAL_VIBE) {
6053
7086
  return "mistral";
6054
7087
  }
6055
- if (fs.existsSync(path.join(homedir(), ".continue")) || process.env.CONTINUE_GLOBAL_DIR) {
7088
+ if (fs4.existsSync(path4.join(homedir2(), ".continue")) || process.env.CONTINUE_GLOBAL_DIR) {
6056
7089
  return "continue";
6057
7090
  }
6058
- const claudeDesktopPath = process.platform === "darwin" ? path.join(homedir(), "Library", "Application Support", "Claude") : process.platform === "win32" ? path.join(homedir(), "AppData", "Roaming", "Claude") : path.join(homedir(), ".config", "claude");
6059
- if (fs.existsSync(claudeDesktopPath)) {
7091
+ const claudeDesktopPath = process.platform === "darwin" ? path4.join(homedir2(), "Library", "Application Support", "Claude") : process.platform === "win32" ? path4.join(homedir2(), "AppData", "Roaming", "Claude") : path4.join(homedir2(), ".config", "claude");
7092
+ if (fs4.existsSync(claudeDesktopPath)) {
6060
7093
  return "claude-desktop";
6061
7094
  }
6062
- if (fs.existsSync(path.join(homedir(), ".codex"))) {
7095
+ if (fs4.existsSync(path4.join(homedir2(), ".codex"))) {
6063
7096
  return "codex";
6064
7097
  }
6065
7098
  return "other";
@@ -6074,12 +7107,12 @@ function detectPipCommand() {
6074
7107
  return null;
6075
7108
  }
6076
7109
  function getExistingConfig() {
6077
- const mcpConfigPath = path.join(process.cwd(), ".mcp.json");
6078
- if (!fs.existsSync(mcpConfigPath)) {
7110
+ const mcpConfigPath = path4.join(process.cwd(), ".mcp.json");
7111
+ if (!fs4.existsSync(mcpConfigPath)) {
6079
7112
  return null;
6080
7113
  }
6081
7114
  try {
6082
- const config = JSON.parse(fs.readFileSync(mcpConfigPath, "utf-8"));
7115
+ const config = JSON.parse(fs4.readFileSync(mcpConfigPath, "utf-8"));
6083
7116
  const sniparaServer = config.mcpServers?.snipara;
6084
7117
  const runtimeServer = config.mcpServers?.[SANDBOX_SERVER_NAME] || config.mcpServers?.[LEGACY_RUNTIME_SERVER_NAME];
6085
7118
  return {
@@ -6099,12 +7132,12 @@ function getExistingConfig() {
6099
7132
  }
6100
7133
  }
6101
7134
  function readExistingCompanionProjectId() {
6102
- const configPath = path.join(process.cwd(), ".snipara", "companion", "config.json");
6103
- if (!fs.existsSync(configPath)) {
7135
+ const configPath = path4.join(process.cwd(), ".snipara", "companion", "config.json");
7136
+ if (!fs4.existsSync(configPath)) {
6104
7137
  return void 0;
6105
7138
  }
6106
7139
  try {
6107
- const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
7140
+ const parsed = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
6108
7141
  return typeof parsed.projectId === "string" && parsed.projectId ? parsed.projectId : void 0;
6109
7142
  } catch {
6110
7143
  return void 0;