@crvouga/personal 0.1.0 → 0.1.2

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/probe.mjs CHANGED
@@ -1,3 +1,8 @@
1
+ // packages/fleet/src/configure.ts
2
+ import { join } from "node:path";
3
+ import { mkdir, writeFile, rename } from "node:fs/promises";
4
+ import { randomUUID } from "node:crypto";
5
+
1
6
  // packages/fleet/src/errors.ts
2
7
  class FleetUsageError extends Error {
3
8
  }
@@ -8,7 +13,11 @@ var CAPABILITIES = [
8
13
  "vault",
9
14
  "t3",
10
15
  "vscode",
11
- "toolchain"
16
+ "toolchain",
17
+ "codex",
18
+ "claude",
19
+ "services",
20
+ "workstation"
12
21
  ];
13
22
  function record(value) {
14
23
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -75,14 +84,119 @@ function parseConfig(value) {
75
84
  return { version: 1, nodes };
76
85
  }
77
86
 
87
+ // packages/fleet/src/configure.ts
88
+ function validConfiguration(value) {
89
+ return record(value) && typeof value["revision"] === "string" && /^[a-f0-9]{40}$/.test(value["revision"]) && ["codex", "claude", "opencode", "gemini"].includes(String(value["tool"])) && ["direct", "proxy"].includes(String(value["profile"])) && typeof value["node"] === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(value["node"]);
90
+ }
91
+ async function configureWorkspace(root, request, run) {
92
+ if (!validConfiguration(request))
93
+ return failure("Invalid reviewed revision or configuration selection.");
94
+ const directory = join(root, "node_modules", ".fleet-revisions", request.revision);
95
+ const head = await run(["git", "rev-parse", "--verify", `${request.revision}^{commit}`], { cwd: root });
96
+ if (!head.ok || head.stdout.trim() !== request.revision)
97
+ return failure("Reviewed commit is unavailable locally. Fetch it explicitly and retry.");
98
+ const prepared = await prepareRevision(root, directory, request.revision, run);
99
+ if (prepared.length)
100
+ return prepared;
101
+ const setup = await run([
102
+ "mise",
103
+ "exec",
104
+ "--locked",
105
+ "bun",
106
+ "--",
107
+ "bun",
108
+ "scripts/dev/setup.ts",
109
+ "--non-interactive"
110
+ ], { cwd: directory, timeoutMs: 1200000 });
111
+ if (!setup.ok)
112
+ return failure("Existing setup owner failed. Partial tool/dependency installation is possible; no configuration receipt was advanced.");
113
+ const applied = await applyOwner(directory, request, run);
114
+ if (!applied.ok)
115
+ return failure("personal workstation configuration failed. Inspect preview on this node; no rollout receipt was advanced.");
116
+ const receipt = decodeReceipt(applied.stdout, request.revision);
117
+ if (!receipt)
118
+ return failure("personal workstation returned an invalid receipt; rollout remains unverified.");
119
+ await recordConfiguration(root, request, receipt);
120
+ return [
121
+ {
122
+ id: "configuration:apply",
123
+ status: "pass",
124
+ summary: `personal workstation applied revision ${request.revision}; configuration fingerprint ${receipt}. Authentication and T3 end-to-end remain separate gates.`
125
+ }
126
+ ];
127
+ }
128
+ function decodeReceipt(output, revision) {
129
+ try {
130
+ const data = JSON.parse(output);
131
+ return record(data) && data["ok"] === true && data["revision"] === revision && typeof data["fingerprint"] === "string" && /^[a-f0-9]{64}$/.test(data["fingerprint"]) ? data["fingerprint"] : null;
132
+ } catch {
133
+ return null;
134
+ }
135
+ }
136
+ function failure(summary) {
137
+ return [{ id: "configuration:apply", status: "fail", summary }];
138
+ }
139
+ async function prepareRevision(root, directory, revision, run) {
140
+ await mkdir(join(root, "node_modules", ".fleet-revisions"), {
141
+ recursive: true
142
+ });
143
+ let deployed = await run(["git", "rev-parse", "HEAD"], { cwd: directory });
144
+ if (!deployed.ok) {
145
+ const created = await run(["git", "worktree", "add", "--detach", directory, revision], { cwd: root, timeoutMs: 30000 });
146
+ if (!created.ok)
147
+ return failure("Cannot create pinned deployment worktree. Existing data was preserved.");
148
+ deployed = await run(["git", "rev-parse", "HEAD"], { cwd: directory });
149
+ }
150
+ const clean = await run(["git", "status", "--porcelain"], { cwd: directory });
151
+ if (!deployed.ok || deployed.stdout.trim() !== revision || !clean.ok || clean.stdout.trim())
152
+ return failure("Pinned deployment checkout differs or is dirty. Inspect it; fleet never resets it.");
153
+ const top = await run(["git", "rev-parse", "--show-toplevel"], {
154
+ cwd: directory
155
+ });
156
+ if (!top.ok || top.stdout.trim() !== directory)
157
+ return failure("Deployment path is not its own Git worktree.");
158
+ return [];
159
+ }
160
+ async function recordConfiguration(root, request, receipt) {
161
+ const receipts = join(root, "node_modules", ".fleet-configuration");
162
+ await mkdir(receipts, { recursive: true, mode: 448 });
163
+ const path = join(receipts, `${request.node}-${request.tool}-${request.profile}.json`);
164
+ const temp = `${path}.${randomUUID()}.tmp`;
165
+ await writeFile(temp, JSON.stringify({ version: 1, ...request, fingerprint: receipt }) + `
166
+ `, { flag: "wx", mode: 384 });
167
+ await rename(temp, path);
168
+ }
169
+ async function applyOwner(directory, request, run) {
170
+ return run([
171
+ "mise",
172
+ "exec",
173
+ "--locked",
174
+ "bun",
175
+ "--",
176
+ "bun",
177
+ "packages/workstation/cli/internal.ts",
178
+ "--json",
179
+ "--yes",
180
+ "--non-interactive",
181
+ "agents",
182
+ "apply",
183
+ "--node",
184
+ request.node,
185
+ "--tool",
186
+ request.tool,
187
+ "--profile",
188
+ request.profile
189
+ ], { cwd: directory, timeoutMs: 120000 });
190
+ }
191
+
78
192
  // packages/fleet/src/fingerprint.ts
79
- import { readFile as readFile2, mkdir, writeFile } from "node:fs/promises";
80
- import { join as join2 } from "node:path";
193
+ import { readFile as readFile2, mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
194
+ import { join as join3 } from "node:path";
81
195
 
82
196
  // packages/fleet/src/input-hashes.ts
83
197
  import { createHash } from "node:crypto";
84
198
  import { readFile, readdir } from "node:fs/promises";
85
- import { join } from "node:path";
199
+ import { join as join2 } from "node:path";
86
200
  var INPUT_FILES = [
87
201
  "package.json",
88
202
  "bun.lock",
@@ -103,12 +217,12 @@ async function fileHash(path) {
103
217
  }
104
218
  async function manifestHash(root) {
105
219
  try {
106
- const entries = await readdir(join(root, "packages"), {
220
+ const entries = await readdir(join2(root, "packages"), {
107
221
  withFileTypes: true
108
222
  });
109
223
  const hashes = [];
110
224
  for (const entry of entries.filter((item) => item.isDirectory()).sort((left, right) => left.name.localeCompare(right.name, "en"))) {
111
- hashes.push(`${entry.name}:${await fileHash(join(root, "packages", entry.name, "package.json"))}`);
225
+ hashes.push(`${entry.name}:${await fileHash(join2(root, "packages", entry.name, "package.json"))}`);
112
226
  }
113
227
  return digest(hashes.join(`
114
228
  `));
@@ -118,19 +232,19 @@ async function manifestHash(root) {
118
232
  }
119
233
  async function sidecarHash(root) {
120
234
  try {
121
- const files = await readdir(join(root, ".mise/locks"), {
235
+ const files = await readdir(join2(root, ".mise/locks"), {
122
236
  recursive: true,
123
237
  withFileTypes: true
124
238
  });
125
239
  if (files.length > 4096 || files.some((entry) => entry.isSymbolicLink()))
126
240
  return null;
127
241
  const hashes = [];
128
- const paths = files.filter((entry) => entry.isFile()).map((entry) => join(entry.parentPath, entry.name)).sort();
242
+ const paths = files.filter((entry) => entry.isFile()).map((entry) => join2(entry.parentPath, entry.name)).sort();
129
243
  for (const path of paths) {
130
244
  const hash = await fileHash(path);
131
245
  if (hash === null)
132
246
  return null;
133
- const relative = path.slice(join(root, ".mise/locks").length).replaceAll("\\", "/");
247
+ const relative = path.slice(join2(root, ".mise/locks").length).replaceAll("\\", "/");
134
248
  hashes.push(`${relative}:${hash}`);
135
249
  }
136
250
  return digest(hashes.join(`
@@ -148,7 +262,7 @@ var RECEIPT = "node_modules/.fleet-sync.json";
148
262
  async function fingerprint(root, run) {
149
263
  const files = {};
150
264
  for (const name of INPUT_FILES)
151
- files[name] = await fileHash(join2(root, name));
265
+ files[name] = await fileHash(join3(root, name));
152
266
  files["workspace-manifests"] = await manifestHash(root);
153
267
  files["mise-sidecars"] = await sidecarHash(root);
154
268
  const head = await run(["git", "rev-parse", "HEAD"], { cwd: root });
@@ -162,7 +276,7 @@ async function dependencyCheck(root, current) {
162
276
  action: "Review fleet sync, then explicitly apply a frozen toolchain/dependency sync."
163
277
  };
164
278
  try {
165
- const receipt = JSON.parse(await readFile2(join2(root, RECEIPT), "utf8"));
279
+ const receipt = JSON.parse(await readFile2(join3(root, RECEIPT), "utf8"));
166
280
  const matches = record(receipt) && receipt["version"] === 1 && JSON.stringify(receipt["files"]) === JSON.stringify(current.files);
167
281
  return {
168
282
  id: "dependencies",
@@ -180,15 +294,204 @@ async function dependencyCheck(root, current) {
180
294
  }
181
295
  }
182
296
  async function writeReceipt(root, current) {
183
- await mkdir(join2(root, "node_modules"), { recursive: true });
184
- await writeFile(join2(root, RECEIPT), JSON.stringify({ version: 1, files: current.files }), { mode: 384 });
297
+ await mkdir2(join3(root, "node_modules"), { recursive: true });
298
+ await writeFile2(join3(root, RECEIPT), JSON.stringify({ version: 1, files: current.files }), { mode: 384 });
299
+ }
300
+
301
+ // packages/fleet/src/service-auth.ts
302
+ function json(result) {
303
+ try {
304
+ const data = JSON.parse(result.stdout);
305
+ return record(data) ? data : null;
306
+ } catch {
307
+ return null;
308
+ }
309
+ }
310
+ async function codingAuth(id, run) {
311
+ return [
312
+ await serviceCheck(id === "codex" ? {
313
+ id,
314
+ command: ["codex", "login", "status"],
315
+ action: "Run codex login on this computer as its operator.",
316
+ valid: (result) => /^Logged in using (ChatGPT|an API key|API key)\b/im.test(`${result.stdout}
317
+ ${result.stderr}`)
318
+ } : {
319
+ id,
320
+ command: ["claude", "auth", "status", "--json"],
321
+ action: "Run claude auth login on this computer as its operator.",
322
+ valid: (result) => json(result)?.["loggedIn"] === true
323
+ }, run)
324
+ ];
325
+ }
326
+ async function serviceAuth(run) {
327
+ const services = [
328
+ {
329
+ id: "railway",
330
+ command: ["railway", "whoami", "--json"],
331
+ action: "Run railway login on this computer.",
332
+ valid: (result) => typeof json(result)?.["id"] === "string"
333
+ },
334
+ {
335
+ id: "vercel",
336
+ command: ["vercel", "whoami"],
337
+ action: "Run vercel login on this computer.",
338
+ valid: (result) => /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,100}$/.test(result.stdout.trim())
339
+ },
340
+ {
341
+ id: "cloudflare",
342
+ command: ["wrangler", "whoami", "--json"],
343
+ action: "Run wrangler login on this computer.",
344
+ valid: (result) => json(result)?.["loggedIn"] === true
345
+ },
346
+ {
347
+ id: "aws",
348
+ command: [
349
+ "aws",
350
+ "sts",
351
+ "get-caller-identity",
352
+ "--output",
353
+ "json",
354
+ "--no-cli-pager"
355
+ ],
356
+ action: "Select and authenticate the intended AWS profile on this computer.",
357
+ valid: (result) => typeof json(result)?.["Account"] === "string" && typeof json(result)?.["Arn"] === "string"
358
+ },
359
+ {
360
+ id: "npm",
361
+ command: ["npm", "whoami", "--registry=https://registry.npmjs.org/"],
362
+ action: "Run npm login only if this computer should publish packages.",
363
+ valid: (result) => /^[a-z0-9][a-z0-9._-]{0,213}$/.test(result.stdout.trim())
364
+ }
365
+ ];
366
+ const checks = [];
367
+ for (const service of services)
368
+ checks.push(await serviceCheck(service, run));
369
+ for (const id of ["gemini", "copilot", "opencode"])
370
+ checks.push({
371
+ id: `auth:${id}`,
372
+ status: "skip",
373
+ summary: `${id} provider authorization has no verified noninteractive status adapter in this CLI. Installed binaries or saved keys are not proof of access.`,
374
+ remedy: {
375
+ action: `Verify ${id} provider access locally using its supported account diagnostics. No prompt or billable request was made.`
376
+ }
377
+ });
378
+ return checks;
379
+ }
380
+ async function serviceCheck(service, run) {
381
+ const result = await run(service.command, { timeoutMs: 15000 });
382
+ const ok = result.ok && service.valid(result);
383
+ return {
384
+ id: `auth:${service.id}`,
385
+ status: ok ? "pass" : "fail",
386
+ summary: ok ? `${service.id} reports an authenticated session in this process context; no credential or account output retained.` : `${service.id} login is missing or could not be verified (${result.reason}); no credential output retained.`,
387
+ ...!ok ? { remedy: { action: service.action } } : {}
388
+ };
389
+ }
390
+
391
+ // packages/fleet/src/workstation-checks.ts
392
+ async function workstationChecks(root, run) {
393
+ const result = await run(["personal", "workstation", "doctor", "--json", "--non-interactive"], {
394
+ cwd: root,
395
+ timeoutMs: 20000
396
+ });
397
+ const fallback = {
398
+ id: "workstation:configuration",
399
+ status: "fail",
400
+ summary: "Managed workstation configuration could not be verified.",
401
+ remedy: {
402
+ action: "Run personal workstation doctor --json on this computer. Review managed links, PATH, coding-tool/provider configuration and notification settings; apply personal workstation sync only explicitly."
403
+ }
404
+ };
405
+ try {
406
+ const data = JSON.parse(result.stdout);
407
+ if (!record(data) || !Array.isArray(data["checks"]) || !data["checks"].length || data["checks"].length > 100)
408
+ return [fallback];
409
+ const checks = data["checks"].map(configurationCheck);
410
+ if (checks.some((check) => check === null))
411
+ return [fallback];
412
+ return [
413
+ ...!result.ok || data["ok"] !== true ? [fallback] : [],
414
+ ...checks
415
+ ];
416
+ } catch {
417
+ return [fallback];
418
+ }
419
+ }
420
+ function configurationCheck(value) {
421
+ if (!record(value) || typeof value["id"] !== "string" || !/^[a-zA-Z0-9:._-]{1,75}$/.test(value["id"]) || !["pass", "warn", "fail"].includes(String(value["severity"])))
422
+ return null;
423
+ const ok = value["severity"] === "pass";
424
+ return {
425
+ id: `workstation:${value["id"].toLowerCase()}`,
426
+ status: value["severity"],
427
+ summary: ok ? "Managed configuration check succeeded (configuration presence; provider authorization is checked separately)." : "Managed configuration is missing, conflicting or unverified.",
428
+ ...!ok ? {
429
+ remedy: {
430
+ action: "Inspect personal workstation doctor --json on this computer. Use explicit personal workstation sync only after reviewing the configuration changes."
431
+ }
432
+ } : {}
433
+ };
434
+ }
435
+
436
+ // packages/fleet/src/toolchain-checks.ts
437
+ import { join as join4 } from "node:path";
438
+ async function toolchainChecks(root, run) {
439
+ const result = await run(["bun", join4(root, "scripts/dev/doctor.ts"), "--json", "--docker"], { cwd: root, timeoutMs: 90000 });
440
+ const fallback = {
441
+ id: "auth:toolchain",
442
+ status: "fail",
443
+ summary: "Pinned tool/dependency and Docker checks could not be verified.",
444
+ remedy: {
445
+ action: "Run the checkout scripts/dev/doctor.ts --json --docker on this computer. Reconcile versions only through the shared mise setup."
446
+ }
447
+ };
448
+ try {
449
+ const data = JSON.parse(result.stdout);
450
+ if (!record(data) || typeof data["ok"] !== "boolean" || !Array.isArray(data["checks"]) || !data["checks"].length || data["checks"].length > 150)
451
+ return [fallback];
452
+ const checks = data["checks"].map(toolCheck);
453
+ if (checks.some((check) => check === null))
454
+ return [fallback];
455
+ return [
456
+ ...!result.ok || !data["ok"] ? [fallback] : [],
457
+ ...checks
458
+ ];
459
+ } catch {
460
+ return [fallback];
461
+ }
462
+ }
463
+ function toolCheck(value) {
464
+ if (!record(value) || typeof value["id"] !== "string" || !/^[a-zA-Z0-9@:/._-]{1,75}$/.test(value["id"]) || !["pass", "fail", "warn", "skip"].includes(String(value["status"])))
465
+ return null;
466
+ const ok = value["status"] === "pass";
467
+ const version = typeof value["detail"] === "string" && /^v?\d{1,6}(?:\.\d{1,6}){1,3}$/.test(value["detail"]) ? ` ${value["detail"]}` : "";
468
+ return {
469
+ id: `tool:${value["id"].toLowerCase().replace(/[^a-z0-9:._-]/g, "_")}`,
470
+ status: value["status"],
471
+ summary: ok ? `Expected tool version or runtime check succeeded${version}.` : "Expected tool version, runtime or authentication check did not pass.",
472
+ ...!ok ? {
473
+ remedy: {
474
+ action: "Review scripts/dev/doctor.ts --json --docker locally. Use shared mise/workstation setup to reconcile this computer."
475
+ }
476
+ } : {}
477
+ };
185
478
  }
186
479
 
187
480
  // packages/fleet/src/auth-checks.ts
188
- import { join as join3 } from "node:path";
189
481
  async function authCheck(capability, run, root) {
190
482
  if (capability === "t3")
191
483
  return t3Checks(run);
484
+ if (capability === "codex" || capability === "claude")
485
+ return codingAuth(capability, run);
486
+ if (capability === "services")
487
+ return serviceAuth(run);
488
+ if (capability === "workstation")
489
+ return workstationChecks(root, run);
490
+ if (capability === "toolchain")
491
+ return toolchainChecks(root, run);
492
+ return basicAuth(capability, run, root);
493
+ }
494
+ async function basicAuth(capability, run, root) {
192
495
  const spec = {
193
496
  github: {
194
497
  command: ["gh", "auth", "status", "--active"],
@@ -201,15 +504,11 @@ async function authCheck(capability, run, root) {
201
504
  vscode: {
202
505
  command: ["code", "--list-extensions"],
203
506
  action: "Install the VS Code CLI on PATH and ms-vscode-remote.remote-ssh in the local editor profile."
204
- },
205
- toolchain: {
206
- command: ["bun", join3(root, "scripts/dev/doctor.ts"), "--json"],
207
- action: "Run ws tools check --json; use the shared mise setup to reconcile tools."
208
507
  }
209
508
  }[capability];
210
509
  const result = await run(spec.command, {
211
510
  cwd: root,
212
- timeoutMs: capability === "toolchain" ? 90000 : 15000
511
+ timeoutMs: 15000
213
512
  });
214
513
  const extensions = result.stdout.toLowerCase().split(/\s+/);
215
514
  const ok = result.ok && (capability !== "vscode" || extensions.includes("ms-vscode-remote.remote-ssh"));
@@ -288,13 +587,7 @@ async function probe(root, required, run) {
288
587
  summary: dirty.ok && !dirty.stdout.trim() ? "Working tree is clean." : "Working tree is dirty or unreadable; sync --pull will refuse."
289
588
  });
290
589
  checks.push(await dependencyCheck(root, current));
291
- for (const capability of [
292
- "github",
293
- "vault",
294
- "t3",
295
- "vscode",
296
- "toolchain"
297
- ]) {
590
+ for (const capability of CAPABILITIES) {
298
591
  if (required.includes(capability))
299
592
  checks.push(...await authCheck(capability, run, root));
300
593
  else
@@ -390,7 +683,7 @@ function runCommand(command, options = {}) {
390
683
 
391
684
  // packages/fleet/src/sync.ts
392
685
  import { unlink } from "node:fs/promises";
393
- import { join as join4 } from "node:path";
686
+ import { join as join5 } from "node:path";
394
687
  async function syncWorkspace(root, pull, run) {
395
688
  const before = await fingerprint(root, run);
396
689
  if (!completeFingerprint(before)) {
@@ -404,7 +697,7 @@ async function syncWorkspace(root, pull, run) {
404
697
  if (!dirty.ok || dirty.stdout.trim() || !branch.ok)
405
698
  return failed("Refusing pull: dirty, detached, or unreadable checkout. Reconcile manually.");
406
699
  }
407
- await unlink(join4(root, "node_modules/.fleet-sync.json")).catch(() => {
700
+ await unlink(join5(root, "node_modules/.fleet-sync.json")).catch(() => {
408
701
  return;
409
702
  });
410
703
  if (pull) {
@@ -456,13 +749,242 @@ function completeFingerprint(value) {
456
749
  return value.head !== null && Object.values(value.files).every(Boolean);
457
750
  }
458
751
 
752
+ // packages/fleet/src/repair-catalog.ts
753
+ var login = (id, title, commands, action) => ({
754
+ id: `login:${id}`,
755
+ title,
756
+ owner: `${id} native CLI`,
757
+ automatic: false,
758
+ action,
759
+ commands
760
+ });
761
+ var REPAIR_RECIPES = [
762
+ {
763
+ id: "workspace",
764
+ title: "Install locked tools and frozen dependencies",
765
+ owner: "mise / scripts/dev/setup.ts",
766
+ automatic: true,
767
+ action: "Runs the existing shared setup and trusted checkout lifecycle scripts. Updates user mise/PATH configuration and writes a sync receipt. Does not pull or reset Git.",
768
+ commands: [
769
+ [
770
+ "mise",
771
+ "exec",
772
+ "--locked",
773
+ "bun",
774
+ "--",
775
+ "bun",
776
+ "scripts/dev/setup.ts",
777
+ "--non-interactive"
778
+ ]
779
+ ]
780
+ },
781
+ {
782
+ id: "workstation",
783
+ title: "Install launcher and converge managed workstation configuration",
784
+ owner: "workstation",
785
+ automatic: true,
786
+ action: "Runs personal:install --yes: frozen install, launcher/PATH, managed links, notifications and provider configuration. Reads available provider keys from this machine’s Vault session and writes owner-managed 0600 configuration. Conflicting unmanaged files fail.",
787
+ commands: [["bun", "run", "personal:install", "--yes"]]
788
+ },
789
+ {
790
+ id: "providers",
791
+ title: "Refresh shared provider configuration from Vault",
792
+ owner: "workstation / secret-store",
793
+ automatic: true,
794
+ action: "Uses this machine’s own Vault authorization to fetch the existing permitted provider keys. The workstation owner writes its 0600 tool configuration; no credentials are copied from another computer. Missing provider keys may remain skipped.",
795
+ commands: [
796
+ [
797
+ "bun",
798
+ "packages/workstation/cli/internal.ts",
799
+ "opencode",
800
+ "sync",
801
+ "--strict",
802
+ "--json",
803
+ "--non-interactive",
804
+ "--yes"
805
+ ]
806
+ ]
807
+ },
808
+ login("codex", "Sign in to Codex on this machine", [["codex", "login", "--device-auth"]], "Approve the device flow in your browser. Run codex login status afterward; retain this machine’s own credential store."),
809
+ login("claude", "Sign in to Claude Code on this machine", [["claude", "auth", "login"]], "Complete the native browser login, then run claude auth status --json. API-key and subscription contexts can differ."),
810
+ login("github", "Sign in to GitHub and configure Git", [
811
+ [
812
+ "gh",
813
+ "auth",
814
+ "login",
815
+ "--hostname",
816
+ "github.com",
817
+ "--web",
818
+ "--git-protocol",
819
+ "https"
820
+ ],
821
+ ["gh", "auth", "setup-git"]
822
+ ], "Approve GitHub in the browser on the intended account, then run gh auth status --active. Environment tokens can override stored login; inspect their source locally."),
823
+ login("vault", "Authorize this machine for the shared Vault", [], "Run vault login -method=userpass username=YOUR_USERNAME in your own terminal. The native CLI prompts privately for the password. Use the existing permitted project/config; never copy VAULT_TOKEN between computers."),
824
+ login("railway", "Sign in to Railway", [["railway", "login", "--browserless"]], "Complete the browserless native flow, then run railway whoami --json."),
825
+ login("vercel", "Sign in to Vercel", [["vercel", "login"]], "Complete native login, then run vercel whoami. Select the intended team/project separately; do not create resources as a login repair."),
826
+ login("cloudflare", "Sign in to Cloudflare", [["wrangler", "login", "--browser=false"]], "Complete native OAuth login, then run wrangler whoami --json. For headless SSH, prefer an existing scoped API token injected by the approved Vault config. --browser=false suppresses browser launch; it does not make a remote loopback callback reachable. Any OAuth callback tunnel needs explicit operator setup. Resource access still depends on account permissions."),
827
+ login("aws", "Refresh the selected AWS SSO session", [["aws", "sso", "login"]], "Configure the intended profile with aws configure sso first if needed. Login and the health check use this shell’s current profile; no access keys are cloned."),
828
+ login("npm", "Sign in to npm on this machine", [
829
+ [
830
+ "npm",
831
+ "login",
832
+ "--auth-type=web",
833
+ "--registry=https://registry.npmjs.org/"
834
+ ]
835
+ ], "Complete the specific CLI approval and run npm whoami. Prefer the existing trusted-publisher CI workflow for releases."),
836
+ login("opencode", "Authorize an OpenCode provider", [["opencode", "auth", "login"]], "Prefer the existing Vault-backed providers repair for managed API keys. Native auth login handles an operator-owned OAuth provider. Credential listing alone does not prove live provider access."),
837
+ login("copilot", "Sign in to Copilot", [["copilot", "login"]], "Complete native login; provider authorization still needs supported operator diagnostics. No model prompt is used for health checking."),
838
+ login("gemini", "Sign in to Gemini", [], "Open Gemini interactively and choose its supported sign-in flow. Provider authorization remains unverified without a supported read-only status adapter."),
839
+ {
840
+ id: "t3",
841
+ title: "Enable and authorize T3 Connect",
842
+ owner: "T3",
843
+ automatic: false,
844
+ action: "Run t3 up --connect in this machine’s terminal and approve the intended connection. Treat pairing material as private. Then check t3 status --json and perform an operator peer test.",
845
+ commands: [["t3", "up", "--connect"]]
846
+ },
847
+ {
848
+ id: "docker",
849
+ title: "Start the intended Docker runtime",
850
+ owner: "OS / Docker",
851
+ automatic: false,
852
+ action: "Start Docker Desktop on desktop systems, or the already-installed Docker service using the OS service manager on Linux. Confirm the intended context with docker context ls. This repair never installs a daemon, invokes sudo, changes contexts or exposes a TCP socket.",
853
+ commands: [
854
+ ["docker", "context", "ls"],
855
+ ["docker", "info"]
856
+ ]
857
+ },
858
+ {
859
+ id: "password-manager",
860
+ title: "Review shared human login onboarding",
861
+ owner: "password-manager / Bitwarden",
862
+ automatic: false,
863
+ action: "Use the existing password-manager doctor and actions list on this computer. Its setup guides native consent and Bitwarden enrollment. Keep its private state and OS credential store local. Password vault access does not authorize native CLI OAuth sessions; imports and rotations remain separate reviewed workflows.",
864
+ commands: [
865
+ ["bun", "run", "password-manager", "doctor"],
866
+ ["bun", "run", "password-manager", "actions", "list"]
867
+ ]
868
+ },
869
+ {
870
+ id: "operator",
871
+ title: "Resolve the check with its owning tool",
872
+ owner: "operator / existing owner",
873
+ automatic: false,
874
+ action: "Follow the check’s remedy on the affected machine. Verify host fingerprints, register real SSH aliases/checkouts, reconcile Git changes, or perform the named end-to-end test. Infrastructure changes remain OpenTofu-owned.",
875
+ commands: []
876
+ }
877
+ ];
878
+ var AUTOMATED_REPAIRS = [
879
+ "workspace",
880
+ "workstation",
881
+ "providers"
882
+ ];
883
+ function recipe(id) {
884
+ return REPAIR_RECIPES.find((entry) => entry.id === id) ?? REPAIR_RECIPES.find((entry) => entry.id === "operator");
885
+ }
886
+
887
+ // packages/fleet/src/repair-runner.ts
888
+ function validRepairs(value) {
889
+ return Array.isArray(value) && value.length > 0 && value.length <= 3 && new Set(value).size === value.length && value.every((id) => AUTOMATED_REPAIRS.includes(id));
890
+ }
891
+ async function repairWorkspace(root, repairs, run, expected) {
892
+ if (!validRepairs(repairs))
893
+ throw new Error("Unsupported repairs");
894
+ const before = await fingerprint(root, run);
895
+ if (!before.head || !Object.values(before.files).every(Boolean))
896
+ return [
897
+ {
898
+ id: "repair:workspace",
899
+ status: "fail",
900
+ summary: "Repairs require a complete existing checkout. Register/bootstrap it first."
901
+ }
902
+ ];
903
+ if (expected && JSON.stringify(before) !== JSON.stringify(expected))
904
+ return [
905
+ {
906
+ id: "repair:inputs",
907
+ status: "fail",
908
+ summary: "Checkout inputs changed since diagnostics. Generate a fresh repair plan before applying."
909
+ }
910
+ ];
911
+ const checks = [];
912
+ for (const id of AUTOMATED_REPAIRS.filter((entry) => repairs.includes(entry))) {
913
+ const result = id === "workspace" ? await syncWorkspace(root, false, run) : await ownerCommand(root, id, run);
914
+ checks.push(...result);
915
+ if (result.some((check) => check.status !== "pass"))
916
+ break;
917
+ }
918
+ const after = await fingerprint(root, run);
919
+ if (JSON.stringify(before) !== JSON.stringify(after))
920
+ checks.push({
921
+ id: "repair:inputs",
922
+ status: "fail",
923
+ summary: "Checkout inputs changed during repair. Stop and review the checkout before retrying."
924
+ });
925
+ return checks;
926
+ }
927
+ async function ownerCommand(root, id, run) {
928
+ const result = await run(recipe(id).commands[0], {
929
+ cwd: root,
930
+ timeoutMs: 1200000
931
+ });
932
+ return [
933
+ {
934
+ id: `repair:${id}`,
935
+ status: result.ok ? "pass" : "fail",
936
+ summary: result.ok ? "The existing owner command completed. Fresh diagnostics determine whether configuration and authorization now pass." : `The owner command failed (${result.reason}); partial changes are possible. Raw output and credentials are withheld. Later repairs on this computer were stopped.`
937
+ }
938
+ ];
939
+ }
940
+
941
+ // packages/fleet/src/protocol.ts
942
+ var STATUSES = ["pass", "warn", "fail", "skip"];
943
+ function validCheck(value) {
944
+ if (!record(value) || typeof value["id"] !== "string" || !/^[a-z0-9:._-]{1,100}$/.test(value["id"]) || !STATUSES.includes(String(value["status"])) || typeof value["summary"] !== "string" || value["summary"].length > 2000)
945
+ return false;
946
+ const remedy = value["remedy"];
947
+ return remedy === undefined || validRemedy(remedy);
948
+ }
949
+ function validRemedy(value) {
950
+ return record(value) && typeof value["action"] === "string" && value["action"].length < 2000 && (value["command"] === undefined || typeof value["command"] === "string");
951
+ }
952
+ function validSnapshot(value) {
953
+ if (!record(value) || value["version"] !== 1 || typeof value["platform"] !== "string" || !Array.isArray(value["checks"]) || value["checks"].length > 200 || !value["checks"].every(validCheck))
954
+ return false;
955
+ return validFingerprint(value["fingerprint"]);
956
+ }
957
+ function validFingerprint(value) {
958
+ if (!record(value) || !record(value["files"]))
959
+ return false;
960
+ if (!Object.keys(value["files"]).every((key) => FINGERPRINT_FILES.includes(key)))
961
+ return false;
962
+ const head = value["head"];
963
+ return (head === null || typeof head === "string" && /^[a-f0-9]{40,64}$/.test(head)) && Object.values(value["files"]).every(validHash);
964
+ }
965
+ function validHash(value) {
966
+ return value === null || typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
967
+ }
968
+
459
969
  // packages/fleet/src/remote-entry.ts
460
970
  async function main() {
461
971
  const raw = JSON.parse(Buffer.from(process.argv.at(-1) ?? "", "base64").toString());
462
- if (!record(raw) || raw["action"] !== "probe" && raw["action"] !== "sync")
972
+ if (!validRequest(raw))
463
973
  throw new Error("Invalid request.");
464
974
  const node = parseConfig({ version: 1, nodes: [raw["node"]] }).nodes[0];
465
- if (raw["action"] === "sync") {
975
+ if (raw["action"] === "configure") {
976
+ await configureRequest(raw, node);
977
+ } else if (raw["action"] === "repair") {
978
+ if (raw["apply"] !== true || !validRepairs(raw["repairs"]) || !validSnapshot({
979
+ version: 1,
980
+ platform: "request",
981
+ checks: [],
982
+ fingerprint: raw["expected"]
983
+ }))
984
+ throw new Error("Repair requires explicit authorization and supported actions.");
985
+ const checks = await repairWorkspace(node.workspace, raw["repairs"], runCommand, raw["expected"]);
986
+ process.stdout.write(JSON.stringify({ version: 1, checks }));
987
+ } else if (raw["action"] === "sync") {
466
988
  if (raw["apply"] !== true)
467
989
  throw new Error("Sync requires explicit authorization.");
468
990
  const checks = await syncWorkspace(node.workspace, raw["pull"] === true, runCommand);
@@ -477,3 +999,12 @@ await main().catch(() => {
477
999
  }));
478
1000
  process.exitCode = 1;
479
1001
  });
1002
+ async function configureRequest(raw, node) {
1003
+ if (raw["apply"] !== true || !validConfiguration(raw["configuration"]) || raw["configuration"].node !== node.id)
1004
+ throw new Error("Invalid configuration request.");
1005
+ const checks = await configureWorkspace(node.workspace, raw["configuration"], runCommand);
1006
+ process.stdout.write(JSON.stringify({ version: 1, checks }));
1007
+ }
1008
+ function validRequest(raw) {
1009
+ return record(raw) && ["probe", "sync", "repair", "configure"].includes(String(raw["action"]));
1010
+ }