@matthesketh/fleet 1.8.1 → 1.11.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.
Files changed (230) hide show
  1. package/README.md +186 -16
  2. package/dist/bin/fleet-agent.d.ts +2 -0
  3. package/dist/bin/fleet-agent.js +7 -0
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +73 -31
  6. package/dist/commands/add.d.ts +2 -1
  7. package/dist/commands/add.js +66 -59
  8. package/dist/commands/audit.d.ts +1 -0
  9. package/dist/commands/audit.js +144 -0
  10. package/dist/commands/backup.d.ts +1 -0
  11. package/dist/commands/backup.js +510 -0
  12. package/dist/commands/boot-start.d.ts +3 -1
  13. package/dist/commands/boot-start.js +39 -47
  14. package/dist/commands/completions.d.ts +6 -0
  15. package/dist/commands/completions.js +83 -0
  16. package/dist/commands/config.d.ts +16 -0
  17. package/dist/commands/config.js +96 -0
  18. package/dist/commands/deploy.js +3 -2
  19. package/dist/commands/deps.js +5 -1
  20. package/dist/commands/doctor.d.ts +32 -0
  21. package/dist/commands/doctor.js +186 -0
  22. package/dist/commands/egress.d.ts +1 -1
  23. package/dist/commands/egress.js +13 -10
  24. package/dist/commands/freeze.d.ts +8 -4
  25. package/dist/commands/freeze.js +77 -59
  26. package/dist/commands/git.js +2 -2
  27. package/dist/commands/health.d.ts +2 -1
  28. package/dist/commands/health.js +38 -56
  29. package/dist/commands/init.d.ts +2 -1
  30. package/dist/commands/init.js +83 -73
  31. package/dist/commands/install-mcp.d.ts +3 -1
  32. package/dist/commands/install-mcp.js +53 -34
  33. package/dist/commands/list.d.ts +2 -1
  34. package/dist/commands/list.js +22 -19
  35. package/dist/commands/logs.js +1 -1
  36. package/dist/commands/patch-systemd.d.ts +7 -1
  37. package/dist/commands/patch-systemd.js +71 -31
  38. package/dist/commands/remove.d.ts +3 -1
  39. package/dist/commands/remove.js +37 -26
  40. package/dist/commands/restart.d.ts +4 -1
  41. package/dist/commands/restart.js +17 -20
  42. package/dist/commands/rollback.d.ts +4 -1
  43. package/dist/commands/rollback.js +33 -42
  44. package/dist/commands/secrets.js +157 -9
  45. package/dist/commands/start.d.ts +4 -1
  46. package/dist/commands/start.js +17 -20
  47. package/dist/commands/status.d.ts +1 -1
  48. package/dist/commands/status.js +21 -26
  49. package/dist/commands/stop.d.ts +4 -1
  50. package/dist/commands/stop.js +17 -20
  51. package/dist/commands/testflight.d.ts +1 -0
  52. package/dist/commands/testflight.js +193 -0
  53. package/dist/commands/update.d.ts +16 -0
  54. package/dist/commands/update.js +95 -0
  55. package/dist/core/audit/cache.d.ts +4 -0
  56. package/dist/core/audit/cache.js +37 -0
  57. package/dist/core/audit/config.d.ts +5 -0
  58. package/dist/core/audit/config.js +35 -0
  59. package/dist/core/audit/greenlight.d.ts +11 -0
  60. package/dist/core/audit/greenlight.js +81 -0
  61. package/dist/core/audit/reporters/cli.d.ts +3 -0
  62. package/dist/core/audit/reporters/cli.js +68 -0
  63. package/dist/core/audit/suppress.d.ts +6 -0
  64. package/dist/core/audit/suppress.js +37 -0
  65. package/dist/core/audit/target.d.ts +5 -0
  66. package/dist/core/audit/target.js +26 -0
  67. package/dist/core/audit/types.d.ts +54 -0
  68. package/dist/core/audit/types.js +5 -0
  69. package/dist/core/backup/browser-api.d.ts +66 -0
  70. package/dist/core/backup/browser-api.js +197 -0
  71. package/dist/core/backup/browser-server.d.ts +11 -0
  72. package/dist/core/backup/browser-server.js +241 -0
  73. package/dist/core/backup/browser-ui.d.ts +5 -0
  74. package/dist/core/backup/browser-ui.js +268 -0
  75. package/dist/core/backup/cloudflare.d.ts +7 -0
  76. package/dist/core/backup/cloudflare.js +82 -0
  77. package/dist/core/backup/config.d.ts +9 -0
  78. package/dist/core/backup/config.js +80 -0
  79. package/dist/core/backup/detect.d.ts +11 -0
  80. package/dist/core/backup/detect.js +71 -0
  81. package/dist/core/backup/dump.d.ts +11 -0
  82. package/dist/core/backup/dump.js +82 -0
  83. package/dist/core/backup/index.d.ts +9 -0
  84. package/dist/core/backup/index.js +9 -0
  85. package/dist/core/backup/repo.d.ts +71 -0
  86. package/dist/core/backup/repo.js +256 -0
  87. package/dist/core/backup/schedule.d.ts +17 -0
  88. package/dist/core/backup/schedule.js +90 -0
  89. package/dist/core/backup/sensitive.d.ts +5 -0
  90. package/dist/core/backup/sensitive.js +37 -0
  91. package/dist/core/backup/status.d.ts +3 -0
  92. package/dist/core/backup/status.js +29 -0
  93. package/dist/core/backup/statuspage.d.ts +23 -0
  94. package/dist/core/backup/statuspage.js +145 -0
  95. package/dist/core/backup/system.d.ts +24 -0
  96. package/dist/core/backup/system.js +209 -0
  97. package/dist/core/backup/totp.d.ts +16 -0
  98. package/dist/core/backup/totp.js +116 -0
  99. package/dist/core/backup/types.d.ts +70 -0
  100. package/dist/core/backup/types.js +7 -0
  101. package/dist/core/backup/unlock.d.ts +19 -0
  102. package/dist/core/backup/unlock.js +69 -0
  103. package/dist/core/boot-refresh.d.ts +1 -1
  104. package/dist/core/boot-refresh.js +10 -9
  105. package/dist/core/deps/actors/pr-creator.d.ts +5 -3
  106. package/dist/core/deps/actors/pr-creator.js +71 -18
  107. package/dist/core/deps/collectors/fetch-with-timeout.d.ts +7 -0
  108. package/dist/core/deps/collectors/fetch-with-timeout.js +16 -0
  109. package/dist/core/deps/collectors/npm.js +3 -1
  110. package/dist/core/deps/collectors/vulnerability.d.ts +8 -0
  111. package/dist/core/deps/collectors/vulnerability.js +31 -2
  112. package/dist/core/deps/config.js +6 -0
  113. package/dist/core/deps/scanner.js +1 -1
  114. package/dist/core/deps/types.d.ts +8 -0
  115. package/dist/core/env.d.ts +3 -0
  116. package/dist/core/env.js +11 -0
  117. package/dist/core/exec.d.ts +1 -0
  118. package/dist/core/exec.js +4 -0
  119. package/dist/core/file-lock.d.ts +18 -0
  120. package/dist/core/file-lock.js +44 -0
  121. package/dist/core/git-onboard.js +10 -13
  122. package/dist/core/github.d.ts +3 -1
  123. package/dist/core/github.js +10 -7
  124. package/dist/core/logs-policy.d.ts +5 -0
  125. package/dist/core/logs-policy.js +20 -1
  126. package/dist/core/operator.d.ts +21 -0
  127. package/dist/core/operator.js +54 -0
  128. package/dist/core/registry.d.ts +18 -0
  129. package/dist/core/registry.js +26 -0
  130. package/dist/core/routines/schema.d.ts +11 -11
  131. package/dist/core/routines/schema.js +14 -3
  132. package/dist/core/routines/store.d.ts +8 -8
  133. package/dist/core/secrets-ops.d.ts +31 -6
  134. package/dist/core/secrets-ops.js +208 -102
  135. package/dist/core/secrets-providers.js +2 -2
  136. package/dist/core/secrets-rotation.d.ts +1 -1
  137. package/dist/core/secrets-rotation.js +58 -52
  138. package/dist/core/secrets-v2-cleanup.d.ts +19 -0
  139. package/dist/core/secrets-v2-cleanup.js +94 -0
  140. package/dist/core/secrets-v2-creds.d.ts +9 -0
  141. package/dist/core/secrets-v2-creds.js +44 -0
  142. package/dist/core/secrets-v2-install.d.ts +13 -0
  143. package/dist/core/secrets-v2-install.js +76 -0
  144. package/dist/core/secrets-v2-keypair.d.ts +10 -0
  145. package/dist/core/secrets-v2-keypair.js +31 -0
  146. package/dist/core/secrets-v2-migrate.d.ts +29 -0
  147. package/dist/core/secrets-v2-migrate.js +395 -0
  148. package/dist/core/secrets-v2-ops.d.ts +36 -0
  149. package/dist/core/secrets-v2-ops.js +184 -0
  150. package/dist/core/secrets-v2-protocol.d.ts +19 -0
  151. package/dist/core/secrets-v2-protocol.js +60 -0
  152. package/dist/core/secrets-v2-snapshot.d.ts +36 -0
  153. package/dist/core/secrets-v2-snapshot.js +115 -0
  154. package/dist/core/secrets-v2.d.ts +21 -0
  155. package/dist/core/secrets-v2.js +249 -0
  156. package/dist/core/secrets.d.ts +39 -4
  157. package/dist/core/secrets.js +91 -11
  158. package/dist/core/self-update.d.ts +32 -11
  159. package/dist/core/self-update.js +52 -14
  160. package/dist/core/testflight/asc.d.ts +12 -0
  161. package/dist/core/testflight/asc.js +101 -0
  162. package/dist/core/testflight/credentials.d.ts +3 -0
  163. package/dist/core/testflight/credentials.js +35 -0
  164. package/dist/core/testflight/resolve.d.ts +6 -0
  165. package/dist/core/testflight/resolve.js +44 -0
  166. package/dist/core/testflight/types.d.ts +13 -0
  167. package/dist/core/testflight/types.js +3 -0
  168. package/dist/core/testflight/workflow.d.ts +17 -0
  169. package/dist/core/testflight/workflow.js +65 -0
  170. package/dist/core/validate.d.ts +1 -0
  171. package/dist/core/validate.js +8 -0
  172. package/dist/index.js +0 -0
  173. package/dist/mcp/audit-tools.d.ts +2 -0
  174. package/dist/mcp/audit-tools.js +94 -0
  175. package/dist/mcp/git-tools.js +1 -1
  176. package/dist/mcp/registry-bridge.d.ts +10 -0
  177. package/dist/mcp/registry-bridge.js +65 -0
  178. package/dist/mcp/secrets-tools.js +2 -2
  179. package/dist/mcp/server.js +16 -82
  180. package/dist/mcp/testflight-tools.d.ts +2 -0
  181. package/dist/mcp/testflight-tools.js +52 -0
  182. package/dist/registry/context.d.ts +7 -0
  183. package/dist/registry/context.js +37 -0
  184. package/dist/registry/index.d.ts +5 -0
  185. package/dist/registry/index.js +44 -0
  186. package/dist/registry/parse-args.d.ts +13 -0
  187. package/dist/registry/parse-args.js +74 -0
  188. package/dist/registry/registry.d.ts +24 -0
  189. package/dist/registry/registry.js +26 -0
  190. package/dist/registry/render.d.ts +3 -0
  191. package/dist/registry/render.js +29 -0
  192. package/dist/registry/types.d.ts +50 -0
  193. package/dist/registry/types.js +1 -0
  194. package/dist/templates/agent-unit.d.ts +5 -0
  195. package/dist/templates/agent-unit.js +40 -0
  196. package/dist/templates/app-unit-edit.d.ts +2 -0
  197. package/dist/templates/app-unit-edit.js +46 -0
  198. package/dist/templates/compose-edit.d.ts +2 -0
  199. package/dist/templates/compose-edit.js +156 -0
  200. package/dist/templates/nginx.js +11 -0
  201. package/dist/templates/systemd.js +6 -0
  202. package/dist/tui/components/ArgForm.d.ts +7 -0
  203. package/dist/tui/components/ArgForm.js +64 -0
  204. package/dist/tui/components/ArgForm.test.d.ts +1 -0
  205. package/dist/tui/components/ArgForm.test.js +19 -0
  206. package/dist/tui/components/KeyHint.js +5 -0
  207. package/dist/tui/hooks/use-secrets.d.ts +8 -8
  208. package/dist/tui/hooks/use-secrets.js +7 -7
  209. package/dist/tui/router.d.ts +1 -0
  210. package/dist/tui/router.js +26 -9
  211. package/dist/tui/router.test.d.ts +1 -0
  212. package/dist/tui/router.test.js +13 -0
  213. package/dist/tui/routines/components/SignalsGrid.test.js +2 -2
  214. package/dist/tui/routines/tabs/ScaffoldTab.js +1 -1
  215. package/dist/tui/tests/redaction-rerender.test.d.ts +1 -0
  216. package/dist/tui/tests/redaction-rerender.test.js +53 -0
  217. package/dist/tui/tests/scroll-flicker-proof.test.d.ts +1 -0
  218. package/dist/tui/tests/scroll-flicker-proof.test.js +145 -0
  219. package/dist/tui/types.d.ts +1 -1
  220. package/dist/tui/views/CommandPalette.d.ts +5 -0
  221. package/dist/tui/views/CommandPalette.js +90 -0
  222. package/dist/tui/views/CommandPalette.test.d.ts +1 -0
  223. package/dist/tui/views/CommandPalette.test.js +117 -0
  224. package/dist/tui/views/Dashboard.js +9 -6
  225. package/dist/tui/views/HealthView.js +9 -4
  226. package/dist/tui/views/SecretEdit.js +15 -16
  227. package/dist/tui/views/SecretEdit.test.d.ts +1 -0
  228. package/dist/tui/views/SecretEdit.test.js +82 -0
  229. package/dist/tui/views/SecretsView.js +26 -16
  230. package/package.json +8 -5
@@ -1,64 +1,82 @@
1
- import { load, save, findApp } from '../core/registry.js';
1
+ import { z } from 'zod';
2
+ import { findApp, withRegistry } from '../core/registry.js';
2
3
  import { stopService, disableService, enableService, startService } from '../core/systemd.js';
3
4
  import { AppNotFoundError } from '../core/errors.js';
4
- import { success, error } from '../ui/output.js';
5
- export function freezeApp(appName, reason) {
6
- const reg = load();
7
- const app = findApp(reg, appName);
8
- if (!app)
9
- throw new AppNotFoundError(appName);
10
- if (app.frozenAt) {
11
- throw new Error(`App "${appName}" is already frozen (since ${app.frozenAt})`);
12
- }
13
- stopService(app.serviceName);
14
- disableService(app.serviceName);
15
- app.frozenAt = new Date().toISOString();
16
- if (reason)
17
- app.frozenReason = reason;
18
- save(reg);
19
- }
20
- export function unfreezeApp(appName) {
21
- const reg = load();
22
- const app = findApp(reg, appName);
23
- if (!app)
24
- throw new AppNotFoundError(appName);
25
- if (!app.frozenAt) {
26
- throw new Error(`App "${appName}" is not frozen`);
27
- }
28
- delete app.frozenAt;
29
- delete app.frozenReason;
30
- save(reg);
31
- enableService(app.serviceName);
32
- startService(app.serviceName);
5
+ import { defineCommand } from '../registry/registry.js';
6
+ export async function freezeApp(appName, reason) {
7
+ await withRegistry(reg => {
8
+ const app = findApp(reg, appName);
9
+ if (!app)
10
+ throw new AppNotFoundError(appName);
11
+ if (app.frozenAt) {
12
+ throw new Error(`App "${appName}" is already frozen (since ${app.frozenAt})`);
13
+ }
14
+ stopService(app.serviceName);
15
+ disableService(app.serviceName);
16
+ app.frozenAt = new Date().toISOString();
17
+ if (reason)
18
+ app.frozenReason = reason;
19
+ return reg;
20
+ });
33
21
  }
34
- export function freezeCommand(args) {
35
- const appName = args[0];
36
- if (!appName) {
37
- error('Usage: fleet freeze <app> [reason]');
38
- process.exit(1);
39
- }
40
- const reason = args.slice(1).join(' ') || undefined;
41
- try {
42
- freezeApp(appName, reason);
43
- success(`Frozen ${appName}${reason ? `: ${reason}` : ''}`);
44
- }
45
- catch (err) {
46
- error(err.message);
47
- process.exit(1);
48
- }
49
- }
50
- export function unfreezeCommand(args) {
51
- const appName = args[0];
52
- if (!appName) {
53
- error('Usage: fleet unfreeze <app>');
54
- process.exit(1);
55
- }
56
- try {
57
- unfreezeApp(appName);
58
- success(`Unfrozen ${appName} — service enabled and started`);
59
- }
60
- catch (err) {
61
- error(err.message);
62
- process.exit(1);
22
+ export async function unfreezeApp(appName) {
23
+ let serviceName = null;
24
+ await withRegistry(reg => {
25
+ const app = findApp(reg, appName);
26
+ if (!app)
27
+ throw new AppNotFoundError(appName);
28
+ if (!app.frozenAt) {
29
+ throw new Error(`App "${appName}" is not frozen`);
30
+ }
31
+ delete app.frozenAt;
32
+ delete app.frozenReason;
33
+ serviceName = app.serviceName;
34
+ return reg;
35
+ });
36
+ // Service operations run AFTER the lock is released so we don't hold the
37
+ // registry lock while systemctl is starting things up.
38
+ if (serviceName) {
39
+ enableService(serviceName);
40
+ startService(serviceName);
63
41
  }
64
42
  }
43
+ export const freezeCommand = defineCommand({
44
+ name: 'freeze',
45
+ summary: 'Freeze a crash-looping service (stop + disable)',
46
+ args: z.object({ app: z.string(), reason: z.string().optional(), yes: z.boolean().default(false) }),
47
+ destructive: true,
48
+ async run(args, ctx) {
49
+ if (!args.yes && !(await ctx.confirm(`Freeze ${args.app}? This stops and disables the service.`))) {
50
+ return { ok: false, summary: 'cancelled', data: { app: args.app } };
51
+ }
52
+ try {
53
+ await freezeApp(args.app, args.reason);
54
+ }
55
+ catch (err) {
56
+ return { ok: false, summary: err instanceof Error ? err.message : String(err), data: { app: args.app } };
57
+ }
58
+ return {
59
+ ok: true,
60
+ summary: `froze ${args.app}${args.reason ? `: ${args.reason}` : ''}`,
61
+ data: { app: args.app },
62
+ };
63
+ },
64
+ });
65
+ export const unfreezeCommand = defineCommand({
66
+ name: 'unfreeze',
67
+ summary: 'Unfreeze and restart a frozen service',
68
+ args: z.object({ app: z.string(), yes: z.boolean().default(false) }),
69
+ destructive: true,
70
+ async run(args, ctx) {
71
+ if (!args.yes && !(await ctx.confirm(`Unfreeze ${args.app}? This re-enables and starts the service.`))) {
72
+ return { ok: false, summary: 'cancelled', data: { app: args.app } };
73
+ }
74
+ try {
75
+ await unfreezeApp(args.app);
76
+ }
77
+ catch (err) {
78
+ return { ok: false, summary: err instanceof Error ? err.message : String(err), data: { app: args.app } };
79
+ }
80
+ return { ok: true, summary: `unfroze ${args.app} — service enabled and started`, data: { app: args.app } };
81
+ },
82
+ });
@@ -105,7 +105,7 @@ async function gitOnboardCmd(args) {
105
105
  info('cancelled');
106
106
  return;
107
107
  }
108
- const result = executeOnboard(scenario, r, app.name, app.name, status);
108
+ const result = await executeOnboard(scenario, r, app.name, app.name, status);
109
109
  heading(`Onboarded: ${app.name}`);
110
110
  result.steps.forEach(s => success(s));
111
111
  info(`repo: ${result.repoUrl}`);
@@ -134,7 +134,7 @@ async function gitOnboardAllCmd(args) {
134
134
  continue;
135
135
  }
136
136
  try {
137
- const result = executeOnboard(scenario, r, app.name, app.name, status);
137
+ const result = await executeOnboard(scenario, r, app.name, app.name, status);
138
138
  success(`${app.name}: onboarded (${result.scenario})`);
139
139
  }
140
140
  catch (err) {
@@ -1 +1,2 @@
1
- export declare function healthCommand(args: string[]): void;
1
+ import { type HealthResult } from '../core/health.js';
2
+ export declare const healthCommand: import("../registry/types.js").CommandDef<HealthResult[]>;
@@ -1,60 +1,42 @@
1
+ import { z } from 'zod';
1
2
  import { load, findApp } from '../core/registry.js';
2
3
  import { checkHealth, checkAllHealth } from '../core/health.js';
3
- import { AppNotFoundError } from '../core/errors.js';
4
- import { c, icon, heading, table } from '../ui/output.js';
5
- export function healthCommand(args) {
6
- const json = args.includes('--json');
7
- const appName = args.find(a => !a.startsWith('-'));
8
- const reg = load();
9
- if (appName) {
10
- const app = findApp(reg, appName);
11
- if (!app)
12
- throw new AppNotFoundError(appName);
13
- const result = checkHealth(app);
14
- if (json) {
15
- process.stdout.write(JSON.stringify(result, null, 2) + '\n');
16
- return;
4
+ import { defineCommand } from '../registry/registry.js';
5
+ export const healthCommand = defineCommand({
6
+ name: 'health',
7
+ summary: 'Health checks: systemd + container + HTTP',
8
+ args: z.object({ app: z.string().optional() }),
9
+ tui: { view: 'health' },
10
+ async run(args) {
11
+ let results;
12
+ if (args.app) {
13
+ const app = findApp(load(), args.app);
14
+ if (!app) {
15
+ return { ok: false, summary: `app not found: ${args.app}`, data: [] };
16
+ }
17
+ results = [checkHealth(app)];
17
18
  }
18
- heading(`Health: ${app.name}`);
19
- const sIcon = result.systemd.ok ? icon.ok : icon.err;
20
- process.stdout.write(` Systemd: ${sIcon} ${result.systemd.state}\n`);
21
- for (const ct of result.containers) {
22
- const cIcon = ct.running ? icon.ok : icon.err;
23
- process.stdout.write(` Container: ${cIcon} ${ct.name} (${ct.health})\n`);
19
+ else {
20
+ results = checkAllHealth(load().apps);
24
21
  }
25
- if (result.http) {
26
- const hIcon = result.http.ok ? icon.ok : icon.err;
27
- const detail = result.http.ok ? `${result.http.status}` : (result.http.error ?? 'failed');
28
- process.stdout.write(` HTTP: ${hIcon} ${detail}\n`);
29
- }
30
- const oColor = result.overall === 'healthy' ? c.green
31
- : result.overall === 'degraded' ? c.yellow : c.red;
32
- process.stdout.write(` Overall: ${oColor}${result.overall}${c.reset}\n\n`);
33
- return;
34
- }
35
- const results = checkAllHealth(reg.apps);
36
- if (json) {
37
- process.stdout.write(JSON.stringify(results, null, 2) + '\n');
38
- return;
39
- }
40
- heading('Health Check');
41
- const rows = results.map(r => {
42
- const oIcon = r.overall === 'healthy' ? icon.ok
43
- : r.overall === 'degraded' ? icon.warn : icon.err;
44
- const sIcon = r.systemd.ok ? icon.ok : icon.err;
45
- const cOk = r.containers.filter(ct => ct.running).length;
46
- const cTotal = r.containers.length;
47
- const httpStatus = r.http
48
- ? (r.http.ok ? `${icon.ok} ${r.http.status}` : `${icon.err} fail`)
49
- : `${c.dim}-${c.reset}`;
50
- return [
51
- `${c.bold}${r.app}${c.reset}`,
52
- `${sIcon} ${r.systemd.state}`,
53
- `${cOk}/${cTotal}`,
54
- httpStatus,
55
- `${oIcon} ${r.overall}`,
56
- ];
57
- });
58
- table(['APP', 'SYSTEMD', 'CONTAINERS', 'HTTP', 'OVERALL'], rows);
59
- process.stdout.write('\n');
60
- }
22
+ const healthy = results.filter(r => r.overall === 'healthy').length;
23
+ const degraded = results.filter(r => r.overall === 'degraded').length;
24
+ const down = results.filter(r => r.overall === 'down').length;
25
+ return {
26
+ ok: true,
27
+ summary: `${results.length} checked | ${healthy} healthy | ${degraded} degraded | ${down} down`,
28
+ data: results,
29
+ render: {
30
+ kind: 'table',
31
+ columns: ['APP', 'SYSTEMD', 'CONTAINERS', 'HTTP', 'OVERALL'],
32
+ rows: results.map(r => [
33
+ r.app,
34
+ r.systemd.state,
35
+ `${r.containers.filter(ct => ct.running).length}/${r.containers.length}`,
36
+ r.http ? (r.http.ok ? String(r.http.status ?? 'ok') : 'fail') : '—',
37
+ r.overall,
38
+ ]),
39
+ },
40
+ };
41
+ },
42
+ });
@@ -1 +1,2 @@
1
- export declare function initCommand(args: string[]): void;
1
+ import type { Registry } from '../core/registry.js';
2
+ export declare const initCommand: import("../registry/types.js").CommandDef<Registry>;
@@ -1,82 +1,92 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
- import { load, save } from '../core/registry.js';
2
+ import { z } from 'zod';
3
+ import { withRegistry } from '../core/registry.js';
3
4
  import { discoverServices, parseServiceFile, readServiceFile } from '../core/systemd.js';
4
5
  import { listContainers, getContainersByCompose } from '../core/docker.js';
5
6
  import { listSites, readConfig, extractPortFromConfig, extractDomainsFromConfig } from '../core/nginx.js';
6
- import { heading, success, info } from '../ui/output.js';
7
+ import { defineCommand } from '../registry/registry.js';
7
8
  const SKIP_SERVICES = ['docker-databases'];
8
- export function initCommand(args) {
9
- const json = args.includes('--json');
10
- heading('Fleet Init - Auto-discovering apps');
11
- const reg = load();
12
- const services = discoverServices();
13
- const containers = listContainers();
14
- const sites = listSites();
15
- info(`Found ${services.length} docker compose services`);
16
- info(`Found ${containers.length} running containers`);
17
- info(`Found ${sites.length} nginx sites`);
18
- let added = 0;
19
- for (const serviceName of services) {
20
- if (SKIP_SERVICES.includes(serviceName))
21
- continue;
22
- const content = readServiceFile(serviceName);
23
- if (!content)
24
- continue;
25
- const parsed = parseServiceFile(content);
26
- if (!parsed.workingDirectory)
27
- continue;
28
- const composePath = parsed.workingDirectory;
29
- const composeFile = parsed.composeFile;
30
- const composeContainers = getContainersByCompose(composePath, composeFile);
31
- const port = detectPort(composePath, composeFile, composeContainers, containers);
32
- const domains = detectDomains(serviceName, sites, port);
33
- const usesSharedDb = detectSharedDb(composePath, composeFile);
34
- const type = detectType(composePath, composeFile, domains);
35
- const displayName = detectDisplayName(serviceName, content);
36
- const app = {
37
- name: serviceName,
38
- displayName,
39
- composePath,
40
- composeFile,
41
- serviceName,
42
- domains,
43
- port,
44
- usesSharedDb,
45
- type,
46
- containers: composeContainers.length > 0 ? composeContainers : [serviceName],
47
- dependsOnDatabases: parsed.dependsOnDatabases,
48
- registeredAt: new Date().toISOString(),
9
+ export const initCommand = defineCommand({
10
+ name: 'init',
11
+ summary: 'Auto-discover all existing apps',
12
+ args: z.object({}),
13
+ async run(_args, ctx) {
14
+ ctx.log({ level: 'info', message: 'fleet init — auto-discovering apps' });
15
+ const services = discoverServices();
16
+ const containers = listContainers();
17
+ const sites = listSites();
18
+ ctx.log({ level: 'info', message: `found ${services.length} compose services, ${containers.length} running containers, ${sites.length} nginx sites` });
19
+ let added = 0;
20
+ // assigned inside the withRegistry callback below, which always runs.
21
+ let discovered;
22
+ await withRegistry(reg => {
23
+ for (const serviceName of services) {
24
+ if (SKIP_SERVICES.includes(serviceName))
25
+ continue;
26
+ const content = readServiceFile(serviceName);
27
+ if (!content)
28
+ continue;
29
+ const parsed = parseServiceFile(content);
30
+ if (!parsed.workingDirectory)
31
+ continue;
32
+ const composePath = parsed.workingDirectory;
33
+ const composeFile = parsed.composeFile;
34
+ const composeContainers = getContainersByCompose(composePath, composeFile);
35
+ const port = detectPort(composePath, composeFile, composeContainers, containers);
36
+ const domains = detectDomains(serviceName, sites, port);
37
+ const usesSharedDb = detectSharedDb(composePath, composeFile);
38
+ const type = detectType(composePath, composeFile, domains);
39
+ const displayName = detectDisplayName(serviceName, content);
40
+ const app = {
41
+ name: serviceName,
42
+ displayName,
43
+ composePath,
44
+ composeFile,
45
+ serviceName,
46
+ domains,
47
+ port,
48
+ usesSharedDb,
49
+ type,
50
+ containers: composeContainers.length > 0 ? composeContainers : [serviceName],
51
+ dependsOnDatabases: parsed.dependsOnDatabases,
52
+ registeredAt: new Date().toISOString(),
53
+ };
54
+ const existing = reg.apps.findIndex(a => a.name === serviceName);
55
+ if (existing >= 0) {
56
+ const prev = reg.apps[existing];
57
+ if (prev.healthPath)
58
+ app.healthPath = prev.healthPath;
59
+ if (prev.gitRepo)
60
+ app.gitRepo = prev.gitRepo;
61
+ if (prev.gitRemoteUrl)
62
+ app.gitRemoteUrl = prev.gitRemoteUrl;
63
+ if (prev.gitOnboardedAt)
64
+ app.gitOnboardedAt = prev.gitOnboardedAt;
65
+ if (prev.secretsManaged)
66
+ app.secretsManaged = prev.secretsManaged;
67
+ reg.apps[existing] = app;
68
+ }
69
+ else {
70
+ reg.apps.push(app);
71
+ }
72
+ added++;
73
+ ctx.log({ level: 'info', message: `${serviceName} (${composePath})` });
74
+ }
75
+ discovered = reg;
76
+ return reg;
77
+ });
78
+ return {
79
+ ok: true,
80
+ summary: `registered ${added} app${added === 1 ? '' : 's'}`,
81
+ data: discovered,
82
+ render: {
83
+ kind: 'table',
84
+ columns: ['NAME', 'PATH', 'TYPE', 'PORT'],
85
+ rows: discovered.apps.map(a => [a.name, a.composePath, a.type, a.port?.toString() ?? '—']),
86
+ },
49
87
  };
50
- const existing = reg.apps.findIndex(a => a.name === serviceName);
51
- if (existing >= 0) {
52
- const prev = reg.apps[existing];
53
- if (prev.healthPath)
54
- app.healthPath = prev.healthPath;
55
- if (prev.gitRepo)
56
- app.gitRepo = prev.gitRepo;
57
- if (prev.gitRemoteUrl)
58
- app.gitRemoteUrl = prev.gitRemoteUrl;
59
- if (prev.gitOnboardedAt)
60
- app.gitOnboardedAt = prev.gitOnboardedAt;
61
- if (prev.secretsManaged)
62
- app.secretsManaged = prev.secretsManaged;
63
- reg.apps[existing] = app;
64
- }
65
- else {
66
- reg.apps.push(app);
67
- }
68
- added++;
69
- success(`${serviceName} (${composePath})`);
70
- }
71
- save(reg);
72
- if (json) {
73
- process.stdout.write(JSON.stringify(reg, null, 2) + '\n');
74
- }
75
- else {
76
- info(`Registered ${added} apps`);
77
- success('Init complete');
78
- }
79
- }
88
+ },
89
+ });
80
90
  function detectPort(composePath, composeFile, composeContainers, allContainers) {
81
91
  const file = composeFile ?? 'docker-compose.yml';
82
92
  const fullPath = `${composePath}/${file}`;
@@ -1 +1,3 @@
1
- export declare function installMcpCommand(args: string[]): Promise<void>;
1
+ export declare const installMcpCommand: import("../registry/types.js").CommandDef<{
2
+ installed: boolean;
3
+ }>;
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
2
2
  import { join, resolve } from 'node:path';
3
- import { success, info, warn } from '../ui/output.js';
3
+ import { z } from 'zod';
4
+ import { defineCommand } from '../registry/registry.js';
4
5
  const FLEET_DIST = resolve(join(import.meta.dirname, '..', '..', 'dist', 'index.js'));
5
6
  function getClaudeConfigPath() {
6
7
  const home = process.env.HOME || process.env.USERPROFILE || '/root';
@@ -16,40 +17,58 @@ function loadConfig(path) {
16
17
  return {};
17
18
  }
18
19
  }
19
- export async function installMcpCommand(args) {
20
- const uninstall = args.includes('--uninstall');
21
- const configPath = getClaudeConfigPath();
22
- const config = loadConfig(configPath);
23
- if (uninstall) {
24
- if (config.mcpServers?.fleet) {
25
- delete config.mcpServers.fleet;
26
- if (Object.keys(config.mcpServers).length === 0) {
27
- delete config.mcpServers;
20
+ export const installMcpCommand = defineCommand({
21
+ name: 'install-mcp',
22
+ summary: 'Install fleet as a Claude Code MCP server',
23
+ args: z.object({ uninstall: z.boolean().default(false) }),
24
+ cliOnly: true,
25
+ async run(args, ctx) {
26
+ const configPath = getClaudeConfigPath();
27
+ const config = loadConfig(configPath);
28
+ if (args.uninstall) {
29
+ if (config.mcpServers?.fleet) {
30
+ delete config.mcpServers.fleet;
31
+ if (Object.keys(config.mcpServers).length === 0) {
32
+ delete config.mcpServers;
33
+ }
34
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
35
+ return {
36
+ ok: true,
37
+ summary: 'Removed fleet MCP server from Claude Code',
38
+ data: { installed: false },
39
+ };
28
40
  }
29
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
30
- success('Removed fleet MCP server from Claude Code');
41
+ else {
42
+ return {
43
+ ok: true,
44
+ summary: 'fleet MCP server not configured — nothing to remove',
45
+ data: { installed: false },
46
+ };
47
+ }
48
+ }
49
+ if (!existsSync(FLEET_DIST)) {
50
+ ctx.log({ level: 'warn', message: 'dist/index.js not found — run "npm run build" first' });
51
+ }
52
+ config.mcpServers = config.mcpServers || {};
53
+ const existed = !!config.mcpServers.fleet;
54
+ config.mcpServers.fleet = {
55
+ command: 'node',
56
+ args: [FLEET_DIST, 'mcp'],
57
+ };
58
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
59
+ let summary;
60
+ if (existed) {
61
+ summary = 'Updated fleet MCP server in Claude Code';
31
62
  }
32
63
  else {
33
- info('fleet MCP server not configured — nothing to remove');
64
+ summary = 'Installed fleet MCP server to Claude Code';
34
65
  }
35
- return;
36
- }
37
- if (!existsSync(FLEET_DIST)) {
38
- warn('dist/index.js not found — run "npm run build" first');
39
- }
40
- config.mcpServers = config.mcpServers || {};
41
- const existed = !!config.mcpServers.fleet;
42
- config.mcpServers.fleet = {
43
- command: 'node',
44
- args: [FLEET_DIST, 'mcp'],
45
- };
46
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
47
- if (existed) {
48
- success('Updated fleet MCP server in Claude Code');
49
- }
50
- else {
51
- success('Installed fleet MCP server to Claude Code');
52
- }
53
- info(`Config: ${configPath}`);
54
- info(`Server: node ${FLEET_DIST} mcp`);
55
- }
66
+ ctx.log({ level: 'info', message: `Config: ${configPath}` });
67
+ ctx.log({ level: 'info', message: `Server: node ${FLEET_DIST} mcp` });
68
+ return {
69
+ ok: true,
70
+ summary,
71
+ data: { installed: true },
72
+ };
73
+ },
74
+ });
@@ -1 +1,2 @@
1
- export declare function listCommand(args: string[]): void;
1
+ import type { AppEntry } from '../core/registry.js';
2
+ export declare const listCommand: import("../registry/types.js").CommandDef<AppEntry[]>;
@@ -1,20 +1,23 @@
1
+ import { z } from 'zod';
1
2
  import { load } from '../core/registry.js';
2
- import { c, heading, table } from '../ui/output.js';
3
- export function listCommand(args) {
4
- const json = args.includes('--json');
5
- const reg = load();
6
- if (json) {
7
- process.stdout.write(JSON.stringify(reg.apps, null, 2) + '\n');
8
- return;
9
- }
10
- heading(`Registered Apps (${reg.apps.length})`);
11
- const rows = reg.apps.map(app => [
12
- `${c.bold}${app.name}${c.reset}`,
13
- app.serviceName,
14
- app.port?.toString() ?? '-',
15
- app.type,
16
- app.domains.join(', ') || '-',
17
- ]);
18
- table(['NAME', 'SERVICE', 'PORT', 'TYPE', 'DOMAINS'], rows);
19
- process.stdout.write('\n');
20
- }
3
+ import { defineCommand } from '../registry/registry.js';
4
+ export const listCommand = defineCommand({
5
+ name: 'list',
6
+ summary: 'List registered apps',
7
+ args: z.object({}),
8
+ async run() {
9
+ const reg = load();
10
+ return {
11
+ ok: true,
12
+ summary: `${reg.apps.length} app${reg.apps.length === 1 ? '' : 's'} registered`,
13
+ data: reg.apps,
14
+ render: {
15
+ kind: 'table',
16
+ columns: ['NAME', 'SERVICE', 'PORT', 'TYPE', 'DOMAINS'],
17
+ rows: reg.apps.map(a => [
18
+ a.name, a.serviceName, a.port?.toString() ?? '—', a.type, a.domains.join(', ') || '—',
19
+ ]),
20
+ },
21
+ };
22
+ },
23
+ });
@@ -53,7 +53,7 @@ function logsMulti(args) {
53
53
  const tail = parseInt(valOf('--tail') ?? valOf('-n') ?? '50', 10) || 50;
54
54
  if (!all && !appsCsv && !containersCsv) {
55
55
  error('Usage: fleet logs --all [-f] [--since 15m] [--grep err] [--level warn]');
56
- error(' fleet logs --apps macpool,shiftfaced [-f]');
56
+ error(' fleet logs --apps poolside,brewco [-f]');
57
57
  error(' fleet logs --containers "*-postgres" [-f]');
58
58
  process.exit(1);
59
59
  }
@@ -1 +1,7 @@
1
- export declare function patchSystemdCommand(args: string[]): void;
1
+ interface PatchSystemdData {
2
+ action: 'patch' | 'rollback';
3
+ changed: number;
4
+ skipped: number;
5
+ }
6
+ export declare const patchSystemdCommand: import("../registry/types.js").CommandDef<PatchSystemdData>;
7
+ export {};