@kitsy/cnos-cli 1.3.0 → 1.4.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 +332 -112
  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
  }
@@ -192,6 +203,9 @@ function parseArgs(argv) {
192
203
  };
193
204
  }
194
205
 
206
+ // src/commands/define.ts
207
+ import path3 from "path";
208
+
195
209
  // src/cli/commandOptions.ts
196
210
  function consumeFlag(args, flag) {
197
211
  const index = args.indexOf(flag);
@@ -219,6 +233,21 @@ function consumeOption(args, flag) {
219
233
  return void 0;
220
234
  }
221
235
 
236
+ // src/format/displayPath.ts
237
+ import path from "path";
238
+ function displayPath(filePath, root = process.cwd()) {
239
+ const absoluteRoot = path.resolve(root);
240
+ const absoluteFile = path.resolve(filePath);
241
+ const relative = path.relative(absoluteRoot, absoluteFile);
242
+ if (!relative || relative === "") {
243
+ return ".";
244
+ }
245
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
246
+ return absoluteFile;
247
+ }
248
+ return relative;
249
+ }
250
+
222
251
  // src/format/printJson.ts
223
252
  function printJson(value) {
224
253
  return JSON.stringify(value, null, 2);
@@ -226,8 +255,9 @@ function printJson(value) {
226
255
 
227
256
  // src/services/writes.ts
228
257
  import { mkdir, readFile, writeFile } from "fs/promises";
229
- import path from "path";
258
+ import path2 from "path";
230
259
  import {
260
+ getNamespaceDefinition,
231
261
  createSecretVaultProvider,
232
262
  parseYaml,
233
263
  resolveConfigDocumentPath,
@@ -330,13 +360,26 @@ async function defineValue(namespace, configPath, rawValue, options = {}) {
330
360
  };
331
361
  }
332
362
  const runtime = await createRuntimeService(options);
363
+ if (namespace !== "value" && namespace !== "secret" && !runtime.manifest.namespaces[namespace]) {
364
+ throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is not declared in .cnos/cnos.yml.`);
365
+ }
366
+ const namespaceDefinition = getNamespaceDefinition(runtime.manifest, namespace);
367
+ if (namespaceDefinition.kind !== "data") {
368
+ throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is not a data namespace.`);
369
+ }
370
+ if (namespaceDefinition.readonly) {
371
+ throw new Error(`Cannot write ${namespace}.${configPath} because namespace "${namespace}" is readonly.`);
372
+ }
373
+ if (namespaceDefinition.sensitive) {
374
+ throw new Error(`Cannot write ${namespace}.${configPath} with the generic data writer because namespace "${namespace}" is sensitive.`);
375
+ }
333
376
  const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
334
377
  const profile = options.profile ?? runtime.graph.profile;
335
378
  const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
336
379
  const document = await readYamlDocument(filePath);
337
380
  const parsedValue = parseScalarValue(rawValue);
338
381
  setNestedValue(document, configPath.split("."), parsedValue);
339
- await mkdir(path.dirname(filePath), { recursive: true });
382
+ await mkdir(path2.dirname(filePath), { recursive: true });
340
383
  await writeFile(filePath, stringifyYaml(document), "utf8");
341
384
  return {
342
385
  filePath,
@@ -374,7 +417,7 @@ async function setSecret(configPath, rawValue, options = {}) {
374
417
  };
375
418
  }
376
419
  setNestedValue(document, configPath.split("."), reference);
377
- await mkdir(path.dirname(filePath), { recursive: true });
420
+ await mkdir(path2.dirname(filePath), { recursive: true });
378
421
  await writeFile(filePath, stringifyYaml(document), "utf8");
379
422
  return {
380
423
  filePath,
@@ -418,6 +461,19 @@ async function deleteValue(namespace, configPath, options = {}) {
418
461
  return deleteSecret(configPath, options);
419
462
  }
420
463
  const runtime = await createRuntimeService(options);
464
+ if (namespace !== "value" && namespace !== "secret" && !runtime.manifest.namespaces[namespace]) {
465
+ throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is not declared in .cnos/cnos.yml.`);
466
+ }
467
+ const namespaceDefinition = getNamespaceDefinition(runtime.manifest, namespace);
468
+ if (namespaceDefinition.kind !== "data") {
469
+ throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is not a data namespace.`);
470
+ }
471
+ if (namespaceDefinition.readonly) {
472
+ throw new Error(`Cannot delete ${namespace}.${configPath} because namespace "${namespace}" is readonly.`);
473
+ }
474
+ if (namespaceDefinition.sensitive) {
475
+ throw new Error(`Cannot delete ${namespace}.${configPath} with the generic data writer because namespace "${namespace}" is sensitive.`);
476
+ }
421
477
  const workspaceRoot = getSelectedWorkspaceRoot(options, runtime);
422
478
  const profile = options.profile ?? runtime.graph.profile;
423
479
  const filePath = resolveConfigDocumentPath(workspaceRoot, namespace, configPath, profile);
@@ -439,6 +495,7 @@ async function deleteValue(namespace, configPath, options = {}) {
439
495
  // src/commands/define.ts
440
496
  async function runDefine(namespace, configPath, rawValue, options = {}) {
441
497
  const cliArgs = [...options.cliArgs ?? []];
498
+ const root = path3.resolve(options.root ?? process.cwd());
442
499
  const target = consumeOption(cliArgs, "--target") ?? "local";
443
500
  const local = consumeFlag(cliArgs, "--local");
444
501
  const remote = consumeFlag(cliArgs, "--remote");
@@ -464,7 +521,7 @@ async function runDefine(namespace, configPath, rawValue, options = {}) {
464
521
  value: result.value
465
522
  });
466
523
  }
467
- return `defined ${namespace}.${configPath} in ${result.filePath}`;
524
+ return `defined ${namespace}.${configPath} in ${displayPath(result.filePath, root)}`;
468
525
  }
469
526
 
470
527
  // src/commands/drift.ts
@@ -517,7 +574,7 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
517
574
 
518
575
  // src/services/doctor.ts
519
576
  import { readdir, readFile as readFile2 } from "fs/promises";
520
- import path2 from "path";
577
+ import path4 from "path";
521
578
  import {
522
579
  detectLegacyVaultFormat,
523
580
  isSecretReference as isSecretReference2,
@@ -539,7 +596,7 @@ async function createValidationSummary(options = {}) {
539
596
 
540
597
  // src/services/doctor.ts
541
598
  async function checkGitignore(root) {
542
- const gitignorePath = path2.join(root, ".gitignore");
599
+ const gitignorePath = path4.join(root, ".gitignore");
543
600
  const expected = [
544
601
  ".cnos/env/.env",
545
602
  ".cnos/env/.env.*",
@@ -574,12 +631,12 @@ async function collectYamlFiles(root) {
574
631
  const entries = await readdir(root, { withFileTypes: true });
575
632
  const results = [];
576
633
  for (const entry of entries) {
577
- const target = path2.join(root, entry.name);
634
+ const target = path4.join(root, entry.name);
578
635
  if (entry.isDirectory()) {
579
636
  results.push(...await collectYamlFiles(target));
580
637
  continue;
581
638
  }
582
- if (entry.isFile() && [".yml", ".yaml"].includes(path2.extname(entry.name).toLowerCase())) {
639
+ if (entry.isFile() && [".yml", ".yaml"].includes(path4.extname(entry.name).toLowerCase())) {
583
640
  results.push(target);
584
641
  }
585
642
  }
@@ -604,7 +661,7 @@ async function checkSecretSecurity(options, runtime) {
604
661
  );
605
662
  const legacyDetected = legacyPaths.filter((entry) => Boolean(entry.path));
606
663
  const secretFiles = await Promise.all(
607
- runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(path2.join(root.path, "secrets")))
664
+ runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => collectYamlFiles(path4.join(root.path, "secrets")))
608
665
  );
609
666
  const plaintextFiles = [];
610
667
  for (const file of secretFiles.flat()) {
@@ -634,10 +691,11 @@ async function checkSecretSecurity(options, runtime) {
634
691
  };
635
692
  }
636
693
  async function evaluateDoctor(options = {}) {
637
- const root = path2.resolve(options.root ?? process.cwd());
694
+ const root = path4.resolve(options.root ?? process.cwd());
638
695
  const { runtime, summary } = await createValidationSummary(options);
639
696
  const localRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "local");
640
697
  const globalRoot = runtime.graph.workspace.workspaceRoots.find((entry) => entry.scope === "global");
698
+ 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
699
  return [
642
700
  {
643
701
  name: "manifest",
@@ -649,6 +707,11 @@ async function evaluateDoctor(options = {}) {
649
707
  ok: true,
650
708
  details: `${runtime.graph.workspace.workspaceId} via ${runtime.graph.workspace.workspaceSource}`
651
709
  },
710
+ {
711
+ name: "namespaces",
712
+ ok: true,
713
+ details: declaredCustomNamespaces.length === 0 ? "built-ins: value, secret, meta, process, public, env" : `built-ins: value, secret, meta, process, public, env | custom: ${declaredCustomNamespaces.join(", ")}`
714
+ },
652
715
  {
653
716
  name: "source-roots",
654
717
  ok: Boolean(localRoot),
@@ -749,7 +812,7 @@ async function runCodegen(options = {}) {
749
812
 
750
813
  // src/commands/exportEnv.ts
751
814
  import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
752
- import path3 from "path";
815
+ import path5 from "path";
753
816
  function formatEnvOutput(env) {
754
817
  return Object.entries(env).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`).join("\n");
755
818
  }
@@ -769,8 +832,8 @@ async function runExportEnv(options = {}) {
769
832
  }) : runtime.toEnv();
770
833
  const output = formatEnvOutput(env);
771
834
  if (to) {
772
- const targetPath = path3.resolve(options.root ?? process.cwd(), to);
773
- await mkdir2(path3.dirname(targetPath), { recursive: true });
835
+ const targetPath = path5.resolve(options.root ?? process.cwd(), to);
836
+ await mkdir2(path5.dirname(targetPath), { recursive: true });
774
837
  await writeFile2(targetPath, output, "utf8");
775
838
  if (options.json) {
776
839
  return printJson({
@@ -1001,7 +1064,7 @@ var COMMANDS = [
1001
1064
  id: "vault create",
1002
1065
  summary: "Create a manifest-defined vault.",
1003
1066
  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.",
1067
+ 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
1068
  examples: [
1006
1069
  "cnos vault create local-dev",
1007
1070
  "cnos vault create github-ci --provider github-secrets --no-passphrase"
@@ -1011,7 +1074,7 @@ var COMMANDS = [
1011
1074
  id: "vault auth",
1012
1075
  summary: "Authenticate a vault for the current shell session.",
1013
1076
  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.",
1077
+ 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
1078
  examples: ["cnos vault auth local-dev", "cnos vault auth local-dev --store-keychain"]
1016
1079
  },
1017
1080
  {
@@ -1084,11 +1147,11 @@ var COMMANDS = [
1084
1147
  {
1085
1148
  id: "list",
1086
1149
  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.",
1150
+ usage: "cnos list [<namespace>|all] [--prefix <path>] [--framework <name>] [global-options]",
1151
+ 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
1152
  options: [
1090
1153
  {
1091
- flag: "--namespace <value|secret|meta|env|public|all>",
1154
+ flag: "--namespace <name>",
1092
1155
  description: "Explicit namespace selector when not using a positional namespace argument."
1093
1156
  },
1094
1157
  {
@@ -1100,7 +1163,7 @@ var COMMANDS = [
1100
1163
  description: "When listing public output, apply framework-specific prefixes such as vite or next."
1101
1164
  }
1102
1165
  ],
1103
- examples: ["cnos list", "cnos list value --prefix app.", "cnos list env", "cnos list public --framework vite"]
1166
+ 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
1167
  },
1105
1168
  {
1106
1169
  id: "profile",
@@ -1110,11 +1173,16 @@ var COMMANDS = [
1110
1173
  options: [
1111
1174
  {
1112
1175
  flag: "--inherit <name>",
1113
- description: "Parent profile to extend when creating a profile."
1176
+ description: "Parent profile to extend when creating a profile. Base inheritance is implicit by default."
1177
+ },
1178
+ {
1179
+ flag: "--no-inherit",
1180
+ description: "Create a clean profile that does not inherit base fallback layers."
1114
1181
  }
1115
1182
  ],
1116
1183
  examples: [
1117
- "cnos profile create stage --inherit base",
1184
+ "cnos profile create stage",
1185
+ "cnos profile create isolated --no-inherit",
1118
1186
  "cnos profile list",
1119
1187
  "cnos profile use stage"
1120
1188
  ]
@@ -1122,9 +1190,9 @@ var COMMANDS = [
1122
1190
  {
1123
1191
  id: "profile create",
1124
1192
  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"]
1193
+ usage: "cnos profile create <name> [--inherit <name> | --no-inherit] [--root <path>] [--json]",
1194
+ description: "Creates .cnos/profiles/<name>.yml for an explicit profile overlay. New profiles inherit base by default unless --no-inherit is set.",
1195
+ examples: ["cnos profile create stage", "cnos profile create isolated --no-inherit"]
1128
1196
  },
1129
1197
  {
1130
1198
  id: "profile list",
@@ -1151,7 +1219,7 @@ var COMMANDS = [
1151
1219
  id: "promote",
1152
1220
  summary: "Promote shareable config into public or env projection surfaces.",
1153
1221
  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.",
1222
+ 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
1223
  options: [
1156
1224
  {
1157
1225
  flag: "--to <public|env>",
@@ -1164,6 +1232,7 @@ var COMMANDS = [
1164
1232
  ],
1165
1233
  examples: [
1166
1234
  "cnos promote value.flag.auth.upi_enabled --to public",
1235
+ "cnos promote flags.upi_enabled --to public",
1167
1236
  "cnos promote value.server.port --to env --as PORT"
1168
1237
  ]
1169
1238
  },
@@ -1586,11 +1655,11 @@ function runHelpAi(topic, cliArgs = []) {
1586
1655
  }
1587
1656
 
1588
1657
  // src/commands/init.ts
1589
- import path5 from "path";
1658
+ import path7 from "path";
1590
1659
 
1591
1660
  // src/services/scaffold.ts
1592
1661
  import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
1593
- import path4 from "path";
1662
+ import path6 from "path";
1594
1663
  function scaffoldManifest(projectName, workspace) {
1595
1664
  const lines = [
1596
1665
  "version: 1",
@@ -1629,7 +1698,7 @@ async function ensureFile(filePath, content) {
1629
1698
  }
1630
1699
  }
1631
1700
  async function ensureGitignore(root) {
1632
- const gitignorePath = path4.join(root, ".gitignore");
1701
+ const gitignorePath = path6.join(root, ".gitignore");
1633
1702
  const requiredEntries = [
1634
1703
  ".cnos/env/.env",
1635
1704
  ".cnos/env/.env.*",
@@ -1657,13 +1726,13 @@ async function ensureGitignore(root) {
1657
1726
  return true;
1658
1727
  }
1659
1728
  async function scaffoldWorkspace(root, workspace) {
1660
- const cnosRoot = path4.join(root, ".cnos");
1661
- const workspaceRoot = workspace ? path4.join(cnosRoot, "workspaces", workspace) : cnosRoot;
1729
+ const cnosRoot = path6.join(root, ".cnos");
1730
+ const workspaceRoot = workspace ? path6.join(cnosRoot, "workspaces", workspace) : cnosRoot;
1662
1731
  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 });
1732
+ await mkdir3(path6.join(workspaceRoot, "profiles"), { recursive: true });
1733
+ await mkdir3(path6.join(workspaceRoot, "values"), { recursive: true });
1734
+ await mkdir3(path6.join(workspaceRoot, "secrets"), { recursive: true });
1735
+ await mkdir3(path6.join(workspaceRoot, "env"), { recursive: true });
1667
1736
  const relativePaths = workspace ? [
1668
1737
  ["workspaces", workspace, "profiles", ".gitkeep"],
1669
1738
  ["workspaces", workspace, "values", ".gitkeep"],
@@ -1676,15 +1745,15 @@ async function scaffoldWorkspace(root, workspace) {
1676
1745
  ["env", ".gitkeep"]
1677
1746
  ];
1678
1747
  for (const relativePath of relativePaths) {
1679
- const filePath = path4.join(cnosRoot, ...relativePath);
1748
+ const filePath = path6.join(cnosRoot, ...relativePath);
1680
1749
  if (await ensureFile(filePath, "")) {
1681
- createdPaths.push(path4.relative(root, filePath).replace(/\\/g, "/"));
1750
+ createdPaths.push(path6.relative(root, filePath).replace(/\\/g, "/"));
1682
1751
  }
1683
1752
  }
1684
- if (await ensureFile(path4.join(cnosRoot, "cnos.yml"), scaffoldManifest(path4.basename(root), workspace))) {
1753
+ if (await ensureFile(path6.join(cnosRoot, "cnos.yml"), scaffoldManifest(path6.basename(root), workspace))) {
1685
1754
  createdPaths.push(".cnos/cnos.yml");
1686
1755
  }
1687
- if (workspace && await ensureFile(path4.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
1756
+ if (workspace && await ensureFile(path6.join(root, ".cnos-workspace.yml"), `workspace: ${workspace}
1688
1757
  globalRoot: ~/.cnos
1689
1758
  `)) {
1690
1759
  createdPaths.push(".cnos-workspace.yml");
@@ -1701,7 +1770,7 @@ globalRoot: ~/.cnos
1701
1770
 
1702
1771
  // src/commands/init.ts
1703
1772
  async function runInit(options = {}) {
1704
- const root = path5.resolve(options.root ?? process.cwd());
1773
+ const root = path7.resolve(options.root ?? process.cwd());
1705
1774
  const result = await scaffoldWorkspace(root, options.workspace);
1706
1775
  if (options.json) {
1707
1776
  return printJson(result);
@@ -1793,7 +1862,7 @@ function matchesPrefix(key, prefix) {
1793
1862
  return key.startsWith(prefix) || key.split(".").slice(1).join(".").startsWith(prefix);
1794
1863
  }
1795
1864
  function toStoredEntry(namespace, entry, filter = {}) {
1796
- const sourceId = namespace === "value" ? "filesystem-values" : "filesystem-secrets";
1865
+ const sourceId = namespace === "secret" ? "filesystem-secrets" : "filesystem-values";
1797
1866
  const candidates = [entry.winner, ...entry.overridden].filter((candidate) => candidate.sourceId === sourceId);
1798
1867
  if (candidates.length === 0) {
1799
1868
  return void 0;
@@ -1814,16 +1883,16 @@ function listStoredNamespace(namespace, options) {
1814
1883
  }
1815
1884
  function listProjectedNamespace(namespace, options) {
1816
1885
  return createRuntimeService(options).then((runtime) => {
1817
- const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : runtime.toPublicEnv({
1886
+ const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : namespace === "public" ? runtime.toPublicEnv({
1818
1887
  ...options.framework ? {
1819
1888
  framework: options.framework
1820
1889
  } : {}
1821
- });
1890
+ }) : flattenObject2(runtime.toNamespace(namespace));
1822
1891
  const entries = namespace === "env" ? Object.entries(projected).map(([envVar, value]) => ({
1823
1892
  key: envVar,
1824
1893
  value
1825
1894
  })) : Object.entries(projected).map(([key, value]) => ({
1826
- key: namespace === "meta" ? `meta.${key}` : key,
1895
+ key: namespace === "meta" || namespace === "process" ? `${namespace}.${key}` : key,
1827
1896
  value
1828
1897
  }));
1829
1898
  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 +1902,21 @@ async function listConfigEntries(namespace, options = {}) {
1833
1902
  if (namespace === "value" || namespace === "secret") {
1834
1903
  return listStoredNamespace(namespace, options);
1835
1904
  }
1836
- if (namespace === "meta" || namespace === "env" || namespace === "public") {
1905
+ if (namespace === "meta" || namespace === "env" || namespace === "public" || namespace === "process") {
1837
1906
  return listProjectedNamespace(namespace, options);
1838
1907
  }
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));
1908
+ if (namespace !== "all") {
1909
+ return listStoredNamespace(namespace, options);
1910
+ }
1911
+ const runtime = await createRuntimeService(options);
1912
+ const namespaces = Array.from(
1913
+ new Set(
1914
+ Array.from(runtime.graph.entries.values()).map((entry) => entry.namespace).filter((entry) => entry !== "meta" && entry !== "env" && entry !== "public")
1915
+ )
1916
+ ).sort((left, right) => left.localeCompare(right));
1917
+ const stored = await Promise.all(namespaces.map((entry) => listStoredNamespace(entry, options)));
1918
+ const meta = await listProjectedNamespace("meta", options);
1919
+ return [...stored.flat(), ...meta].sort((left, right) => left.key.localeCompare(right.key));
1845
1920
  }
1846
1921
 
1847
1922
  // src/commands/list.ts
@@ -1858,13 +1933,16 @@ function normalizeNamespace(value) {
1858
1933
  if (value === "meta") {
1859
1934
  return "meta";
1860
1935
  }
1936
+ if (value === "process") {
1937
+ return "process";
1938
+ }
1861
1939
  if (value === "env") {
1862
1940
  return "env";
1863
1941
  }
1864
1942
  if (value === "public") {
1865
1943
  return "public";
1866
1944
  }
1867
- throw new Error(`Unsupported list namespace: ${value}`);
1945
+ return value;
1868
1946
  }
1869
1947
  async function runList(args = [], options = {}) {
1870
1948
  const cliArgs = [...options.cliArgs ?? []];
@@ -1887,7 +1965,7 @@ async function runList(args = [], options = {}) {
1887
1965
  }
1888
1966
 
1889
1967
  // src/commands/migrate.ts
1890
- import path6 from "path";
1968
+ import path8 from "path";
1891
1969
  import {
1892
1970
  applyManifestMappings,
1893
1971
  loadManifest,
@@ -1905,7 +1983,7 @@ async function runMigrate(options = {}) {
1905
1983
  throw new Error(`Unknown migrate options: ${cliArgs.join(" ")}`);
1906
1984
  }
1907
1985
  const manifest = await loadManifest(options.root ? { root: options.root } : {});
1908
- const scanRoot = path6.resolve(manifest.repoRoot, scan ?? "src");
1986
+ const scanRoot = path8.resolve(manifest.repoRoot, scan ?? "src");
1909
1987
  const usages = await scanEnvUsage(scanRoot);
1910
1988
  const uniqueProposals = new Map(usages.map((usage) => [usage.envVar, proposeMapping(usage.envVar)]));
1911
1989
  const proposals = Array.from(uniqueProposals.values()).sort((left, right) => left.envVar.localeCompare(right.envVar));
@@ -1966,33 +2044,118 @@ async function runMigrate(options = {}) {
1966
2044
  return lines.join("\n");
1967
2045
  }
1968
2046
 
2047
+ // src/commands/namespace.ts
2048
+ import path9 from "path";
2049
+ function normalizeCommand2(args) {
2050
+ const [actionOrPath, ...tail] = args;
2051
+ if (!actionOrPath) {
2052
+ return {
2053
+ action: "list",
2054
+ tail: []
2055
+ };
2056
+ }
2057
+ if (["get", "set", "create", "add", "list", "delete", "remove"].includes(actionOrPath)) {
2058
+ return {
2059
+ action: actionOrPath === "remove" ? "delete" : actionOrPath === "create" || actionOrPath === "add" ? "set" : actionOrPath,
2060
+ tail
2061
+ };
2062
+ }
2063
+ return {
2064
+ action: "get",
2065
+ tail: args
2066
+ };
2067
+ }
2068
+ async function runNamespace(namespace, args = [], options = {}) {
2069
+ const { action, tail } = normalizeCommand2(args);
2070
+ const cliArgs = [...options.cliArgs ?? []];
2071
+ const root = path9.resolve(options.root ?? process.cwd());
2072
+ if (action === "list") {
2073
+ const prefix = consumeOption(cliArgs, "--prefix");
2074
+ const entries = await listConfigEntries(namespace, {
2075
+ ...options,
2076
+ cliArgs,
2077
+ ...prefix ? { prefix } : {}
2078
+ });
2079
+ if (options.json) {
2080
+ return printJson(entries);
2081
+ }
2082
+ return entries.map((entry) => `${entry.key}=${printValue(entry.value)}`).join("\n");
2083
+ }
2084
+ if (action === "set") {
2085
+ const configPath2 = tail[0] ?? "app.name";
2086
+ const rawValue = tail[1] ?? "";
2087
+ const target = consumeOption(cliArgs, "--target") ?? "local";
2088
+ const result = await defineValue(namespace, configPath2, rawValue, {
2089
+ ...options,
2090
+ cliArgs,
2091
+ target
2092
+ });
2093
+ if (options.json) {
2094
+ return printJson({
2095
+ namespace,
2096
+ path: configPath2,
2097
+ target,
2098
+ filePath: result.filePath,
2099
+ value: result.value
2100
+ });
2101
+ }
2102
+ return `set ${namespace}.${configPath2} in ${displayPath(result.filePath, root)}`;
2103
+ }
2104
+ if (action === "delete") {
2105
+ const configPath2 = tail[0] ?? "app.name";
2106
+ const target = consumeOption(cliArgs, "--target") ?? "local";
2107
+ const result = await deleteValue(namespace, configPath2, {
2108
+ ...options,
2109
+ cliArgs,
2110
+ target
2111
+ });
2112
+ if (options.json) {
2113
+ return printJson(result);
2114
+ }
2115
+ return result.deleted ? `deleted ${namespace}.${configPath2} from ${displayPath(result.filePath, root)}` : `no ${namespace}.${configPath2} found in ${displayPath(result.filePath, root)}`;
2116
+ }
2117
+ const runtime = await createRuntimeService(options);
2118
+ const configPath = tail[0] ?? "app.name";
2119
+ const value = runtime.read(`${namespace}.${configPath}`);
2120
+ if (value === void 0) {
2121
+ throw new Error(`Missing CNOS ${namespace} path: ${configPath}`);
2122
+ }
2123
+ if (options.json) {
2124
+ return printJson({
2125
+ key: `${namespace}.${configPath}`,
2126
+ value
2127
+ });
2128
+ }
2129
+ return printValue(value);
2130
+ }
2131
+
1969
2132
  // src/commands/onboard.ts
1970
2133
  import { copyFile, readdir as readdir2, rm } from "fs/promises";
1971
- import path7 from "path";
2134
+ import path10 from "path";
1972
2135
  var ROOT_ENV_FILE_PATTERN = /^\.env(?:\.[A-Za-z0-9_-]+)*(?:\.example)?$/;
1973
2136
  async function listRootEnvFiles(root) {
1974
2137
  const entries = await readdir2(root, { withFileTypes: true });
1975
2138
  return entries.filter((entry) => entry.isFile() && ROOT_ENV_FILE_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
1976
2139
  }
1977
2140
  async function runOnboard(options = {}) {
1978
- const root = path7.resolve(options.root ?? process.cwd());
1979
- const workspace = options.workspace ?? path7.basename(root);
2141
+ const root = path10.resolve(options.root ?? process.cwd());
2142
+ const workspace = options.workspace ?? path10.basename(root);
1980
2143
  const cliArgs = [...options.cliArgs ?? []];
1981
2144
  const move = consumeFlag(cliArgs, "--move");
1982
2145
  if (cliArgs.length > 0) {
1983
2146
  throw new Error(`Unsupported onboard arguments: ${cliArgs.join(" ")}`);
1984
2147
  }
1985
2148
  const scaffold = await scaffoldWorkspace(root, workspace);
1986
- const envRoot = path7.join(root, ".cnos", "workspaces", workspace, "env");
2149
+ const envRoot = path10.join(root, ".cnos", "workspaces", workspace, "env");
1987
2150
  const rootFiles = await listRootEnvFiles(root);
1988
2151
  const imported = [];
1989
2152
  const skipped = [];
1990
2153
  for (const fileName of rootFiles) {
1991
- const sourcePath = path7.join(root, fileName);
1992
- const targetPath = path7.join(envRoot, fileName);
2154
+ const sourcePath = path10.join(root, fileName);
2155
+ const targetPath = path10.join(envRoot, fileName);
1993
2156
  try {
1994
2157
  await copyFile(sourcePath, targetPath);
1995
- imported.push(path7.relative(root, targetPath).replace(/\\/g, "/"));
2158
+ imported.push(path10.relative(root, targetPath).replace(/\\/g, "/"));
1996
2159
  if (move) {
1997
2160
  await rm(sourcePath);
1998
2161
  }
@@ -2017,14 +2180,14 @@ async function runOnboard(options = {}) {
2017
2180
  }
2018
2181
 
2019
2182
  // src/commands/profile.ts
2020
- import path10 from "path";
2183
+ import path13 from "path";
2021
2184
 
2022
2185
  // src/services/context.ts
2023
2186
  import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2024
- import path8 from "path";
2187
+ import path11 from "path";
2025
2188
  import { parseYaml as parseYaml3, stringifyYaml as stringifyYaml2 } from "@kitsy/cnos/internal";
2026
2189
  async function loadCliContext(root = process.cwd()) {
2027
- const filePath = path8.join(path8.resolve(root), ".cnos-workspace.yml");
2190
+ const filePath = path11.join(path11.resolve(root), ".cnos-workspace.yml");
2028
2191
  try {
2029
2192
  const source = await readFile4(filePath, "utf8");
2030
2193
  const parsed = parseYaml3(source);
@@ -2037,8 +2200,8 @@ async function loadCliContext(root = process.cwd()) {
2037
2200
  }
2038
2201
  }
2039
2202
  async function saveCliContext(options = {}) {
2040
- const root = path8.resolve(options.root ?? process.cwd());
2041
- const filePath = path8.join(root, ".cnos-workspace.yml");
2203
+ const root = path11.resolve(options.root ?? process.cwd());
2204
+ const filePath = path11.join(root, ".cnos-workspace.yml");
2042
2205
  const current = await loadCliContext(root);
2043
2206
  const next = {
2044
2207
  ...current.workspace ? { workspace: current.workspace } : {},
@@ -2057,12 +2220,19 @@ async function saveCliContext(options = {}) {
2057
2220
 
2058
2221
  // src/services/profiles.ts
2059
2222
  import { mkdir as mkdir4, readdir as readdir3, readFile as readFile5, rm as rm2, writeFile as writeFile5 } from "fs/promises";
2060
- import path9 from "path";
2223
+ import path12 from "path";
2061
2224
  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" ? {
2225
+ async function createProfileDefinition(root = process.cwd(), profile, inherit, options = {}) {
2226
+ const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2227
+ await mkdir4(path12.dirname(filePath), { recursive: true });
2228
+ const document = options.noInherit ? {
2229
+ name: profile,
2230
+ activate: {
2231
+ values: [`profiles/${profile}/values`, `values/${profile}`],
2232
+ secrets: [`profiles/${profile}/secrets`, `secrets/${profile}`],
2233
+ envFiles: [`.env.${profile}`]
2234
+ }
2235
+ } : inherit && inherit !== "base" ? {
2066
2236
  name: profile,
2067
2237
  extends: [inherit]
2068
2238
  } : {
@@ -2072,11 +2242,12 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit) {
2072
2242
  return {
2073
2243
  filePath,
2074
2244
  profile,
2075
- ...inherit ? { inherit } : {}
2245
+ ...inherit ? { inherit } : {},
2246
+ ...options.noInherit ? { noInherit: true } : {}
2076
2247
  };
2077
2248
  }
2078
2249
  async function listProfiles(root = process.cwd()) {
2079
- const profilesRoot = path9.join(path9.resolve(root), ".cnos", "profiles");
2250
+ const profilesRoot = path12.join(path12.resolve(root), ".cnos", "profiles");
2080
2251
  try {
2081
2252
  const entries = await readdir3(profilesRoot, { withFileTypes: true });
2082
2253
  const discovered = /* @__PURE__ */ new Set(["base"]);
@@ -2091,7 +2262,7 @@ async function listProfiles(root = process.cwd()) {
2091
2262
  }
2092
2263
  }
2093
2264
  async function deleteProfileDefinition(root = process.cwd(), profile) {
2094
- const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2265
+ const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2095
2266
  try {
2096
2267
  await rm2(filePath);
2097
2268
  return {
@@ -2111,7 +2282,7 @@ async function readProfileDefinition(root = process.cwd(), profile = "base") {
2111
2282
  name: "base"
2112
2283
  };
2113
2284
  }
2114
- const filePath = path9.join(path9.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2285
+ const filePath = path12.join(path12.resolve(root), ".cnos", "profiles", `${profile}.yml`);
2115
2286
  try {
2116
2287
  return parseYaml4(await readFile5(filePath, "utf8")) ?? void 0;
2117
2288
  } catch {
@@ -2135,16 +2306,23 @@ function normalizeProfileAction(args) {
2135
2306
  }
2136
2307
  async function runProfile(args, options = {}) {
2137
2308
  const { action, tail } = normalizeProfileAction(args);
2138
- const root = path10.resolve(options.root ?? process.cwd());
2309
+ const root = path13.resolve(options.root ?? process.cwd());
2139
2310
  const cliArgs = [...options.cliArgs ?? []];
2140
2311
  if (action === "create") {
2141
2312
  const profile = tail[0] ?? "stage";
2142
2313
  const inherit = consumeOption(cliArgs, "--inherit");
2143
- const result = await createProfileDefinition(root, profile, inherit);
2314
+ const noInherit = consumeFlag(cliArgs, "--no-inherit");
2315
+ if (inherit && noInherit) {
2316
+ throw new Error("profile create accepts either --inherit <name> or --no-inherit, not both");
2317
+ }
2318
+ const result = await createProfileDefinition(root, profile, inherit, { noInherit });
2144
2319
  if (options.json) {
2145
2320
  return printJson(result);
2146
2321
  }
2147
- return `created profile ${profile} at ${result.filePath}`;
2322
+ if (noInherit) {
2323
+ return `created profile ${profile} at ${displayPath(result.filePath, root)} without inheriting base`;
2324
+ }
2325
+ return `created profile ${profile} at ${displayPath(result.filePath, root)}; inherits values from base by default`;
2148
2326
  }
2149
2327
  if (action === "use") {
2150
2328
  const profile = tail[0] ?? "base";
@@ -2155,7 +2333,7 @@ async function runProfile(args, options = {}) {
2155
2333
  if (options.json) {
2156
2334
  return printJson(result);
2157
2335
  }
2158
- return `active profile set to ${profile} in ${result.filePath}`;
2336
+ return `active profile set to ${profile} in ${displayPath(result.filePath, root)}`;
2159
2337
  }
2160
2338
  if (action === "delete") {
2161
2339
  const profile = tail[0] ?? "base";
@@ -2179,6 +2357,7 @@ async function runProfile(args, options = {}) {
2179
2357
  }
2180
2358
 
2181
2359
  // src/commands/promote.ts
2360
+ import path14 from "path";
2182
2361
  import { writeFile as writeFile6 } from "fs/promises";
2183
2362
  import {
2184
2363
  ensureProjectionAllowed,
@@ -2195,6 +2374,7 @@ function sortRecord(record) {
2195
2374
  return Object.fromEntries(Object.entries(record).sort(([left], [right]) => left.localeCompare(right)));
2196
2375
  }
2197
2376
  async function runPromote(args = [], options = {}) {
2377
+ const root = path14.resolve(options.root ?? process.cwd());
2198
2378
  const cliArgs = [...options.cliArgs ?? []];
2199
2379
  const target = normalizeTarget(consumeOption(cliArgs, "--to"));
2200
2380
  const alias = consumeOption(cliArgs, "--as");
@@ -2242,7 +2422,7 @@ async function runPromote(args = [], options = {}) {
2242
2422
  manifestPath: loadedManifest.manifestPath
2243
2423
  });
2244
2424
  }
2245
- return target === "public" ? `promoted ${keys.join(", ")} to public in ${loadedManifest.manifestPath}` : `promoted ${keys[0]} to env as ${alias} in ${loadedManifest.manifestPath}`;
2425
+ 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
2426
  }
2247
2427
 
2248
2428
  // src/commands/read.ts
@@ -2253,7 +2433,9 @@ async function runRead(key, options = {}) {
2253
2433
  throw new Error(`Missing CNOS config key: ${key}`);
2254
2434
  }
2255
2435
  const isSecret = key.startsWith("secret.");
2256
- const valueForOutput = isSecret ? maskSecretValue(value) : value;
2436
+ const cliArgs = [...options.cliArgs ?? []];
2437
+ const reveal = consumeFlag(cliArgs, "--reveal");
2438
+ const valueForOutput = isSecret && !reveal ? maskSecretValue(value) : value;
2257
2439
  if (options.json) {
2258
2440
  return printJson({ key, value: valueForOutput });
2259
2441
  }
@@ -2362,14 +2544,21 @@ async function runCommand(command, options = {}) {
2362
2544
  });
2363
2545
  }
2364
2546
 
2547
+ // src/commands/secret.ts
2548
+ import path17 from "path";
2549
+
2550
+ // src/commands/vault.ts
2551
+ import path16 from "path";
2552
+
2365
2553
  // src/services/vaults.ts
2366
2554
  import { rm as rm3, writeFile as writeFile7 } from "fs/promises";
2367
- import path11 from "path";
2555
+ import path15 from "path";
2368
2556
  import {
2369
2557
  clearAllVaultSessionKeys,
2370
2558
  clearVaultSessionKey,
2371
2559
  createSecretVault,
2372
2560
  deriveVaultKey,
2561
+ listLocalSecrets,
2373
2562
  loadManifest as loadManifest3,
2374
2563
  listSecretVaults,
2375
2564
  readVaultMetadata,
@@ -2380,6 +2569,21 @@ import {
2380
2569
  writeKeychain,
2381
2570
  writeVaultSessionKey
2382
2571
  } from "@kitsy/cnos/internal";
2572
+ function buildVaultDefinition(vault, provider) {
2573
+ return provider === "local" ? {
2574
+ provider: "local",
2575
+ auth: {
2576
+ passphrase: {
2577
+ from: defaultLocalAuthSources(vault)
2578
+ }
2579
+ }
2580
+ } : {
2581
+ provider,
2582
+ auth: {
2583
+ method: "environment"
2584
+ }
2585
+ };
2586
+ }
2383
2587
  function sortVaults(vaults) {
2384
2588
  return Object.fromEntries(Object.entries(vaults).sort(([left], [right]) => left.localeCompare(right)));
2385
2589
  }
@@ -2394,27 +2598,27 @@ async function createVaultDefinition(name, options = {}) {
2394
2598
  throw new Error("Local vaults cannot be passwordless.");
2395
2599
  }
2396
2600
  const loadedManifest = await loadManifest3(options.root ? { root: options.root } : {});
2601
+ const vaultDefinition = buildVaultDefinition(vault, provider);
2397
2602
  const rawManifest = {
2398
2603
  ...loadedManifest.rawManifest,
2399
2604
  vaults: {
2400
2605
  ...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
- }
2606
+ [vault]: vaultDefinition
2414
2607
  }
2415
2608
  };
2416
2609
  await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
2417
- const definition = resolveVaultDefinition({ [vault]: rawManifest.vaults[vault] }, vault);
2610
+ const definition = resolveVaultDefinition({ [vault]: vaultDefinition }, vault);
2611
+ if (provider === "local") {
2612
+ const auth = await resolveVaultAuth2(vault, vaultDefinition, options.processEnv ?? process.env);
2613
+ if (!auth.passphrase) {
2614
+ throw new Error(`Vault "${vault}" requires passphrase-based authentication during creation.`);
2615
+ }
2616
+ const storeRoot = resolveSecretStoreRoot2(options.processEnv);
2617
+ const existing = await readVaultMetadata(storeRoot, vault);
2618
+ if (!existing) {
2619
+ await createSecretVault(storeRoot, vault, auth.passphrase);
2620
+ }
2621
+ }
2418
2622
  return {
2419
2623
  ...definition,
2420
2624
  authMethod: definition.auth?.method ?? (provider === "local" ? "passphrase" : "environment"),
@@ -2454,7 +2658,7 @@ async function removeVaultDefinition(name, options = {}) {
2454
2658
  delete rawManifest.vaults;
2455
2659
  }
2456
2660
  await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
2457
- const vaultRoot = path11.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
2661
+ const vaultRoot = path15.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
2458
2662
  let removedStore;
2459
2663
  try {
2460
2664
  await rm3(vaultRoot, { recursive: true, force: true });
@@ -2486,15 +2690,22 @@ async function authenticateVault(name, options = {}) {
2486
2690
  if (!auth.passphrase) {
2487
2691
  throw new Error(`Vault "${vault}" requires passphrase-based authentication.`);
2488
2692
  }
2489
- const existing = await readVaultMetadata(storeRoot, vault);
2490
- if (!existing) {
2491
- await createSecretVault(storeRoot, vault, auth.passphrase);
2492
- }
2493
2693
  const metadata = await readVaultMetadata(storeRoot, vault);
2494
2694
  if (!metadata) {
2495
- throw new Error(`Failed to initialize vault "${vault}"`);
2695
+ throw new Error(
2696
+ `Vault "${vault}" has not been initialized yet. Run cnos vault create ${vault} first.`
2697
+ );
2496
2698
  }
2497
2699
  const derivedKey = deriveVaultKey(auth.passphrase, Buffer.from(metadata.salt, "base64"), metadata.iterations);
2700
+ await listLocalSecrets(
2701
+ storeRoot,
2702
+ {
2703
+ derivedKey,
2704
+ method: auth.method,
2705
+ ...definition.auth?.config ? { config: definition.auth.config } : {}
2706
+ },
2707
+ vault
2708
+ );
2498
2709
  const sessionPath2 = await writeVaultSessionKey(vault, derivedKey, options.processEnv);
2499
2710
  if (options.storeKeychain) {
2500
2711
  await writeKeychain(`cnos/${vault}`, derivedKey.toString("hex"));
@@ -2541,6 +2752,7 @@ function normalizeVaultAction(args) {
2541
2752
  async function runVault(args = [], options = {}) {
2542
2753
  const { action, tail } = normalizeVaultAction(args);
2543
2754
  const cliArgs = [...options.cliArgs ?? []];
2755
+ const root = path16.resolve(options.root ?? process.cwd());
2544
2756
  if (consumeOption(cliArgs, "--passphrase")) {
2545
2757
  throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
2546
2758
  }
@@ -2557,7 +2769,7 @@ async function runVault(args = [], options = {}) {
2557
2769
  if (options.json) {
2558
2770
  return printJson(result);
2559
2771
  }
2560
- return `created vault "${result.name}" with provider "${result.provider}" in ${result.manifestPath}`;
2772
+ return `created vault "${result.name}" with provider "${result.provider}" in ${displayPath(result.manifestPath, root)}`;
2561
2773
  }
2562
2774
  if (action === "auth") {
2563
2775
  const result = await authenticateVault(tail[0] ?? "default", {
@@ -2646,6 +2858,7 @@ async function runSecret(argsOrPath, options = {}) {
2646
2858
  const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
2647
2859
  const { action, tail } = normalizeSecretCommand(args);
2648
2860
  const cliArgs = [...options.cliArgs ?? []];
2861
+ const root = path17.resolve(options.root ?? process.cwd());
2649
2862
  if (consumeOption(cliArgs, "--passphrase")) {
2650
2863
  throw new Error("The --passphrase option is not supported in CNOS 1.4. Use env, keychain, or prompt-based auth.");
2651
2864
  }
@@ -2701,7 +2914,7 @@ async function runSecret(argsOrPath, options = {}) {
2701
2914
  if (options.json) {
2702
2915
  return printJson(result);
2703
2916
  }
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}`;
2917
+ 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
2918
  }
2706
2919
  if (action === "delete") {
2707
2920
  const secretPath2 = tail[0];
@@ -2714,7 +2927,7 @@ async function runSecret(argsOrPath, options = {}) {
2714
2927
  if (options.json) {
2715
2928
  return printJson(result);
2716
2929
  }
2717
- return result.deleted ? `deleted secret.${secretPath2} from ${result.filePath}` : `no secret.${secretPath2} found in ${result.filePath}`;
2930
+ return result.deleted ? `deleted secret.${secretPath2} from ${displayPath(result.filePath, root)}` : `no secret.${secretPath2} found in ${displayPath(result.filePath, root)}`;
2718
2931
  }
2719
2932
  const runtime = await createRuntimeService(options);
2720
2933
  const secretPath = tail[0] ?? "app.token";
@@ -2741,12 +2954,12 @@ async function runSecret(argsOrPath, options = {}) {
2741
2954
  }
2742
2955
 
2743
2956
  // src/commands/use.ts
2744
- import path12 from "path";
2957
+ import path18 from "path";
2745
2958
  async function runUse(args = [], options = {}) {
2746
- const root = path12.resolve(options.root ?? process.cwd());
2747
- const action = args[0] ?? "show";
2959
+ const root = path18.resolve(options.root ?? process.cwd());
2960
+ const action = args[0];
2748
2961
  const hasUpdates = Boolean(options.workspace || options.profile || options.globalRoot);
2749
- if (action === "show" || !hasUpdates) {
2962
+ if (action === "show" || !action && !hasUpdates) {
2750
2963
  const context = await loadCliContext(root);
2751
2964
  if (options.json) {
2752
2965
  return printJson(context);
@@ -2762,7 +2975,7 @@ async function runUse(args = [], options = {}) {
2762
2975
  if (options.json) {
2763
2976
  return printJson(result);
2764
2977
  }
2765
- return `updated CLI context in ${result.filePath}`;
2978
+ return `updated CLI context in ${displayPath(result.filePath, root)}`;
2766
2979
  }
2767
2980
 
2768
2981
  // src/commands/validate.ts
@@ -2780,7 +2993,7 @@ async function runValidate(options = {}) {
2780
2993
  // package.json
2781
2994
  var package_default = {
2782
2995
  name: "@kitsy/cnos-cli",
2783
- version: "1.3.0",
2996
+ version: "1.4.0",
2784
2997
  description: "CLI entry point and developer tooling for CNOS.",
2785
2998
  type: "module",
2786
2999
  main: "./dist/index.js",
@@ -2835,6 +3048,7 @@ function runVersion() {
2835
3048
  }
2836
3049
 
2837
3050
  // src/commands/value.ts
3051
+ import path19 from "path";
2838
3052
  function normalizeValueCommand(args) {
2839
3053
  const [actionOrPath, ...tail] = args;
2840
3054
  if (!actionOrPath) {
@@ -2858,6 +3072,7 @@ async function runValue(argsOrPath, options = {}) {
2858
3072
  const args = Array.isArray(argsOrPath) ? argsOrPath : [argsOrPath];
2859
3073
  const { action, tail } = normalizeValueCommand(args);
2860
3074
  const cliArgs = [...options.cliArgs ?? []];
3075
+ const root = path19.resolve(options.root ?? process.cwd());
2861
3076
  if (action === "list") {
2862
3077
  const prefix = consumeOption(cliArgs, "--prefix");
2863
3078
  const entries = await listConfigEntries("value", {
@@ -2888,7 +3103,7 @@ async function runValue(argsOrPath, options = {}) {
2888
3103
  value: result.value
2889
3104
  });
2890
3105
  }
2891
- return `set value.${valuePath} in ${result.filePath}`;
3106
+ return `set value.${valuePath} in ${displayPath(result.filePath, root)}`;
2892
3107
  }
2893
3108
  if (action === "delete") {
2894
3109
  const valuePath = tail[0] ?? "app.name";
@@ -2901,7 +3116,7 @@ async function runValue(argsOrPath, options = {}) {
2901
3116
  if (options.json) {
2902
3117
  return printJson(result);
2903
3118
  }
2904
- return result.deleted ? `deleted value.${valuePath} from ${result.filePath}` : `no value.${valuePath} found in ${result.filePath}`;
3119
+ return result.deleted ? `deleted value.${valuePath} from ${displayPath(result.filePath, root)}` : `no value.${valuePath} found in ${displayPath(result.filePath, root)}`;
2905
3120
  }
2906
3121
  const runtime = await createRuntimeService(options);
2907
3122
  const value = runtime.value(tail[0] ?? "app.name");
@@ -3260,6 +3475,11 @@ async function main(argv) {
3260
3475
  `);
3261
3476
  return;
3262
3477
  default:
3478
+ if (args.length > 0 && /^[a-z][a-z0-9-]*$/i.test(command)) {
3479
+ process.stdout.write(`${await runNamespace(command, args, runtimeOptions)}
3480
+ `);
3481
+ return;
3482
+ }
3263
3483
  process.stderr.write(`Unknown command: ${command}
3264
3484
  `);
3265
3485
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitsy/cnos-cli",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "CLI entry point and developer tooling for CNOS.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,7 +36,7 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@kitsy/cnos": "1.3.0"
39
+ "@kitsy/cnos": "1.4.0"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsup src/index.ts --format esm --dts",