@celilo/cli 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 (267) hide show
  1. package/README.md +1566 -0
  2. package/bin/celilo +16 -0
  3. package/drizzle/0000_complex_puma.sql +179 -0
  4. package/drizzle/0001_dizzy_wolfpack.sql +2 -0
  5. package/drizzle/0002_web_routes.sql +16 -0
  6. package/drizzle/0003_backup_storage.sql +32 -0
  7. package/drizzle/meta/0000_snapshot.json +1151 -0
  8. package/drizzle/meta/0001_snapshot.json +1167 -0
  9. package/drizzle/meta/0002_snapshot.json +1257 -0
  10. package/drizzle/meta/_journal.json +27 -0
  11. package/package.json +64 -0
  12. package/schemas/system_config.json +106 -0
  13. package/src/__integration__/container-services-cli.integration.test.ts +246 -0
  14. package/src/ansible/dependencies.test.ts +309 -0
  15. package/src/ansible/dependencies.ts +896 -0
  16. package/src/ansible/inventory.test.ts +463 -0
  17. package/src/ansible/inventory.ts +445 -0
  18. package/src/ansible/secrets.ts +222 -0
  19. package/src/ansible/validation.test.ts +92 -0
  20. package/src/ansible/validation.ts +272 -0
  21. package/src/api-clients/digitalocean.ts +94 -0
  22. package/src/api-clients/proxmox.ts +655 -0
  23. package/src/capabilities/logging-wrapper.test.ts +217 -0
  24. package/src/capabilities/lookup.test.ts +149 -0
  25. package/src/capabilities/lookup.ts +89 -0
  26. package/src/capabilities/public-web-helpers.test.ts +198 -0
  27. package/src/capabilities/public-web-publish.test.ts +458 -0
  28. package/src/capabilities/registration.test.ts +395 -0
  29. package/src/capabilities/registration.ts +200 -0
  30. package/src/capabilities/route-validation.test.ts +121 -0
  31. package/src/capabilities/route-validation.ts +96 -0
  32. package/src/capabilities/secret-ref.test.ts +313 -0
  33. package/src/capabilities/secret-validation.ts +157 -0
  34. package/src/capabilities/secrets.test.ts +750 -0
  35. package/src/capabilities/secrets.ts +244 -0
  36. package/src/capabilities/validation.test.ts +613 -0
  37. package/src/capabilities/validation.ts +160 -0
  38. package/src/capabilities/well-known.test.ts +238 -0
  39. package/src/capabilities/well-known.ts +222 -0
  40. package/src/cli/cli.test.ts +654 -0
  41. package/src/cli/command-registry.ts +742 -0
  42. package/src/cli/command-tree-parser.test.ts +180 -0
  43. package/src/cli/command-tree-parser.ts +193 -0
  44. package/src/cli/commands/backup-create.ts +137 -0
  45. package/src/cli/commands/backup-delete.ts +74 -0
  46. package/src/cli/commands/backup-import.ts +97 -0
  47. package/src/cli/commands/backup-list.ts +132 -0
  48. package/src/cli/commands/backup-name.ts +73 -0
  49. package/src/cli/commands/backup-prune.ts +98 -0
  50. package/src/cli/commands/backup-restore.ts +122 -0
  51. package/src/cli/commands/capability-info.ts +121 -0
  52. package/src/cli/commands/capability-list.ts +47 -0
  53. package/src/cli/commands/completion.ts +87 -0
  54. package/src/cli/commands/hook-run.ts +176 -0
  55. package/src/cli/commands/ipam.ts +607 -0
  56. package/src/cli/commands/machine-add.ts +235 -0
  57. package/src/cli/commands/machine-earmark.ts +82 -0
  58. package/src/cli/commands/machine-list.ts +77 -0
  59. package/src/cli/commands/machine-remove.ts +90 -0
  60. package/src/cli/commands/machine-status.ts +131 -0
  61. package/src/cli/commands/module-audit.ts +51 -0
  62. package/src/cli/commands/module-build.ts +60 -0
  63. package/src/cli/commands/module-config.ts +170 -0
  64. package/src/cli/commands/module-deploy.ts +71 -0
  65. package/src/cli/commands/module-generate.ts +236 -0
  66. package/src/cli/commands/module-health.ts +108 -0
  67. package/src/cli/commands/module-import.ts +80 -0
  68. package/src/cli/commands/module-list.ts +43 -0
  69. package/src/cli/commands/module-logs.ts +73 -0
  70. package/src/cli/commands/module-remove.ts +162 -0
  71. package/src/cli/commands/module-show.ts +208 -0
  72. package/src/cli/commands/module-status.ts +131 -0
  73. package/src/cli/commands/module-types.ts +189 -0
  74. package/src/cli/commands/module-upgrade.ts +192 -0
  75. package/src/cli/commands/package.ts +68 -0
  76. package/src/cli/commands/secret-list.ts +99 -0
  77. package/src/cli/commands/secret-set.ts +134 -0
  78. package/src/cli/commands/service-add-digitalocean.ts +133 -0
  79. package/src/cli/commands/service-add-proxmox.ts +342 -0
  80. package/src/cli/commands/service-config-get.ts +83 -0
  81. package/src/cli/commands/service-config-set.ts +145 -0
  82. package/src/cli/commands/service-list.ts +74 -0
  83. package/src/cli/commands/service-reconfigure.ts +230 -0
  84. package/src/cli/commands/service-remove.ts +103 -0
  85. package/src/cli/commands/service-verify.ts +240 -0
  86. package/src/cli/commands/status.ts +216 -0
  87. package/src/cli/commands/storage-add-local.ts +106 -0
  88. package/src/cli/commands/storage-add-s3.ts +114 -0
  89. package/src/cli/commands/storage-list.ts +72 -0
  90. package/src/cli/commands/storage-remove.ts +54 -0
  91. package/src/cli/commands/storage-set-default.ts +44 -0
  92. package/src/cli/commands/storage-verify.ts +54 -0
  93. package/src/cli/commands/system-config.ts +168 -0
  94. package/src/cli/commands/system-init.ts +314 -0
  95. package/src/cli/commands/system-secret-get.ts +98 -0
  96. package/src/cli/commands/system-secret-set.ts +76 -0
  97. package/src/cli/commands/system-vault-password.ts +34 -0
  98. package/src/cli/completion.test.ts +37 -0
  99. package/src/cli/completion.ts +482 -0
  100. package/src/cli/fuel-gauge.test.ts +208 -0
  101. package/src/cli/fuel-gauge.ts +405 -0
  102. package/src/cli/generate-zsh-completion.test.ts +95 -0
  103. package/src/cli/generate-zsh-completion.ts +497 -0
  104. package/src/cli/index.ts +1583 -0
  105. package/src/cli/interactive-config.test.ts +201 -0
  106. package/src/cli/interactive-config.ts +62 -0
  107. package/src/cli/parser.test.ts +227 -0
  108. package/src/cli/parser.ts +244 -0
  109. package/src/cli/prompts.test.ts +33 -0
  110. package/src/cli/prompts.ts +121 -0
  111. package/src/cli/types.ts +38 -0
  112. package/src/cli/validators.test.ts +235 -0
  113. package/src/cli/validators.ts +188 -0
  114. package/src/config/env.ts +41 -0
  115. package/src/config/paths.test.ts +172 -0
  116. package/src/config/paths.ts +108 -0
  117. package/src/db/client.ts +190 -0
  118. package/src/db/migrate.ts +30 -0
  119. package/src/db/schema.test.ts +221 -0
  120. package/src/db/schema.ts +434 -0
  121. package/src/hooks/capability-loader-firewall.test.ts +246 -0
  122. package/src/hooks/capability-loader.test.ts +100 -0
  123. package/src/hooks/capability-loader.ts +520 -0
  124. package/src/hooks/define-hook.test.ts +488 -0
  125. package/src/hooks/executor.test.ts +462 -0
  126. package/src/hooks/executor.ts +469 -0
  127. package/src/hooks/logger.test.ts +54 -0
  128. package/src/hooks/logger.ts +95 -0
  129. package/src/hooks/test-fixtures/failing-hook.ts +13 -0
  130. package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
  131. package/src/hooks/test-fixtures/success-hook.ts +20 -0
  132. package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
  133. package/src/hooks/test-fixtures/void-hook.ts +13 -0
  134. package/src/hooks/types.ts +89 -0
  135. package/src/infrastructure/property-extractor.test.ts +194 -0
  136. package/src/infrastructure/property-extractor.ts +151 -0
  137. package/src/ipam/allocator.test.ts +442 -0
  138. package/src/ipam/allocator.ts +369 -0
  139. package/src/ipam/auto-allocator.test.ts +247 -0
  140. package/src/ipam/auto-allocator.ts +270 -0
  141. package/src/ipam/subnet-parser.test.ts +107 -0
  142. package/src/ipam/subnet-parser.ts +136 -0
  143. package/src/manifest/contracts/index.ts +61 -0
  144. package/src/manifest/contracts/v1.ts +118 -0
  145. package/src/manifest/json-schema-roundtrip.test.ts +99 -0
  146. package/src/manifest/schema.ts +367 -0
  147. package/src/manifest/template-validator.test.ts +231 -0
  148. package/src/manifest/template-validator.ts +322 -0
  149. package/src/manifest/validate.test.ts +1180 -0
  150. package/src/manifest/validate.ts +415 -0
  151. package/src/module/import.test.ts +355 -0
  152. package/src/module/import.ts +676 -0
  153. package/src/module/packaging/audit.ts +169 -0
  154. package/src/module/packaging/build.ts +228 -0
  155. package/src/module/packaging/checksum.ts +41 -0
  156. package/src/module/packaging/extract.ts +234 -0
  157. package/src/module/packaging/signature.ts +47 -0
  158. package/src/secrets/encryption.test.ts +284 -0
  159. package/src/secrets/encryption.ts +162 -0
  160. package/src/secrets/generators.test.ts +112 -0
  161. package/src/secrets/generators.ts +127 -0
  162. package/src/secrets/master-key.test.ts +159 -0
  163. package/src/secrets/master-key.ts +114 -0
  164. package/src/secrets/storage.test.ts +115 -0
  165. package/src/secrets/storage.ts +106 -0
  166. package/src/secrets/vault.test.ts +35 -0
  167. package/src/secrets/vault.ts +42 -0
  168. package/src/services/backup-create.ts +532 -0
  169. package/src/services/backup-metadata.ts +198 -0
  170. package/src/services/backup-restore.ts +229 -0
  171. package/src/services/backup-retention.ts +84 -0
  172. package/src/services/backup-storage.ts +281 -0
  173. package/src/services/build-stream.test.ts +122 -0
  174. package/src/services/build-stream.ts +201 -0
  175. package/src/services/config-interview.ts +694 -0
  176. package/src/services/container-service.test.ts +298 -0
  177. package/src/services/container-service.ts +401 -0
  178. package/src/services/cross-module-data-manager.test.ts +405 -0
  179. package/src/services/cross-module-data-manager.ts +412 -0
  180. package/src/services/deploy-ansible.ts +88 -0
  181. package/src/services/deploy-planner.ts +153 -0
  182. package/src/services/deploy-preflight.ts +274 -0
  183. package/src/services/deploy-ssh.ts +131 -0
  184. package/src/services/deploy-terraform.test.ts +55 -0
  185. package/src/services/deploy-terraform.ts +445 -0
  186. package/src/services/deploy-validation.ts +311 -0
  187. package/src/services/dns-auto-register.ts +211 -0
  188. package/src/services/health-runner.ts +184 -0
  189. package/src/services/infrastructure-selector.test.ts +485 -0
  190. package/src/services/infrastructure-selector.ts +245 -0
  191. package/src/services/infrastructure-variable-resolver.test.ts +751 -0
  192. package/src/services/infrastructure-variable-resolver.ts +234 -0
  193. package/src/services/machine-detector.ts +328 -0
  194. package/src/services/machine-pool.test.ts +405 -0
  195. package/src/services/machine-pool.ts +316 -0
  196. package/src/services/manifest-validation.ts +120 -0
  197. package/src/services/module-build.test.ts +290 -0
  198. package/src/services/module-build.ts +431 -0
  199. package/src/services/module-config.test.ts +237 -0
  200. package/src/services/module-config.ts +298 -0
  201. package/src/services/module-deploy.ts +862 -0
  202. package/src/services/module-types-drift.test.ts +73 -0
  203. package/src/services/module-types-generator.test.ts +288 -0
  204. package/src/services/module-types-generator.ts +189 -0
  205. package/src/services/proxmox-state-recovery.ts +140 -0
  206. package/src/services/schema-validation.ts +155 -0
  207. package/src/services/secret-schema-loader.test.ts +311 -0
  208. package/src/services/secret-schema-loader.ts +239 -0
  209. package/src/services/ssh-key-manager.test.ts +283 -0
  210. package/src/services/ssh-key-manager.ts +193 -0
  211. package/src/services/storage-providers/local.ts +105 -0
  212. package/src/services/storage-providers/s3.ts +182 -0
  213. package/src/services/storage-providers/types.ts +24 -0
  214. package/src/services/system-config-schema-types.ts +25 -0
  215. package/src/services/system-config-validator.test.ts +160 -0
  216. package/src/services/system-config-validator.ts +74 -0
  217. package/src/services/system-init.test.ts +153 -0
  218. package/src/services/system-init.ts +253 -0
  219. package/src/services/terraform-safety.ts +174 -0
  220. package/src/services/zone-detector.test.ts +110 -0
  221. package/src/services/zone-detector.ts +102 -0
  222. package/src/services/zone-policy.test.ts +97 -0
  223. package/src/services/zone-policy.ts +126 -0
  224. package/src/templates/generator.test.ts +645 -0
  225. package/src/templates/generator.ts +1119 -0
  226. package/src/templates/types.ts +62 -0
  227. package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
  228. package/src/test-utils/cli-context-interactive.test.ts +152 -0
  229. package/src/test-utils/cli-context-server.test.ts +66 -0
  230. package/src/test-utils/cli-context.test.ts +273 -0
  231. package/src/test-utils/cli-context.ts +677 -0
  232. package/src/test-utils/cli-result.test.ts +282 -0
  233. package/src/test-utils/cli-result.ts +241 -0
  234. package/src/test-utils/cli.ts +55 -0
  235. package/src/test-utils/completion-harness.test.ts +126 -0
  236. package/src/test-utils/completion-harness.ts +82 -0
  237. package/src/test-utils/database.test.ts +182 -0
  238. package/src/test-utils/database.ts +126 -0
  239. package/src/test-utils/filesystem.test.ts +208 -0
  240. package/src/test-utils/filesystem.ts +142 -0
  241. package/src/test-utils/fixtures.test.ts +123 -0
  242. package/src/test-utils/fixtures.ts +160 -0
  243. package/src/test-utils/golden-diff.ts +197 -0
  244. package/src/test-utils/index.ts +77 -0
  245. package/src/test-utils/integration.ts +81 -0
  246. package/src/test-utils/module-fixtures.ts +468 -0
  247. package/src/test-utils/modules.test.ts +144 -0
  248. package/src/test-utils/modules.ts +183 -0
  249. package/src/test-utils/setup-test-db.ts +90 -0
  250. package/src/test-utils/value-extractor.test.ts +231 -0
  251. package/src/test-utils/value-extractor.ts +228 -0
  252. package/src/types/infrastructure.ts +157 -0
  253. package/src/utils/shell.test.ts +365 -0
  254. package/src/utils/shell.ts +159 -0
  255. package/src/validation/schemas.ts +166 -0
  256. package/src/variables/ansible-resolver.test.ts +142 -0
  257. package/src/variables/ansible-resolver.ts +69 -0
  258. package/src/variables/capability-self-ref.test.ts +220 -0
  259. package/src/variables/context.test.ts +1265 -0
  260. package/src/variables/context.ts +624 -0
  261. package/src/variables/declarative-derivation.test.ts +743 -0
  262. package/src/variables/declarative-derivation.ts +200 -0
  263. package/src/variables/parser.test.ts +231 -0
  264. package/src/variables/parser.ts +76 -0
  265. package/src/variables/resolver.test.ts +458 -0
  266. package/src/variables/resolver.ts +282 -0
  267. package/src/variables/types.ts +59 -0
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Module build command
3
+ *
4
+ * Executes module build scripts (Ansible playbooks) for imported modules
5
+ */
6
+
7
+ import { getDb } from '../../db/client';
8
+ import { buildModuleFromSource } from '../../services/module-build';
9
+ import { getArg, validateRequiredArgs } from '../parser';
10
+ import type { CommandResult } from '../types';
11
+
12
+ /**
13
+ * Handle module build command
14
+ *
15
+ * Usage:
16
+ * celilo module build <module-id>
17
+ *
18
+ * @param args - Command arguments
19
+ * @param flags - Command flags (unused)
20
+ * @returns Command result
21
+ */
22
+ export async function handleModuleBuild(
23
+ args: string[],
24
+ _flags: Record<string, string | boolean>,
25
+ ): Promise<CommandResult> {
26
+ // Validate arguments
27
+ const error = validateRequiredArgs(args, 1);
28
+ if (error) {
29
+ return {
30
+ success: false,
31
+ error: `${error}\n\nUsage:\n celilo module build <module-id>`,
32
+ };
33
+ }
34
+
35
+ const moduleId = getArg(args, 0);
36
+ if (!moduleId) {
37
+ return {
38
+ success: false,
39
+ error: 'Module ID is required',
40
+ };
41
+ }
42
+
43
+ const db = getDb();
44
+ const result = await buildModuleFromSource(moduleId, db);
45
+
46
+ if (!result.success) {
47
+ return {
48
+ success: false,
49
+ error: result.error || 'Build failed',
50
+ };
51
+ }
52
+
53
+ return {
54
+ success: true,
55
+ message: `āœ“ Build successful\nArtifacts: ${result.artifacts?.join(', ') || 'none'}`,
56
+ data: {
57
+ artifacts: result.artifacts,
58
+ },
59
+ };
60
+ }
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Module config command
3
+ */
4
+
5
+ import { eq } from 'drizzle-orm';
6
+ import { getDb } from '../../db/client';
7
+ import { modules } from '../../db/schema';
8
+ import {
9
+ formatConfigValue,
10
+ getAllModuleConfigValues,
11
+ getModuleConfigValue,
12
+ setModuleConfigValue,
13
+ } from '../../services/module-config';
14
+ import { getArg, validateRequiredArgs } from '../parser';
15
+ import type { CommandResult } from '../types';
16
+
17
+ /**
18
+ * Handle module config set command
19
+ *
20
+ * Usage: celilo module config set <module-id> <key> <value>
21
+ *
22
+ * @param args - Command arguments
23
+ * @returns Command result
24
+ */
25
+ export async function handleModuleConfigSet(args: string[]): Promise<CommandResult> {
26
+ // Validate arguments
27
+ const error = validateRequiredArgs(args, 3);
28
+ if (error) {
29
+ return {
30
+ success: false,
31
+ error: `${error}\n\nUsage: celilo module config set <module-id> <key> <value>`,
32
+ };
33
+ }
34
+
35
+ const moduleId = getArg(args, 0);
36
+ const key = getArg(args, 1);
37
+ const value = getArg(args, 2);
38
+
39
+ if (!moduleId || !key || !value) {
40
+ return {
41
+ success: false,
42
+ error: 'Module ID, key, and value are required',
43
+ };
44
+ }
45
+
46
+ const db = getDb();
47
+
48
+ // Check if module exists
49
+ const module = db.select().from(modules).where(eq(modules.id, moduleId)).get();
50
+ if (!module) {
51
+ return {
52
+ success: false,
53
+ error: `Module not found: ${moduleId}`,
54
+ };
55
+ }
56
+
57
+ // Validate key against manifest
58
+ const manifest = module.manifestData as Record<string, unknown>;
59
+ const variables = manifest.variables as
60
+ | { owns?: Array<{ name: string; required?: boolean; default?: string }> }
61
+ | undefined;
62
+ const declaredVars = variables?.owns || [];
63
+
64
+ // Check if key is declared in manifest
65
+ const declaredVar = declaredVars.find((v) => v.name === key);
66
+ if (!declaredVar) {
67
+ const validKeys = declaredVars.map((v) => v.name).join(', ');
68
+ return {
69
+ success: false,
70
+ error: `Invalid config key '${key}' for module ${moduleId}.\n\nValid keys: ${validKeys || '(none declared)'}`,
71
+ };
72
+ }
73
+
74
+ // Set config value using service (handles primitive and complex types)
75
+ try {
76
+ await setModuleConfigValue(moduleId, key, value);
77
+
78
+ return {
79
+ success: true,
80
+ message: `Set config for ${moduleId}: ${key}`,
81
+ };
82
+ } catch (error) {
83
+ return {
84
+ success: false,
85
+ error: `Failed to set config: ${error instanceof Error ? error.message : String(error)}`,
86
+ };
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Handle module config get command
92
+ *
93
+ * Usage: celilo module config get <module-id> [key]
94
+ *
95
+ * @param args - Command arguments
96
+ * @returns Command result
97
+ */
98
+ export async function handleModuleConfigGet(args: string[]): Promise<CommandResult> {
99
+ // Validate arguments
100
+ const error = validateRequiredArgs(args, 1);
101
+ if (error) {
102
+ return {
103
+ success: false,
104
+ error: `${error}\n\nUsage: celilo module config get <module-id> [key]`,
105
+ };
106
+ }
107
+
108
+ const moduleId = getArg(args, 0);
109
+ const key = getArg(args, 1);
110
+
111
+ if (!moduleId) {
112
+ return {
113
+ success: false,
114
+ error: 'Module ID is required',
115
+ };
116
+ }
117
+
118
+ const db = getDb();
119
+
120
+ // Check if module exists
121
+ const module = db.select().from(modules).where(eq(modules.id, moduleId)).get();
122
+ if (!module) {
123
+ return {
124
+ success: false,
125
+ error: `Module not found: ${moduleId}`,
126
+ };
127
+ }
128
+
129
+ if (key) {
130
+ // Get specific config value
131
+ const configValue = getModuleConfigValue(moduleId, key);
132
+
133
+ if (!configValue) {
134
+ return {
135
+ success: false,
136
+ error: `Config key not found: ${key}`,
137
+ };
138
+ }
139
+
140
+ const formatted = formatConfigValue(configValue);
141
+
142
+ return {
143
+ success: true,
144
+ message: `${key} = ${formatted}`,
145
+ data: { key, value: configValue.value },
146
+ };
147
+ }
148
+
149
+ // Get all config for module
150
+ const configs = getAllModuleConfigValues(moduleId);
151
+
152
+ if (configs.length === 0) {
153
+ return {
154
+ success: true,
155
+ message: `No configuration set for ${moduleId}`,
156
+ };
157
+ }
158
+
159
+ const lines = [`Configuration for ${moduleId}:`, ''];
160
+ for (const config of configs) {
161
+ const formatted = formatConfigValue(config);
162
+ lines.push(`${config.key} = ${formatted}`);
163
+ }
164
+
165
+ return {
166
+ success: true,
167
+ message: lines.join('\n'),
168
+ data: configs.map((c) => ({ key: c.key, value: c.value })),
169
+ };
170
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Module deploy command
3
+ *
4
+ * Deploys module to infrastructure (Terraform + Ansible)
5
+ */
6
+
7
+ import { getDb } from '../../db/client';
8
+ import { formatPreflightResult, runPreflight } from '../../services/deploy-preflight';
9
+ import { deployModule } from '../../services/module-deploy';
10
+ import { getArg, hasFlag, validateRequiredArgs } from '../parser';
11
+ import type { CommandResult } from '../types';
12
+
13
+ /**
14
+ * Handle module deploy command
15
+ *
16
+ * Usage:
17
+ * celilo module deploy <module-id> [--no-interactive] [--debug] [--preflight]
18
+ *
19
+ * @param args - Command arguments
20
+ * @param flags - Command flags
21
+ * @returns Command result
22
+ */
23
+ export async function handleModuleDeploy(
24
+ args: string[],
25
+ flags: Record<string, string | boolean>,
26
+ ): Promise<CommandResult> {
27
+ // Validate arguments
28
+ const error = validateRequiredArgs(args, 1);
29
+ if (error) {
30
+ return {
31
+ success: false,
32
+ error: `${error}\n\nUsage:\n celilo module deploy <module-id> [--no-interactive] [--preflight]`,
33
+ };
34
+ }
35
+
36
+ const moduleId = getArg(args, 0);
37
+ if (!moduleId) {
38
+ return {
39
+ success: false,
40
+ error: 'Module ID is required',
41
+ };
42
+ }
43
+
44
+ const db = getDb();
45
+
46
+ // --preflight: fast validation only, no actual deployment
47
+ if (hasFlag(flags, 'preflight')) {
48
+ const preflight = await runPreflight(moduleId, db);
49
+ const message = formatPreflightResult(preflight);
50
+ return preflight.success ? { success: true, message } : { success: false, error: message };
51
+ }
52
+
53
+ const noInteractive = hasFlag(flags, 'no-interactive');
54
+ const debug = hasFlag(flags, 'debug');
55
+
56
+ const result = await deployModule(moduleId, db, { noInteractive, debug });
57
+
58
+ if (!result.success) {
59
+ return {
60
+ success: false,
61
+ error: result.error || 'Deployment failed',
62
+ details: result.phases,
63
+ };
64
+ }
65
+
66
+ return {
67
+ success: true,
68
+ message: `āœ“ Module '${moduleId}' deployed successfully`,
69
+ data: result.phases,
70
+ };
71
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Module generate command
3
+ */
4
+
5
+ import { existsSync } from 'node:fs';
6
+ import { resolve } from 'node:path';
7
+ import { eq } from 'drizzle-orm';
8
+ import {
9
+ promptForMissingCapabilitySecrets,
10
+ validateCapabilitySecrets,
11
+ } from '../../capabilities/secret-validation';
12
+ import { getDb } from '../../db/client';
13
+ import { moduleConfigs, modules } from '../../db/schema';
14
+ import type { ModuleManifest } from '../../manifest/schema';
15
+ import { interviewForMissingSecrets, validateModuleSecrets } from '../../services/config-interview';
16
+ import { getModuleBuildStatus, verifyArtifactsExist } from '../../services/module-build';
17
+ import { generateTemplates } from '../../templates/generator';
18
+ import { promptForMissingConfig } from '../interactive-config';
19
+ import { getArg, getFlag, validateRequiredArgs } from '../parser';
20
+ import type { CommandResult } from '../types';
21
+
22
+ /**
23
+ * Handle module generate command
24
+ *
25
+ * Usage: celilo module generate <module-id> [--output <path>]
26
+ *
27
+ * @param args - Command arguments
28
+ * @param flags - Command flags
29
+ * @returns Command result
30
+ */
31
+ export async function handleModuleGenerate(
32
+ args: string[],
33
+ flags: Record<string, string | boolean>,
34
+ ): Promise<CommandResult> {
35
+ // Validate arguments
36
+ const error = validateRequiredArgs(args, 1);
37
+ if (error) {
38
+ return {
39
+ success: false,
40
+ error: `${error}\n\nUsage: celilo module generate <module-id> [--output <path>]`,
41
+ };
42
+ }
43
+
44
+ const moduleId = getArg(args, 0);
45
+ if (!moduleId) {
46
+ return {
47
+ success: false,
48
+ error: 'Module ID is required',
49
+ };
50
+ }
51
+
52
+ const db = getDb();
53
+
54
+ // Check if module exists
55
+ const module = db.select().from(modules).where(eq(modules.id, moduleId)).get();
56
+ if (!module) {
57
+ return {
58
+ success: false,
59
+ error: `Module not found: ${moduleId}`,
60
+ };
61
+ }
62
+
63
+ // Get module path and output path
64
+ const modulePath = module.sourcePath;
65
+
66
+ // Check if module files exist (may have been deleted from /tmp)
67
+ if (!existsSync(modulePath)) {
68
+ return {
69
+ success: false,
70
+ error: `Module files not found at: ${modulePath}\n\nThe module files may have been deleted (e.g., /tmp cleanup).\n\nTo fix this:\n 1. Remove the module: celilo module remove ${moduleId}\n 2. Re-import it: celilo module import <original-module-path>\n 3. Reconfigure if needed: celilo module config set ${moduleId} <key> <value>\n 4. Regenerate: celilo module generate ${moduleId}`,
71
+ };
72
+ }
73
+
74
+ // Check if module requires build step
75
+ const manifest = module.manifestData as ModuleManifest;
76
+ if (manifest.build) {
77
+ const buildStatus = await getModuleBuildStatus(moduleId, db);
78
+
79
+ if (!buildStatus || buildStatus.status !== 'success') {
80
+ return {
81
+ success: false,
82
+ error:
83
+ `Module ${moduleId} requires build step before generation.\n\n` +
84
+ `Run: celilo module build ${moduleId}`,
85
+ };
86
+ }
87
+
88
+ // Verify artifacts still exist
89
+ if (buildStatus.artifacts.length > 0) {
90
+ const artifactsValid = verifyArtifactsExist(buildStatus.artifacts);
91
+
92
+ if (!artifactsValid) {
93
+ return {
94
+ success: false,
95
+ error: `Build artifacts missing or invalid.\n\nRebuild: celilo module build ${moduleId}`,
96
+ };
97
+ }
98
+ }
99
+ }
100
+
101
+ const outputPathFlag = getFlag(flags, 'output', '');
102
+ const outputPath = outputPathFlag || `${modulePath}/generated`;
103
+ const resolvedOutputPath = resolve(outputPath);
104
+
105
+ // Check for missing capability secrets
106
+ const secretValidation = await validateCapabilitySecrets(moduleId, db.$client);
107
+
108
+ if (!secretValidation.success) {
109
+ // If missing secrets and in interactive mode, prompt user
110
+ if (process.stdin.isTTY && secretValidation.missingSecrets) {
111
+ const promptResult = await promptForMissingCapabilitySecrets(
112
+ moduleId,
113
+ secretValidation.missingSecrets,
114
+ db.$client,
115
+ );
116
+
117
+ if (!promptResult.success) {
118
+ return {
119
+ success: false,
120
+ error: promptResult.error || 'Failed to collect capability secrets',
121
+ };
122
+ }
123
+ } else {
124
+ // Non-interactive: error with helpful message
125
+ return {
126
+ success: false,
127
+ error: secretValidation.error || 'Missing capability secrets',
128
+ };
129
+ }
130
+ }
131
+
132
+ // Check for missing module secrets
133
+ const moduleSecretsMissing = await validateModuleSecrets(moduleId, db);
134
+
135
+ if (moduleSecretsMissing.length > 0) {
136
+ // Always try to interview for missing secrets
137
+ // Auto-generated secrets work in non-interactive mode
138
+ const result = await interviewForMissingSecrets(moduleId, moduleSecretsMissing, db);
139
+ if (!result.success) {
140
+ return {
141
+ success: false,
142
+ error: result.error || 'Failed to collect module secrets',
143
+ };
144
+ }
145
+ }
146
+
147
+ // Try to generate templates
148
+ let result = await generateTemplates({
149
+ moduleId,
150
+ modulePath,
151
+ outputPath: resolvedOutputPath,
152
+ db,
153
+ });
154
+
155
+ // If missing config, prompt for it and retry
156
+ if (!result.success && result.error?.includes('Missing required configuration')) {
157
+ // Get missing required variables
158
+ const requiredVars = manifest.variables?.owns?.filter((v) => v.required) || [];
159
+
160
+ if (requiredVars.length > 0) {
161
+ // Get current module configuration
162
+ const configs = db
163
+ .select()
164
+ .from(moduleConfigs)
165
+ .where(eq(moduleConfigs.moduleId, moduleId))
166
+ .all();
167
+
168
+ const configMap = new Map(configs.map((c) => [c.key, c.value || c.valueJson]));
169
+
170
+ // Find missing variables
171
+ const missingVars = requiredVars.filter((v) => !configMap.has(v.name));
172
+
173
+ if (missingVars.length > 0) {
174
+ // Only prompt if in interactive mode
175
+ if (!process.stdin.isTTY) {
176
+ // Non-interactive: return error
177
+ return {
178
+ success: false,
179
+ error: result.error || 'Missing required configuration',
180
+ };
181
+ }
182
+
183
+ // Prompt for missing config
184
+ const collected = await promptForMissingConfig(moduleId, missingVars, db);
185
+
186
+ if (!collected) {
187
+ return {
188
+ success: false,
189
+ error: 'Failed to collect required configuration',
190
+ };
191
+ }
192
+
193
+ // Retry generation with new config
194
+ result = await generateTemplates({
195
+ moduleId,
196
+ modulePath,
197
+ outputPath: resolvedOutputPath,
198
+ db,
199
+ });
200
+ }
201
+ }
202
+ }
203
+
204
+ if (!result.success) {
205
+ return {
206
+ success: false,
207
+ error: result.error,
208
+ details: result.details,
209
+ };
210
+ }
211
+
212
+ // Build infrastructure info message
213
+ let infrastructureMsg = '';
214
+ if (result.infrastructure) {
215
+ const infra = result.infrastructure;
216
+
217
+ if (infra.type === 'machine') {
218
+ infrastructureMsg = `\nšŸ“¦ Infrastructure: Existing machine "${infra.machineName || infra.machineId}" (zone: ${infra.zone})`;
219
+ } else if (infra.type === 'container_service') {
220
+ infrastructureMsg = `\nšŸ“¦ Infrastructure: Container service "${infra.serviceName || infra.serviceId}" (zone: ${infra.zone})`;
221
+ }
222
+ }
223
+
224
+ const filesList = result.files.map((f) => ` - ${f.path}`).join('\n');
225
+
226
+ return {
227
+ success: true,
228
+ message: `Successfully generated ${result.files.length} files:\n${filesList}\n\nOutput: ${result.outputPath}${infrastructureMsg}`,
229
+ data: {
230
+ fileCount: result.files.length,
231
+ outputPath: result.outputPath,
232
+ files: result.files,
233
+ infrastructure: result.infrastructure,
234
+ },
235
+ };
236
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Module health check command
3
+ *
4
+ * Usage:
5
+ * celilo module health [module-id] [--json] [--debug]
6
+ */
7
+
8
+ import { getDb } from '../../db/client';
9
+ import type { HealthCheckResult } from '../../services/health-runner';
10
+ import { runAllHealthChecks, runModuleHealthCheck } from '../../services/health-runner';
11
+ import { getArg, hasFlag } from '../parser';
12
+ import type { CommandResult } from '../types';
13
+
14
+ const STATUS_ICONS: Record<string, string> = {
15
+ healthy: 'ā—',
16
+ degraded: '⚠',
17
+ unhealthy: 'āœ—',
18
+ 'no-checks': '—',
19
+ error: 'āœ—',
20
+ };
21
+
22
+ const CHECK_ICONS: Record<string, string> = {
23
+ pass: 'āœ“',
24
+ warn: '⚠',
25
+ fail: 'āœ—',
26
+ };
27
+
28
+ function formatResult(result: HealthCheckResult): string {
29
+ const icon = STATUS_ICONS[result.status] || '?';
30
+ const lines: string[] = [];
31
+
32
+ if (result.status === 'no-checks') {
33
+ lines.push(` ${result.moduleId} ${icon} no health check defined`);
34
+ return lines.join('\n');
35
+ }
36
+
37
+ if (result.status === 'error') {
38
+ lines.push(` ${result.moduleId} ${icon} error: ${result.error}`);
39
+ return lines.join('\n');
40
+ }
41
+
42
+ const stateNote =
43
+ result.status === 'healthy' || result.status === 'degraded' ? ' → VERIFIED' : '';
44
+ lines.push(` ${result.moduleId} ${icon} ${result.status}${stateNote}`);
45
+
46
+ for (const check of result.checks) {
47
+ const checkIcon = CHECK_ICONS[check.status] || '?';
48
+ lines.push(` ${checkIcon} ${check.name.padEnd(20)} ${check.message}`);
49
+ if (check.details) {
50
+ lines.push(` ${check.details}`);
51
+ }
52
+ }
53
+
54
+ return lines.join('\n');
55
+ }
56
+
57
+ /**
58
+ * Handle module health command
59
+ */
60
+ export async function handleModuleHealth(
61
+ args: string[],
62
+ flags: Record<string, string | boolean>,
63
+ ): Promise<CommandResult> {
64
+ const db = getDb();
65
+ const debug = hasFlag(flags, 'debug');
66
+ const jsonOutput = hasFlag(flags, 'json');
67
+ const moduleId = getArg(args, 0);
68
+
69
+ let results: HealthCheckResult[];
70
+
71
+ if (moduleId) {
72
+ const result = await runModuleHealthCheck(moduleId, db, { debug });
73
+ results = [result];
74
+ } else {
75
+ results = await runAllHealthChecks(db, { debug });
76
+ }
77
+
78
+ if (jsonOutput) {
79
+ return {
80
+ success: true,
81
+ message: JSON.stringify({ modules: results }, null, 2),
82
+ };
83
+ }
84
+
85
+ if (results.length === 0) {
86
+ return {
87
+ success: true,
88
+ message: 'No deployed modules to check',
89
+ };
90
+ }
91
+
92
+ const output = results.map(formatResult).join('\n\n');
93
+ const allHealthy = results.every(
94
+ (r) => r.status === 'healthy' || r.status === 'degraded' || r.status === 'no-checks',
95
+ );
96
+
97
+ if (!allHealthy) {
98
+ return {
99
+ success: false,
100
+ error: output,
101
+ };
102
+ }
103
+
104
+ return {
105
+ success: true,
106
+ message: output,
107
+ };
108
+ }