@i4ctime/q-ring 0.2.8 → 0.3.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.
package/dist/index.js CHANGED
@@ -1,585 +1,26 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ checkDecay,
4
+ collapseEnvironment,
5
+ deleteSecret,
6
+ detectAnomalies,
7
+ entangleSecrets,
8
+ exportSecrets,
9
+ getEnvelope,
10
+ getSecret,
11
+ listSecrets,
12
+ logAudit,
13
+ queryAudit,
14
+ setSecret,
15
+ tunnelCreate,
16
+ tunnelDestroy,
17
+ tunnelList,
18
+ tunnelRead
19
+ } from "./chunk-WQPJ2FTM.js";
2
20
 
3
21
  // src/cli/commands.ts
4
22
  import { Command } from "commander";
5
23
 
6
- // src/core/keyring.ts
7
- import { Entry, findCredentials } from "@napi-rs/keyring";
8
-
9
- // src/utils/hash.ts
10
- import { createHash } from "crypto";
11
- function hashProjectPath(projectPath) {
12
- return createHash("sha256").update(projectPath).digest("hex").slice(0, 12);
13
- }
14
-
15
- // src/core/scope.ts
16
- var SERVICE_PREFIX = "q-ring";
17
- function globalService() {
18
- return `${SERVICE_PREFIX}:global`;
19
- }
20
- function projectService(projectPath) {
21
- const hash = hashProjectPath(projectPath);
22
- return `${SERVICE_PREFIX}:project:${hash}`;
23
- }
24
- function resolveScope(opts) {
25
- const { scope, projectPath } = opts;
26
- if (scope === "global") {
27
- return [{ scope: "global", service: globalService() }];
28
- }
29
- if (scope === "project") {
30
- if (!projectPath) {
31
- throw new Error("Project path is required for project scope");
32
- }
33
- return [
34
- { scope: "project", service: projectService(projectPath), projectPath }
35
- ];
36
- }
37
- if (projectPath) {
38
- return [
39
- { scope: "project", service: projectService(projectPath), projectPath },
40
- { scope: "global", service: globalService() }
41
- ];
42
- }
43
- return [{ scope: "global", service: globalService() }];
44
- }
45
-
46
- // src/core/envelope.ts
47
- function createEnvelope(value, opts) {
48
- const now = (/* @__PURE__ */ new Date()).toISOString();
49
- let expiresAt = opts?.expiresAt;
50
- if (!expiresAt && opts?.ttlSeconds) {
51
- expiresAt = new Date(Date.now() + opts.ttlSeconds * 1e3).toISOString();
52
- }
53
- return {
54
- v: 1,
55
- value: opts?.states ? void 0 : value,
56
- states: opts?.states,
57
- defaultEnv: opts?.defaultEnv,
58
- meta: {
59
- createdAt: now,
60
- updatedAt: now,
61
- expiresAt,
62
- ttlSeconds: opts?.ttlSeconds,
63
- description: opts?.description,
64
- tags: opts?.tags,
65
- entangled: opts?.entangled,
66
- accessCount: 0
67
- }
68
- };
69
- }
70
- function parseEnvelope(raw) {
71
- try {
72
- const parsed = JSON.parse(raw);
73
- if (parsed && typeof parsed === "object" && parsed.v === 1) {
74
- return parsed;
75
- }
76
- } catch {
77
- }
78
- return null;
79
- }
80
- function wrapLegacy(rawValue) {
81
- const now = (/* @__PURE__ */ new Date()).toISOString();
82
- return {
83
- v: 1,
84
- value: rawValue,
85
- meta: {
86
- createdAt: now,
87
- updatedAt: now,
88
- accessCount: 0
89
- }
90
- };
91
- }
92
- function serializeEnvelope(envelope) {
93
- return JSON.stringify(envelope);
94
- }
95
- function collapseValue(envelope, env) {
96
- if (envelope.states) {
97
- const targetEnv = env ?? envelope.defaultEnv;
98
- if (targetEnv && envelope.states[targetEnv]) {
99
- return envelope.states[targetEnv];
100
- }
101
- if (envelope.defaultEnv && envelope.states[envelope.defaultEnv]) {
102
- return envelope.states[envelope.defaultEnv];
103
- }
104
- const keys = Object.keys(envelope.states);
105
- if (keys.length > 0) {
106
- return envelope.states[keys[0]];
107
- }
108
- return null;
109
- }
110
- return envelope.value ?? null;
111
- }
112
- function checkDecay(envelope) {
113
- if (!envelope.meta.expiresAt) {
114
- return {
115
- isExpired: false,
116
- isStale: false,
117
- lifetimePercent: 0,
118
- secondsRemaining: null,
119
- timeRemaining: null
120
- };
121
- }
122
- const now = Date.now();
123
- const expires = new Date(envelope.meta.expiresAt).getTime();
124
- const created = new Date(envelope.meta.createdAt).getTime();
125
- const totalLifetime = expires - created;
126
- const elapsed = now - created;
127
- const remaining = expires - now;
128
- const lifetimePercent = totalLifetime > 0 ? Math.round(elapsed / totalLifetime * 100) : 100;
129
- const secondsRemaining = Math.floor(remaining / 1e3);
130
- let timeRemaining = null;
131
- if (remaining > 0) {
132
- const days = Math.floor(remaining / 864e5);
133
- const hours = Math.floor(remaining % 864e5 / 36e5);
134
- const minutes = Math.floor(remaining % 36e5 / 6e4);
135
- if (days > 0) timeRemaining = `${days}d ${hours}h`;
136
- else if (hours > 0) timeRemaining = `${hours}h ${minutes}m`;
137
- else timeRemaining = `${minutes}m`;
138
- } else {
139
- timeRemaining = "expired";
140
- }
141
- return {
142
- isExpired: remaining <= 0,
143
- isStale: lifetimePercent >= 75,
144
- lifetimePercent,
145
- secondsRemaining,
146
- timeRemaining
147
- };
148
- }
149
- function recordAccess(envelope) {
150
- return {
151
- ...envelope,
152
- meta: {
153
- ...envelope.meta,
154
- accessCount: envelope.meta.accessCount + 1,
155
- lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
156
- }
157
- };
158
- }
159
-
160
- // src/core/collapse.ts
161
- import { execSync } from "child_process";
162
- import { existsSync, readFileSync } from "fs";
163
- import { join } from "path";
164
- var BRANCH_ENV_MAP = {
165
- main: "prod",
166
- master: "prod",
167
- production: "prod",
168
- develop: "dev",
169
- development: "dev",
170
- dev: "dev",
171
- staging: "staging",
172
- stage: "staging",
173
- test: "test",
174
- testing: "test"
175
- };
176
- function detectGitBranch(cwd) {
177
- try {
178
- const branch = execSync("git rev-parse --abbrev-ref HEAD", {
179
- cwd: cwd ?? process.cwd(),
180
- stdio: ["pipe", "pipe", "pipe"],
181
- encoding: "utf8",
182
- timeout: 3e3
183
- }).trim();
184
- return branch || null;
185
- } catch {
186
- return null;
187
- }
188
- }
189
- function readProjectConfig(projectPath) {
190
- const configPath = join(projectPath ?? process.cwd(), ".q-ring.json");
191
- try {
192
- if (existsSync(configPath)) {
193
- return JSON.parse(readFileSync(configPath, "utf8"));
194
- }
195
- } catch {
196
- }
197
- return null;
198
- }
199
- function collapseEnvironment(ctx = {}) {
200
- if (ctx.explicit) {
201
- return { env: ctx.explicit, source: "explicit" };
202
- }
203
- const qringEnv = process.env.QRING_ENV;
204
- if (qringEnv) {
205
- return { env: qringEnv, source: "QRING_ENV" };
206
- }
207
- const nodeEnv = process.env.NODE_ENV;
208
- if (nodeEnv) {
209
- const mapped = mapEnvName(nodeEnv);
210
- return { env: mapped, source: "NODE_ENV" };
211
- }
212
- const config = readProjectConfig(ctx.projectPath);
213
- if (config?.env) {
214
- return { env: config.env, source: "project-config" };
215
- }
216
- const branch = detectGitBranch(ctx.projectPath);
217
- if (branch) {
218
- const branchMap = { ...BRANCH_ENV_MAP, ...config?.branchMap };
219
- const mapped = branchMap[branch];
220
- if (mapped) {
221
- return { env: mapped, source: "git-branch" };
222
- }
223
- }
224
- if (config?.defaultEnv) {
225
- return { env: config.defaultEnv, source: "project-config" };
226
- }
227
- return null;
228
- }
229
- function mapEnvName(raw) {
230
- const lower = raw.toLowerCase();
231
- if (lower === "production") return "prod";
232
- if (lower === "development") return "dev";
233
- return lower;
234
- }
235
-
236
- // src/core/observer.ts
237
- import { existsSync as existsSync2, mkdirSync, appendFileSync, readFileSync as readFileSync2 } from "fs";
238
- import { join as join2 } from "path";
239
- import { homedir } from "os";
240
- function getAuditDir() {
241
- const dir = join2(homedir(), ".config", "q-ring");
242
- if (!existsSync2(dir)) {
243
- mkdirSync(dir, { recursive: true });
244
- }
245
- return dir;
246
- }
247
- function getAuditPath() {
248
- return join2(getAuditDir(), "audit.jsonl");
249
- }
250
- function logAudit(event) {
251
- const full = {
252
- ...event,
253
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
254
- pid: process.pid
255
- };
256
- try {
257
- appendFileSync(getAuditPath(), JSON.stringify(full) + "\n");
258
- } catch {
259
- }
260
- }
261
- function queryAudit(query = {}) {
262
- const path = getAuditPath();
263
- if (!existsSync2(path)) return [];
264
- try {
265
- const lines = readFileSync2(path, "utf8").split("\n").filter((l) => l.trim());
266
- let events = lines.map((line) => {
267
- try {
268
- return JSON.parse(line);
269
- } catch {
270
- return null;
271
- }
272
- }).filter((e) => e !== null);
273
- if (query.key) {
274
- events = events.filter((e) => e.key === query.key);
275
- }
276
- if (query.action) {
277
- events = events.filter((e) => e.action === query.action);
278
- }
279
- if (query.since) {
280
- const since = new Date(query.since).getTime();
281
- events = events.filter(
282
- (e) => new Date(e.timestamp).getTime() >= since
283
- );
284
- }
285
- events.sort(
286
- (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
287
- );
288
- if (query.limit) {
289
- events = events.slice(0, query.limit);
290
- }
291
- return events;
292
- } catch {
293
- return [];
294
- }
295
- }
296
- function detectAnomalies(key) {
297
- const recent = queryAudit({
298
- key,
299
- action: "read",
300
- since: new Date(Date.now() - 36e5).toISOString()
301
- // last hour
302
- });
303
- const anomalies = [];
304
- if (key && recent.length > 50) {
305
- anomalies.push({
306
- type: "burst",
307
- description: `${recent.length} reads of "${key}" in the last hour`,
308
- events: recent.slice(0, 10)
309
- });
310
- }
311
- const nightAccess = recent.filter((e) => {
312
- const hour = new Date(e.timestamp).getHours();
313
- return hour >= 1 && hour < 5;
314
- });
315
- if (nightAccess.length > 0) {
316
- anomalies.push({
317
- type: "unusual-hour",
318
- description: `${nightAccess.length} access(es) during unusual hours (1am-5am)`,
319
- events: nightAccess
320
- });
321
- }
322
- return anomalies;
323
- }
324
-
325
- // src/core/entanglement.ts
326
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
327
- import { join as join3 } from "path";
328
- import { homedir as homedir2 } from "os";
329
- function getRegistryPath() {
330
- const dir = join3(homedir2(), ".config", "q-ring");
331
- if (!existsSync3(dir)) {
332
- mkdirSync2(dir, { recursive: true });
333
- }
334
- return join3(dir, "entanglement.json");
335
- }
336
- function loadRegistry() {
337
- const path = getRegistryPath();
338
- if (!existsSync3(path)) {
339
- return { pairs: [] };
340
- }
341
- try {
342
- return JSON.parse(readFileSync3(path, "utf8"));
343
- } catch {
344
- return { pairs: [] };
345
- }
346
- }
347
- function saveRegistry(registry) {
348
- writeFileSync(getRegistryPath(), JSON.stringify(registry, null, 2));
349
- }
350
- function entangle(source, target) {
351
- const registry = loadRegistry();
352
- const exists = registry.pairs.some(
353
- (p) => p.source.service === source.service && p.source.key === source.key && p.target.service === target.service && p.target.key === target.key
354
- );
355
- if (!exists) {
356
- registry.pairs.push({
357
- source,
358
- target,
359
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
360
- });
361
- registry.pairs.push({
362
- source: target,
363
- target: source,
364
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
365
- });
366
- saveRegistry(registry);
367
- }
368
- }
369
- function findEntangled(source) {
370
- const registry = loadRegistry();
371
- return registry.pairs.filter(
372
- (p) => p.source.service === source.service && p.source.key === source.key
373
- ).map((p) => p.target);
374
- }
375
-
376
- // src/core/keyring.ts
377
- function readEnvelope(service, key) {
378
- const entry = new Entry(service, key);
379
- const raw = entry.getPassword();
380
- if (raw === null) return null;
381
- const envelope = parseEnvelope(raw);
382
- return envelope ?? wrapLegacy(raw);
383
- }
384
- function writeEnvelope(service, key, envelope) {
385
- const entry = new Entry(service, key);
386
- entry.setPassword(serializeEnvelope(envelope));
387
- }
388
- function resolveEnv(opts) {
389
- if (opts.env) return opts.env;
390
- const result = collapseEnvironment({ projectPath: opts.projectPath });
391
- return result?.env;
392
- }
393
- function getSecret(key, opts = {}) {
394
- const scopes = resolveScope(opts);
395
- const env = resolveEnv(opts);
396
- const source = opts.source ?? "cli";
397
- for (const { service, scope } of scopes) {
398
- const envelope = readEnvelope(service, key);
399
- if (!envelope) continue;
400
- const decay = checkDecay(envelope);
401
- if (decay.isExpired) {
402
- logAudit({
403
- action: "read",
404
- key,
405
- scope,
406
- source,
407
- detail: "blocked: secret expired (quantum decay)"
408
- });
409
- continue;
410
- }
411
- const value = collapseValue(envelope, env);
412
- if (value === null) continue;
413
- const updated = recordAccess(envelope);
414
- writeEnvelope(service, key, updated);
415
- logAudit({ action: "read", key, scope, env, source });
416
- return value;
417
- }
418
- return null;
419
- }
420
- function getEnvelope(key, opts = {}) {
421
- const scopes = resolveScope(opts);
422
- for (const { service, scope } of scopes) {
423
- const envelope = readEnvelope(service, key);
424
- if (envelope) return { envelope, scope };
425
- }
426
- return null;
427
- }
428
- function setSecret(key, value, opts = {}) {
429
- const scope = opts.scope ?? "global";
430
- const scopes = resolveScope({ ...opts, scope });
431
- const { service } = scopes[0];
432
- const source = opts.source ?? "cli";
433
- const existing = readEnvelope(service, key);
434
- let envelope;
435
- if (opts.states) {
436
- envelope = createEnvelope("", {
437
- states: opts.states,
438
- defaultEnv: opts.defaultEnv,
439
- description: opts.description,
440
- tags: opts.tags,
441
- ttlSeconds: opts.ttlSeconds,
442
- expiresAt: opts.expiresAt,
443
- entangled: existing?.meta.entangled
444
- });
445
- } else {
446
- envelope = createEnvelope(value, {
447
- description: opts.description,
448
- tags: opts.tags,
449
- ttlSeconds: opts.ttlSeconds,
450
- expiresAt: opts.expiresAt,
451
- entangled: existing?.meta.entangled
452
- });
453
- }
454
- if (existing) {
455
- envelope.meta.createdAt = existing.meta.createdAt;
456
- envelope.meta.accessCount = existing.meta.accessCount;
457
- }
458
- writeEnvelope(service, key, envelope);
459
- logAudit({ action: "write", key, scope, source });
460
- const entangled = findEntangled({ service, key });
461
- for (const target of entangled) {
462
- try {
463
- const targetEnvelope = readEnvelope(target.service, target.key);
464
- if (targetEnvelope) {
465
- if (opts.states) {
466
- targetEnvelope.states = opts.states;
467
- } else {
468
- targetEnvelope.value = value;
469
- }
470
- targetEnvelope.meta.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
471
- writeEnvelope(target.service, target.key, targetEnvelope);
472
- logAudit({
473
- action: "entangle",
474
- key: target.key,
475
- scope: "global",
476
- source,
477
- detail: `propagated from ${key}`
478
- });
479
- }
480
- } catch {
481
- }
482
- }
483
- }
484
- function deleteSecret(key, opts = {}) {
485
- const scopes = resolveScope(opts);
486
- const source = opts.source ?? "cli";
487
- let deleted = false;
488
- for (const { service, scope } of scopes) {
489
- const entry = new Entry(service, key);
490
- try {
491
- if (entry.deleteCredential()) {
492
- deleted = true;
493
- logAudit({ action: "delete", key, scope, source });
494
- }
495
- } catch {
496
- }
497
- }
498
- return deleted;
499
- }
500
- function listSecrets(opts = {}) {
501
- const source = opts.source ?? "cli";
502
- const services = [];
503
- if (!opts.scope || opts.scope === "global") {
504
- services.push({ service: globalService(), scope: "global" });
505
- }
506
- if ((!opts.scope || opts.scope === "project") && opts.projectPath) {
507
- services.push({
508
- service: projectService(opts.projectPath),
509
- scope: "project"
510
- });
511
- }
512
- const results = [];
513
- const seen = /* @__PURE__ */ new Set();
514
- for (const { service, scope } of services) {
515
- try {
516
- const credentials = findCredentials(service);
517
- for (const cred of credentials) {
518
- const id = `${scope}:${cred.account}`;
519
- if (seen.has(id)) continue;
520
- seen.add(id);
521
- const envelope = parseEnvelope(cred.password) ?? wrapLegacy(cred.password);
522
- const decay = checkDecay(envelope);
523
- results.push({
524
- key: cred.account,
525
- scope,
526
- envelope,
527
- decay
528
- });
529
- }
530
- } catch {
531
- }
532
- }
533
- logAudit({ action: "list", source });
534
- return results.sort((a, b) => a.key.localeCompare(b.key));
535
- }
536
- function exportSecrets(opts = {}) {
537
- const format = opts.format ?? "env";
538
- const env = resolveEnv(opts);
539
- const entries = listSecrets(opts);
540
- const source = opts.source ?? "cli";
541
- const merged = /* @__PURE__ */ new Map();
542
- const globalEntries = entries.filter((e) => e.scope === "global");
543
- const projectEntries = entries.filter((e) => e.scope === "project");
544
- for (const entry of [...globalEntries, ...projectEntries]) {
545
- if (entry.envelope) {
546
- const decay = checkDecay(entry.envelope);
547
- if (decay.isExpired) continue;
548
- const value = collapseValue(entry.envelope, env);
549
- if (value !== null) {
550
- merged.set(entry.key, value);
551
- }
552
- }
553
- }
554
- logAudit({ action: "export", source, detail: `format=${format}` });
555
- if (format === "json") {
556
- const obj = {};
557
- for (const [key, value] of merged) {
558
- obj[key] = value;
559
- }
560
- return JSON.stringify(obj, null, 2);
561
- }
562
- const lines = [];
563
- for (const [key, value] of merged) {
564
- const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
565
- lines.push(`${key}="${escaped}"`);
566
- }
567
- return lines.join("\n");
568
- }
569
- function entangleSecrets(sourceKey, sourceOpts, targetKey, targetOpts) {
570
- const sourceScopes = resolveScope({ ...sourceOpts, scope: sourceOpts.scope ?? "global" });
571
- const targetScopes = resolveScope({ ...targetOpts, scope: targetOpts.scope ?? "global" });
572
- const source = { service: sourceScopes[0].service, key: sourceKey };
573
- const target = { service: targetScopes[0].service, key: targetKey };
574
- entangle(source, target);
575
- logAudit({
576
- action: "entangle",
577
- key: sourceKey,
578
- source: sourceOpts.source ?? "cli",
579
- detail: `entangled with ${targetKey}`
580
- });
581
- }
582
-
583
24
  // src/core/noise.ts
584
25
  import { randomBytes, randomInt } from "crypto";
585
26
  var ALPHA_NUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -851,77 +292,6 @@ ${c.dim("q-ring agent stopped")}`);
851
292
  });
852
293
  }
853
294
 
854
- // src/core/tunnel.ts
855
- var tunnelStore = /* @__PURE__ */ new Map();
856
- var cleanupInterval = null;
857
- function ensureCleanup() {
858
- if (cleanupInterval) return;
859
- cleanupInterval = setInterval(() => {
860
- const now = Date.now();
861
- for (const [id, entry] of tunnelStore) {
862
- if (entry.expiresAt && now >= entry.expiresAt) {
863
- tunnelStore.delete(id);
864
- }
865
- }
866
- if (tunnelStore.size === 0 && cleanupInterval) {
867
- clearInterval(cleanupInterval);
868
- cleanupInterval = null;
869
- }
870
- }, 5e3);
871
- if (cleanupInterval && typeof cleanupInterval === "object" && "unref" in cleanupInterval) {
872
- cleanupInterval.unref();
873
- }
874
- }
875
- function tunnelCreate(value, opts = {}) {
876
- const id = `tun_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
877
- const now = Date.now();
878
- tunnelStore.set(id, {
879
- value,
880
- createdAt: now,
881
- expiresAt: opts.ttlSeconds ? now + opts.ttlSeconds * 1e3 : void 0,
882
- accessCount: 0,
883
- maxReads: opts.maxReads
884
- });
885
- ensureCleanup();
886
- return id;
887
- }
888
- function tunnelRead(id) {
889
- const entry = tunnelStore.get(id);
890
- if (!entry) return null;
891
- if (entry.expiresAt && Date.now() >= entry.expiresAt) {
892
- tunnelStore.delete(id);
893
- return null;
894
- }
895
- entry.accessCount++;
896
- if (entry.maxReads && entry.accessCount >= entry.maxReads) {
897
- const value = entry.value;
898
- tunnelStore.delete(id);
899
- return value;
900
- }
901
- return entry.value;
902
- }
903
- function tunnelDestroy(id) {
904
- return tunnelStore.delete(id);
905
- }
906
- function tunnelList() {
907
- const now = Date.now();
908
- const result = [];
909
- for (const [id, entry] of tunnelStore) {
910
- if (entry.expiresAt && now >= entry.expiresAt) {
911
- tunnelStore.delete(id);
912
- continue;
913
- }
914
- result.push({
915
- id,
916
- createdAt: entry.createdAt,
917
- expiresAt: entry.expiresAt,
918
- accessCount: entry.accessCount,
919
- maxReads: entry.maxReads
920
- });
921
- }
922
- return result;
923
- }
924
-
925
295
  // src/core/teleport.ts
926
296
  import {
927
297
  randomBytes as randomBytes2,
@@ -1493,6 +863,36 @@ ${SYMBOLS.warning} ${c.bold(c.yellow(`${anomalies.length} anomaly/anomalies dete
1493
863
  }
1494
864
  console.log();
1495
865
  });
866
+ program2.command("status").description("Launch the quantum status dashboard in your browser").option("--port <port>", "Port to serve on", "9876").option("--no-open", "Don't auto-open the browser").action(async (cmd) => {
867
+ const { startDashboardServer } = await import("./dashboard-JTALLJM6.js");
868
+ const { exec } = await import("child_process");
869
+ const { platform } = await import("os");
870
+ const port = Number(cmd.port);
871
+ const { close } = startDashboardServer({ port });
872
+ const url = `http://127.0.0.1:${port}`;
873
+ console.log(
874
+ `
875
+ ${SYMBOLS.zap} ${c.bold("q-ring quantum status dashboard")}
876
+ `
877
+ );
878
+ console.log(` ${c.cyan(url)}
879
+ `);
880
+ console.log(c.dim(" Press Ctrl+C to stop\n"));
881
+ if (cmd.open !== false) {
882
+ const openCmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
883
+ exec(`${openCmd} ${url}`);
884
+ }
885
+ const shutdown = () => {
886
+ console.log(`
887
+ ${c.dim(" dashboard stopped")}`);
888
+ close();
889
+ process.exit(0);
890
+ };
891
+ process.on("SIGINT", shutdown);
892
+ process.on("SIGTERM", shutdown);
893
+ await new Promise(() => {
894
+ });
895
+ });
1496
896
  program2.command("agent").description("Start the autonomous agent (background monitor)").option(
1497
897
  "-i, --interval <seconds>",
1498
898
  "Scan interval in seconds",