@celilo/cli 0.3.12 → 0.3.13

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celilo/cli",
3
- "version": "0.3.12",
3
+ "version": "0.3.13",
4
4
  "description": "Celilo — home lab orchestration CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -221,11 +221,22 @@ export async function handleModuleRemove(
221
221
  log.success('Infrastructure destroyed');
222
222
  }
223
223
 
224
- // Remove DNS record (if dns_internal is available)
224
+ // Remove DNS record (if dns_internal is available). Wrapped in a
225
+ // FuelGauge with a gauge logger so the dns_internal capability calls
226
+ // inside (`deleteRecord`, etc.) nest as sub-events rather than
227
+ // leaking to scrollback as unindented top-level lines via
228
+ // cli/prompts.log.instantEvent (which pops every pending step).
229
+ const dnsGauge = new FuelGauge(`Removing ${moduleId} from DNS`, {
230
+ skipAnimation: !process.stdout.isTTY,
231
+ });
232
+ dnsGauge.start();
225
233
  try {
234
+ const dnsLogger = createGaugeLogger(dnsGauge, moduleId, 'auto_deregister_dns');
226
235
  const { autoDeregisterDns } = await import('../../services/dns-auto-register');
227
- await autoDeregisterDns(moduleId, db, log);
236
+ await autoDeregisterDns(moduleId, db, dnsLogger);
237
+ dnsGauge.stop(true);
228
238
  } catch {
239
+ dnsGauge.stop(false);
229
240
  // Non-fatal -- continue with removal
230
241
  }
231
242
 
@@ -4,38 +4,22 @@ import { join } from 'node:path';
4
4
  /**
5
5
  * Get the module storage path based on environment and platform
6
6
  *
7
- * Priority:
8
- * 1. CELILO_DATA_DIR environment variable (explicit override)
9
- * 2. ENVIRONMENT=dev uses ./celilo-data/modules/ (for testing)
10
- * 3. Platform defaults:
11
- * - macOS: ~/Library/Application Support/celilo/modules/
12
- * - Linux: /var/lib/celilo/modules/
13
- *
14
7
  * @returns Absolute path to module storage directory
15
8
  */
16
9
  export function getModuleStoragePath(): string {
17
- // Explicit override
18
- if (process.env.CELILO_DATA_DIR) {
19
- return join(process.env.CELILO_DATA_DIR, 'modules');
20
- }
21
-
22
- // Development mode
23
- if (process.env.ENVIRONMENT === 'dev') {
24
- return join(process.cwd(), 'celilo-data', 'modules');
25
- }
26
-
27
- // Platform defaults
28
- if (platform() === 'darwin') {
29
- return join(homedir(), 'Library', 'Application Support', 'celilo', 'modules');
30
- }
31
-
32
- // Linux/other
33
- return '/var/lib/celilo/modules';
10
+ return join(getDataDir(), 'modules');
34
11
  }
35
12
 
36
13
  /**
37
14
  * Get the base data directory for Celilo
38
15
  *
16
+ * Priority:
17
+ * 1. CELILO_DATA_DIR environment variable (explicit override)
18
+ * 2. ENVIRONMENT=dev uses ./celilo-data/ (for testing)
19
+ * 3. Platform defaults — always user-scoped so the CLI works without root:
20
+ * - macOS: ~/Library/Application Support/celilo/
21
+ * - Linux: $XDG_DATA_HOME/celilo/ (defaults to ~/.local/share/celilo/)
22
+ *
39
23
  * @returns Absolute path to base data directory
40
24
  */
41
25
  export function getDataDir(): string {
@@ -49,13 +33,15 @@ export function getDataDir(): string {
49
33
  return join(process.cwd(), 'celilo-data');
50
34
  }
51
35
 
52
- // Platform defaults
36
+ // macOS
53
37
  if (platform() === 'darwin') {
54
38
  return join(homedir(), 'Library', 'Application Support', 'celilo');
55
39
  }
56
40
 
57
- // Linux/other
58
- return '/var/lib/celilo';
41
+ // Linux/other — XDG Base Directory spec, user-scoped (no sudo needed).
42
+ // System-wide installs can opt in via CELILO_DATA_DIR=/var/lib/celilo.
43
+ const xdgDataHome = process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share');
44
+ return join(xdgDataHome, 'celilo');
59
45
  }
60
46
 
61
47
  /**
package/src/db/client.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Database } from 'bun:sqlite';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { drizzle } from 'drizzle-orm/bun-sqlite';
@@ -166,6 +166,13 @@ export function createDbClient(config?: Partial<DatabaseConfig>) {
166
166
  const dbPath = config?.path ?? getDbPath();
167
167
  const readonly = config?.readonly ?? false;
168
168
 
169
+ // bun:sqlite's `create: true` makes the file but not the parent directory.
170
+ // First-run after a fresh install hits this — without recursive mkdir we
171
+ // get SQLITE_CANTOPEN before init can write its config.
172
+ if (!readonly) {
173
+ mkdirSync(dirname(dbPath), { recursive: true });
174
+ }
175
+
169
176
  const sqlite = new Database(dbPath, {
170
177
  readonly,
171
178
  create: true,
@@ -72,7 +72,12 @@ export function createGaugeLogger(
72
72
  // pushStep nests under the FuelGauge step that wraps the hook,
73
73
  // so inner log calls (logger.info within the capability impl)
74
74
  // are sub-events of this nested step.
75
- display.pushStep(`→ ${name}`, `✓ ${name}`);
75
+ //
76
+ // No `→`/`✓` glyph in the message — the display's spinner
77
+ // (in-progress) and green ✔ (done) already convey the state.
78
+ // Sticking a literal `✓` into the doneMsg produced a double-
79
+ // checkmark line on completion (`✔ ✓ name`).
80
+ display.pushStep(name, name);
76
81
  } else {
77
82
  // Without a display, fall back to a plain info-style marker so
78
83
  // the line still appears in raw log output.
@@ -1234,12 +1234,23 @@ async function deployModuleImpl(
1234
1234
  }
1235
1235
  }
1236
1236
 
1237
- // Auto-register module hostname in internal DNS (if available)
1237
+ // Auto-register module hostname in internal DNS (if available).
1238
+ // Wrapped in a FuelGauge with a gauge logger so the capability calls
1239
+ // inside (`dns_internal.registerRecord`, etc.) nest as sub-events
1240
+ // under a single step rather than leaking to scrollback as
1241
+ // unindented top-level lines via cli/prompts.log.instantEvent.
1238
1242
  let dnsRegistered = true;
1243
+ const dnsGauge = new FuelGauge(`Registering ${moduleId} in DNS`, {
1244
+ skipAnimation: !process.stdout.isTTY,
1245
+ });
1246
+ dnsGauge.start();
1239
1247
  try {
1248
+ const dnsLogger = createGaugeLogger(dnsGauge, moduleId, 'auto_register_dns');
1240
1249
  const { autoRegisterDns } = await import('./dns-auto-register');
1241
- await autoRegisterDns(moduleId, db, log);
1250
+ await autoRegisterDns(moduleId, db, dnsLogger);
1251
+ dnsGauge.stop(true);
1242
1252
  } catch (error) {
1253
+ dnsGauge.stop(false);
1243
1254
  const msg = error instanceof Error ? error.message : String(error);
1244
1255
  log.warn(`DNS auto-registration failed: ${msg}`);
1245
1256
  dnsRegistered = false;