@crvouga/dev-fleet 0.0.0-stage → 0.1.0

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.
Files changed (333) hide show
  1. package/.mise/locks/aws-cdk/2.1146.1/aube-lock.yaml +24 -0
  2. package/.mise/locks/aws-cdk/2.1146.1/package.json +7 -0
  3. package/.mise/locks/azure-cli/2.91.0~a837e014/pyproject.toml +5 -0
  4. package/.mise/locks/azure-cli/2.91.0~a837e014/uv.lock +2794 -0
  5. package/.mise/locks/gemini/0.66.0~84aac8e6/aube-lock.yaml +123 -0
  6. package/.mise/locks/gemini/0.66.0~84aac8e6/package.json +12 -0
  7. package/.mise/locks/npm-devcontainers-cli/0.90.0/aube-lock.yaml +24 -0
  8. package/.mise/locks/npm-devcontainers-cli/0.90.0/package.json +7 -0
  9. package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/aube-lock.yaml +1432 -0
  10. package/.mise/locks/npm-modelcontextprotocol-inspector/2.13.0~4c109bea/package.json +7 -0
  11. package/.mise/locks/npm-vercel/65.8.0/aube-lock.yaml +3595 -0
  12. package/.mise/locks/npm-vercel/65.8.0/package.json +7 -0
  13. package/.mise/locks/npm-wrangler/4.156.0/aube-lock.yaml +895 -0
  14. package/.mise/locks/npm-wrangler/4.156.0/package.json +7 -0
  15. package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/aube-lock.yaml +24 -0
  16. package/.mise/locks/npm-yarnpkg-cli-dist/4.18.1/package.json +7 -0
  17. package/LICENSE +21 -0
  18. package/README.md +157 -2
  19. package/agents/antigravity-proxy.ts +3 -0
  20. package/agents/audit.md +89 -0
  21. package/agents/claude-proxy +5 -0
  22. package/agents/claude-proxy.ts +3 -0
  23. package/agents/codex-proxy.ts +3 -0
  24. package/agents/grok-proxy.ts +3 -0
  25. package/agents/managed-proxy.ts +55 -0
  26. package/agents/opencode-proxy.ts +3 -0
  27. package/agents/registry.json +209 -0
  28. package/agents/registry.schema.json +149 -0
  29. package/agents/setup.md +128 -0
  30. package/agents/t3.json +40 -0
  31. package/ansible/macos/manager.yml +30 -0
  32. package/ansible/macos/policy.json +17 -0
  33. package/ansible/monitoring/README.md +154 -0
  34. package/ansible/monitoring/agent-darwin.yml +198 -0
  35. package/ansible/monitoring/agent.yml +134 -0
  36. package/ansible/monitoring/beszel-agent.service +42 -0
  37. package/ansible/monitoring/compose.yml +74 -0
  38. package/ansible/monitoring/hub-darwin.yml +102 -0
  39. package/ansible/monitoring/lock.json +11 -0
  40. package/ansible/net/remote-probe.sh +13 -0
  41. package/ansible/ubuntu/authorize-sudo.sh +51 -0
  42. package/ansible/ubuntu/baseline.yml +89 -0
  43. package/ansible/ubuntu/disk-guard.py +104 -0
  44. package/ansible/ubuntu/policy.json +43 -0
  45. package/ansible/ubuntu/recovery.yml +15 -0
  46. package/ansible/ubuntu/t3/policy.json +7 -0
  47. package/ansible/ubuntu/t3-desktop.yml +220 -0
  48. package/ansible/ubuntu/t3-health.sh +33 -0
  49. package/ansible/ubuntu/t3-policy.yml +76 -0
  50. package/ansible/ubuntu/t3-route.yml +28 -0
  51. package/ansible/ubuntu/tailscale.yml +75 -0
  52. package/ansible/ubuntu/tasks/credentials.yml +47 -0
  53. package/ansible/ubuntu/tasks/docker.yml +31 -0
  54. package/ansible/ubuntu/tasks/packages.yml +56 -0
  55. package/ansible/ubuntu/tasks/power.yml +51 -0
  56. package/ansible/ubuntu/tasks/recovery.yml +39 -0
  57. package/ansible/ubuntu/tasks/resources.yml +252 -0
  58. package/ansible/ubuntu/tasks/services.yml +31 -0
  59. package/ansible/ubuntu/tasks/ssh.yml +71 -0
  60. package/ansible/ubuntu/tasks/user.yml +120 -0
  61. package/bin/devfleet.mjs +36 -0
  62. package/bootstrap/bootstrap.ps1 +50 -0
  63. package/bootstrap/bootstrap.sh +73 -0
  64. package/bootstrap/devfleet +7 -0
  65. package/bootstrap/docker-desktop.sh +33 -0
  66. package/bootstrap/doctor.ts +189 -0
  67. package/bootstrap/fleet-manager-run.sh +76 -0
  68. package/bootstrap/fleet-remote-bootstrap.sh +185 -0
  69. package/bootstrap/global-config.ts +113 -0
  70. package/bootstrap/install-launcher.sh +7 -0
  71. package/bootstrap/native-aws.ts +86 -0
  72. package/bootstrap/native-t3.ts +154 -0
  73. package/bootstrap/npm.ts +350 -0
  74. package/bootstrap/setup-status.ts +51 -0
  75. package/bootstrap/setup.ts +88 -0
  76. package/bootstrap/shell-path.ts +105 -0
  77. package/bootstrap/verify-t3-archive.py +49 -0
  78. package/bootstrap/worker-auth.ts +65 -0
  79. package/bootstrap/worker-github.ts +47 -0
  80. package/bootstrap/worker-tools.ts +24 -0
  81. package/cliproxy/AGENTS.md +11 -0
  82. package/cliproxy/README.md +269 -0
  83. package/cliproxy/clients/bootstrap.py +143 -0
  84. package/cliproxy/clients/launcher.py +57 -0
  85. package/cliproxy/compose.yaml +90 -0
  86. package/cliproxy/config.template.yaml +70 -0
  87. package/cliproxy/docs/cloud-adoption.md +16 -0
  88. package/cliproxy/docs/deployment.png +0 -0
  89. package/cliproxy/docs/deployment.svg +109 -0
  90. package/cliproxy/docs/health-workflow.example.yml +185 -0
  91. package/cliproxy/docs/migration.md +131 -0
  92. package/cliproxy/docs/model-health-report.schema.json +364 -0
  93. package/cliproxy/docs/model-health.md +393 -0
  94. package/cliproxy/image.lock.json +5 -0
  95. package/cliproxy/launchd/dev.devfleet.cliproxy-backup-check.plist +38 -0
  96. package/cliproxy/launchd/dev.devfleet.cliproxy-backup.plist +48 -0
  97. package/cliproxy/launchd/dev.devfleet.cliproxy-fleet.plist +32 -0
  98. package/cliproxy/launchd/dev.devfleet.cliproxy-gc.plist +38 -0
  99. package/cliproxy/launchd/dev.devfleet.cliproxy-monitor.plist +29 -0
  100. package/cliproxy/launchd/dev.devfleet.cliproxy-vault-renew.plist +31 -0
  101. package/cliproxy/models.json +1618 -0
  102. package/cliproxy/requirements.lock +78 -0
  103. package/cliproxy/scripts/accounts.py +38 -0
  104. package/cliproxy/scripts/audit.py +123 -0
  105. package/cliproxy/scripts/backup +234 -0
  106. package/cliproxy/scripts/check.py +34 -0
  107. package/cliproxy/scripts/checkpoint.py +226 -0
  108. package/cliproxy/scripts/cli.py +64 -0
  109. package/cliproxy/scripts/cliproxy_host.py +257 -0
  110. package/cliproxy/scripts/cloud_state.py +150 -0
  111. package/cliproxy/scripts/container-health +6 -0
  112. package/cliproxy/scripts/docker +24 -0
  113. package/cliproxy/scripts/external-health.py +79 -0
  114. package/cliproxy/scripts/fleet_config.py +43 -0
  115. package/cliproxy/scripts/fleet_sync.py +274 -0
  116. package/cliproxy/scripts/initialize +84 -0
  117. package/cliproxy/scripts/install-dependencies +48 -0
  118. package/cliproxy/scripts/install.py +406 -0
  119. package/cliproxy/scripts/model_health.py +16 -0
  120. package/cliproxy/scripts/modelhealth/__init__.py +5 -0
  121. package/cliproxy/scripts/modelhealth/checks.py +429 -0
  122. package/cliproxy/scripts/modelhealth/cli.py +117 -0
  123. package/cliproxy/scripts/modelhealth/config.py +186 -0
  124. package/cliproxy/scripts/modelhealth/emulator.py +536 -0
  125. package/cliproxy/scripts/modelhealth/expect.py +88 -0
  126. package/cliproxy/scripts/modelhealth/findings.py +183 -0
  127. package/cliproxy/scripts/modelhealth/protocol.py +330 -0
  128. package/cliproxy/scripts/modelhealth/redact.py +28 -0
  129. package/cliproxy/scripts/modelhealth/report.py +150 -0
  130. package/cliproxy/scripts/modelhealth/runner.py +390 -0
  131. package/cliproxy/scripts/modelhealth/scenario.py +131 -0
  132. package/cliproxy/scripts/modelhealth/schema.py +63 -0
  133. package/cliproxy/scripts/modelhealth/simulate.py +111 -0
  134. package/cliproxy/scripts/modelhealth/transport.py +213 -0
  135. package/cliproxy/scripts/monitor +68 -0
  136. package/cliproxy/scripts/ops +527 -0
  137. package/cliproxy/scripts/public-endpoint +127 -0
  138. package/cliproxy/scripts/recover_node.py +177 -0
  139. package/cliproxy/scripts/restore-verify +60 -0
  140. package/cliproxy/scripts/retention.py +144 -0
  141. package/cliproxy/scripts/service-start +7 -0
  142. package/cliproxy/scripts/validate-config +56 -0
  143. package/cliproxy/scripts/vault-access.py +57 -0
  144. package/cliproxy/scripts/vault-backup-login +55 -0
  145. package/cliproxy/scripts/vault-recovery +56 -0
  146. package/cliproxy/scripts/vault-renew +30 -0
  147. package/cliproxy/systemd/cliproxy-backup-check.service +10 -0
  148. package/cliproxy/systemd/cliproxy-backup-check.timer +8 -0
  149. package/cliproxy/systemd/cliproxy-backup.service +13 -0
  150. package/cliproxy/systemd/cliproxy-backup.timer +8 -0
  151. package/cliproxy/systemd/cliproxy-fleet.service +17 -0
  152. package/cliproxy/systemd/cliproxy-gc.service +13 -0
  153. package/cliproxy/systemd/cliproxy-gc.timer +8 -0
  154. package/cliproxy/systemd/cliproxy-monitor.service +8 -0
  155. package/cliproxy/systemd/cliproxy-monitor.timer +7 -0
  156. package/cliproxy/systemd/cliproxy-vault-renew.service +8 -0
  157. package/cliproxy/systemd/cliproxy-vault-renew.timer +7 -0
  158. package/cliproxy/vendor/LICENSE +22 -0
  159. package/cliproxy/vendor/config.upstream.yaml +1349 -0
  160. package/examples/fleet.json +95 -0
  161. package/examples/roster.json +13 -0
  162. package/mise.lock +1492 -0
  163. package/mise.toml +99 -0
  164. package/package.json +65 -4
  165. package/schema/fleet.schema.json +364 -0
  166. package/schema/roster.schema.json +77 -0
  167. package/src/agents/account-relay.ts +111 -0
  168. package/src/agents/accounts.ts +85 -0
  169. package/src/agents/audit.ts +77 -0
  170. package/src/agents/cli.ts +13 -0
  171. package/src/agents/command.ts +204 -0
  172. package/src/agents/converge.ts +123 -0
  173. package/src/agents/doctor.ts +107 -0
  174. package/src/agents/errors.ts +2 -0
  175. package/src/agents/files.ts +73 -0
  176. package/src/agents/fleet-verify.ts +156 -0
  177. package/src/agents/launch.ts +290 -0
  178. package/src/agents/native-verify.ts +226 -0
  179. package/src/agents/provider-errors.ts +50 -0
  180. package/src/agents/registry.ts +55 -0
  181. package/src/agents/render.ts +110 -0
  182. package/src/agents/round-trip.ts +192 -0
  183. package/src/agents/secrets.ts +13 -0
  184. package/src/agents/toolchain.ts +34 -0
  185. package/src/agents/verify-cache.ts +103 -0
  186. package/src/agents/verify.ts +127 -0
  187. package/src/auth-checks.ts +163 -0
  188. package/src/cli/agent-guide.ts +95 -0
  189. package/src/cli/app.ts +115 -0
  190. package/src/cli/cli.ts +21 -0
  191. package/src/cli/command-index.ts +50 -0
  192. package/src/cli/contracts.ts +25 -0
  193. package/src/cli/fleet-reference.ts +17 -0
  194. package/src/cli/fleet-sync-checkout.ts +91 -0
  195. package/src/cli/fleet-sync-enrollment.ts +144 -0
  196. package/src/cli/fleet-sync-forward.ts +134 -0
  197. package/src/cli/fleet-sync-manager.ts +193 -0
  198. package/src/cli/fleet-sync-monitoring.ts +185 -0
  199. package/src/cli/fleet-sync-output.ts +314 -0
  200. package/src/cli/fleet-sync-privilege.ts +27 -0
  201. package/src/cli/fleet-sync-progress.ts +109 -0
  202. package/src/cli/fleet-sync-remote.ts +236 -0
  203. package/src/cli/fleet-sync-report.ts +26 -0
  204. package/src/cli/fleet-sync-schedule.ts +138 -0
  205. package/src/cli/fleet-sync-vault.ts +166 -0
  206. package/src/cli/fleet-sync.ts +309 -0
  207. package/src/cli/fleet.ts +163 -0
  208. package/src/cli/managed-health.ts +219 -0
  209. package/src/cli/node-deployment.ts +34 -0
  210. package/src/cli/node-sync.ts +114 -0
  211. package/src/cli/node-t3.ts +31 -0
  212. package/src/cli/options.ts +141 -0
  213. package/src/cli/probe-source.ts +7 -0
  214. package/src/cli/program.ts +169 -0
  215. package/src/cli/register-fleet.ts +246 -0
  216. package/src/cli/registry.ts +158 -0
  217. package/src/cli/repair-apply.ts +67 -0
  218. package/src/cli/repair-guard.ts +11 -0
  219. package/src/cli/repair-inspect.ts +150 -0
  220. package/src/cli/repair-script.ts +76 -0
  221. package/src/cli/repair.ts +209 -0
  222. package/src/cli/roster.ts +89 -0
  223. package/src/cli/search.ts +35 -0
  224. package/src/cli/settings.ts +64 -0
  225. package/src/cli/source-operations.ts +78 -0
  226. package/src/cli/t3/command.ts +90 -0
  227. package/src/cli/t3/register.ts +55 -0
  228. package/src/cli/tailnet.ts +127 -0
  229. package/src/cli/ui-actions.ts +133 -0
  230. package/src/cli/ui-controller.ts +113 -0
  231. package/src/cli/ui-data.ts +106 -0
  232. package/src/cli/ui-execute.ts +163 -0
  233. package/src/cli/ui-repairs.ts +34 -0
  234. package/src/cli/ui-reports.ts +211 -0
  235. package/src/cli/ui-targets.ts +100 -0
  236. package/src/cli/ui-terminal.ts +109 -0
  237. package/src/cli/ui-types.ts +49 -0
  238. package/src/cli/ui-wizard.ts +101 -0
  239. package/src/cli/ui.ts +9 -0
  240. package/src/cli/worker-health-output.ts +37 -0
  241. package/src/config.ts +186 -0
  242. package/src/configure.ts +255 -0
  243. package/src/definition.ts +260 -0
  244. package/src/diagnostics.ts +92 -0
  245. package/src/doctor-check.ts +10 -0
  246. package/src/errors.ts +1 -0
  247. package/src/fingerprint.ts +120 -0
  248. package/src/fleet-config.ts +88 -0
  249. package/src/index.ts +6 -0
  250. package/src/input-hashes.ts +73 -0
  251. package/src/machine/local-source.ts +52 -0
  252. package/src/machine/lock-error.ts +2 -0
  253. package/src/machine/platform.ts +44 -0
  254. package/src/machine/source.ts +68 -0
  255. package/src/machine/state.ts +185 -0
  256. package/src/machine-health.ts +95 -0
  257. package/src/managed-source.ts +47 -0
  258. package/src/manager-failure.ts +48 -0
  259. package/src/manager-sync.ts +400 -0
  260. package/src/manager.ts +62 -0
  261. package/src/monitoring/apply.ts +171 -0
  262. package/src/monitoring/beszel.ts +191 -0
  263. package/src/monitoring/enroll.ts +110 -0
  264. package/src/monitoring/hub-access.ts +113 -0
  265. package/src/monitoring/hub-viewer.ts +142 -0
  266. package/src/monitoring/hub.ts +93 -0
  267. package/src/monitoring/manager-agent.ts +73 -0
  268. package/src/monitoring/manager-hub.ts +232 -0
  269. package/src/monitoring/remote.ts +65 -0
  270. package/src/network-definition.ts +83 -0
  271. package/src/node-converge-linux.ts +372 -0
  272. package/src/node-converge-shared.ts +83 -0
  273. package/src/node-converge.ts +40 -0
  274. package/src/onboard.ts +35 -0
  275. package/src/platform/ansible-receipt.ts +128 -0
  276. package/src/platform/ansible.ts +62 -0
  277. package/src/platform/darwin.ts +203 -0
  278. package/src/platform/detect.ts +11 -0
  279. package/src/platform/fallback.ts +110 -0
  280. package/src/platform/index.ts +38 -0
  281. package/src/platform/launchd.ts +98 -0
  282. package/src/platform/linux.ts +139 -0
  283. package/src/platform/ps.ts +45 -0
  284. package/src/platform/resource-verify.ts +128 -0
  285. package/src/platform/systemd-user.ts +95 -0
  286. package/src/platform/t3-policy.ts +137 -0
  287. package/src/platform/t3-route.ts +68 -0
  288. package/src/platform/t3-service.ts +75 -0
  289. package/src/platform/types.ts +123 -0
  290. package/src/platform/ubuntu-audit.ts +63 -0
  291. package/src/platform/ubuntu-verify.ts +105 -0
  292. package/src/platform/windows.ts +148 -0
  293. package/src/power-recovery.ts +55 -0
  294. package/src/probe.ts +51 -0
  295. package/src/protocol.ts +119 -0
  296. package/src/proxy-converge.ts +85 -0
  297. package/src/remote-bootstrap.ts +212 -0
  298. package/src/remote-entry.ts +89 -0
  299. package/src/remote-lock.ts +12 -0
  300. package/src/repair-catalog.ts +206 -0
  301. package/src/repair-plan.ts +78 -0
  302. package/src/repair-runner.ts +84 -0
  303. package/src/report.ts +71 -0
  304. package/src/runner.ts +104 -0
  305. package/src/secrets.ts +169 -0
  306. package/src/service-auth.ts +115 -0
  307. package/src/ssh.ts +135 -0
  308. package/src/sync.ts +104 -0
  309. package/src/t3/accounts.ts +81 -0
  310. package/src/t3/desired.ts +165 -0
  311. package/src/t3/files.ts +81 -0
  312. package/src/t3/names-check.ts +53 -0
  313. package/src/t3/names.ts +155 -0
  314. package/src/t3/plan.ts +82 -0
  315. package/src/t3/providers.ts +74 -0
  316. package/src/t3/reconcile.ts +123 -0
  317. package/src/t3-endpoint.ts +48 -0
  318. package/src/t3-linked-check.ts +101 -0
  319. package/src/tailnet-health.ts +231 -0
  320. package/src/tailnet-matching.ts +37 -0
  321. package/src/tailscale.ts +141 -0
  322. package/src/test-fixtures.ts +36 -0
  323. package/src/toolchain-checks.ts +83 -0
  324. package/src/topology.ts +24 -0
  325. package/src/transport.ts +122 -0
  326. package/src/types.ts +95 -0
  327. package/src/vault-config.ts +38 -0
  328. package/src/worker-access-checks.ts +115 -0
  329. package/src/worker-capacity.ts +34 -0
  330. package/src/worker-host-checks.ts +176 -0
  331. package/src/worker-policy.ts +64 -0
  332. package/src/worker-readiness.ts +64 -0
  333. package/src/workstation-checks.ts +29 -0
@@ -0,0 +1,34 @@
1
+ import { join } from 'node:path';
2
+ import { read, object, write } from '../t3/files';
3
+ import { FleetUsageError } from '../errors';
4
+
5
+ export async function deployment(root: string) {
6
+ const raw = await read(join(root, 'node_modules/.personal-node.json'));
7
+ if (raw === null) return null;
8
+ const data = object(raw);
9
+ if (
10
+ data['version'] !== 1 ||
11
+ typeof data['revision'] !== 'string' ||
12
+ !/^[a-f0-9]{40}$/.test(data['revision']) ||
13
+ typeof data['node'] !== 'string' ||
14
+ !/^[a-z0-9][a-z0-9-]{0,62}$/.test(data['node'])
15
+ )
16
+ throw new FleetUsageError(
17
+ 'Invalid local node receipt. Restore it before reconciling.'
18
+ );
19
+ return {
20
+ workspace: join(root, 'node_modules/.fleet-revisions', data['revision']),
21
+ node: data['node'],
22
+ revision: data['revision'],
23
+ };
24
+ }
25
+
26
+ export async function recordDeployment(
27
+ root: string,
28
+ node: string,
29
+ revision: string
30
+ ) {
31
+ const path = join(root, 'node_modules/.personal-node.json');
32
+ const content = JSON.stringify({ version: 1, node, revision }) + '\n';
33
+ if ((await read(path)) !== content) await write(path, content);
34
+ }
@@ -0,0 +1,114 @@
1
+ import { join } from 'node:path';
2
+ import { configureWorkspace } from '../configure';
3
+ import { runCommand } from '../runner';
4
+ import { FleetUsageError } from '../errors';
5
+ import type { Runner } from '../types';
6
+ import type { Settings } from './settings';
7
+ import type { CommandOutcome } from './contracts';
8
+ import { syncT3 } from './node-t3';
9
+ import { nodeId, type T3Options } from '../t3/desired';
10
+ import { recordDeployment } from './node-deployment';
11
+
12
+ export interface NodeSyncOptions extends T3Options {
13
+ revision?: string;
14
+ }
15
+
16
+ /** One local convergence: locked tools, public launcher, profiles, then T3. */
17
+ export async function syncNode(
18
+ options: NodeSyncOptions,
19
+ settings: Settings,
20
+ run: Runner = runCommand
21
+ ): Promise<CommandOutcome> {
22
+ if (process.platform === 'win32' && !options.dryRun)
23
+ throw new FleetUsageError(
24
+ 'Codex proxy node sync requires macOS/Linux/WSL. Use dev-fleet commands for native Windows configuration.'
25
+ );
26
+ const node = nodeId(options.id);
27
+ const head = await run(['git', 'rev-parse', '--verify', 'HEAD'], {
28
+ cwd: settings.workspace,
29
+ });
30
+ const revision = options.revision ?? head.stdout.trim();
31
+ if (!head.ok || !/^[a-f0-9]{40}$/.test(revision))
32
+ throw new FleetUsageError(
33
+ 'An existing personal checkout is required; set --workspace PATH.'
34
+ );
35
+ const request = { node, revision, tool: 'codex', profile: 'proxy' };
36
+ if (options.dryRun)
37
+ return {
38
+ exitCode: 0,
39
+ data: {
40
+ version: 1,
41
+ command: 'fleet-node-sync',
42
+ ok: true,
43
+ applied: false,
44
+ ...request,
45
+ steps: [
46
+ 'Reconcile locked tools',
47
+ 'Install personal and retire owned ws launcher',
48
+ 'Upsert Codex proxy profile',
49
+ 'Reconcile declared T3 instances and remove stale owned entries',
50
+ ],
51
+ },
52
+ };
53
+ const checks = await configureWorkspace(settings.workspace, request, run);
54
+ if (checks.some((check) => check.status !== 'pass'))
55
+ return {
56
+ exitCode: 1,
57
+ data: { version: 1, command: 'fleet-node-sync', ok: false, checks },
58
+ };
59
+ return finishSync(options, settings, { request, checks }, run);
60
+ }
61
+
62
+ async function finishSync(
63
+ options: NodeSyncOptions,
64
+ settings: Settings,
65
+ input: {
66
+ request: { node: string; revision: string; tool: string; profile: string };
67
+ checks: Awaited<ReturnType<typeof configureWorkspace>>;
68
+ },
69
+ run: Runner
70
+ ): Promise<CommandOutcome> {
71
+ const { request, checks } = input;
72
+ const { node, revision } = request;
73
+ const workspace = join(
74
+ settings.workspace,
75
+ 'node_modules/.fleet-revisions',
76
+ revision
77
+ );
78
+ const installed = await run(['sh', 'bootstrap/install-launcher.sh'], {
79
+ cwd: workspace,
80
+ timeoutMs: 30_000,
81
+ });
82
+ if (!installed.ok)
83
+ return {
84
+ exitCode: 1,
85
+ data: {
86
+ version: 1,
87
+ ok: false,
88
+ checks,
89
+ error:
90
+ 'Launcher installation failed; profile changes may already have applied.',
91
+ },
92
+ };
93
+ const t3 = await syncT3({ ...options, id: node }, { ...settings, workspace });
94
+ if (t3.exitCode !== 0) return t3;
95
+ await recordDeployment(settings.workspace, node, revision);
96
+ return {
97
+ exitCode: 0,
98
+ data: {
99
+ version: 1,
100
+ command: 'fleet-node-sync',
101
+ ok: true,
102
+ applied: true,
103
+ ...request,
104
+ checks,
105
+ t3: t3.data,
106
+ readiness: {
107
+ authentication: 'unverified',
108
+ inference: 'unverified',
109
+ pairing: 'unverified',
110
+ },
111
+ next: 'Use devfleet t3-status for configuration drift and devfleet fleet-status for machine health.',
112
+ },
113
+ };
114
+ }
@@ -0,0 +1,31 @@
1
+ import { FleetUsageError } from '../errors';
2
+ import type { Settings } from './settings';
3
+ import type { CommandOutcome } from './contracts';
4
+ import { t3Command } from './t3/command';
5
+ import type { T3Options } from '../t3/desired';
6
+
7
+ export async function syncT3(
8
+ options: T3Options,
9
+ settings: Settings
10
+ ): Promise<CommandOutcome> {
11
+ try {
12
+ return await t3Command('t3-apply', options, settings);
13
+ } catch (error) {
14
+ return {
15
+ exitCode: 1,
16
+ data: {
17
+ version: 1,
18
+ command: 'fleet-node-sync',
19
+ ok: false,
20
+ applied: true,
21
+ stage: 't3',
22
+ error:
23
+ error instanceof FleetUsageError
24
+ ? error.message
25
+ : 'T3 reconciliation failed; inspect local configuration and ownership state.',
26
+ progress:
27
+ 'Tools, launcher and profile steps completed. Node receipt was not advanced. Fix the T3 conflict and repeat fleet-node-sync.',
28
+ },
29
+ };
30
+ }
31
+ }
@@ -0,0 +1,141 @@
1
+ import { CAPABILITIES } from '../config';
2
+ import { FleetUsageError } from '../errors';
3
+ import type { Capability } from '../types';
4
+ import type { Settings } from './settings';
5
+
6
+ export interface CliOptions extends Partial<Settings> {
7
+ managed?: boolean;
8
+ workers?: boolean;
9
+ tailnet?: boolean;
10
+ list?: boolean;
11
+ onboard?: boolean;
12
+ schema?: boolean;
13
+ json?: boolean;
14
+ nonInteractive?: boolean;
15
+ interactive?: boolean;
16
+ local?: boolean;
17
+ all?: boolean;
18
+ node?: string[];
19
+ require?: string;
20
+ timeout?: string;
21
+ strict?: boolean;
22
+ repair?: string;
23
+ script?: boolean;
24
+ planHash?: string;
25
+ apply?: boolean;
26
+ yes?: boolean;
27
+ id?: string;
28
+ ssh?: string;
29
+ path?: string;
30
+ runtime?: string;
31
+ search?: string;
32
+ group?: string;
33
+ }
34
+
35
+ export function capabilities(value?: string): Capability[] {
36
+ const values =
37
+ value === 'none'
38
+ ? []
39
+ : (value?.split(',') ??
40
+ CAPABILITIES.filter((entry) => entry !== 'worker'));
41
+ if (values.some((entry) => !CAPABILITIES.includes(entry as Capability)))
42
+ throw new FleetUsageError(
43
+ `--require expects ${CAPABILITIES.join(',')} or none.`
44
+ );
45
+ return [...new Set(values)] as Capability[];
46
+ }
47
+
48
+ export function timeout(options: CliOptions): number {
49
+ const value = Number(
50
+ options.timeout ?? (options.apply ? 1_500_000 : 120_000)
51
+ );
52
+ if (!Number.isInteger(value) || value < 1000 || value > 1_800_000)
53
+ throw new FleetUsageError('--timeout must be 1000–1800000 milliseconds.');
54
+ return value;
55
+ }
56
+
57
+ /** Validation runs on the internal action selected by fleet-* flags. */
58
+ export function validateOptions(command: string, options: CliOptions): void {
59
+ if (options.tailnet && options.workers)
60
+ throw new FleetUsageError('Choose --tailnet or --workers, not both.');
61
+ if (options.onboard && options.schema)
62
+ throw new FleetUsageError('Choose --onboard or --schema, not both.');
63
+ validateMutation(command, options);
64
+ validateRepair(command, options);
65
+ validateScope(command, options);
66
+ if (
67
+ (['discover', 'health'].includes(command) ||
68
+ (command === 'repair' && !options.local)) &&
69
+ options.require !== undefined
70
+ )
71
+ throw new FleetUsageError(
72
+ '--require is supported for roster status and local repair only; tailnet checks use declared roster requirements.'
73
+ );
74
+ if (options.timeout !== undefined) timeout(options);
75
+ }
76
+
77
+ function validateMutation(command: string, options: CliOptions): void {
78
+ if (options.apply && !['add', 'repair'].includes(command))
79
+ throw new FleetUsageError(
80
+ '--apply is only valid for fleet-node-add and fleet-repair.'
81
+ );
82
+ if (options.apply && !options.yes)
83
+ throw new FleetUsageError('Mutation requires both --apply and --yes.');
84
+ }
85
+
86
+ function validateScope(command: string, options: CliOptions): void {
87
+ const selected = [options.local, options.all, options.node?.length].filter(
88
+ Boolean
89
+ );
90
+ if (['discover', 'health'].includes(command) && selected.length)
91
+ throw new FleetUsageError(
92
+ 'Tailscale discovery, --tailnet and --workers always cover all visible computers; --local, --all and --node are not supported.'
93
+ );
94
+ if (selected.length > 1)
95
+ throw new FleetUsageError(
96
+ 'Choose --local, --all, or --node, not a combination.'
97
+ );
98
+ validateRosterScope(command, options);
99
+ }
100
+
101
+ function validateRosterScope(command: string, options: CliOptions): void {
102
+ if ((options.local || options.all) && !['doctor', 'repair'].includes(command))
103
+ throw new FleetUsageError(
104
+ '--local and --all are only valid for fleet-status and fleet-repair.'
105
+ );
106
+ }
107
+
108
+ function validateRepair(command: string, options: CliOptions): void {
109
+ if (
110
+ options.planHash !== undefined &&
111
+ !/^[a-f0-9]{64}$/.test(options.planHash)
112
+ )
113
+ throw new FleetUsageError(
114
+ '--plan-hash expects the SHA-256 value from a repair preview.'
115
+ );
116
+ const repairFlags =
117
+ options.repair !== undefined ||
118
+ options.script === true ||
119
+ options.planHash !== undefined;
120
+ if (repairFlags && command !== 'repair')
121
+ throw new FleetUsageError(
122
+ '--repair, --script and --plan-hash are only valid for fleet-repair without --list.'
123
+ );
124
+ if (options.script && options.apply)
125
+ throw new FleetUsageError(
126
+ '--script only generates a preview; run a reviewed repair separately with --apply --yes.'
127
+ );
128
+ validateCatalog(command, options);
129
+ }
130
+
131
+ function validateCatalog(command: string, options: CliOptions): void {
132
+ const filtered =
133
+ options.local ||
134
+ options.all ||
135
+ Boolean(options.node?.length) ||
136
+ options.require !== undefined;
137
+ if (command === 'repairs' && filtered)
138
+ throw new FleetUsageError(
139
+ 'fleet-repair --list is a static catalog and does not accept target or capability filters.'
140
+ );
141
+ }
@@ -0,0 +1,7 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { bundleProbe } from '../transport';
3
+
4
+ export async function probeSource(): Promise<string> {
5
+ if (typeof Bun !== 'undefined') return bundleProbe();
6
+ return readFile(new URL('./probe.mjs', import.meta.url), 'utf8');
7
+ }
@@ -0,0 +1,169 @@
1
+ import { Command, CommanderError } from 'commander';
2
+ import { FleetUsageError } from '../errors';
3
+ import { indexedCommands } from './command-index';
4
+ import { registerT3 } from './t3/register';
5
+ import { registerSourceOperations } from './source-operations';
6
+ import { registerFleet } from './register-fleet';
7
+ import { agentGuide } from './agent-guide';
8
+ import type { Settings } from './settings';
9
+ import type { CliOptions } from './options';
10
+ import type { CommandOutcome } from './contracts';
11
+
12
+ export interface Release {
13
+ version: string;
14
+ /** ISO timestamp recorded when this distribution was built; null for source runs. */
15
+ releasedAt: string | null;
16
+ }
17
+
18
+ export const GLOBAL_OPTIONS = [
19
+ {
20
+ flag: '--json',
21
+ description: 'One versioned JSON document on stdout; never prompt',
22
+ },
23
+ { flag: '--non-interactive', description: 'Never open interactive prompts' },
24
+ {
25
+ flag: '--workspace <path>',
26
+ description: 'Existing manager checkout; does not clone or change it',
27
+ },
28
+ { flag: '--config <path>', description: 'Local, secret-free fleet roster' },
29
+ ];
30
+
31
+ /** The version a checkout carries; a published package has a real one. */
32
+ export const SOURCE_VERSION = '0.0.0-development';
33
+
34
+ export function releaseLabel(release: Release): string {
35
+ if (release.releasedAt)
36
+ return `${release.version} (released ${release.releasedAt})`;
37
+ return release.version === SOURCE_VERSION
38
+ ? `${release.version} (unreleased source build)`
39
+ : release.version;
40
+ }
41
+
42
+ function createProgram(
43
+ release: Release,
44
+ output: (text: string) => void
45
+ ): Command {
46
+ const program = new Command()
47
+ .name('devfleet')
48
+ .description('Developer fleet, T3 and platform operations')
49
+ .version(releaseLabel(release))
50
+ .exitOverride()
51
+ .configureOutput({ writeOut: output, writeErr: () => undefined })
52
+ .showSuggestionAfterError(false)
53
+ .addHelpCommand('help [command]', 'Show help for a command')
54
+ .option(
55
+ '--interactive',
56
+ 'Open the interactive terminal application (TTY required)'
57
+ );
58
+ for (const option of GLOBAL_OPTIONS)
59
+ program.option(option.flag, option.description);
60
+ program.addHelpText(
61
+ 'after',
62
+ [
63
+ '',
64
+ `Release: ${releaseLabel(release)}`,
65
+ 'Groups: fleet-* developer computers · t3-* T3 provider instances · cliproxy the CLI proxy service.',
66
+ 'Apply on invocation (--dry-run previews): fleet-node-sync, t3-apply, t3-cleanup. fleet-sync confirms interactively or with --apply --yes.',
67
+ 'Preview until --apply --yes: fleet-repair, fleet-node-add. --json does not make a command read-only.',
68
+ 'Agents: devfleet commands --json. Menu: devfleet --interactive.',
69
+ ].join('\n')
70
+ );
71
+ return program;
72
+ }
73
+
74
+ function catalog(options: CliOptions, release: Release): CommandOutcome {
75
+ const commands = indexedCommands(options.search, options.group);
76
+ return {
77
+ exitCode: 0,
78
+ data: {
79
+ version: 1,
80
+ cliVersion: release.version,
81
+ releasedAt: release.releasedAt,
82
+ command: 'commands',
83
+ ok: true,
84
+ commands,
85
+ globalOptions: GLOBAL_OPTIONS,
86
+ defaultCommand: 'help',
87
+ schema: 'devfleet fleet-nodes --schema --json',
88
+ agent: agentGuide(release),
89
+ },
90
+ text: [
91
+ `Personal command catalog · ${releaseLabel(release)}`,
92
+ ...commands.map((entry) => `\n ${entry.id}\n ${entry.description}`),
93
+ '\nUse personal <command> --help for options; personal --interactive opens the menu.',
94
+ ].join('\n'),
95
+ };
96
+ }
97
+
98
+ export async function execute(
99
+ args: string[],
100
+ defaults: Settings,
101
+ version: string | Release
102
+ ): Promise<CommandOutcome> {
103
+ const release: Release =
104
+ typeof version === 'string' ? { version, releasedAt: null } : version;
105
+ let outcome: CommandOutcome | undefined;
106
+ let output = '';
107
+ const program = createProgram(release, (text) => {
108
+ output += text;
109
+ });
110
+ const complete = (result: CommandOutcome) => {
111
+ outcome = result;
112
+ };
113
+ registerFleet(program, defaults, complete);
114
+ registerT3(program, defaults, complete);
115
+ registerSourceOperations(program, defaults, complete);
116
+ program
117
+ .command('commands')
118
+ .description('Search the command catalog and agent contract')
119
+ .option('--search <text>', 'Filter names, descriptions and keywords')
120
+ .option(
121
+ '--group <name>',
122
+ 'Filter group: Fleet, T3, Infra, Workstation, Reference'
123
+ )
124
+ .action((_options, command: Command) => {
125
+ outcome = catalog(command.optsWithGlobals<CliOptions>(), release);
126
+ });
127
+ program.action(() => {
128
+ if (program.opts<CliOptions>().json)
129
+ outcome = catalog(program.opts<CliOptions>(), release);
130
+ else program.help();
131
+ });
132
+ try {
133
+ await program.parseAsync(args, { from: 'user' });
134
+ return (
135
+ outcome ?? {
136
+ exitCode: 0,
137
+ data: { version: 1, help: output.trim() },
138
+ text: output.trim(),
139
+ }
140
+ );
141
+ } catch (error) {
142
+ return commandError(error, output);
143
+ }
144
+ }
145
+
146
+ function commandError(error: unknown, output: string): CommandOutcome {
147
+ if (error instanceof CommanderError && error.exitCode === 0)
148
+ return {
149
+ exitCode: 0,
150
+ data: { version: 1, help: output.trim() },
151
+ text: output.trim(),
152
+ };
153
+ const usage =
154
+ error instanceof CommanderError || error instanceof FleetUsageError;
155
+ return {
156
+ exitCode: usage ? 2 : 1,
157
+ data: {
158
+ version: 1,
159
+ ok: false,
160
+ code: usage ? 'invalid_usage' : 'operation_failed',
161
+ error:
162
+ error instanceof FleetUsageError
163
+ ? error.message
164
+ : usage
165
+ ? 'Invalid command or option. Run personal --help or personal commands --json.'
166
+ : 'Operation failed. Inspect configuration and permissions locally; raw errors are withheld to protect credentials.',
167
+ },
168
+ };
169
+ }