@celilo/cli 0.1.4 → 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 (161) 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 +9 -8
  5. package/src/ansible/inventory.ts +9 -7
  6. package/src/capabilities/public-web-helpers.test.ts +2 -2
  7. package/src/capabilities/public-web-publish.test.ts +45 -12
  8. package/src/capabilities/registration.test.ts +6 -6
  9. package/src/capabilities/well-known.test.ts +2 -2
  10. package/src/capabilities/well-known.ts +5 -5
  11. package/src/cli/cli.test.ts +2 -2
  12. package/src/cli/command-registry.ts +146 -3
  13. package/src/cli/command-tree-parser.test.ts +1 -1
  14. package/src/cli/command-tree-parser.ts +9 -8
  15. package/src/cli/commands/hook-run.ts +15 -66
  16. package/src/cli/commands/module-audit.ts +14 -44
  17. package/src/cli/commands/module-deploy.ts +4 -1
  18. package/src/cli/commands/module-import-registry.test.ts +115 -0
  19. package/src/cli/commands/module-import.ts +106 -22
  20. package/src/cli/commands/module-publish.test.ts +235 -0
  21. package/src/cli/commands/module-publish.ts +234 -0
  22. package/src/cli/commands/module-remove.ts +82 -2
  23. package/src/cli/commands/module-search.ts +57 -0
  24. package/src/cli/commands/module-secret-get.ts +59 -0
  25. package/src/cli/commands/module-show.ts +1 -1
  26. package/src/cli/commands/module-terraform-unlock.ts +57 -0
  27. package/src/cli/commands/module-verify.test.ts +59 -0
  28. package/src/cli/commands/module-verify.ts +53 -0
  29. package/src/cli/commands/status.ts +30 -20
  30. package/src/cli/commands/system-audit.test.ts +138 -0
  31. package/src/cli/commands/system-audit.ts +571 -0
  32. package/src/cli/commands/system-update.ts +391 -0
  33. package/src/cli/completion.ts +15 -1
  34. package/src/cli/fuel-gauge.ts +68 -3
  35. package/src/cli/generate-zsh-completion.ts +13 -3
  36. package/src/cli/index.ts +112 -5
  37. package/src/cli/parser.ts +11 -0
  38. package/src/cli/prompts.ts +36 -5
  39. package/src/cli/tui/audit-state.test.ts +246 -0
  40. package/src/cli/tui/audit-state.ts +525 -0
  41. package/src/cli/tui/audit-tui.test.tsx +135 -0
  42. package/src/cli/tui/audit-tui.tsx +624 -0
  43. package/src/cli/tui/celebration.tsx +29 -0
  44. package/src/cli/tui/clipboard.test.ts +94 -0
  45. package/src/cli/tui/clipboard.ts +101 -0
  46. package/src/cli/tui/icons.ts +22 -0
  47. package/src/cli/tui/keybar.tsx +65 -0
  48. package/src/cli/tui/keymap.test.ts +105 -0
  49. package/src/cli/tui/keymap.ts +70 -0
  50. package/src/cli/tui/modals/analyzing.tsx +75 -0
  51. package/src/cli/tui/modals/celebration.tsx +44 -0
  52. package/src/cli/tui/modals/reaudit-prompt.tsx +35 -0
  53. package/src/cli/tui/modals/remediate.tsx +44 -0
  54. package/src/cli/tui/modals.test.ts +137 -0
  55. package/src/cli/tui/mouse.test.ts +78 -0
  56. package/src/cli/tui/mouse.ts +114 -0
  57. package/src/cli/tui/panes/categories.tsx +62 -0
  58. package/src/cli/tui/panes/command-log.tsx +87 -0
  59. package/src/cli/tui/panes/detail.tsx +175 -0
  60. package/src/cli/tui/panes/findings.tsx +97 -0
  61. package/src/cli/tui/panes/summary.tsx +64 -0
  62. package/src/cli/tui/spawn.ts +130 -0
  63. package/src/cli/tui/theme.ts +42 -0
  64. package/src/cli/tui/wrap.test.ts +43 -0
  65. package/src/cli/tui/wrap.ts +45 -0
  66. package/src/cli/types.ts +5 -0
  67. package/src/db/client.ts +55 -2
  68. package/src/db/schema.test.ts +3 -3
  69. package/src/db/schema.ts +26 -17
  70. package/src/hooks/capability-loader.ts +135 -72
  71. package/src/hooks/define-hook.test.ts +11 -3
  72. package/src/hooks/executor.ts +22 -1
  73. package/src/hooks/load-hook-config.test.ts +165 -0
  74. package/src/hooks/load-hook-config.ts +60 -0
  75. package/src/hooks/logger.ts +42 -12
  76. package/src/hooks/run-named-hook.ts +128 -0
  77. package/src/hooks/types.ts +19 -0
  78. package/src/manifest/ensure-schema.test.ts +115 -0
  79. package/src/manifest/schema.ts +76 -0
  80. package/src/manifest/template-validator.test.ts +1 -1
  81. package/src/manifest/template-validator.ts +1 -1
  82. package/src/manifest/validate.test.ts +1 -1
  83. package/src/module/import.ts +20 -12
  84. package/src/module/packaging/build.ts +121 -25
  85. package/src/module/packaging/release-metadata.test.ts +103 -0
  86. package/src/module/packaging/release-metadata.ts +145 -0
  87. package/src/registry/client.test.ts +228 -0
  88. package/src/registry/client.ts +157 -0
  89. package/src/services/audit/backups.test.ts +233 -0
  90. package/src/services/audit/backups.ts +128 -0
  91. package/src/services/audit/capability-abi.test.ts +153 -0
  92. package/src/services/audit/capability-abi.ts +204 -0
  93. package/src/services/audit/cli-version.test.ts +60 -0
  94. package/src/services/audit/cli-version.ts +87 -0
  95. package/src/services/audit/health.test.ts +84 -0
  96. package/src/services/audit/health.ts +43 -0
  97. package/src/services/audit/index.test.ts +99 -0
  98. package/src/services/audit/index.ts +118 -0
  99. package/src/services/audit/machines-reachable.test.ts +87 -0
  100. package/src/services/audit/machines-reachable.ts +87 -0
  101. package/src/services/audit/module-configs.test.ts +131 -0
  102. package/src/services/audit/module-configs.ts +80 -0
  103. package/src/services/audit/module-versions.test.ts +99 -0
  104. package/src/services/audit/module-versions.ts +154 -0
  105. package/src/services/audit/schema.test.ts +68 -0
  106. package/src/services/audit/schema.ts +115 -0
  107. package/src/services/audit/secrets-decryptable.test.ts +82 -0
  108. package/src/services/audit/secrets-decryptable.ts +97 -0
  109. package/src/services/audit/services-credentials.test.ts +54 -0
  110. package/src/services/audit/services-credentials.ts +64 -0
  111. package/src/services/audit/services-reachable.test.ts +60 -0
  112. package/src/services/audit/services-reachable.ts +64 -0
  113. package/src/services/audit/terraform-plan.test.ts +127 -0
  114. package/src/services/audit/terraform-plan.ts +153 -0
  115. package/src/services/audit/types.test.ts +36 -0
  116. package/src/services/audit/types.ts +90 -0
  117. package/src/services/audit/unconfigured-modules.test.ts +48 -0
  118. package/src/services/audit/unconfigured-modules.ts +71 -0
  119. package/src/services/audit/undeployed-modules.test.ts +66 -0
  120. package/src/services/audit/undeployed-modules.ts +72 -0
  121. package/src/services/build-stream.ts +122 -122
  122. package/src/services/config-interview.ts +407 -2
  123. package/src/services/deploy-ansible.ts +73 -7
  124. package/src/services/deploy-planner.ts +5 -5
  125. package/src/services/deploy-preflight.ts +45 -4
  126. package/src/services/deploy-terraform.ts +31 -24
  127. package/src/services/deploy-validation.ts +167 -23
  128. package/src/services/dns-auto-register.ts +4 -4
  129. package/src/services/ensure-interview.test.ts +245 -0
  130. package/src/services/health-runner.ts +110 -38
  131. package/src/services/infrastructure-variable-resolver.test.ts +1 -1
  132. package/src/services/infrastructure-variable-resolver.ts +3 -3
  133. package/src/services/module-build.ts +11 -13
  134. package/src/services/module-deploy.ts +372 -61
  135. package/src/services/proxmox-state-recovery.ts +6 -6
  136. package/src/services/ssh-key-manager.test.ts +1 -1
  137. package/src/services/ssh-key-manager.ts +3 -2
  138. package/src/services/terraform-env.ts +62 -0
  139. package/src/services/update/dep-graph.test.ts +214 -0
  140. package/src/services/update/dep-graph.ts +215 -0
  141. package/src/services/update/orchestrator.test.ts +463 -0
  142. package/src/services/update/orchestrator.ts +359 -0
  143. package/src/services/update/progress.ts +49 -0
  144. package/src/services/update/self-update.test.ts +68 -0
  145. package/src/services/update/self-update.ts +57 -0
  146. package/src/services/update/types.ts +94 -0
  147. package/src/templates/generator.test.ts +3 -3
  148. package/src/templates/generator.ts +43 -2
  149. package/src/test-utils/completion-harness.test.ts +1 -1
  150. package/src/test-utils/completion-harness.ts +4 -4
  151. package/src/variables/capability-self-ref.test.ts +203 -0
  152. package/src/variables/context.test.ts +31 -31
  153. package/src/variables/context.ts +65 -17
  154. package/src/variables/declarative-derivation.test.ts +306 -0
  155. package/src/variables/declarative-derivation.ts +4 -2
  156. package/src/variables/parser.test.ts +64 -9
  157. package/src/variables/parser.ts +47 -6
  158. package/src/variables/resolver.test.ts +14 -14
  159. package/src/variables/resolver.ts +27 -9
  160. package/src/variables/types.ts +1 -1
  161. package/tsconfig.json +1 -0
@@ -0,0 +1,25 @@
1
+ -- Caddy hostname list refactor (CADDY_HOSTNAME_LIST design).
2
+ -- web_routes used to carry `subdomain` + `custom_domain` and a flat
3
+ -- unique index on `path`. The new model has a single `hostname` FQDN
4
+ -- and a (hostname, path) unique index. Phase 0 / no production users
5
+ -- means this is a destructive migration — modules repopulate routes
6
+ -- on their next deploy after the migration runs.
7
+ DROP TABLE IF EXISTS `web_routes`;
8
+ --> statement-breakpoint
9
+ CREATE TABLE `web_routes` (
10
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
11
+ `slug` text NOT NULL,
12
+ `module_id` text NOT NULL,
13
+ `type` text NOT NULL,
14
+ `path` text NOT NULL,
15
+ `hostname` text NOT NULL,
16
+ `target_host` text,
17
+ `target_port` integer,
18
+ `websocket` integer DEFAULT false NOT NULL,
19
+ `content_hash` text,
20
+ `created_at` integer DEFAULT (unixepoch()) NOT NULL,
21
+ `updated_at` integer DEFAULT (unixepoch()) NOT NULL,
22
+ FOREIGN KEY (`module_id`) REFERENCES `modules`(`id`) ON UPDATE no action ON DELETE cascade
23
+ );
24
+ --> statement-breakpoint
25
+ CREATE UNIQUE INDEX `web_routes_hostname_path_idx` ON `web_routes` (`hostname`,`path`);
@@ -22,6 +22,20 @@
22
22
  "when": 1774881303520,
23
23
  "tag": "0002_web_routes",
24
24
  "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "6",
29
+ "when": 1774924800000,
30
+ "tag": "0003_backup_storage",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "6",
36
+ "when": 1777680000000,
37
+ "tag": "0004_caddy_hostname_list",
38
+ "breakpoints": true
25
39
  }
26
40
  ]
27
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celilo/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Celilo — home lab orchestration CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,10 +46,15 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@aws-sdk/client-s3": "^3.1024.0",
49
+ "@celilo/capabilities": "^0.1.2",
50
+ "@celilo/cli-display": "0.1.0",
49
51
  "@clack/prompts": "^1.1.0",
50
- "@celilo/capabilities": "^0.1.1",
51
52
  "ajv": "^8.18.0",
52
53
  "drizzle-orm": "^0.36.4",
54
+ "ink": "^7.0.1",
55
+ "ink-spinner": "^5.0.0",
56
+ "ink-text-input": "^6.0.0",
57
+ "react": "^19.2.5",
53
58
  "tar": "^7.5.10",
54
59
  "xxhash-wasm": "^1.1.0",
55
60
  "yaml": "^2.8.2",
@@ -58,7 +63,9 @@
58
63
  "devDependencies": {
59
64
  "@biomejs/biome": "^1.9.4",
60
65
  "@types/bun": "^1.1.14",
66
+ "@types/react": "^19.2.14",
61
67
  "drizzle-kit": "^0.30.0",
68
+ "ink-testing-library": "^4.0.0",
62
69
  "typescript": "^5.9.3",
63
70
  "zod-to-json-schema": "^3.25.2"
64
71
  }
@@ -75,13 +75,14 @@ describe('generateHostsIni', () => {
75
75
  });
76
76
 
77
77
  test('includes SSH private key file path when provided', () => {
78
+ const keyPath = '/tmp/celilo-ansible-keys/machine-machine-1.key';
78
79
  const hosts: InventoryHost[] = [
79
80
  {
80
81
  hostname: 'machine-host',
81
82
  ansibleHost: '192.168.1.100',
82
83
  ansibleUser: 'ubuntu',
83
84
  groups: ['machines'],
84
- ansibleSshPrivateKeyFile: '/var/lib/celilo/tmp/ansible-keys/machine-machine-1.key',
85
+ ansibleSshPrivateKeyFile: keyPath,
85
86
  },
86
87
  ];
87
88
 
@@ -89,7 +90,7 @@ describe('generateHostsIni', () => {
89
90
 
90
91
  expect(result).toContain('[machines]');
91
92
  expect(result).toContain(
92
- 'machine-host ansible_host=192.168.1.100 ansible_user=ubuntu ansible_ssh_private_key_file=/var/lib/celilo/tmp/ansible-keys/machine-machine-1.key',
93
+ `machine-host ansible_host=192.168.1.100 ansible_user=ubuntu ansible_ssh_private_key_file=${keyPath}`,
93
94
  );
94
95
  });
95
96
  });
@@ -99,14 +100,14 @@ describe('generateHostVarsYaml', () => {
99
100
  const vars = {
100
101
  vmid: 2110,
101
102
  hostname: 'iot',
102
- container_ip: '192.168.0.110/24',
103
+ target_ip: '192.168.0.110/24',
103
104
  };
104
105
 
105
106
  const result = generateHostVarsYaml(vars);
106
107
 
107
108
  expect(result).toContain('vmid: 2110');
108
109
  expect(result).toContain('hostname: iot');
109
- expect(result).toContain('container_ip: 192.168.0.110/24');
110
+ expect(result).toContain('target_ip: 192.168.0.110/24');
110
111
  });
111
112
 
112
113
  test('generates YAML for arrays', () => {
@@ -289,7 +290,7 @@ describe('Database integration', () => {
289
290
  { moduleId: 'homebridge', key: 'hostname', value: 'iot', valueJson: null },
290
291
  {
291
292
  moduleId: 'homebridge',
292
- key: 'container_ip',
293
+ key: 'target_ip',
293
294
  value: '192.168.0.110/24',
294
295
  valueJson: null,
295
296
  },
@@ -301,7 +302,7 @@ describe('Database integration', () => {
301
302
 
302
303
  expect(vars.vmid).toBe(2110);
303
304
  expect(vars.hostname).toBe('iot');
304
- expect(vars.container_ip).toBe('192.168.0.110/24');
305
+ expect(vars.target_ip).toBe('192.168.0.110/24');
305
306
  expect(vars.cores).toBe(1);
306
307
  });
307
308
 
@@ -404,7 +405,7 @@ describe('Database integration', () => {
404
405
  db.insert(moduleConfigs)
405
406
  .values([
406
407
  { moduleId: 'homebridge', key: 'hostname', value: 'iot' },
407
- { moduleId: 'homebridge', key: 'container_ip', value: '192.168.0.110/24' },
408
+ { moduleId: 'homebridge', key: 'target_ip', value: '192.168.0.110/24' },
408
409
  ])
409
410
  .run();
410
411
 
@@ -446,7 +447,7 @@ describe('Database integration', () => {
446
447
  db.insert(moduleConfigs)
447
448
  .values([
448
449
  { moduleId: 'test', key: 'hostname', value: 'iot' },
449
- // Missing container_ip or vps_ip for ansible_host
450
+ // Missing target_ip or vps_ip for ansible_host
450
451
  ])
451
452
  .run();
452
453
 
@@ -66,7 +66,11 @@ export function generateHostsIni(hosts: InventoryHost[]): string {
66
66
  // Add [all:vars] section with common variables
67
67
  lines.push('[all:vars]');
68
68
  lines.push('ansible_python_interpreter=/usr/bin/python3');
69
- lines.push('ansible_ssh_common_args=-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null');
69
+ // Disable ControlMaster to avoid Python format string issues with ControlPath
70
+ // ControlPath uses %h, %p, %r placeholders which trigger Python's % formatter
71
+ lines.push(
72
+ 'ansible_ssh_common_args=-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlMaster=no',
73
+ );
70
74
  lines.push('');
71
75
 
72
76
  return lines.join('\n');
@@ -259,13 +263,11 @@ export function extractInventoryHost(moduleId: string, db: DbClient): InventoryH
259
263
  }
260
264
 
261
265
  // Auto-derive ansible_host from infrastructure variables
262
- // Priority: container_ip > ip.primary > vps_ip (for backward compatibility)
263
- if (moduleConfig.container_ip) {
264
- const slashIndex = moduleConfig.container_ip.indexOf('/');
266
+ // Priority: target_ip > ip.primary > vps_ip (for backward compatibility)
267
+ if (moduleConfig.target_ip) {
268
+ const slashIndex = moduleConfig.target_ip.indexOf('/');
265
269
  derived['inventory.ansible_host'] =
266
- slashIndex === -1
267
- ? moduleConfig.container_ip
268
- : moduleConfig.container_ip.slice(0, slashIndex);
270
+ slashIndex === -1 ? moduleConfig.target_ip : moduleConfig.target_ip.slice(0, slashIndex);
269
271
  } else if (moduleConfig['ip.primary']) {
270
272
  derived['inventory.ansible_host'] = moduleConfig['ip.primary'];
271
273
  } else if (moduleConfig.vps_ip) {
@@ -101,11 +101,11 @@ describe('validatePublishStaticSiteRequest', () => {
101
101
  expect(result.errors).toEqual([]);
102
102
  });
103
103
 
104
- test('accepts a request with optional clientConfig and subdomain', () => {
104
+ test('accepts a request with optional clientConfig and hostname', () => {
105
105
  const result = validatePublishStaticSiteRequest(
106
106
  basePublishRequest({
107
107
  clientConfig: { FOO: 'bar' },
108
- subdomain: 'app',
108
+ hostname: 'app.iamtheinternet.org',
109
109
  }),
110
110
  );
111
111
  expect(result.valid).toBe(true);
@@ -53,9 +53,9 @@ function makeRouteOps(): { ops: RouteOps; routes: WebRoute[] } {
53
53
  moduleId: route.moduleId,
54
54
  type: route.type,
55
55
  path: route.path,
56
+ hostname: route.hostname,
56
57
  targetHost: route.targetHost ?? null,
57
58
  targetPort: route.targetPort ?? null,
58
- subdomain: route.subdomain ?? null,
59
59
  websocket: route.websocket ?? false,
60
60
  contentHash: route.contentHash ?? null,
61
61
  createdAt: existing >= 0 ? routes[existing].createdAt : now,
@@ -109,13 +109,16 @@ describe('publishStaticSite — clientConfig injection', () => {
109
109
  moduleId: 'lunacycle',
110
110
  logger: noopLogger,
111
111
  config: {
112
- container_ip: '10.0.10.20/24',
112
+ target_ip: '10.0.10.20/24',
113
113
  hostname: 'www',
114
114
  primary_domain: 'example.com',
115
115
  email: 'admin@example.com',
116
116
  },
117
117
  secrets: {},
118
118
  routeOps: ops,
119
+ hostnames: ['www.example.com'],
120
+ caddyModuleId: 'caddy',
121
+ dnsManagedDomains: ['www.example.com'],
119
122
  });
120
123
 
121
124
  const result = await cap.publishStaticSite({
@@ -150,13 +153,16 @@ describe('publishStaticSite — clientConfig injection', () => {
150
153
  moduleId: 'lunacycle',
151
154
  logger: noopLogger,
152
155
  config: {
153
- container_ip: '10.0.10.20/24',
156
+ target_ip: '10.0.10.20/24',
154
157
  hostname: 'www',
155
158
  primary_domain: 'example.com',
156
159
  email: 'admin@example.com',
157
160
  },
158
161
  secrets: {},
159
162
  routeOps: ops,
163
+ hostnames: ['www.example.com'],
164
+ caddyModuleId: 'caddy',
165
+ dnsManagedDomains: ['www.example.com'],
160
166
  });
161
167
 
162
168
  await cap.publishStaticSite({
@@ -174,13 +180,16 @@ describe('publishStaticSite — clientConfig injection', () => {
174
180
  moduleId: 'lunacycle',
175
181
  logger: noopLogger,
176
182
  config: {
177
- container_ip: '10.0.10.20/24',
183
+ target_ip: '10.0.10.20/24',
178
184
  hostname: 'www',
179
185
  primary_domain: 'example.com',
180
186
  email: 'admin@example.com',
181
187
  },
182
188
  secrets: {},
183
189
  routeOps: ops,
190
+ hostnames: ['www.example.com'],
191
+ caddyModuleId: 'caddy',
192
+ dnsManagedDomains: ['www.example.com'],
184
193
  });
185
194
 
186
195
  await cap.publishStaticSite({
@@ -201,13 +210,16 @@ describe('publishStaticSite — clientConfig injection', () => {
201
210
  moduleId: 'lunacycle',
202
211
  logger: noopLogger,
203
212
  config: {
204
- container_ip: '10.0.10.20/24',
213
+ target_ip: '10.0.10.20/24',
205
214
  hostname: 'www',
206
215
  primary_domain: 'example.com',
207
216
  email: 'admin@example.com',
208
217
  },
209
218
  secrets: {},
210
219
  routeOps: ops,
220
+ hostnames: ['www.example.com'],
221
+ caddyModuleId: 'caddy',
222
+ dnsManagedDomains: ['www.example.com'],
211
223
  });
212
224
 
213
225
  await expect(
@@ -225,13 +237,16 @@ describe('publishStaticSite — clientConfig injection', () => {
225
237
  moduleId: 'lunacycle',
226
238
  logger: noopLogger,
227
239
  config: {
228
- container_ip: '10.0.10.20/24',
240
+ target_ip: '10.0.10.20/24',
229
241
  hostname: 'www',
230
242
  primary_domain: 'example.com',
231
243
  email: 'admin@example.com',
232
244
  },
233
245
  secrets: {},
234
246
  routeOps: ops,
247
+ hostnames: ['www.example.com'],
248
+ caddyModuleId: 'caddy',
249
+ dnsManagedDomains: ['www.example.com'],
235
250
  });
236
251
 
237
252
  await expect(
@@ -261,13 +276,16 @@ describe('registerReverseProxy', () => {
261
276
  moduleId: 'lunacycle',
262
277
  logger: noopLogger,
263
278
  config: {
264
- container_ip: '10.0.10.20/24',
279
+ target_ip: '10.0.10.20/24',
265
280
  hostname: 'www',
266
281
  primary_domain: 'example.com',
267
282
  email: 'admin@example.com',
268
283
  },
269
284
  secrets: {},
270
285
  routeOps: ops,
286
+ hostnames: ['www.example.com'],
287
+ caddyModuleId: 'caddy',
288
+ dnsManagedDomains: ['www.example.com'],
271
289
  });
272
290
 
273
291
  const result = await cap.registerReverseProxy({
@@ -294,13 +312,16 @@ describe('registerReverseProxy', () => {
294
312
  moduleId: 'lunacycle',
295
313
  logger: noopLogger,
296
314
  config: {
297
- container_ip: '10.0.10.20/24',
315
+ target_ip: '10.0.10.20/24',
298
316
  hostname: 'www',
299
317
  primary_domain: 'example.com',
300
318
  email: 'admin@example.com',
301
319
  },
302
320
  secrets: {},
303
321
  routeOps: ops,
322
+ hostnames: ['www.example.com'],
323
+ caddyModuleId: 'caddy',
324
+ dnsManagedDomains: ['www.example.com'],
304
325
  });
305
326
 
306
327
  await expect(
@@ -337,13 +358,16 @@ describe('auto-logging — end-to-end through createPublicWeb', () => {
337
358
  moduleId: 'lunacycle',
338
359
  logger,
339
360
  config: {
340
- container_ip: '10.0.10.20/24',
361
+ target_ip: '10.0.10.20/24',
341
362
  hostname: 'www',
342
363
  primary_domain: 'example.com',
343
364
  email: 'admin@example.com',
344
365
  },
345
366
  secrets: {},
346
367
  routeOps: ops,
368
+ hostnames: ['www.example.com'],
369
+ caddyModuleId: 'caddy',
370
+ dnsManagedDomains: ['www.example.com'],
347
371
  });
348
372
 
349
373
  await cap.register_route({
@@ -368,13 +392,16 @@ describe('auto-logging — end-to-end through createPublicWeb', () => {
368
392
  moduleId: 'lunacycle',
369
393
  logger,
370
394
  config: {
371
- container_ip: '10.0.10.20/24',
395
+ target_ip: '10.0.10.20/24',
372
396
  hostname: 'www',
373
397
  primary_domain: 'example.com',
374
398
  email: 'admin@example.com',
375
399
  },
376
400
  secrets: {},
377
401
  routeOps: ops,
402
+ hostnames: ['www.example.com'],
403
+ caddyModuleId: 'caddy',
404
+ dnsManagedDomains: ['www.example.com'],
378
405
  });
379
406
 
380
407
  // Bad path triggers the validator inside register_route, which
@@ -400,13 +427,16 @@ describe('auto-logging — end-to-end through createPublicWeb', () => {
400
427
  moduleId: 'lunacycle',
401
428
  logger,
402
429
  config: {
403
- container_ip: '10.0.10.20/24',
430
+ target_ip: '10.0.10.20/24',
404
431
  hostname: 'www',
405
432
  primary_domain: 'example.com',
406
433
  email: 'admin@example.com',
407
434
  },
408
435
  secrets: {},
409
436
  routeOps: ops,
437
+ hostnames: ['www.example.com'],
438
+ caddyModuleId: 'caddy',
439
+ dnsManagedDomains: ['www.example.com'],
410
440
  });
411
441
 
412
442
  await cap.publishStaticSite({ path: '/lunacycle', sourceDir });
@@ -432,13 +462,16 @@ describe('auto-logging — end-to-end through createPublicWeb', () => {
432
462
  moduleId: 'lunacycle',
433
463
  logger,
434
464
  config: {
435
- container_ip: '10.0.10.20/24',
465
+ target_ip: '10.0.10.20/24',
436
466
  hostname: 'www',
437
467
  primary_domain: 'example.com',
438
468
  email: 'admin@example.com',
439
469
  },
440
470
  secrets: {},
441
471
  routeOps: ops,
472
+ hostnames: ['www.example.com'],
473
+ caddyModuleId: 'caddy',
474
+ dnsManagedDomains: ['www.example.com'],
442
475
  });
443
476
 
444
477
  await cap.register_route({
@@ -43,7 +43,7 @@ describe('Capability Registration', () => {
43
43
  version: '1.0.0',
44
44
  data: {
45
45
  server: {
46
- ip: '$self:container_ip',
46
+ ip: '$self:target_ip',
47
47
  port: 443,
48
48
  },
49
49
  },
@@ -65,7 +65,7 @@ describe('Capability Registration', () => {
65
65
  // Variables are preserved, not resolved
66
66
  expect(result).toEqual({
67
67
  server: {
68
- ip: '$self:container_ip',
68
+ ip: '$self:target_ip',
69
69
  port: 443,
70
70
  },
71
71
  });
@@ -177,7 +177,7 @@ describe('Capability Registration', () => {
177
177
  name: 'test_capability',
178
178
  version: '1.0.0',
179
179
  data: {
180
- variable: '$self:container_ip',
180
+ variable: '$self:target_ip',
181
181
  literal: 'not-a-variable',
182
182
  with_dollar: '$100',
183
183
  empty: '',
@@ -198,7 +198,7 @@ describe('Capability Registration', () => {
198
198
  const result = buildCapabilityData(capability, manifest);
199
199
 
200
200
  expect(result).toEqual({
201
- variable: '$self:container_ip',
201
+ variable: '$self:target_ip',
202
202
  literal: 'not-a-variable',
203
203
  with_dollar: '$100',
204
204
  empty: '',
@@ -337,7 +337,7 @@ describe('Capability Registration', () => {
337
337
  version: '1.0.0',
338
338
  data: {
339
339
  server: {
340
- ip: '$self:container_ip',
340
+ ip: '$self:target_ip',
341
341
  },
342
342
  },
343
343
  };
@@ -360,7 +360,7 @@ describe('Capability Registration', () => {
360
360
  (result as any).server.ip = 'modified';
361
361
 
362
362
  // Original should be unchanged
363
- expect(capability.data.server.ip).toBe('$self:container_ip');
363
+ expect(capability.data.server.ip).toBe('$self:target_ip');
364
364
  });
365
365
  });
366
366
 
@@ -170,7 +170,7 @@ describe('Well-Known Capabilities Registry', () => {
170
170
  expect(schema).toEqual({
171
171
  server: {
172
172
  ip: {
173
- primary: '$self:container_ip',
173
+ primary: '$self:target_ip',
174
174
  },
175
175
  port: 443,
176
176
  },
@@ -196,7 +196,7 @@ describe('Well-Known Capabilities Registry', () => {
196
196
  expect(schema).toEqual({
197
197
  server: {
198
198
  ip: {
199
- primary: '$self:container_ip',
199
+ primary: '$self:target_ip',
200
200
  },
201
201
  port: 9000,
202
202
  },
@@ -41,7 +41,7 @@ export const WELL_KNOWN_CAPABILITIES: Record<string, WellKnownCapability> = {
41
41
  data_schema: {
42
42
  server: {
43
43
  ip: {
44
- primary: '$self:container_ip',
44
+ primary: '$self:target_ip',
45
45
  },
46
46
  port: 443,
47
47
  },
@@ -76,7 +76,7 @@ export const WELL_KNOWN_CAPABILITIES: Record<string, WellKnownCapability> = {
76
76
  data_schema: {
77
77
  server: {
78
78
  ip: {
79
- primary: '$self:container_ip',
79
+ primary: '$self:target_ip',
80
80
  },
81
81
  port: 53,
82
82
  },
@@ -102,7 +102,7 @@ export const WELL_KNOWN_CAPABILITIES: Record<string, WellKnownCapability> = {
102
102
  data_schema: {
103
103
  server: {
104
104
  ip: {
105
- primary: '$self:container_ip',
105
+ primary: '$self:target_ip',
106
106
  },
107
107
  port: 9000,
108
108
  },
@@ -121,12 +121,12 @@ export const WELL_KNOWN_CAPABILITIES: Record<string, WellKnownCapability> = {
121
121
  data_schema: {
122
122
  server: {
123
123
  ip: {
124
- primary: '$self:container_ip',
124
+ primary: '$self:target_ip',
125
125
  },
126
126
  port: '$self:port', // Database-specific port
127
127
  },
128
128
  connection: {
129
- host: '$self:container_ip',
129
+ host: '$self:target_ip',
130
130
  port: '$self:port',
131
131
  name: '$self:database_name',
132
132
  },
@@ -574,12 +574,12 @@ secrets:
574
574
  });
575
575
 
576
576
  describe('module import', () => {
577
- test('should error on missing path', async () => {
577
+ test('should error on missing subcommand', async () => {
578
578
  const result = await runCli(['node', 'celilo', 'module', 'import']);
579
579
 
580
580
  expect(result.success).toBe(false);
581
581
  if (result.success) return;
582
- expect(result.error).toContain('Missing required arguments');
582
+ expect(result.error).toContain('Subcommand required');
583
583
  });
584
584
 
585
585
  test('should error on non-existent path', async () => {