@celilo/cli 0.1.5 → 0.1.6

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 (145) hide show
  1. package/drizzle/0004_caddy_hostname_list.sql +25 -0
  2. package/drizzle/meta/_journal.json +14 -0
  3. package/package.json +9 -2
  4. package/src/ansible/inventory.test.ts +3 -2
  5. package/src/ansible/inventory.ts +5 -1
  6. package/src/capabilities/public-web-helpers.test.ts +2 -2
  7. package/src/capabilities/public-web-publish.test.ts +34 -1
  8. package/src/cli/cli.test.ts +2 -2
  9. package/src/cli/command-registry.ts +146 -3
  10. package/src/cli/command-tree-parser.test.ts +1 -1
  11. package/src/cli/command-tree-parser.ts +9 -8
  12. package/src/cli/commands/hook-run.ts +15 -66
  13. package/src/cli/commands/module-audit.ts +14 -44
  14. package/src/cli/commands/module-deploy.ts +4 -1
  15. package/src/cli/commands/module-import-registry.test.ts +115 -0
  16. package/src/cli/commands/module-import.ts +106 -22
  17. package/src/cli/commands/module-publish.test.ts +235 -0
  18. package/src/cli/commands/module-publish.ts +234 -0
  19. package/src/cli/commands/module-remove.ts +82 -2
  20. package/src/cli/commands/module-search.ts +57 -0
  21. package/src/cli/commands/module-secret-get.ts +59 -0
  22. package/src/cli/commands/module-terraform-unlock.ts +57 -0
  23. package/src/cli/commands/module-verify.test.ts +59 -0
  24. package/src/cli/commands/module-verify.ts +53 -0
  25. package/src/cli/commands/status.ts +30 -20
  26. package/src/cli/commands/system-audit.test.ts +138 -0
  27. package/src/cli/commands/system-audit.ts +571 -0
  28. package/src/cli/commands/system-update.ts +391 -0
  29. package/src/cli/completion.ts +15 -1
  30. package/src/cli/fuel-gauge.ts +68 -3
  31. package/src/cli/generate-zsh-completion.ts +13 -3
  32. package/src/cli/index.ts +112 -5
  33. package/src/cli/parser.ts +11 -0
  34. package/src/cli/prompts.ts +36 -5
  35. package/src/cli/tui/audit-state.test.ts +246 -0
  36. package/src/cli/tui/audit-state.ts +525 -0
  37. package/src/cli/tui/audit-tui.test.tsx +135 -0
  38. package/src/cli/tui/audit-tui.tsx +624 -0
  39. package/src/cli/tui/celebration.tsx +29 -0
  40. package/src/cli/tui/clipboard.test.ts +94 -0
  41. package/src/cli/tui/clipboard.ts +101 -0
  42. package/src/cli/tui/icons.ts +22 -0
  43. package/src/cli/tui/keybar.tsx +65 -0
  44. package/src/cli/tui/keymap.test.ts +105 -0
  45. package/src/cli/tui/keymap.ts +70 -0
  46. package/src/cli/tui/modals/analyzing.tsx +75 -0
  47. package/src/cli/tui/modals/celebration.tsx +44 -0
  48. package/src/cli/tui/modals/reaudit-prompt.tsx +35 -0
  49. package/src/cli/tui/modals/remediate.tsx +44 -0
  50. package/src/cli/tui/modals.test.ts +137 -0
  51. package/src/cli/tui/mouse.test.ts +78 -0
  52. package/src/cli/tui/mouse.ts +114 -0
  53. package/src/cli/tui/panes/categories.tsx +62 -0
  54. package/src/cli/tui/panes/command-log.tsx +87 -0
  55. package/src/cli/tui/panes/detail.tsx +175 -0
  56. package/src/cli/tui/panes/findings.tsx +97 -0
  57. package/src/cli/tui/panes/summary.tsx +64 -0
  58. package/src/cli/tui/spawn.ts +130 -0
  59. package/src/cli/tui/theme.ts +42 -0
  60. package/src/cli/tui/wrap.test.ts +43 -0
  61. package/src/cli/tui/wrap.ts +45 -0
  62. package/src/cli/types.ts +5 -0
  63. package/src/db/client.ts +55 -2
  64. package/src/db/schema.ts +26 -17
  65. package/src/hooks/capability-loader.ts +133 -73
  66. package/src/hooks/define-hook.test.ts +9 -1
  67. package/src/hooks/executor.ts +22 -1
  68. package/src/hooks/load-hook-config.test.ts +165 -0
  69. package/src/hooks/load-hook-config.ts +60 -0
  70. package/src/hooks/logger.ts +42 -12
  71. package/src/hooks/run-named-hook.ts +128 -0
  72. package/src/hooks/types.ts +19 -0
  73. package/src/manifest/ensure-schema.test.ts +115 -0
  74. package/src/manifest/schema.ts +76 -0
  75. package/src/module/import.ts +20 -12
  76. package/src/module/packaging/build.ts +85 -16
  77. package/src/module/packaging/release-metadata.test.ts +103 -0
  78. package/src/module/packaging/release-metadata.ts +145 -0
  79. package/src/registry/client.test.ts +228 -0
  80. package/src/registry/client.ts +157 -0
  81. package/src/services/audit/backups.test.ts +233 -0
  82. package/src/services/audit/backups.ts +128 -0
  83. package/src/services/audit/capability-abi.test.ts +153 -0
  84. package/src/services/audit/capability-abi.ts +204 -0
  85. package/src/services/audit/cli-version.test.ts +60 -0
  86. package/src/services/audit/cli-version.ts +87 -0
  87. package/src/services/audit/health.test.ts +84 -0
  88. package/src/services/audit/health.ts +43 -0
  89. package/src/services/audit/index.test.ts +99 -0
  90. package/src/services/audit/index.ts +118 -0
  91. package/src/services/audit/machines-reachable.test.ts +87 -0
  92. package/src/services/audit/machines-reachable.ts +87 -0
  93. package/src/services/audit/module-configs.test.ts +131 -0
  94. package/src/services/audit/module-configs.ts +80 -0
  95. package/src/services/audit/module-versions.test.ts +99 -0
  96. package/src/services/audit/module-versions.ts +154 -0
  97. package/src/services/audit/schema.test.ts +68 -0
  98. package/src/services/audit/schema.ts +115 -0
  99. package/src/services/audit/secrets-decryptable.test.ts +82 -0
  100. package/src/services/audit/secrets-decryptable.ts +97 -0
  101. package/src/services/audit/services-credentials.test.ts +54 -0
  102. package/src/services/audit/services-credentials.ts +64 -0
  103. package/src/services/audit/services-reachable.test.ts +60 -0
  104. package/src/services/audit/services-reachable.ts +64 -0
  105. package/src/services/audit/terraform-plan.test.ts +127 -0
  106. package/src/services/audit/terraform-plan.ts +153 -0
  107. package/src/services/audit/types.test.ts +36 -0
  108. package/src/services/audit/types.ts +90 -0
  109. package/src/services/audit/unconfigured-modules.test.ts +48 -0
  110. package/src/services/audit/unconfigured-modules.ts +71 -0
  111. package/src/services/audit/undeployed-modules.test.ts +66 -0
  112. package/src/services/audit/undeployed-modules.ts +72 -0
  113. package/src/services/build-stream.ts +122 -122
  114. package/src/services/config-interview.ts +407 -2
  115. package/src/services/deploy-ansible.ts +73 -7
  116. package/src/services/deploy-preflight.ts +45 -4
  117. package/src/services/deploy-terraform.ts +31 -24
  118. package/src/services/deploy-validation.ts +167 -23
  119. package/src/services/ensure-interview.test.ts +245 -0
  120. package/src/services/health-runner.ts +110 -38
  121. package/src/services/module-build.ts +11 -13
  122. package/src/services/module-deploy.ts +370 -59
  123. package/src/services/ssh-key-manager.test.ts +1 -1
  124. package/src/services/ssh-key-manager.ts +3 -2
  125. package/src/services/terraform-env.ts +62 -0
  126. package/src/services/update/dep-graph.test.ts +214 -0
  127. package/src/services/update/dep-graph.ts +215 -0
  128. package/src/services/update/orchestrator.test.ts +463 -0
  129. package/src/services/update/orchestrator.ts +359 -0
  130. package/src/services/update/progress.ts +49 -0
  131. package/src/services/update/self-update.test.ts +68 -0
  132. package/src/services/update/self-update.ts +57 -0
  133. package/src/services/update/types.ts +94 -0
  134. package/src/templates/generator.test.ts +1 -1
  135. package/src/templates/generator.ts +42 -1
  136. package/src/test-utils/completion-harness.test.ts +1 -1
  137. package/src/test-utils/completion-harness.ts +4 -4
  138. package/src/variables/capability-self-ref.test.ts +203 -0
  139. package/src/variables/context.ts +49 -1
  140. package/src/variables/declarative-derivation.test.ts +306 -0
  141. package/src/variables/declarative-derivation.ts +4 -2
  142. package/src/variables/parser.test.ts +56 -1
  143. package/src/variables/parser.ts +47 -6
  144. package/src/variables/resolver.ts +27 -9
  145. package/tsconfig.json +1 -0
@@ -13,40 +13,40 @@ import type { ModuleManifest } from '../../manifest/schema';
13
13
  import type { CommandResult } from '../types';
14
14
 
15
15
  /**
16
- * Determine module status based on configuration and filesystem
16
+ * Determine module status for display.
17
+ *
18
+ * Uses the DB state as the primary source of truth.
19
+ * Falls back to filesystem / config inspection for IMPORTED/CONFIGURED modules
20
+ * that haven't yet been deployed.
17
21
  */
18
22
  async function determineModuleStatus(
19
23
  _moduleId: string,
20
24
  manifest: ModuleManifest,
21
25
  configs: (typeof moduleConfigs.$inferSelect)[],
22
26
  generatedPath: string,
27
+ dbState: string,
23
28
  ): Promise<{
24
- status: 'IMPORTED' | 'CONFIGURED' | 'GENERATED' | 'DEPLOYED' | 'NEEDS_UPDATE';
29
+ status: 'IMPORTED' | 'CONFIGURED' | 'GENERATED' | 'DEPLOYED' | 'VERIFIED' | 'NEEDS_UPDATE';
25
30
  missingCount?: number;
26
31
  }> {
27
- // Check if generated directory exists and has recent files
32
+ // Deployed states come directly from the DB don't infer from filesystem
33
+ if (dbState === 'VERIFIED') return { status: 'VERIFIED' };
34
+ if (dbState === 'INSTALLED') return { status: 'DEPLOYED' };
35
+
36
+ // For pre-deploy states, derive from filesystem / config
28
37
  if (existsSync(generatedPath)) {
29
- // TODO: Add DEPLOYED and NEEDS_UPDATE detection
30
- // For now, if generated exists, it's GENERATED
31
38
  return { status: 'GENERATED' };
32
39
  }
33
40
 
34
- // Check if all required variables are set
35
41
  const requiredVars = manifest.variables?.owns?.filter((v) => v.required) || [];
36
42
  const configMap = new Map(configs.map((c) => [c.key, true]));
37
-
38
43
  const missingVars = requiredVars.filter((v) => !configMap.has(v.name));
39
44
 
40
- if (missingVars.length === 0 && requiredVars.length > 0) {
41
- return { status: 'CONFIGURED' };
42
- }
43
-
44
45
  if (missingVars.length > 0) {
45
46
  return { status: 'IMPORTED', missingCount: missingVars.length };
46
47
  }
47
48
 
48
- // No required variables, but some config set
49
- if (configs.length > 0) {
49
+ if (requiredVars.length > 0 || configs.length > 0) {
50
50
  return { status: 'CONFIGURED' };
51
51
  }
52
52
 
@@ -116,10 +116,16 @@ export async function handleStatus(): Promise<CommandResult> {
116
116
  manifest,
117
117
  moduleConfigsList,
118
118
  generatedPath,
119
+ module.state,
119
120
  );
120
121
 
121
122
  // Status icon
122
- const icon = statusInfo.status === 'GENERATED' ? '✓' : '⚠';
123
+ const icon =
124
+ statusInfo.status === 'VERIFIED' ||
125
+ statusInfo.status === 'DEPLOYED' ||
126
+ statusInfo.status === 'GENERATED'
127
+ ? '✓'
128
+ : '⚠';
123
129
 
124
130
  lines.push(` ${icon} ${module.id} (v${module.version})`);
125
131
  lines.push(` Status: ${statusInfo.status}`);
@@ -169,7 +175,11 @@ export async function handleStatus(): Promise<CommandResult> {
169
175
  }
170
176
 
171
177
  // Show when last generated (if applicable)
172
- if (statusInfo.status === 'GENERATED') {
178
+ if (
179
+ statusInfo.status === 'GENERATED' ||
180
+ statusInfo.status === 'DEPLOYED' ||
181
+ statusInfo.status === 'VERIFIED'
182
+ ) {
173
183
  try {
174
184
  const stats = await stat(generatedPath);
175
185
  const age = Date.now() - stats.mtimeMs;
@@ -197,11 +207,11 @@ export async function handleStatus(): Promise<CommandResult> {
197
207
 
198
208
  // Legend
199
209
  lines.push('Legend:');
200
- lines.push(' IMPORTED - Module imported, not configured');
201
- lines.push(' CONFIGURED - Configuration complete, not generated');
202
- lines.push(' GENERATED - Infrastructure code generated, not deployed');
203
- lines.push(' DEPLOYED - Running in production (not yet implemented)');
204
- lines.push(' NEEDS_UPDATE - Configuration changed, needs regeneration (not yet implemented)');
210
+ lines.push(' IMPORTED - Module imported, not configured');
211
+ lines.push(' CONFIGURED - Configuration complete, not generated');
212
+ lines.push(' GENERATED - Infrastructure code generated, not deployed');
213
+ lines.push(' DEPLOYED - Deployed (Ansible complete, health check pending)');
214
+ lines.push(' VERIFIED - Deployed and health checks passed');
205
215
 
206
216
  return {
207
217
  success: true,
@@ -0,0 +1,138 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import type { SystemAuditReport } from '../../services/audit';
3
+ import { formatReport } from './system-audit';
4
+
5
+ const baseReport = (overrides: Partial<SystemAuditReport> = {}): SystemAuditReport => ({
6
+ version: 1,
7
+ verdict: 'READY',
8
+ generatedAt: '2026-04-25T00:00:00.000Z',
9
+ findings: [],
10
+ ...overrides,
11
+ });
12
+
13
+ describe('formatReport', () => {
14
+ test('READY report calls out no drift', () => {
15
+ const out = formatReport(baseReport());
16
+ expect(out).toContain('READY');
17
+ expect(out).toContain('No drift detected');
18
+ });
19
+
20
+ test('DRIFT report lists each finding with remediation', () => {
21
+ const out = formatReport(
22
+ baseReport({
23
+ verdict: 'DRIFT',
24
+ findings: [
25
+ {
26
+ category: 'cli_version',
27
+ severity: 'drift',
28
+ code: 'cli_version_drift',
29
+ message: '@celilo/cli 0.1.5 → 0.1.7 available',
30
+ remediation: '`celilo system update` will self-update first',
31
+ subject: 'system',
32
+ },
33
+ ],
34
+ }),
35
+ );
36
+
37
+ expect(out).toContain('DRIFT (1 finding)');
38
+ expect(out).toContain('@celilo/cli 0.1.5 → 0.1.7');
39
+ expect(out).toContain('→ `celilo system update`');
40
+ });
41
+
42
+ test('BLOCKED report puts blocked findings first', () => {
43
+ const out = formatReport(
44
+ baseReport({
45
+ verdict: 'BLOCKED',
46
+ findings: [
47
+ {
48
+ category: 'cli_version',
49
+ severity: 'drift',
50
+ code: 'cli_version_drift',
51
+ message: 'cli is old',
52
+ subject: 'system',
53
+ },
54
+ {
55
+ category: 'schema',
56
+ severity: 'blocked',
57
+ code: 'schema_pending_migrations',
58
+ message: '2 pending DB migrations',
59
+ subject: 'system',
60
+ },
61
+ ],
62
+ }),
63
+ );
64
+
65
+ const blockedIdx = out.indexOf('BLOCKED • schema');
66
+ const driftIdx = out.indexOf('DRIFT • cli_version');
67
+ expect(blockedIdx).toBeGreaterThanOrEqual(0);
68
+ expect(driftIdx).toBeGreaterThan(blockedIdx);
69
+ });
70
+
71
+ test('groups findings by category', () => {
72
+ const out = formatReport(
73
+ baseReport({
74
+ verdict: 'DRIFT',
75
+ findings: [
76
+ {
77
+ category: 'module_versions',
78
+ severity: 'drift',
79
+ code: 'x',
80
+ message: 'caddy: 1.0 → 1.1',
81
+ subject: 'caddy',
82
+ },
83
+ {
84
+ category: 'module_versions',
85
+ severity: 'drift',
86
+ code: 'x',
87
+ message: 'iptables: 1.0 → 1.1',
88
+ subject: 'iptables',
89
+ },
90
+ ],
91
+ }),
92
+ );
93
+
94
+ expect(out).toContain('DRIFT • module_versions (2)');
95
+ expect(out).toContain('caddy: 1.0 → 1.1');
96
+ expect(out).toContain('iptables: 1.0 → 1.1');
97
+ });
98
+
99
+ test('renders details on indented lines', () => {
100
+ const out = formatReport(
101
+ baseReport({
102
+ verdict: 'BLOCKED',
103
+ findings: [
104
+ {
105
+ category: 'schema',
106
+ severity: 'blocked',
107
+ code: 'schema_pending_migrations',
108
+ message: '2 pending DB migrations',
109
+ details: ' • 0001_zones\n • 0002_backups',
110
+ subject: 'system',
111
+ },
112
+ ],
113
+ }),
114
+ );
115
+
116
+ expect(out).toContain('0001_zones');
117
+ expect(out).toContain('0002_backups');
118
+ });
119
+
120
+ test('singular wording for exactly one finding', () => {
121
+ const out = formatReport(
122
+ baseReport({
123
+ verdict: 'DRIFT',
124
+ findings: [
125
+ {
126
+ category: 'cli_version',
127
+ severity: 'drift',
128
+ code: 'x',
129
+ message: 'cli is old',
130
+ subject: 'system',
131
+ },
132
+ ],
133
+ }),
134
+ );
135
+ expect(out).toContain('DRIFT (1 finding)');
136
+ expect(out).not.toContain('1 findings');
137
+ });
138
+ });