@marcusrbrown/infra 0.13.1 → 0.13.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/cli.js +11965 -0
  2. package/package.json +7 -3
  3. package/src/__snapshots__/cli.test.ts.snap +0 -219
  4. package/src/__test__/mcp-ctx-fixture.test.ts +0 -108
  5. package/src/__test__/mcp-ctx-fixture.ts +0 -104
  6. package/src/cli.test.ts +0 -66
  7. package/src/cli.ts +0 -43
  8. package/src/commands/cliproxy/config.test.ts +0 -475
  9. package/src/commands/cliproxy/config.ts +0 -238
  10. package/src/commands/cliproxy/deploy.test.ts +0 -181
  11. package/src/commands/cliproxy/deploy.ts +0 -157
  12. package/src/commands/cliproxy/host.test.ts +0 -73
  13. package/src/commands/cliproxy/host.ts +0 -31
  14. package/src/commands/cliproxy/index.ts +0 -21
  15. package/src/commands/cliproxy/keys.test.ts +0 -326
  16. package/src/commands/cliproxy/keys.ts +0 -201
  17. package/src/commands/cliproxy/login.test.ts +0 -415
  18. package/src/commands/cliproxy/login.ts +0 -131
  19. package/src/commands/cliproxy/models.test.ts +0 -592
  20. package/src/commands/cliproxy/models.ts +0 -237
  21. package/src/commands/cliproxy/open.test.ts +0 -132
  22. package/src/commands/cliproxy/open.ts +0 -78
  23. package/src/commands/cliproxy/setup/gh.test.ts +0 -218
  24. package/src/commands/cliproxy/setup/gh.ts +0 -250
  25. package/src/commands/cliproxy/setup/preview.test.ts +0 -159
  26. package/src/commands/cliproxy/setup/preview.ts +0 -41
  27. package/src/commands/cliproxy/setup/prompts.test.ts +0 -58
  28. package/src/commands/cliproxy/setup/prompts.ts +0 -99
  29. package/src/commands/cliproxy/setup/providers.test.ts +0 -245
  30. package/src/commands/cliproxy/setup/providers.ts +0 -136
  31. package/src/commands/cliproxy/setup/smoke-test.test.ts +0 -821
  32. package/src/commands/cliproxy/setup/smoke-test.ts +0 -223
  33. package/src/commands/cliproxy/setup/templates.test.ts +0 -357
  34. package/src/commands/cliproxy/setup/templates.ts +0 -145
  35. package/src/commands/cliproxy/setup/validation.test.ts +0 -567
  36. package/src/commands/cliproxy/setup/validation.ts +0 -209
  37. package/src/commands/cliproxy/setup/workflow-analyzer.test.ts +0 -363
  38. package/src/commands/cliproxy/setup/workflow-analyzer.ts +0 -136
  39. package/src/commands/cliproxy/setup.test.ts +0 -2366
  40. package/src/commands/cliproxy/setup.ts +0 -809
  41. package/src/commands/cliproxy/shared.test.ts +0 -118
  42. package/src/commands/cliproxy/shared.ts +0 -84
  43. package/src/commands/cliproxy/status.test.ts +0 -1157
  44. package/src/commands/cliproxy/status.ts +0 -597
  45. package/src/commands/dashboard/deploy.test.ts +0 -215
  46. package/src/commands/dashboard/deploy.ts +0 -135
  47. package/src/commands/dashboard/host.test.ts +0 -79
  48. package/src/commands/dashboard/host.ts +0 -33
  49. package/src/commands/dashboard/index.ts +0 -13
  50. package/src/commands/dashboard/logs.test.ts +0 -317
  51. package/src/commands/dashboard/logs.ts +0 -180
  52. package/src/commands/dashboard/status.test.ts +0 -708
  53. package/src/commands/dashboard/status.ts +0 -293
  54. package/src/commands/gateway/backup.test.ts +0 -426
  55. package/src/commands/gateway/backup.ts +0 -205
  56. package/src/commands/gateway/deploy.test.ts +0 -491
  57. package/src/commands/gateway/deploy.ts +0 -173
  58. package/src/commands/gateway/host.test.ts +0 -89
  59. package/src/commands/gateway/host.ts +0 -33
  60. package/src/commands/gateway/index.ts +0 -17
  61. package/src/commands/gateway/logs.test.ts +0 -263
  62. package/src/commands/gateway/logs.ts +0 -160
  63. package/src/commands/gateway/restore.test.ts +0 -650
  64. package/src/commands/gateway/restore.ts +0 -308
  65. package/src/commands/gateway/status.test.ts +0 -755
  66. package/src/commands/gateway/status.ts +0 -255
  67. package/src/commands/keeweb/deploy.test.ts +0 -177
  68. package/src/commands/keeweb/deploy.ts +0 -191
  69. package/src/commands/keeweb/index.ts +0 -11
  70. package/src/commands/keeweb/open.test.ts +0 -54
  71. package/src/commands/keeweb/open.ts +0 -30
  72. package/src/commands/keeweb/status.test.ts +0 -509
  73. package/src/commands/keeweb/status.ts +0 -359
  74. package/src/commands/mcp.test.ts +0 -260
  75. package/src/commands/mcp.ts +0 -58
  76. package/src/commands/status.test.ts +0 -222
  77. package/src/commands/status.ts +0 -161
  78. package/src/commands/umami/deploy.test.ts +0 -202
  79. package/src/commands/umami/deploy.ts +0 -132
  80. package/src/commands/umami/host.test.ts +0 -79
  81. package/src/commands/umami/host.ts +0 -33
  82. package/src/commands/umami/index.ts +0 -13
  83. package/src/commands/umami/logs.test.ts +0 -191
  84. package/src/commands/umami/logs.ts +0 -163
  85. package/src/commands/umami/status.test.ts +0 -633
  86. package/src/commands/umami/status.ts +0 -285
  87. package/src/commands/vpn/client.test.ts +0 -949
  88. package/src/commands/vpn/client.ts +0 -467
  89. package/src/commands/vpn/deploy.test.ts +0 -208
  90. package/src/commands/vpn/deploy.ts +0 -143
  91. package/src/commands/vpn/host.test.ts +0 -89
  92. package/src/commands/vpn/host.ts +0 -36
  93. package/src/commands/vpn/index.ts +0 -15
  94. package/src/commands/vpn/logs.test.ts +0 -234
  95. package/src/commands/vpn/logs.ts +0 -136
  96. package/src/commands/vpn/peers.test.ts +0 -478
  97. package/src/commands/vpn/shared.test.ts +0 -454
  98. package/src/commands/vpn/shared.ts +0 -170
  99. package/src/commands/vpn/status.test.ts +0 -142
  100. package/src/commands/vpn/status.ts +0 -78
  101. package/src/conventions.test.ts +0 -1180
  102. package/src/lib/action-ctx.ts +0 -25
  103. package/src/lib/known-hosts.test.ts +0 -192
  104. package/src/lib/known-hosts.ts +0 -76
  105. package/src/lib/redact.ts +0 -21
  106. package/src/lib/ssh-identity.test.ts +0 -134
  107. package/src/lib/ssh-identity.ts +0 -76
  108. /package/{src/resources/known_hosts → dist/known_hosts-tk724cp0.} +0 -0
@@ -1,293 +0,0 @@
1
- import type {goke} from 'goke'
2
- import type {ActionCtx} from '../../lib/action-ctx'
3
- import type {StatusSummary} from '../status'
4
- import {z} from 'zod'
5
-
6
- import {buildKnownHostsArgs} from '../../lib/known-hosts'
7
- import {redactHost} from '../../lib/redact'
8
- import {buildIdentityArgs} from '../../lib/ssh-identity'
9
- import {validateDashboardHost} from './host'
10
-
11
- declare const process: {
12
- env: Record<string, string | undefined>
13
- exitCode?: number
14
- }
15
-
16
- const COMPOSE_PROJECT_DIR = '/opt/dashboard'
17
-
18
- // ─── Types ───────────────────────────────────────────────────────────────────
19
-
20
- export interface ComposePsEntry {
21
- Name: string
22
- State: string
23
- Health: string
24
- }
25
-
26
- export type HealthStatus = 'healthy' | 'unhealthy' | 'starting' | 'n-a'
27
-
28
- export interface ServiceRow {
29
- service: string
30
- state: string
31
- health: HealthStatus
32
- }
33
-
34
- export interface DashboardStatusResult {
35
- ok: boolean
36
- services: ServiceRow[]
37
- error?: string
38
- }
39
-
40
- export type SpawnFn = (
41
- cmd: string[],
42
- opts: {env: Record<string, string>; stdout: 'pipe'; stderr: 'pipe'},
43
- ) => {
44
- stdout: ReadableStream<Uint8Array>
45
- stderr: ReadableStream<Uint8Array>
46
- exited: Promise<number>
47
- }
48
-
49
- // ─── Pure helpers ─────────────────────────────────────────────────────────────
50
-
51
- export function parseComposePsOutput(stdoutText: string): ComposePsEntry[] {
52
- const trimmed = stdoutText.trim()
53
- if (trimmed.length === 0) return []
54
-
55
- // Legacy compose may emit a single JSON array; current versions emit NDJSON.
56
- if (trimmed.startsWith('[')) {
57
- const parsed: unknown = JSON.parse(trimmed)
58
- return Array.isArray(parsed) ? (parsed as ComposePsEntry[]) : []
59
- }
60
-
61
- // NDJSON: one JSON object per non-empty line.
62
- return trimmed
63
- .split('\n')
64
- .map(line => line.trim())
65
- .filter(line => line.length > 0)
66
- .map(line => JSON.parse(line) as ComposePsEntry)
67
- }
68
-
69
- function normalizeHealth(raw: string): HealthStatus {
70
- if (raw === 'healthy' || raw === 'unhealthy' || raw === 'starting') {
71
- return raw
72
- }
73
-
74
- return 'n-a'
75
- }
76
-
77
- export function parseComposePs(entries: ComposePsEntry[]): ServiceRow[] {
78
- return entries.map(entry => ({
79
- service: entry.Name,
80
- state: entry.State,
81
- health: normalizeHealth(entry.Health),
82
- }))
83
- }
84
-
85
- export function isAllRunning(rows: ServiceRow[]): boolean {
86
- return rows.every(row => row.state === 'running' && (row.health === 'healthy' || row.health === 'n-a'))
87
- }
88
-
89
- // ─── SSH-backed status fetch ──────────────────────────────────────────────────
90
-
91
- function defaultSpawn(
92
- cmd: string[],
93
- opts: {env: Record<string, string>; stdout: 'pipe'; stderr: 'pipe'},
94
- ): ReturnType<SpawnFn> {
95
- return Bun.spawn(cmd, opts) as ReturnType<SpawnFn>
96
- }
97
-
98
- export async function getDashboardComposeStatus(
99
- host: string,
100
- spawn: SpawnFn = defaultSpawn,
101
- ): Promise<DashboardStatusResult> {
102
- validateDashboardHost(host)
103
-
104
- const knownHostsArgs = buildKnownHostsArgs()
105
- const {args: identityArgs, cleanup} = buildIdentityArgs(process.env.DASHBOARD_SSH_KEY)
106
-
107
- const sshCmd = [
108
- 'ssh',
109
- '-o',
110
- 'BatchMode=yes',
111
- '-o',
112
- 'ConnectTimeout=10',
113
- '-o',
114
- 'StrictHostKeyChecking=yes',
115
- ...knownHostsArgs,
116
- ...identityArgs,
117
- `root@${host}`,
118
- `docker compose --project-directory ${COMPOSE_PROJECT_DIR} ps --format json`,
119
- ]
120
-
121
- const env: Record<string, string> = {
122
- PATH: process.env.PATH ?? '/usr/bin:/bin',
123
- HOME: process.env.HOME ?? '/tmp',
124
- ...(process.env.SSH_AUTH_SOCK ? {SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK} : {}),
125
- }
126
-
127
- let stdoutText: string
128
- let stderrText: string
129
- let exitCode: number
130
-
131
- try {
132
- const child = spawn(sshCmd, {env, stdout: 'pipe', stderr: 'pipe'})
133
- ;[stdoutText, stderrText, exitCode] = await Promise.all([
134
- new Response(child.stdout).text(),
135
- new Response(child.stderr).text(),
136
- child.exited,
137
- ])
138
- } finally {
139
- cleanup()
140
- }
141
-
142
- if (exitCode !== 0) {
143
- return {
144
- ok: false,
145
- services: [],
146
- error: `SSH command failed (exit ${exitCode}): ${redactHost(stderrText.trim(), host) || 'unknown error'}`,
147
- }
148
- }
149
-
150
- let entries: ComposePsEntry[]
151
-
152
- try {
153
- entries = parseComposePsOutput(stdoutText)
154
- } catch {
155
- return {
156
- ok: false,
157
- services: [],
158
- error: `Failed to parse docker compose ps output: ${redactHost(stdoutText.slice(0, 200), host)}`,
159
- }
160
- }
161
-
162
- const services = parseComposePs(entries)
163
- const ok = isAllRunning(services)
164
-
165
- return {ok, services}
166
- }
167
-
168
- // ─── Unified status aggregator export ────────────────────────────────────────
169
-
170
- export async function getDashboardStatusSummary(host: string, spawn?: SpawnFn): Promise<StatusSummary> {
171
- let result: DashboardStatusResult
172
-
173
- try {
174
- result = await getDashboardComposeStatus(host, spawn)
175
- } catch (error) {
176
- const errorMsg = error instanceof Error ? error.message : String(error)
177
- return {
178
- app: 'dashboard',
179
- http: `ERROR: ${errorMsg}`,
180
- lastDeploy: '—',
181
- version: '—',
182
- contentHash: '—',
183
- usageStats: '—',
184
- }
185
- }
186
-
187
- // Three-way split: no services → ERROR, services present but degraded → DEGRADED, all healthy → OK
188
- if (result.services.length === 0) {
189
- const errorMsg = result.error ?? 'No services reported'
190
- return {
191
- app: 'dashboard',
192
- http: `ERROR: ${errorMsg}`,
193
- lastDeploy: '—',
194
- version: '—',
195
- contentHash: '—',
196
- usageStats: '—',
197
- }
198
- }
199
-
200
- const rows = result.services.map(s => `${s.service}:${s.state}/${s.health}`).join(', ')
201
-
202
- if (!result.ok) {
203
- return {
204
- app: 'dashboard',
205
- http: `DEGRADED: ${rows}`,
206
- lastDeploy: '—',
207
- version: '—',
208
- contentHash: '—',
209
- usageStats: '—',
210
- }
211
- }
212
-
213
- return {
214
- app: 'dashboard',
215
- http: `OK: ${rows}`,
216
- lastDeploy: '—',
217
- version: '—',
218
- contentHash: '—',
219
- usageStats: '—',
220
- }
221
- }
222
-
223
- // ─── Action (exported for direct testing) ────────────────────────────────────
224
-
225
- export async function dashboardStatusAction(
226
- options: {key?: string | undefined},
227
- ctx: ActionCtx,
228
- spawn?: SpawnFn,
229
- ): Promise<void> {
230
- const hostEnvKey = options.key ?? 'DASHBOARD_DOMAIN'
231
- const host = process.env[hostEnvKey]
232
-
233
- if (!host) {
234
- ctx.console.error(
235
- 'Dashboard host not set. Export DASHBOARD_DOMAIN or pass --key <env-name> pointing to a set variable.',
236
- )
237
- ctx.process.exit(1)
238
- return
239
- }
240
-
241
- ctx.console.log('Dashboard status')
242
- ctx.console.log('')
243
-
244
- let result: DashboardStatusResult
245
-
246
- try {
247
- result = await getDashboardComposeStatus(host, spawn)
248
- } catch (error) {
249
- ctx.console.error(`Error: ${error instanceof Error ? error.message : String(error)}`)
250
- ctx.process.exit(1)
251
- return
252
- }
253
-
254
- if (result.services.length === 0) {
255
- const msg = result.error ?? 'No services reported by docker compose ps'
256
- ctx.console.error(`Error: ${msg}`)
257
- ctx.process.exit(1)
258
- return
259
- }
260
-
261
- ctx.console.log('Service State Health')
262
- ctx.console.log('─────────────────────────────────────')
263
-
264
- for (const row of result.services) {
265
- const svc = row.service.padEnd(16)
266
- const state = row.state.padEnd(10)
267
- ctx.console.log(`${svc} ${state} ${row.health}`)
268
- }
269
-
270
- ctx.console.log('')
271
-
272
- if (result.ok) {
273
- ctx.console.log('Status: OK')
274
- } else {
275
- ctx.console.log('Status: DEGRADED (one or more services not running)')
276
- ctx.process.exit(1)
277
- }
278
- }
279
-
280
- // ─── Command registration ─────────────────────────────────────────────────────
281
-
282
- export function registerDashboardStatus(cli: ReturnType<typeof goke>): void {
283
- cli
284
- .command('dashboard status', 'Show operational health of the dashboard deployment via docker compose ps.')
285
- .option(
286
- '--key [key]',
287
- z
288
- .string()
289
- .optional()
290
- .describe('Environment variable name holding the SSH host. Falls back to DASHBOARD_DOMAIN when omitted.'),
291
- )
292
- .action((options, ctx) => dashboardStatusAction(options, ctx))
293
- }