@kitsy/cnos-cli 1.3.0 → 1.5.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 (2) hide show
  1. package/dist/index.js +765 -232
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -44,9 +44,17 @@ function normalizeCommand(argv) {
44
44
  const [command = "doctor", ...rest] = argv;
45
45
  const resource = rest[0];
46
46
  const remaining = rest.slice(1);
47
+ const dottedNamespace = resource?.includes(".") ? {
48
+ namespace: resource.slice(0, resource.indexOf(".")),
49
+ path: resource.slice(resource.indexOf(".") + 1)
50
+ } : void 0;
51
+ const normalizedVerb = command === "remove" || command === "delete" ? "delete" : command === "create" || command === "add" ? "set" : command === "set" || command === "get" ? command : void 0;
47
52
  if ((command === "set" || command === "get") && (resource === "value" || resource === "secret")) {
48
53
  return [resource, command, ...remaining];
49
54
  }
55
+ if (normalizedVerb && dottedNamespace && dottedNamespace.namespace && dottedNamespace.path) {
56
+ return [dottedNamespace.namespace, normalizedVerb, dottedNamespace.path, ...remaining];
57
+ }
50
58
  if ((command === "create" || command === "add") && resource === "profile") {
51
59
  return ["profile", "create", ...remaining];
52
60
  }
@@ -86,6 +94,9 @@ function normalizeCommand(argv) {
86
94
  if ((command === "delete" || command === "remove") && resource === "value") {
87
95
  return ["value", "delete", ...remaining];
88
96
  }
97
+ if (normalizedVerb && resource && !["profile", "vault", "secret", "value"].includes(resource)) {
98
+ return [resource, normalizedVerb, ...remaining];
99
+ }
89
100
  if (command === "list" && resource === "value") {
90
101
  return ["value", "list", ...remaining];
91
102
  }
@@ -219,21 +230,29 @@ function consumeOption(args, flag) {
219
230
  return void 0;
220
231
  }
221
232
 
233
+ // src/format/displayPath.ts
234
+ import path from "path";
235
+ function displayPath(filePath, root = process.cwd()) {
236
+ const absoluteRoot = path.resolve(root);
237
+ const absoluteFile = path.resolve(filePath);
238
+ const relative = path.relative(absoluteRoot, absoluteFile);
239
+ if (!relative || relative === "") {
240
+ return ".";
241
+ }
242
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
243
+ return absoluteFile;
244
+ }
245
+ return relative;
246
+ }
247
+
222
248
  // src/format/printJson.ts
223
249
  function printJson(value) {
224
250
  return JSON.stringify(value, null, 2);
225
251
  }
226
252
 
227
- // src/services/writes.ts
228
- import { mkdir, readFile, writeFile } from "fs/promises";
229
- import path from "path";
230
- import {
231
- createSecretVaultProvider,
232
- parseYaml,
233
- resolveConfigDocumentPath,
234
- resolveVaultAuth,
235
- stringifyYaml
236
- } from "@kitsy/cnos/internal";
253
+ // src/services/envMaterialization.ts
254
+ import { mkdir, writeFile } from "fs/promises";
255
+ import path2 from "path";
237
256
 
238
257
  // src/services/runtime.ts
239
258
  import { createCnos } from "@kitsy/cnos/configure";
@@ -249,7 +268,92 @@ async function createRuntimeService(options = {}) {
249
268
  });
250
269
  }
251
270
 
271
+ // src/services/envMaterialization.ts
272
+ function resolveEnvFromRuntime(runtime, cliArgs = []) {
273
+ const args = [...cliArgs];
274
+ const isPublic = consumeFlag(args, "--public");
275
+ const framework = consumeOption(args, "--framework");
276
+ const prefix = consumeOption(args, "--prefix");
277
+ return isPublic ? runtime.toPublicEnv({
278
+ ...framework ? { framework } : {},
279
+ ...prefix ? { prefix } : {}
280
+ }) : runtime.toEnv();
281
+ }
282
+ function formatEnvOutput(env) {
283
+ return Object.entries(env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
284
+ }
285
+ async function resolveMaterializedEnv(options = {}) {
286
+ const runtime = await createRuntimeService({
287
+ ...options,
288
+ cliArgs: [...options.cliArgs ?? []]
289
+ });
290
+ const env = resolveEnvFromRuntime(runtime, options.cliArgs ?? []);
291
+ return {
292
+ runtime,
293
+ env,
294
+ output: formatEnvOutput(env)
295
+ };
296
+ }
297
+ function resolveMaterializedEnvTarget(to, root = process.cwd()) {
298
+ return path2.resolve(root, to);
299
+ }
300
+ async function writeMaterializedEnvFile(to, output, root = process.cwd()) {
301
+ const targetPath = resolveMaterializedEnvTarget(to, root);
302
+ await mkdir(path2.dirname(targetPath), { recursive: true });
303
+ await writeFile(targetPath, output, "utf8");
304
+ return targetPath;
305
+ }
306
+ async function materializeEnvToFile(to, options = {}) {
307
+ const result = await resolveMaterializedEnv(options);
308
+ const targetPath = await writeMaterializedEnvFile(to, result.output, options.root ?? process.cwd());
309
+ return {
310
+ ...result,
311
+ targetPath
312
+ };
313
+ }
314
+
315
+ // src/commands/build.ts
316
+ async function runBuild(subcommand, options = {}) {
317
+ if (subcommand !== "env") {
318
+ throw new Error(`Unsupported build target: ${subcommand ?? "(missing)"}`);
319
+ }
320
+ const infoArgs = [...options.cliArgs ?? []];
321
+ const isPublic = consumeFlag(infoArgs, "--public");
322
+ const framework = consumeOption(infoArgs, "--framework");
323
+ consumeOption(infoArgs, "--prefix");
324
+ const to = consumeOption(infoArgs, "--to");
325
+ if (!to) {
326
+ throw new Error("build env requires --to <path>");
327
+ }
328
+ const result = await materializeEnvToFile(to, {
329
+ ...options,
330
+ cliArgs: [...options.cliArgs ?? []]
331
+ });
332
+ if (options.json) {
333
+ return printJson({
334
+ to: result.targetPath,
335
+ count: Object.keys(result.env).length,
336
+ public: isPublic,
337
+ ...framework ? { framework } : {}
338
+ });
339
+ }
340
+ return `built env artifact at ${displayPath(result.targetPath, options.root ?? process.cwd())}`;
341
+ }
342
+
343
+ // src/commands/define.ts
344
+ import path4 from "path";
345
+
252
346
  // src/services/writes.ts
347
+ import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "fs/promises";
348
+ import path3 from "path";
349
+ import {
350
+ getNamespaceDefinition,
351
+ createSecretVaultProvider,
352
+ parseYaml,
353
+ resolveConfigDocumentPath,
354
+ resolveVaultAuth,
355
+ stringifyYaml
356
+ } from "@kitsy/cnos/internal";
253
357
  function setNestedValue(target, pathSegments, value) {
254
358
  const [head, ...tail] = pathSegments;
255
359
  if (!head) {
@@ -330,14 +434,27 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
330
434
  };
331
435
  }
332
436
  const runtime = await createRuntimeService(options);
437
+ if (namespace !== "value" && namespace !== "secret" && !runtime.manifest.namespaces[namespace]) {
438
+ throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is not declared in .cnos/cnos.yml.`);
439
+ }
440
+ const namespaceDefinition = getNamespaceDefinition(runtime.manifest, namespace);
441
+ if (namespaceDefinition.kind !== "data") {
442
+ throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is not a data namespace.`);
443
+ }
444
+ if (namespaceDefinition.readonly) {
445
+ throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is readonly.`);
446
+ }
447
+ if (namespaceDefinition.sensitive) {
448
+ throw new Error(`Cannot write ${namespace}.${configPath} with the generic data writer because namespace "${namespace}" is sensitive.`);
449
+ }
333
450
  const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
334
451
  const profile = options.profile ?? runtime.graph.profile;
335
452
  const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
336
453
  const document = await readYamlDocument(filePath);
337
454
  const parsedValue = parseScalarValue(rawValue);
338
455
  setNestedValue(document, configPath.split("."), parsedValue);
339
- await mkdir(path.dirname(filePath), { recursive: true });
340
- await writeFile(filePath, stringifyYaml(document), "utf8");
456
+ await mkdir2(path3.dirname(filePath), { recursive: true });
457
+ await writeFile2(filePath, stringifyYaml(document), "utf8");
341
458
  return {
342
459
  filePath,
343
460
  value: parsedValue
@@ -374,8 +491,8 @@ async function setSecret(configPath, rawValue, options = {}) {
374
491
  };
375
492
  }
376
493
  setNestedValue(document, configPath.split("."), reference);
377
- await mkdir(path.dirname(filePath), { recursive: true });
378
- await writeFile(filePath, stringifyYaml(document), "utf8");
494
+ await mkdir2(path3.dirname(filePath), { recursive: true });
495
+ await writeFile2(filePath, stringifyYaml(document), "utf8");
379
496
  return {
380
497
  filePath,
381
498
  provider: reference.provider,
@@ -397,7 +514,7 @@ async function deleteSecret(configPath, options = {}) {
397
514
  deleted: false
398
515
  };
399
516
  }
400
- await writeFile(filePath, stringifyYaml(document), "utf8");
517
+ await writeFile2(filePath, stringifyYaml(document), "utf8");
401
518
  const secretRef = metadata?.secretRef;
402
519
  if (isSecretReference(secretRef) && secretRef.provider === "local") {
403
520
  const definition = runtime.manifest.vaults[secretRef.vault ?? "default"];
@@ -418,6 +535,19 @@ async function deleteValue(namespace, configPath, options = {}) {
418
535
  return deleteSecret(configPath, options);
419
536
  }
420
537
  const runtime = await createRuntimeService(options);
538
+ if (namespace !== "value" && namespace !== "secret" && !runtime.manifest.namespaces[namespace]) {
539
+ throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is not declared in .cnos/cnos.yml.`);
540
+ }
541
+ const namespaceDefinition = getNamespaceDefinition(runtime.manifest, namespace);
542
+ if (namespaceDefinition.kind !== "data") {
543
+ throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is not a data namespace.`);
544
+ }
545
+ if (namespaceDefinition.readonly) {
546
+ throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is readonly.`);
547
+ }
548
+ if (namespaceDefinition.sensitive) {
549
+ throw new Error(`Cannot delete ${namespace}.${configPath} with the generic data writer because namespace "${namespace}" is sensitive.`);
550
+ }
421
551
  const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
422
552
  const profile = options.profile ?? runtime.graph.profile;
423
553
  const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
@@ -429,7 +559,7 @@ async function deleteValue(namespace, configPath, options = {}) {
429
559
  deleted: false
430
560
  };
431
561
  }
432
- await writeFile(filePath, stringifyYaml(document), "utf8");
562
+ await writeFile2(filePath, stringifyYaml(document), "utf8");
433
563
  return {
434
564
  filePath,
435
565
  deleted: true
@@ -439,6 +569,7 @@ async function deleteValue(namespace, configPath, options = {}) {
439
569
  // src/commands/define.ts
440
570
  async function runDefine(namespace, configPath, rawValue, options = {}) {
441
571
  const cliArgs = [...options.cliArgs ?? []];
572
+ const root = path4.resolve(options.root ?? process.cwd());
442
573
  const target = consumeOption(cliArgs, "--target") ?? "local";
443
574
  const local = consumeFlag(cliArgs, "--local");
444
575
  const remote = consumeFlag(cliArgs, "--remote");
@@ -464,7 +595,194 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
464
595
  value: result.value
465
596
  });
466
597
  }
467
- return `defined ${namespace}.${configPath} in ${result.filePath}`;
598
+ return `defined ${namespace}.${configPath} in ${displayPath(result.filePath, root)}`;
599
+ }
600
+
601
+ // src/services/spawn.ts
602
+ import { spawn } from "child_process";
603
+ function shouldUseShellForCommand(command) {
604
+ if (process.platform !== "win32") {
605
+ return false;
606
+ }
607
+ return !/[\\/]/.test(command);
608
+ }
609
+ function spawnCommand(command, options) {
610
+ const executable = command[0];
611
+ if (!executable) {
612
+ throw new Error("A command is required.");
613
+ }
614
+ return spawn(executable, command.slice(1), {
615
+ cwd: options.cwd,
616
+ env: options.env,
617
+ stdio: options.stdio ?? "inherit",
618
+ shell: shouldUseShellForCommand(executable)
619
+ });
620
+ }
621
+
622
+ // src/services/watchLoop.ts
623
+ import { watch } from "fs";
624
+ import { diffGraphs, watchFiles } from "@kitsy/cnos/internal";
625
+ async function startGraphWatchLoop(options) {
626
+ const debounceMs = options.debounceMs ?? 300;
627
+ let current = await createRuntimeService(options);
628
+ const watcherMap = /* @__PURE__ */ new Map();
629
+ let timer;
630
+ let closed = false;
631
+ const attachWatcher = (targetPath, recursive = false) => {
632
+ if (watcherMap.has(targetPath)) {
633
+ return;
634
+ }
635
+ try {
636
+ const watcher = watch(
637
+ targetPath,
638
+ recursive ? {
639
+ recursive: true
640
+ } : void 0,
641
+ () => {
642
+ if (timer) {
643
+ clearTimeout(timer);
644
+ }
645
+ timer = setTimeout(() => {
646
+ void handleChange();
647
+ }, debounceMs);
648
+ }
649
+ );
650
+ watcherMap.set(targetPath, watcher);
651
+ } catch {
652
+ if (recursive) {
653
+ attachWatcher(targetPath, false);
654
+ }
655
+ }
656
+ };
657
+ const refreshWatchers = async () => {
658
+ const nextTargets = await watchFiles(current, options.root);
659
+ attachWatcher(nextTargets.manifestPath, false);
660
+ for (const workspaceRoot of nextTargets.roots) {
661
+ attachWatcher(workspaceRoot, true);
662
+ }
663
+ for (const filePath of nextTargets.files) {
664
+ attachWatcher(filePath, false);
665
+ }
666
+ };
667
+ const handleChange = async () => {
668
+ if (closed) {
669
+ return;
670
+ }
671
+ const next = await createRuntimeService(options);
672
+ const changedKeys = diffGraphs(current.graph, next.graph);
673
+ current = next;
674
+ await refreshWatchers();
675
+ if (changedKeys.length === 0) {
676
+ return;
677
+ }
678
+ await options.onChange?.({
679
+ runtime: next,
680
+ changedKeys
681
+ });
682
+ };
683
+ await refreshWatchers();
684
+ return {
685
+ async close() {
686
+ closed = true;
687
+ if (timer) {
688
+ clearTimeout(timer);
689
+ }
690
+ for (const watcher of watcherMap.values()) {
691
+ watcher.close();
692
+ }
693
+ watcherMap.clear();
694
+ }
695
+ };
696
+ }
697
+
698
+ // src/commands/dev.ts
699
+ async function startDevEnvLoop(command, options = {}) {
700
+ if (command.length === 0) {
701
+ throw new Error("dev env requires a command after --");
702
+ }
703
+ const cliArgs = [...options.cliArgs ?? []];
704
+ const to = consumeOption(cliArgs, "--to");
705
+ const isSignal = consumeFlag(cliArgs, "--signal");
706
+ const debounceMs = Number(consumeOption(cliArgs, "--debounce") ?? "300");
707
+ if (!to) {
708
+ throw new Error("dev env requires --to <path>");
709
+ }
710
+ const root = options.root ?? process.cwd();
711
+ let child;
712
+ const writeCurrent = async () => {
713
+ await materializeEnvToFile(to, {
714
+ ...options,
715
+ cliArgs: [...cliArgs]
716
+ });
717
+ };
718
+ await writeCurrent();
719
+ if (!isSignal) {
720
+ child = spawnCommand(command, {
721
+ cwd: root,
722
+ env: process.env,
723
+ stdio: "inherit"
724
+ });
725
+ }
726
+ const watcher = await startGraphWatchLoop({
727
+ ...options,
728
+ cliArgs,
729
+ debounceMs,
730
+ async onChange(payload) {
731
+ await writeCurrent();
732
+ if (isSignal) {
733
+ process.stdout.write(`${printJson({ changedKeys: payload.changedKeys })}
734
+ `);
735
+ return;
736
+ }
737
+ if (child && !child.killed) {
738
+ await new Promise((resolve) => {
739
+ child?.once("close", () => resolve());
740
+ child?.kill();
741
+ });
742
+ }
743
+ child = spawnCommand(command, {
744
+ cwd: root,
745
+ env: process.env,
746
+ stdio: "inherit"
747
+ });
748
+ }
749
+ });
750
+ return {
751
+ async close() {
752
+ await watcher.close();
753
+ if (child && !child.killed) {
754
+ await new Promise((resolve) => {
755
+ child?.once("close", () => resolve());
756
+ child?.kill();
757
+ });
758
+ }
759
+ }
760
+ };
761
+ }
762
+ async function runDev(subcommand, command, options = {}) {
763
+ if (subcommand !== "env") {
764
+ throw new Error(`Unsupported dev target: ${subcommand ?? "(missing)"}`);
765
+ }
766
+ const cliArgs = [...options.cliArgs ?? []];
767
+ const to = consumeOption(cliArgs, "--to");
768
+ const isSignal = consumeFlag(cliArgs, "--signal");
769
+ if (!to) {
770
+ throw new Error("dev env requires --to <path>");
771
+ }
772
+ if (command.length === 0) {
773
+ throw new Error("dev env requires a command after --");
774
+ }
775
+ const handle = await startDevEnvLoop(command, {
776
+ ...options,
777
+ cliArgs
778
+ });
779
+ const closeLoop = () => {
780
+ void handle.close();
781
+ };
782
+ process.once("SIGINT", closeLoop);
783
+ process.once("SIGTERM", closeLoop);
784
+ const targetPath = displayPath(to, options.root ?? process.cwd());
785
+ return isSignal ? `watching config changes and rewriting ${targetPath} in signal mode` : `watching config changes, rewriting ${targetPath}, and restarting the child process`;
468
786
  }
469
787
 
470
788
  // src/commands/drift.ts
@@ -517,7 +835,7 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
517
835
 
518
836
  // src/services/doctor.ts
519
837
  import { readdir, readFile as readFile2 } from "fs/promises";
520
- import path2 from "path";
838
+ import path5 from "path";
521
839
  import {
522
840
  detectLegacyVaultFormat,
523
841
  isSecretReference as isSecretReference2,
@@ -539,7 +857,7 @@ async function createValidationSummary(options = {}) {
539
857
 
540
858
  // src/services/doctor.ts
541
859
  async function checkGitignore(root) {
542
- const gitignorePath = path2.join(root, ".gitignore");
860
+ const gitignorePath = path5.join(root, ".gitignore");
543
861
  const expected = [
544
862
  ".cnos/env/.env",
545
863
  ".cnos/env/.env.*",
@@ -574,12 +892,12 @@ async function collectYamlFiles(root) {
574
892
  const entries = await readdir(root, { withFileTypes: true });
575
893
  const results = [];
576
894
  for (const entry of entries) {
577
- const target = path2.join(root, entry.name);
895
+ const target = path5.join(root, entry.name);
578
896
  if (entry.isDirectory()) {
579
897
  results.push(...await collectYamlFiles(target));
580
898
  continue;
581
899
  }
582
- if (entry.isFile() && [".yml", ".yaml"].includes(path2.extname(entry.name).toLowerCase())) {
900
+ if (entry.isFile() && [".yml", ".yaml"].includes(path5.extname(entry.name).toLowerCase())) {
583
901
  results.push(target);
584
902
  }
585
903
  }
@@ -604,7 +922,7 @@ async function checkSecretSecurity(options, runtime) {
604
922
  );
605
923
  const legacyDetected = legacyPaths.filter((entry) => Boolean(entry.path));
606
924
  const secretFiles = await Promise.all(
607
- runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(path2.join(root.path, "secrets")))
925
+ runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(path5.join(root.path, "secrets")))
608
926
  );
609
927
  const plaintextFiles = [];
610
928
  for (const file of secretFiles.flat()) {
@@ -634,10 +952,11 @@ async function checkSecretSecurity(options, runtime) {
634
952
  };
635
953
  }
636
954
  async function evaluateDoctor(options = {}) {
637
- const root = path2.resolve(options.root ?? process.cwd());
955
+ const root = path5.resolve(options.root ?? process.cwd());
638
956
  const { runtime, summary } = await createValidationSummary(options);
639
957
  const localRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "local");
640
958
  const globalRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "global");
959
+ const declaredCustomNamespaces = Object.entries(runtime.manifest.namespaces).filter(([namespace]) => !["value", "secret", "meta", "process", "public", "env"].includes(namespace)).map(([namespace, definition]) => `${namespace}(${definition.kind}${definition.shareable ? ",shareable" : ""}${definition.readonly ? ",readonly" : ""})`).sort((left, right) => left.localeCompare(right));
641
960
  return [
642
961
  {
643
962
  name: "manifest",
@@ -649,6 +968,11 @@ async function evaluateDoctor(options = {}) {
649
968
  ok: true,
650
969
  details: `${runtime.graph.workspace.workspaceId} via ${runtime.graph.workspace.workspaceSource}`
651
970
  },
971
+ {
972
+ name: "namespaces",
973
+ ok: true,
974
+ details: declaredCustomNamespaces.length === 0 ? "built-ins: value, secret, meta, process, public, env" : `built-ins: value, secret, meta, process, public, env | custom: ${declaredCustomNamespaces.join(", ")}`
975
+ },
652
976
  {
653
977
  name: "source-roots",
654
978
  ok: Boolean(localRoot),
@@ -748,44 +1072,33 @@ async function runCodegen(options = {}) {
748
1072
  }
749
1073
 
750
1074
  // src/commands/exportEnv.ts
751
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
752
- import path3 from "path";
753
- function formatEnvOutput(env) {
754
- return Object.entries(env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
755
- }
756
1075
  async function runExportEnv(options = {}) {
757
- const cliArgs = [...options.cliArgs ?? []];
758
- const isPublic = consumeFlag(cliArgs, "--public");
759
- const framework = consumeOption(cliArgs, "--framework");
760
- const prefix = consumeOption(cliArgs, "--prefix");
761
- const to = consumeOption(cliArgs, "--to");
762
- const runtime = await createRuntimeService({
1076
+ const infoArgs = [...options.cliArgs ?? []];
1077
+ const isPublic = consumeFlag(infoArgs, "--public");
1078
+ const framework = consumeOption(infoArgs, "--framework");
1079
+ consumeOption(infoArgs, "--prefix");
1080
+ const to = consumeOption(infoArgs, "--to");
1081
+ const baseOptions = {
763
1082
  ...options,
764
- cliArgs
765
- });
766
- const env = isPublic ? runtime.toPublicEnv({
767
- ...framework ? { framework } : {},
768
- ...prefix ? { prefix } : {}
769
- }) : runtime.toEnv();
770
- const output = formatEnvOutput(env);
1083
+ cliArgs: [...options.cliArgs ?? []]
1084
+ };
771
1085
  if (to) {
772
- const targetPath = path3.resolve(options.root ?? process.cwd(), to);
773
- await mkdir2(path3.dirname(targetPath), { recursive: true });
774
- await writeFile2(targetPath, output, "utf8");
1086
+ const result2 = await materializeEnvToFile(to, baseOptions);
775
1087
  if (options.json) {
776
1088
  return printJson({
777
- to: targetPath,
778
- count: Object.keys(env).length,
1089
+ to: result2.targetPath,
1090
+ count: Object.keys(result2.env).length,
779
1091
  public: isPublic,
780
1092
  ...framework ? { framework } : {}
781
1093
  });
782
1094
  }
783
- return `Wrote ${Object.keys(env).length} env vars to ${targetPath}`;
1095
+ return `Wrote ${Object.keys(result2.env).length} env vars to ${displayPath(result2.targetPath, options.root ?? process.cwd())}`;
784
1096
  }
1097
+ const result = await resolveMaterializedEnv(baseOptions);
785
1098
  if (options.json) {
786
- return printJson(env);
1099
+ return printJson(result.env);
787
1100
  }
788
- return output;
1101
+ return result.output;
789
1102
  }
790
1103
 
791
1104
  // src/commands/export.ts
@@ -1001,7 +1314,7 @@ var COMMANDS = [
1001
1314
  id: "vault create",
1002
1315
  summary: "Create a manifest-defined vault.",
1003
1316
  usage: "cnos vault create <name> [--provider <local|github-secrets>] [--no-passphrase] [global-options]",
1004
- description: "Creates a vault definition in .cnos/cnos.yml and, for local vaults, initializes the encrypted store under ~/.cnos/secrets.",
1317
+ description: "Creates a vault definition in .cnos/cnos.yml and, for local vaults, initializes the encrypted store under ~/.cnos/secrets. CNOS prompts for a passphrase when one is not already available from env or keychain.",
1005
1318
  examples: [
1006
1319
  "cnos vault create local-dev",
1007
1320
  "cnos vault create github-ci --provider github-secrets --no-passphrase"
@@ -1011,7 +1324,7 @@ var COMMANDS = [
1011
1324
  id: "vault auth",
1012
1325
  summary: "Authenticate a vault for the current shell session.",
1013
1326
  usage: "cnos vault auth <name> [--store-keychain] [global-options]",
1014
- description: "Authenticates a local vault using env, keychain, or prompt-based auth and stores a derived session key for later CNOS commands in the same shell.",
1327
+ description: "Authenticates an existing local vault using env, keychain, or prompt-based auth and stores a derived session key for later CNOS commands in the same shell. Wrong passphrases fail authentication.",
1015
1328
  examples: ["cnos vault auth local-dev", "cnos vault auth local-dev --store-keychain"]
1016
1329
  },
1017
1330
  {
@@ -1084,11 +1397,11 @@ var COMMANDS = [
1084
1397
  {
1085
1398
  id: "list",
1086
1399
  summary: "List resolved config entries.",
1087
- usage: "cnos list [value|secret|meta|env|public|all] [--prefix <path>] [--framework <name>] [global-options]",
1088
- description: "Lists stored config or derived projections across one namespace or the full effective graph, with optional prefix filtering.",
1400
+ usage: "cnos list [<namespace>|all] [--prefix <path>] [--framework <name>] [global-options]",
1401
+ description: "Lists stored config or derived projections across one namespace or the full effective graph, with optional prefix filtering. Custom data namespaces such as flags are supported, and process exposes server-only ambient runtime state.",
1089
1402
  options: [
1090
1403
  {
1091
- flag: "--namespace <value|secret|meta|env|public|all>",
1404
+ flag: "--namespace <name>",
1092
1405
  description: "Explicit namespace selector when not using a positional namespace argument."
1093
1406
  },
1094
1407
  {
@@ -1100,7 +1413,7 @@ var COMMANDS = [
1100
1413
  description: "When listing public output, apply framework-specific prefixes such as vite or next."
1101
1414
  }
1102
1415
  ],
1103
- examples: ["cnos list", "cnos list value --prefix app.", "cnos list env", "cnos list public --framework vite"]
1416
+ examples: ["cnos list", "cnos list value --prefix app.", "cnos list flags", "cnos list process --prefix env.PATH", "cnos list env", "cnos list public --framework vite"]
1104
1417
  },
1105
1418
  {
1106
1419
  id: "profile",
@@ -1110,11 +1423,16 @@ var COMMANDS = [
1110
1423
  options: [
1111
1424
  {
1112
1425
  flag: "--inherit <name>",
1113
- description: "Parent profile to extend when creating a profile."
1426
+ description: "Parent profile to extend when creating a profile. Base inheritance is implicit by default."
1427
+ },
1428
+ {
1429
+ flag: "--no-inherit",
1430
+ description: "Create a clean profile that does not inherit base fallback layers."
1114
1431
  }
1115
1432
  ],
1116
1433
  examples: [
1117
- "cnos profile create stage --inherit base",
1434
+ "cnos profile create stage",
1435
+ "cnos profile create isolated --no-inherit",
1118
1436
  "cnos profile list",
1119
1437
  "cnos profile use stage"
1120
1438
  ]
@@ -1122,9 +1440,9 @@ var COMMANDS = [
1122
1440
  {
1123
1441
  id: "profile create",
1124
1442
  summary: "Create a profile definition.",
1125
- usage: "cnos profile create <name> [--inherit <name>] [--root <path>] [--json]",
1126
- description: "Creates .cnos/profiles/<name>.yml for an explicit profile overlay.",
1127
- examples: ["cnos profile create stage --inherit base"]
1443
+ usage: "cnos profile create <name> [--inherit <name> | --no-inherit] [--root <path>] [--json]",
1444
+ description: "Creates .cnos/profiles/<name>.yml for an explicit profile overlay. New profiles inherit base by default unless --no-inherit is set.",
1445
+ examples: ["cnos profile create stage", "cnos profile create isolated --no-inherit"]
1128
1446
  },
1129
1447
  {
1130
1448
  id: "profile list",
@@ -1151,7 +1469,7 @@ var COMMANDS = [
1151
1469
  id: "promote",
1152
1470
  summary: "Promote shareable config into public or env projection surfaces.",
1153
1471
  usage: "cnos promote <key...> --to <public|env> [--as <ENV_VAR>] [global-options]",
1154
- description: "Adds keys to public.promote or envMapping.explicit in .cnos/cnos.yml. Sensitive or non-shareable namespaces are rejected.",
1472
+ description: "Adds keys to public.promote or envMapping.explicit in .cnos/cnos.yml. Sensitive or non-shareable namespaces are rejected, but declared shareable data namespaces such as flags are allowed.",
1155
1473
  options: [
1156
1474
  {
1157
1475
  flag: "--to <public|env>",
@@ -1164,6 +1482,7 @@ var COMMANDS = [
1164
1482
  ],
1165
1483
  examples: [
1166
1484
  "cnos promote value.flag.auth.upi_enabled --to public",
1485
+ "cnos promote flags.upi_enabled --to public",
1167
1486
  "cnos promote value.server.port --to env --as PORT"
1168
1487
  ]
1169
1488
  },
@@ -1240,6 +1559,52 @@ var COMMANDS = [
1240
1559
  "cnos export env --public --framework next --workspace webapp"
1241
1560
  ]
1242
1561
  },
1562
+ {
1563
+ id: "build",
1564
+ summary: "Build derived configuration artifacts from CNOS.",
1565
+ usage: "cnos build <subcommand> [options] [global-options]",
1566
+ description: "Builds deterministic derived outputs from the selected workspace. Currently supports env artifact generation.",
1567
+ arguments: [
1568
+ {
1569
+ name: "subcommand",
1570
+ description: "Supported value: env.",
1571
+ required: true
1572
+ }
1573
+ ],
1574
+ examples: [
1575
+ "cnos build env --profile local --to .env.local",
1576
+ "cnos build env --public --framework vite --profile prod --to .env.production"
1577
+ ]
1578
+ },
1579
+ {
1580
+ id: "build env",
1581
+ summary: "Build a flat env-file artifact from CNOS.",
1582
+ usage: "cnos build env --to <path> [--public] [--framework <name>] [--prefix <prefix>] [global-options]",
1583
+ description: "Builds a deterministic KEY=VALUE artifact for legacy build and runtime workflows. The target file is derived output, not the CNOS source of truth.",
1584
+ options: [
1585
+ {
1586
+ flag: "--to <path>",
1587
+ description: "Write the rendered KEY=VALUE output to a file. Required."
1588
+ },
1589
+ {
1590
+ flag: "--public",
1591
+ description: "Build only public values based on manifest promotion rules."
1592
+ },
1593
+ {
1594
+ flag: "--framework <name>",
1595
+ description: "Apply framework-specific public env conventions such as vite or next."
1596
+ },
1597
+ {
1598
+ flag: "--prefix <prefix>",
1599
+ description: "Override the generated public env prefix."
1600
+ }
1601
+ ],
1602
+ examples: [
1603
+ "cnos build env --profile local --to .env.local",
1604
+ "cnos build env --profile stage --to .env.stage",
1605
+ "cnos build env --public --framework vite --to .env.local"
1606
+ ]
1607
+ },
1243
1608
  {
1244
1609
  id: "export env",
1245
1610
  summary: "Render environment variables for the selected workspace.",
@@ -1269,6 +1634,60 @@ var COMMANDS = [
1269
1634
  "cnos export env --public --framework vite --to .env.local --workspace api"
1270
1635
  ]
1271
1636
  },
1637
+ {
1638
+ id: "dev",
1639
+ summary: "Run watched CNOS-driven development workflows.",
1640
+ usage: "cnos dev <subcommand> [options] [global-options] -- <command...>",
1641
+ description: "Runs higher-level development workflows that derive config artifacts from CNOS and keep them up to date while a child process is running.",
1642
+ arguments: [
1643
+ {
1644
+ name: "subcommand",
1645
+ description: "Supported value: env.",
1646
+ required: true
1647
+ }
1648
+ ],
1649
+ examples: [
1650
+ "cnos dev env --profile local --to .env.local -- pnpm dev",
1651
+ "cnos dev env --public --framework vite --to .env.local -- pnpm dev"
1652
+ ]
1653
+ },
1654
+ {
1655
+ id: "dev env",
1656
+ summary: "Watch CNOS config, rewrite an env file, and restart a child process.",
1657
+ usage: "cnos dev env --to <path> [--public] [--framework <name>] [--prefix <prefix>] [--debounce <ms>] [--signal] [global-options] -- <command...>",
1658
+ description: "Writes a derived env file before first launch, watches CNOS inputs, rewrites the file on change, and restarts the child process by default.",
1659
+ options: [
1660
+ {
1661
+ flag: "--to <path>",
1662
+ description: "Write the rendered KEY=VALUE output to a file. Required."
1663
+ },
1664
+ {
1665
+ flag: "--public",
1666
+ description: "Build only public values based on manifest promotion rules."
1667
+ },
1668
+ {
1669
+ flag: "--framework <name>",
1670
+ description: "Apply framework-specific public env conventions such as vite or next."
1671
+ },
1672
+ {
1673
+ flag: "--prefix <prefix>",
1674
+ description: "Override the generated public env prefix."
1675
+ },
1676
+ {
1677
+ flag: "--debounce <ms>",
1678
+ description: "Debounce config changes before rebuilding the env artifact. Defaults to 300ms."
1679
+ },
1680
+ {
1681
+ flag: "--signal",
1682
+ description: "Rewrite the env artifact and emit changed keys as JSON instead of restarting the child process."
1683
+ }
1684
+ ],
1685
+ examples: [
1686
+ "cnos dev env --profile local --to .env.local -- pnpm dev",
1687
+ "cnos dev env --profile stage --to .env.stage -- node server.js",
1688
+ "cnos dev env --public --framework vite --to .env.local --signal -- pnpm dev"
1689
+ ]
1690
+ },
1272
1691
  {
1273
1692
  id: "dump",
1274
1693
  summary: "Materialize the selected workspace into files.",
@@ -1473,6 +1892,8 @@ var HELP_DOCUMENT = {
1473
1892
  examples: [
1474
1893
  "cnos use --profile stage",
1475
1894
  "cnos doctor --workspace api",
1895
+ "cnos build env --profile stage --to .env.stage",
1896
+ "cnos dev env --profile local --to .env.local -- pnpm dev",
1476
1897
  "cnos export env --public --framework vite",
1477
1898
  "cnos export env --public --framework next",
1478
1899
  "cnos help-ai --format json"
@@ -1586,11 +2007,11 @@ function runHelpAi(topic, cliArgs = []) {
1586
2007
  }
1587
2008
 
1588
2009
  // src/commands/init.ts
1589
- import path5 from "path";
2010
+ import path7 from "path";
1590
2011
 
1591
2012
  // src/services/scaffold.ts
1592
2013
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
1593
- import path4 from "path";
2014
+ import path6 from "path";
1594
2015
  function scaffoldManifest(projectName, workspace) {
1595
2016
  const lines = [
1596
2017
  "version: 1",
@@ -1629,7 +2050,7 @@ async function ensureFile(filePath, content) {
1629
2050
  }
1630
2051
  }
1631
2052
  async function ensureGitignore(root) {
1632
- const gitignorePath = path4.join(root, ".gitignore");
2053
+ const gitignorePath = path6.join(root, ".gitignore");
1633
2054
  const requiredEntries = [
1634
2055
  ".cnos/env/.env",
1635
2056
  ".cnos/env/.env.*",
@@ -1657,13 +2078,13 @@ async function ensureGitignore(root) {
1657
2078
  return true;
1658
2079
  }
1659
2080
  async function scaffoldWorkspace(root, workspace) {
1660
- const cnosRoot = path4.join(root, ".cnos");
1661
- const workspaceRoot = workspace ? path4.join(cnosRoot, "workspaces", workspace) : cnosRoot;
2081
+ const cnosRoot = path6.join(root, ".cnos");
2082
+ const workspaceRoot = workspace ? path6.join(cnosRoot, "workspaces", workspace) : cnosRoot;
1662
2083
  const createdPaths = [];
1663
- await mkdir3(path4.join(workspaceRoot, "profiles"), { recursive: true });
1664
- await mkdir3(path4.join(workspaceRoot, "values"), { recursive: true });
1665
- await mkdir3(path4.join(workspaceRoot, "secrets"), { recursive: true });
1666
- await mkdir3(path4.join(workspaceRoot, "env"), { recursive: true });
2084
+ await mkdir3(path6.join(workspaceRoot, "profiles"), { recursive: true });
2085
+ await mkdir3(path6.join(workspaceRoot, "values"), { recursive: true });
2086
+ await mkdir3(path6.join(workspaceRoot, "secrets"), { recursive: true });
2087
+ await mkdir3(path6.join(workspaceRoot, "env"), { recursive: true });
1667
2088
  const relativePaths = workspace ? [
1668
2089
  ["workspaces", workspace, "profiles", ".gitkeep"],
1669
2090
  ["workspaces", workspace, "values", ".gitkeep"],
@@ -1676,15 +2097,15 @@ async function scaffoldWorkspace(root, workspace) {
1676
2097
  ["env", ".gitkeep"]
1677
2098
  ];
1678
2099
  for (const relativePath of relativePaths) {
1679
- const filePath = path4.join(cnosRoot, ...relativePath);
2100
+ const filePath = path6.join(cnosRoot, ...relativePath);
1680
2101
  if (await ensureFile(filePath, "")) {
1681
- createdPaths.push(path4.relative(root, filePath).replace(/\\/g, "/"));
2102
+ createdPaths.push(path6.relative(root, filePath).replace(/\\/g, "/"));
1682
2103
  }
1683
2104
  }
1684
- if (await ensureFile(path4.join(cnosRoot, "cnos.yml"), scaffoldManifest(path4.basename(root), workspace))) {
2105
+ if (await ensureFile(path6.join(cnosRoot, "cnos.yml"), scaffoldManifest(path6.basename(root), workspace))) {
1685
2106
  createdPaths.push(".cnos/cnos.yml");
1686
2107
  }
1687
- if (workspace && await ensureFile(path4.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
2108
+ if (workspace && await ensureFile(path6.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
1688
2109
  globalRoot: ~/.cnos
1689
2110
  `)) {
1690
2111
  createdPaths.push(".cnos-workspace.yml");
@@ -1701,7 +2122,7 @@ globalRoot: ~/.cnos
1701
2122
 
1702
2123
  // src/commands/init.ts
1703
2124
  async function runInit(options = {}) {
1704
- const root = path5.resolve(options.root ?? process.cwd());
2125
+ const root = path7.resolve(options.root ?? process.cwd());
1705
2126
  const result = await scaffoldWorkspace(root, options.workspace);
1706
2127
  if (options.json) {
1707
2128
  return printJson(result);
@@ -1793,7 +2214,7 @@ function matchesPrefix(key, prefix) {
1793
2214
  return key.startsWith(prefix) || key.split(".").slice(1).join(".").startsWith(prefix);
1794
2215
  }
1795
2216
  function toStoredEntry(namespace, entry, filter = {}) {
1796
- const sourceId = namespace === "value" ? "filesystem-values" : "filesystem-secrets";
2217
+ const sourceId = namespace === "secret" ? "filesystem-secrets" : "filesystem-values";
1797
2218
  const candidates = [entry.winner, ...entry.overridden].filter((candidate) => candidate.sourceId === sourceId);
1798
2219
  if (candidates.length === 0) {
1799
2220
  return void 0;
@@ -1814,16 +2235,16 @@ function listStoredNamespace(namespace, options) {
1814
2235
  }
1815
2236
  function listProjectedNamespace(namespace, options) {
1816
2237
  return createRuntimeService(options).then((runtime) => {
1817
- const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : runtime.toPublicEnv({
2238
+ const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : namespace === "public" ? runtime.toPublicEnv({
1818
2239
  ...options.framework ? {
1819
2240
  framework: options.framework
1820
2241
  } : {}
1821
- });
2242
+ }) : flattenObject2(runtime.toNamespace(namespace));
1822
2243
  const entries = namespace === "env" ? Object.entries(projected).map(([envVar, value]) => ({
1823
2244
  key: envVar,
1824
2245
  value
1825
2246
  })) : Object.entries(projected).map(([key, value]) => ({
1826
- key: namespace === "meta" ? `meta.${key}` : key,
2247
+ key: namespace === "meta" || namespace === "process" ? `${namespace}.${key}` : key,
1827
2248
  value
1828
2249
  }));
1829
2250
  return entries.filter((entry) => entry.value !== void 0).filter((entry) => matchesPrefix(entry.key, options.prefix)).sort((left, right) => left.key.localeCompare(right.key));
@@ -1833,15 +2254,21 @@ async function listConfigEntries(namespace, options = {}) {
1833
2254
  if (namespace === "value" || namespace === "secret") {
1834
2255
  return listStoredNamespace(namespace, options);
1835
2256
  }
1836
- if (namespace === "meta" || namespace === "env" || namespace === "public") {
2257
+ if (namespace === "meta" || namespace === "env" || namespace === "public" || namespace === "process") {
1837
2258
  return listProjectedNamespace(namespace, options);
1838
2259
  }
1839
- const [values, secrets, meta] = await Promise.all([
1840
- listStoredNamespace("value", options),
1841
- listStoredNamespace("secret", options),
1842
- listProjectedNamespace("meta", options)
1843
- ]);
1844
- return [...values, ...secrets, ...meta].sort((left, right) => left.key.localeCompare(right.key));
2260
+ if (namespace !== "all") {
2261
+ return listStoredNamespace(namespace, options);
2262
+ }
2263
+ const runtime = await createRuntimeService(options);
2264
+ const namespaces = Array.from(
2265
+ new Set(
2266
+ Array.from(runtime.graph.entries.values()).map((entry) => entry.namespace).filter((entry) => entry !== "meta" && entry !== "env" && entry !== "public")
2267
+ )
2268
+ ).sort((left, right) => left.localeCompare(right));
2269
+ const stored = await Promise.all(namespaces.map((entry) => listStoredNamespace(entry, options)));
2270
+ const meta = await listProjectedNamespace("meta", options);
2271
+ return [...stored.flat(), ...meta].sort((left, right) => left.key.localeCompare(right.key));
1845
2272
  }
1846
2273
 
1847
2274
  // src/commands/list.ts
@@ -1858,13 +2285,16 @@ function normalizeNamespace(value) {
1858
2285
  if (value === "meta") {
1859
2286
  return "meta";
1860
2287
  }
2288
+ if (value === "process") {
2289
+ return "process";
2290
+ }
1861
2291
  if (value === "env") {
1862
2292
  return "env";
1863
2293
  }
1864
2294
  if (value === "public") {
1865
2295
  return "public";
1866
2296
  }
1867
- throw new Error(`Unsupported list namespace: ${value}`);
2297
+ return value;
1868
2298
  }
1869
2299
  async function runList(args = [], options = {}) {
1870
2300
  const cliArgs = [...options.cliArgs ?? []];
@@ -1887,7 +2317,7 @@ async function runList(args = [], options = {}) {
1887
2317
  }
1888
2318
 
1889
2319
  // src/commands/migrate.ts
1890
- import path6 from "path";
2320
+ import path8 from "path";
1891
2321
  import {
1892
2322
  applyManifestMappings,
1893
2323
  loadManifest,
@@ -1905,7 +2335,7 @@ async function runMigrate(options = {}) {
1905
2335
  throw new Error(`Unknown migrate options: ${cliArgs.join(" ")}`);
1906
2336
  }
1907
2337
  const manifest = await loadManifest(options.root ? { root: options.root } : {});
1908
- const scanRoot = path6.resolve(manifest.repoRoot, scan ?? "src");
2338
+ const scanRoot = path8.resolve(manifest.repoRoot, scan ?? "src");
1909
2339
  const usages = await scanEnvUsage(scanRoot);
1910
2340
  const uniqueProposals = new Map(usages.map((usage) => [usage.envVar, proposeMapping(usage.envVar)]));
1911
2341
  const proposals = Array.from(uniqueProposals.values()).sort((left, right) => left.envVar.localeCompare(right.envVar));
@@ -1966,33 +2396,118 @@ async function runMigrate(options = {}) {
1966
2396
  return lines.join("\n");
1967
2397
  }
1968
2398
 
2399
+ // src/commands/namespace.ts
2400
+ import path9 from "path";
2401
+ function normalizeCommand2(args) {
2402
+ const [actionOrPath, ...tail] = args;
2403
+ if (!actionOrPath) {
2404
+ return {
2405
+ action: "list",
2406
+ tail: []
2407
+ };
2408
+ }
2409
+ if (["get", "set", "create", "add", "list", "delete", "remove"].includes(actionOrPath)) {
2410
+ return {
2411
+ action: actionOrPath === "remove" ? "delete" : actionOrPath === "create" || actionOrPath === "add" ? "set" : actionOrPath,
2412
+ tail
2413
+ };
2414
+ }
2415
+ return {
2416
+ action: "get",
2417
+ tail: args
2418
+ };
2419
+ }
2420
+ async function runNamespace(namespace, args = [], options = {}) {
2421
+ const { action, tail } = normalizeCommand2(args);
2422
+ const cliArgs = [...options.cliArgs ?? []];
2423
+ const root = path9.resolve(options.root ?? process.cwd());
2424
+ if (action === "list") {
2425
+ const prefix = consumeOption(cliArgs, "--prefix");
2426
+ const entries = await listConfigEntries(namespace, {
2427
+ ...options,
2428
+ cliArgs,
2429
+ ...prefix ? { prefix } : {}
2430
+ });
2431
+ if (options.json) {
2432
+ return printJson(entries);
2433
+ }
2434
+ return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
2435
+ }
2436
+ if (action === "set") {
2437
+ const configPath2 = tail[0] ?? "app.name";
2438
+ const rawValue = tail[1] ?? "";
2439
+ const target = consumeOption(cliArgs, "--target") ?? "local";
2440
+ const result = await defineValue(namespace, configPath2, rawValue, {
2441
+ ...options,
2442
+ cliArgs,
2443
+ target
2444
+ });
2445
+ if (options.json) {
2446
+ return printJson({
2447
+ namespace,
2448
+ path: configPath2,
2449
+ target,
2450
+ filePath: result.filePath,
2451
+ value: result.value
2452
+ });
2453
+ }
2454
+ return `set ${namespace}.${configPath2} in ${displayPath(result.filePath, root)}`;
2455
+ }
2456
+ if (action === "delete") {
2457
+ const configPath2 = tail[0] ?? "app.name";
2458
+ const target = consumeOption(cliArgs, "--target") ?? "local";
2459
+ const result = await deleteValue(namespace, configPath2, {
2460
+ ...options,
2461
+ cliArgs,
2462
+ target
2463
+ });
2464
+ if (options.json) {
2465
+ return printJson(result);
2466
+ }
2467
+ return result.deleted ? `deleted ${namespace}.${configPath2} from ${displayPath(result.filePath, root)}` : `no ${namespace}.${configPath2} found in ${displayPath(result.filePath, root)}`;
2468
+ }
2469
+ const runtime = await createRuntimeService(options);
2470
+ const configPath = tail[0] ?? "app.name";
2471
+ const value = runtime.read(`${namespace}.${configPath}`);
2472
+ if (value === void 0) {
2473
+ throw new Error(`Missing CNOS ${namespace} path: ${configPath}`);
2474
+ }
2475
+ if (options.json) {
2476
+ return printJson({
2477
+ key: `${namespace}.${configPath}`,
2478
+ value
2479
+ });
2480
+ }
2481
+ return printValue(value);
2482
+ }
2483
+
1969
2484
  // src/commands/onboard.ts
1970
2485
  import { copyFile, readdir as readdir2, rm } from "fs/promises";
1971
- import path7 from "path";
2486
+ import path10 from "path";
1972
2487
  var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
1973
2488
  async function listRootEnvFiles(root) {
1974
2489
  const entries = await readdir2(root, { withFileTypes: true });
1975
2490
  return entries.filter((entry) => entry.isFile() && ROOT_ENV_FILE_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
1976
2491
  }
1977
2492
  async function runOnboard(options = {}) {
1978
- const root = path7.resolve(options.root ?? process.cwd());
1979
- const workspace = options.workspace ?? path7.basename(root);
2493
+ const root = path10.resolve(options.root ?? process.cwd());
2494
+ const workspace = options.workspace ?? path10.basename(root);
1980
2495
  const cliArgs = [...options.cliArgs ?? []];
1981
2496
  const move = consumeFlag(cliArgs, "--move");
1982
2497
  if (cliArgs.length > 0) {
1983
2498
  throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
1984
2499
  }
1985
2500
  const scaffold = await scaffoldWorkspace(root, workspace);
1986
- const envRoot = path7.join(root, ".cnos", "workspaces", workspace, "env");
2501
+ const envRoot = path10.join(root, ".cnos", "workspaces", workspace, "env");
1987
2502
  const rootFiles = await listRootEnvFiles(root);
1988
2503
  const imported = [];
1989
2504
  const skipped = [];
1990
2505
  for (const fileName of rootFiles) {
1991
- const sourcePath = path7.join(root, fileName);
1992
- const targetPath = path7.join(envRoot, fileName);
2506
+ const sourcePath = path10.join(root, fileName);
2507
+ const targetPath = path10.join(envRoot, fileName);
1993
2508
  try {
1994
2509
  await copyFile(sourcePath, targetPath);
1995
- imported.push(path7.relative(root, targetPath).replace(/\\/g, "/"));
2510
+ imported.push(path10.relative(root, targetPath).replace(/\\/g, "/"));
1996
2511
  if (move) {
1997
2512
  await rm(sourcePath);
1998
2513
  }
@@ -2017,14 +2532,14 @@ async function runOnboard(options = {}) {
2017
2532
  }
2018
2533
 
2019
2534
  // src/commands/profile.ts
2020
- import path10 from "path";
2535
+ import path13 from "path";
2021
2536
 
2022
2537
  // src/services/context.ts
2023
2538
  import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2024
- import path8 from "path";
2539
+ import path11 from "path";
2025
2540
  import { parseYaml as parseYaml3, stringifyYaml as stringifyYaml2 } from "@kitsy/cnos/internal";
2026
2541
  async function loadCliContext(root = process.cwd()) {
2027
- const filePath = path8.join(path8.resolve(root), ".cnos-workspace.yml");
2542
+ const filePath = path11.join(path11.resolve(root), ".cnos-workspace.yml");
2028
2543
  try {
2029
2544
  const source = await readFile4(filePath, "utf8");
2030
2545
  const parsed = parseYaml3(source);
@@ -2037,8 +2552,8 @@ async function loadCliContext(root = process.cwd()) {
2037
2552
  }
2038
2553
  }
2039
2554
  async function saveCliContext(options = {}) {
2040
- const root = path8.resolve(options.root ?? process.cwd());
2041
- const filePath = path8.join(root, ".cnos-workspace.yml");
2555
+ const root = path11.resolve(options.root ?? process.cwd());
2556
+ const filePath = path11.join(root, ".cnos-workspace.yml");
2042
2557
  const current = await loadCliContext(root);
2043
2558
  const next = {
2044
2559
  ...current.workspace ? { workspace: current.workspace } : {},
@@ -2057,12 +2572,19 @@ async function saveCliContext(options = {}) {
2057
2572
 
2058
2573
  // src/services/profiles.ts
2059
2574
  import { mkdir as mkdir4, readdir as readdir3, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
2060
- import path9 from "path";
2575
+ import path12 from "path";
2061
2576
  import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
2062
- async function createProfileDefinition(root = process.cwd(), profile, inherit) {
2063
- const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2064
- await mkdir4(path9.dirname(filePath), { recursive: true });
2065
- const document = inherit && inherit !== "base" ? {
2577
+ async function createProfileDefinition(root = process.cwd(), profile, inherit, options = {}) {
2578
+ const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2579
+ await mkdir4(path12.dirname(filePath), { recursive: true });
2580
+ const document = options.noInherit ? {
2581
+ name: profile,
2582
+ activate: {
2583
+ values: [`profiles/${profile}/values`, `values/${profile}`],
2584
+ secrets: [`profiles/${profile}/secrets`, `secrets/${profile}`],
2585
+ envFiles: [`.env.${profile}`]
2586
+ }
2587
+ } : inherit && inherit !== "base" ? {
2066
2588
  name: profile,
2067
2589
  extends: [inherit]
2068
2590
  } : {
@@ -2072,11 +2594,12 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit) {
2072
2594
  return {
2073
2595
  filePath,
2074
2596
  profile,
2075
- ...inherit ? { inherit } : {}
2597
+ ...inherit ? { inherit } : {},
2598
+ ...options.noInherit ? { noInherit: true } : {}
2076
2599
  };
2077
2600
  }
2078
2601
  async function listProfiles(root = process.cwd()) {
2079
- const profilesRoot = path9.join(path9.resolve(root), ".cnos", "profiles");
2602
+ const profilesRoot = path12.join(path12.resolve(root), ".cnos", "profiles");
2080
2603
  try {
2081
2604
  const entries = await readdir3(profilesRoot, { withFileTypes: true });
2082
2605
  const discovered = /* @__PURE__ */ new Set(["base"]);
@@ -2091,7 +2614,7 @@ async function listProfiles(root = process.cwd()) {
2091
2614
  }
2092
2615
  }
2093
2616
  async function deleteProfileDefinition(root = process.cwd(), profile) {
2094
- const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2617
+ const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2095
2618
  try {
2096
2619
  await rm2(filePath);
2097
2620
  return {
@@ -2111,7 +2634,7 @@ async function readProfileDefinition(root = process.cwd(), profile = "base") {
2111
2634
  name: "base"
2112
2635
  };
2113
2636
  }
2114
- const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2637
+ const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2115
2638
  try {
2116
2639
  return parseYaml4(await readFile5(filePath, "utf8")) ?? void 0;
2117
2640
  } catch {
@@ -2135,16 +2658,23 @@ function normalizeProfileAction(args) {
2135
2658
  }
2136
2659
  async function runProfile(args, options = {}) {
2137
2660
  const { action, tail } = normalizeProfileAction(args);
2138
- const root = path10.resolve(options.root ?? process.cwd());
2661
+ const root = path13.resolve(options.root ?? process.cwd());
2139
2662
  const cliArgs = [...options.cliArgs ?? []];
2140
2663
  if (action === "create") {
2141
2664
  const profile = tail[0] ?? "stage";
2142
2665
  const inherit = consumeOption(cliArgs, "--inherit");
2143
- const result = await createProfileDefinition(root, profile, inherit);
2666
+ const noInherit = consumeFlag(cliArgs, "--no-inherit");
2667
+ if (inherit && noInherit) {
2668
+ throw new Error("profile create accepts either --inherit <name> or --no-inherit, not both");
2669
+ }
2670
+ const result = await createProfileDefinition(root, profile, inherit, { noInherit });
2144
2671
  if (options.json) {
2145
2672
  return printJson(result);
2146
2673
  }
2147
- return `created profile ${profile} at ${result.filePath}`;
2674
+ if (noInherit) {
2675
+ return `created profile ${profile} at ${displayPath(result.filePath, root)} without inheriting base`;
2676
+ }
2677
+ return `created profile ${profile} at ${displayPath(result.filePath, root)}; inherits values from base by default`;
2148
2678
  }
2149
2679
  if (action === "use") {
2150
2680
  const profile = tail[0] ?? "base";
@@ -2155,7 +2685,7 @@ async function runProfile(args, options = {}) {
2155
2685
  if (options.json) {
2156
2686
  return printJson(result);
2157
2687
  }
2158
- return `active profile set to ${profile} in ${result.filePath}`;
2688
+ return `active profile set to ${profile} in ${displayPath(result.filePath, root)}`;
2159
2689
  }
2160
2690
  if (action === "delete") {
2161
2691
  const profile = tail[0] ?? "base";
@@ -2179,6 +2709,7 @@ async function runProfile(args, options = {}) {
2179
2709
  }
2180
2710
 
2181
2711
  // src/commands/promote.ts
2712
+ import path14 from "path";
2182
2713
  import { writeFile as writeFile6 } from "fs/promises";
2183
2714
  import {
2184
2715
  ensureProjectionAllowed,
@@ -2195,6 +2726,7 @@ function sortRecord(record) {
2195
2726
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
2196
2727
  }
2197
2728
  async function runPromote(args = [], options = {}) {
2729
+ const root = path14.resolve(options.root ?? process.cwd());
2198
2730
  const cliArgs = [...options.cliArgs ?? []];
2199
2731
  const target = normalizeTarget(consumeOption(cliArgs, "--to"));
2200
2732
  const alias = consumeOption(cliArgs, "--as");
@@ -2242,7 +2774,7 @@ async function runPromote(args = [], options = {}) {
2242
2774
  manifestPath: loadedManifest.manifestPath
2243
2775
  });
2244
2776
  }
2245
- return target === "public" ? `promoted ${keys.join(", ")} to public in ${loadedManifest.manifestPath}` : `promoted ${keys[0]} to env as ${alias} in ${loadedManifest.manifestPath}`;
2777
+ return target === "public" ? `promoted ${keys.join(", ")} to public in ${displayPath(loadedManifest.manifestPath, root)}` : `promoted ${keys[0]} to env as ${alias} in ${displayPath(loadedManifest.manifestPath, root)}`;
2246
2778
  }
2247
2779
 
2248
2780
  // src/commands/read.ts
@@ -2253,7 +2785,9 @@ async function runRead(key, options = {}) {
2253
2785
  throw new Error(`Missing CNOS config key: ${key}`);
2254
2786
  }
2255
2787
  const isSecret = key.startsWith("secret.");
2256
- const valueForOutput = isSecret ? maskSecretValue(value) : value;
2788
+ const cliArgs = [...options.cliArgs ?? []];
2789
+ const reveal = consumeFlag(cliArgs, "--reveal");
2790
+ const valueForOutput = isSecret && !reveal ? maskSecretValue(value) : value;
2257
2791
  if (options.json) {
2258
2792
  return printJson({ key, value: valueForOutput });
2259
2793
  }
@@ -2261,7 +2795,6 @@ async function runRead(key, options = {}) {
2261
2795
  }
2262
2796
 
2263
2797
  // src/commands/run.ts
2264
- import { spawn } from "child_process";
2265
2798
  import {
2266
2799
  CNOS_GRAPH_ENV_VAR,
2267
2800
  CNOS_SECRET_PAYLOAD_ENV_VAR,
@@ -2335,11 +2868,10 @@ async function runCommand(command, options = {}) {
2335
2868
  reject(new Error("run requires a command after --"));
2336
2869
  return;
2337
2870
  }
2338
- const child = spawn(executable, command.slice(1), {
2871
+ const child = spawnCommand(command, {
2339
2872
  cwd: options.root ?? process.cwd(),
2340
2873
  env,
2341
- stdio: options.stdio === "pipe" ? "pipe" : "inherit",
2342
- shell: false
2874
+ stdio: options.stdio === "pipe" ? "pipe" : "inherit"
2343
2875
  });
2344
2876
  let stdout = "";
2345
2877
  let stderr = "";
@@ -2362,14 +2894,21 @@ async function runCommand(command, options = {}) {
2362
2894
  });
2363
2895
  }
2364
2896
 
2897
+ // src/commands/secret.ts
2898
+ import path17 from "path";
2899
+
2900
+ // src/commands/vault.ts
2901
+ import path16 from "path";
2902
+
2365
2903
  // src/services/vaults.ts
2366
2904
  import { rm as rm3, writeFile as writeFile7 } from "fs/promises";
2367
- import path11 from "path";
2905
+ import path15 from "path";
2368
2906
  import {
2369
2907
  clearAllVaultSessionKeys,
2370
2908
  clearVaultSessionKey,
2371
2909
  createSecretVault,
2372
2910
  deriveVaultKey,
2911
+ listLocalSecrets,
2373
2912
  loadManifest as loadManifest3,
2374
2913
  listSecretVaults,
2375
2914
  readVaultMetadata,
@@ -2380,6 +2919,21 @@ import {
2380
2919
  writeKeychain,
2381
2920
  writeVaultSessionKey
2382
2921
  } from "@kitsy/cnos/internal";
2922
+ function buildVaultDefinition(vault, provider) {
2923
+ return provider === "local" ? {
2924
+ provider: "local",
2925
+ auth: {
2926
+ passphrase: {
2927
+ from: defaultLocalAuthSources(vault)
2928
+ }
2929
+ }
2930
+ } : {
2931
+ provider,
2932
+ auth: {
2933
+ method: "environment"
2934
+ }
2935
+ };
2936
+ }
2383
2937
  function sortVaults(vaults) {
2384
2938
  return Object.fromEntries(Object.entries(vaults).sort(([left], [right]) => left.localeCompare(right)));
2385
2939
  }
@@ -2394,27 +2948,27 @@ async function createVaultDefinition(name, options = {}) {
2394
2948
  throw new Error("Local vaults cannot be passwordless.");
2395
2949
  }
2396
2950
  const loadedManifest = await loadManifest3(options.root ? { root: options.root } : {});
2951
+ const vaultDefinition = buildVaultDefinition(vault, provider);
2397
2952
  const rawManifest = {
2398
2953
  ...loadedManifest.rawManifest,
2399
2954
  vaults: {
2400
2955
  ...loadedManifest.rawManifest.vaults ?? {},
2401
- [vault]: provider === "local" ? {
2402
- provider: "local",
2403
- auth: {
2404
- passphrase: {
2405
- from: defaultLocalAuthSources(vault)
2406
- }
2407
- }
2408
- } : {
2409
- provider,
2410
- auth: {
2411
- method: "environment"
2412
- }
2413
- }
2956
+ [vault]: vaultDefinition
2414
2957
  }
2415
2958
  };
2416
2959
  await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
2417
- const definition = resolveVaultDefinition({ [vault]: rawManifest.vaults[vault] }, vault);
2960
+ const definition = resolveVaultDefinition({ [vault]: vaultDefinition }, vault);
2961
+ if (provider === "local") {
2962
+ const auth = await resolveVaultAuth2(vault, vaultDefinition, options.processEnv ?? process.env);
2963
+ if (!auth.passphrase) {
2964
+ throw new Error(`Vault "${vault}" requires passphrase-based authentication during creation.`);
2965
+ }
2966
+ const storeRoot = resolveSecretStoreRoot2(options.processEnv);
2967
+ const existing = await readVaultMetadata(storeRoot, vault);
2968
+ if (!existing) {
2969
+ await createSecretVault(storeRoot, vault, auth.passphrase);
2970
+ }
2971
+ }
2418
2972
  return {
2419
2973
  ...definition,
2420
2974
  authMethod: definition.auth?.method ?? (provider === "local" ? "passphrase" : "environment"),
@@ -2454,7 +3008,7 @@ async function removeVaultDefinition(name, options = {}) {
2454
3008
  delete rawManifest.vaults;
2455
3009
  }
2456
3010
  await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
2457
- const vaultRoot = path11.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
3011
+ const vaultRoot = path15.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
2458
3012
  let removedStore;
2459
3013
  try {
2460
3014
  await rm3(vaultRoot, { recursive: true, force: true });
@@ -2486,15 +3040,22 @@ async function authenticateVault(name, options = {}) {
2486
3040
  if (!auth.passphrase) {
2487
3041
  throw new Error(`Vault "${vault}" requires passphrase-based authentication.`);
2488
3042
  }
2489
- const existing = await readVaultMetadata(storeRoot, vault);
2490
- if (!existing) {
2491
- await createSecretVault(storeRoot, vault, auth.passphrase);
2492
- }
2493
3043
  const metadata = await readVaultMetadata(storeRoot, vault);
2494
3044
  if (!metadata) {
2495
- throw new Error(`Failed to initialize vault "${vault}"`);
3045
+ throw new Error(
3046
+ `Vault "${vault}" has not been initialized yet. Run cnos vault create ${vault} first.`
3047
+ );
2496
3048
  }
2497
3049
  const derivedKey = deriveVaultKey(auth.passphrase, Buffer.from(metadata.salt, "base64"), metadata.iterations);
3050
+ await listLocalSecrets(
3051
+ storeRoot,
3052
+ {
3053
+ derivedKey,
3054
+ method: auth.method,
3055
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
3056
+ },
3057
+ vault
3058
+ );
2498
3059
  const sessionPath2 = await writeVaultSessionKey(vault, derivedKey, options.processEnv);
2499
3060
  if (options.storeKeychain) {
2500
3061
  await writeKeychain(`cnos/${vault}`, derivedKey.toString("hex"));
@@ -2541,6 +3102,7 @@ function normalizeVaultAction(args) {
2541
3102
  async function runVault(args = [], options = {}) {
2542
3103
  const { action, tail } = normalizeVaultAction(args);
2543
3104
  const cliArgs = [...options.cliArgs ?? []];
3105
+ const root = path16.resolve(options.root ?? process.cwd());
2544
3106
  if (consumeOption(cliArgs, "--passphrase")) {
2545
3107
  throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
2546
3108
  }
@@ -2557,7 +3119,7 @@ async function runVault(args = [], options = {}) {
2557
3119
  if (options.json) {
2558
3120
  return printJson(result);
2559
3121
  }
2560
- return `created vault "${result.name}" with provider "${result.provider}" in ${result.manifestPath}`;
3122
+ return `created vault "${result.name}" with provider "${result.provider}" in ${displayPath(result.manifestPath, root)}`;
2561
3123
  }
2562
3124
  if (action === "auth") {
2563
3125
  const result = await authenticateVault(tail[0] ?? "default", {
@@ -2646,6 +3208,7 @@ async function runSecret(argsOrPath, options = {}) {
2646
3208
  const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
2647
3209
  const { action, tail } = normalizeSecretCommand(args);
2648
3210
  const cliArgs = [...options.cliArgs ?? []];
3211
+ const root = path17.resolve(options.root ?? process.cwd());
2649
3212
  if (consumeOption(cliArgs, "--passphrase")) {
2650
3213
  throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
2651
3214
  }
@@ -2701,7 +3264,7 @@ async function runSecret(argsOrPath, options = {}) {
2701
3264
  if (options.json) {
2702
3265
  return printJson(result);
2703
3266
  }
2704
- return result.provider === "local" ? `set secret.${secretPath2} in vault "${result.vault ?? "default"}" with ref "${result.ref}" and repo pointer ${result.filePath}` : `set secret.${secretPath2} via ${result.provider} in ${result.filePath}`;
3267
+ return result.provider === "local" ? `set secret.${secretPath2} in vault "${result.vault ?? "default"}" with ref "${result.ref}" and repo pointer ${displayPath(result.filePath, root)}` : `set secret.${secretPath2} via ${result.provider} in ${displayPath(result.filePath, root)}`;
2705
3268
  }
2706
3269
  if (action === "delete") {
2707
3270
  const secretPath2 = tail[0];
@@ -2714,7 +3277,7 @@ async function runSecret(argsOrPath, options = {}) {
2714
3277
  if (options.json) {
2715
3278
  return printJson(result);
2716
3279
  }
2717
- return result.deleted ? `deleted secret.${secretPath2} from ${result.filePath}` : `no secret.${secretPath2} found in ${result.filePath}`;
3280
+ return result.deleted ? `deleted secret.${secretPath2} from ${displayPath(result.filePath, root)}` : `no secret.${secretPath2} found in ${displayPath(result.filePath, root)}`;
2718
3281
  }
2719
3282
  const runtime = await createRuntimeService(options);
2720
3283
  const secretPath = tail[0] ?? "app.token";
@@ -2741,12 +3304,12 @@ async function runSecret(argsOrPath, options = {}) {
2741
3304
  }
2742
3305
 
2743
3306
  // src/commands/use.ts
2744
- import path12 from "path";
3307
+ import path18 from "path";
2745
3308
  async function runUse(args = [], options = {}) {
2746
- const root = path12.resolve(options.root ?? process.cwd());
2747
- const action = args[0] ?? "show";
3309
+ const root = path18.resolve(options.root ?? process.cwd());
3310
+ const action = args[0];
2748
3311
  const hasUpdates = Boolean(options.workspace || options.profile || options.globalRoot);
2749
- if (action === "show" || !hasUpdates) {
3312
+ if (action === "show" || !action && !hasUpdates) {
2750
3313
  const context = await loadCliContext(root);
2751
3314
  if (options.json) {
2752
3315
  return printJson(context);
@@ -2762,7 +3325,7 @@ async function runUse(args = [], options = {}) {
2762
3325
  if (options.json) {
2763
3326
  return printJson(result);
2764
3327
  }
2765
- return `updated CLI context in ${result.filePath}`;
3328
+ return `updated CLI context in ${displayPath(result.filePath, root)}`;
2766
3329
  }
2767
3330
 
2768
3331
  // src/commands/validate.ts
@@ -2780,7 +3343,7 @@ async function runValidate(options = {}) {
2780
3343
  // package.json
2781
3344
  var package_default = {
2782
3345
  name: "@kitsy/cnos-cli",
2783
- version: "1.3.0",
3346
+ version: "1.5.0",
2784
3347
  description: "CLI entry point and developer tooling for CNOS.",
2785
3348
  type: "module",
2786
3349
  main: "./dist/index.js",
@@ -2835,6 +3398,7 @@ function runVersion() {
2835
3398
  }
2836
3399
 
2837
3400
  // src/commands/value.ts
3401
+ import path19 from "path";
2838
3402
  function normalizeValueCommand(args) {
2839
3403
  const [actionOrPath, ...tail] = args;
2840
3404
  if (!actionOrPath) {
@@ -2858,6 +3422,7 @@ async function runValue(argsOrPath, options = {}) {
2858
3422
  const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
2859
3423
  const { action, tail } = normalizeValueCommand(args);
2860
3424
  const cliArgs = [...options.cliArgs ?? []];
3425
+ const root = path19.resolve(options.root ?? process.cwd());
2861
3426
  if (action === "list") {
2862
3427
  const prefix = consumeOption(cliArgs, "--prefix");
2863
3428
  const entries = await listConfigEntries("value", {
@@ -2888,7 +3453,7 @@ async function runValue(argsOrPath, options = {}) {
2888
3453
  value: result.value
2889
3454
  });
2890
3455
  }
2891
- return `set value.${valuePath} in ${result.filePath}`;
3456
+ return `set value.${valuePath} in ${displayPath(result.filePath, root)}`;
2892
3457
  }
2893
3458
  if (action === "delete") {
2894
3459
  const valuePath = tail[0] ?? "app.name";
@@ -2901,7 +3466,7 @@ async function runValue(argsOrPath, options = {}) {
2901
3466
  if (options.json) {
2902
3467
  return printJson(result);
2903
3468
  }
2904
- return result.deleted ? `deleted value.${valuePath} from ${result.filePath}` : `no value.${valuePath} found in ${result.filePath}`;
3469
+ return result.deleted ? `deleted value.${valuePath} from ${displayPath(result.filePath, root)}` : `no value.${valuePath} found in ${displayPath(result.filePath, root)}`;
2905
3470
  }
2906
3471
  const runtime = await createRuntimeService(options);
2907
3472
  const value = runtime.value(tail[0] ?? "app.name");
@@ -2918,16 +3483,12 @@ async function runValue(argsOrPath, options = {}) {
2918
3483
  }
2919
3484
 
2920
3485
  // src/commands/watch.ts
2921
- import { watch } from "fs";
2922
- import { spawn as spawn2 } from "child_process";
2923
3486
  import {
2924
3487
  CNOS_GRAPH_ENV_VAR as CNOS_GRAPH_ENV_VAR2,
2925
3488
  CNOS_SECRET_PAYLOAD_ENV_VAR as CNOS_SECRET_PAYLOAD_ENV_VAR2,
2926
3489
  CNOS_SESSION_KEY_ENV_VAR as CNOS_SESSION_KEY_ENV_VAR2,
2927
- diffGraphs,
2928
3490
  serializeRuntimeGraph as serializeRuntimeGraph2,
2929
- serializeSecretPayload as serializeSecretPayload2,
2930
- watchFiles
3491
+ serializeSecretPayload as serializeSecretPayload2
2931
3492
  } from "@kitsy/cnos/internal";
2932
3493
  async function buildRunEnvironment(options) {
2933
3494
  const cliArgs = [...options.cliArgs ?? []];
@@ -2964,11 +3525,10 @@ function spawnWatchedChild(command, cwd, env) {
2964
3525
  if (!executable) {
2965
3526
  throw new Error("watch requires a command after -- unless --signal is used");
2966
3527
  }
2967
- return spawn2(executable, command.slice(1), {
3528
+ return spawnCommand(command, {
2968
3529
  cwd,
2969
3530
  env,
2970
- stdio: "inherit",
2971
- shell: false
3531
+ stdio: "inherit"
2972
3532
  });
2973
3533
  }
2974
3534
  async function startWatchLoop(options) {
@@ -2982,85 +3542,39 @@ async function startWatchLoop(options) {
2982
3542
  cliArgs
2983
3543
  });
2984
3544
  let child = !isSignal ? spawnWatchedChild(command, root, current.env) : void 0;
2985
- const watcherMap = /* @__PURE__ */ new Map();
2986
- let timer;
2987
3545
  let closed = false;
2988
- const attachWatcher = (targetPath, recursive = false) => {
2989
- if (watcherMap.has(targetPath)) {
2990
- return;
2991
- }
2992
- try {
2993
- const watcher = watch(
2994
- targetPath,
2995
- recursive ? {
2996
- recursive: true
2997
- } : void 0,
2998
- () => {
2999
- if (timer) {
3000
- clearTimeout(timer);
3001
- }
3002
- timer = setTimeout(() => {
3003
- void handleChange();
3004
- }, debounceMs);
3005
- }
3006
- );
3007
- watcherMap.set(targetPath, watcher);
3008
- } catch {
3009
- if (recursive) {
3010
- attachWatcher(targetPath, false);
3546
+ const watcher = await startGraphWatchLoop({
3547
+ ...options,
3548
+ cliArgs,
3549
+ debounceMs,
3550
+ async onChange(payload) {
3551
+ if (closed) {
3552
+ return;
3011
3553
  }
3012
- }
3013
- };
3014
- const refreshWatchers = async () => {
3015
- const nextTargets = await watchFiles(current.runtime, options.root);
3016
- attachWatcher(nextTargets.manifestPath, false);
3017
- for (const workspaceRoot of nextTargets.roots) {
3018
- attachWatcher(workspaceRoot, true);
3019
- }
3020
- for (const filePath of nextTargets.files) {
3021
- attachWatcher(filePath, false);
3022
- }
3023
- };
3024
- const handleChange = async () => {
3025
- if (closed) {
3026
- return;
3027
- }
3028
- const next = await buildRunEnvironment({
3029
- ...options,
3030
- cliArgs
3031
- });
3032
- const changedKeys = diffGraphs(current.runtime.graph, next.runtime.graph);
3033
- current = next;
3034
- await refreshWatchers();
3035
- if (changedKeys.length === 0) {
3036
- return;
3037
- }
3038
- if (isSignal) {
3039
- await options.onSignal?.({ changedKeys });
3040
- process.stdout.write(`${printJson({ changedKeys })}
3041
- `);
3042
- return;
3043
- }
3044
- if (child && !child.killed) {
3045
- await new Promise((resolve) => {
3046
- child?.once("close", () => resolve());
3047
- child?.kill();
3554
+ current = await buildRunEnvironment({
3555
+ ...options,
3556
+ cliArgs
3048
3557
  });
3558
+ if (isSignal) {
3559
+ await options.onSignal?.({ changedKeys: payload.changedKeys });
3560
+ process.stdout.write(`${printJson({ changedKeys: payload.changedKeys })}
3561
+ `);
3562
+ return;
3563
+ }
3564
+ if (child && !child.killed) {
3565
+ await new Promise((resolve) => {
3566
+ child?.once("close", () => resolve());
3567
+ child?.kill();
3568
+ });
3569
+ }
3570
+ child = spawnWatchedChild(command, root, current.env);
3571
+ await options.onRestart?.({ changedKeys: payload.changedKeys });
3049
3572
  }
3050
- child = spawnWatchedChild(command, root, current.env);
3051
- await options.onRestart?.({ changedKeys });
3052
- };
3053
- await refreshWatchers();
3573
+ });
3054
3574
  return {
3055
3575
  async close() {
3056
3576
  closed = true;
3057
- if (timer) {
3058
- clearTimeout(timer);
3059
- }
3060
- for (const watcher of watcherMap.values()) {
3061
- watcher.close();
3062
- }
3063
- watcherMap.clear();
3577
+ await watcher.close();
3064
3578
  if (child && !child.killed) {
3065
3579
  await new Promise((resolve) => {
3066
3580
  child?.once("close", () => resolve());
@@ -3099,6 +3613,12 @@ function resolveHelpTopic(command, args) {
3099
3613
  if (command === "export" && args[0] === "env") {
3100
3614
  return normalizeHelpTopic([command, args[0]]);
3101
3615
  }
3616
+ if (command === "build" && args[0] === "env") {
3617
+ return normalizeHelpTopic([command, args[0]]);
3618
+ }
3619
+ if (command === "dev" && args[0] === "env") {
3620
+ return normalizeHelpTopic([command, args[0]]);
3621
+ }
3102
3622
  if (command === "vault" && args[0] && ["create", "add", "list", "delete", "remove"].includes(args[0])) {
3103
3623
  return normalizeHelpTopic([command, args[0] === "delete" ? "remove" : args[0] === "add" ? "create" : args[0]]);
3104
3624
  }
@@ -3229,6 +3749,14 @@ async function main(argv) {
3229
3749
  return;
3230
3750
  case "export":
3231
3751
  process.stdout.write(`${await runExport(args[0], runtimeOptions)}
3752
+ `);
3753
+ return;
3754
+ case "build":
3755
+ process.stdout.write(`${await runBuild(args[0], runtimeOptions)}
3756
+ `);
3757
+ return;
3758
+ case "dev":
3759
+ process.stdout.write(`${await runDev(args[0], passthrough.length > 0 ? passthrough : args.slice(1), runtimeOptions)}
3232
3760
  `);
3233
3761
  return;
3234
3762
  case "dump":
@@ -3260,6 +3788,11 @@ async function main(argv) {
3260
3788
  `);
3261
3789
  return;
3262
3790
  default:
3791
+ if (args.length > 0 && /^[a-z][a-z0-9-]*$/i.test(command)) {
3792
+ process.stdout.write(`${await runNamespace(command, args, runtimeOptions)}
3793
+ `);
3794
+ return;
3795
+ }
3263
3796
  process.stderr.write(`Unknown command: ${command}
3264
3797
  `);
3265
3798
  process.exitCode = 1;