@kitsy/cnos-cli 1.9.0 → 1.9.1

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 +320 -99
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -54,7 +54,9 @@ var COMMAND_FLAG_KEYS = /* @__PURE__ */ new Set([
54
54
  "--signal",
55
55
  "--derive",
56
56
  "--materialize",
57
- "--source-only"
57
+ "--source-only",
58
+ "--allow-secret",
59
+ "--fix-secret-env-mappings"
58
60
  ]);
59
61
  function normalizeCommand(argv) {
60
62
  const [command = "doctor", ...rest] = argv;
@@ -324,12 +326,31 @@ import { spawnSync } from "child_process";
324
326
  function isInteractiveSession() {
325
327
  return process.stdin.isTTY && process.stdout.isTTY && !process.env.CI;
326
328
  }
329
+ function printSecretEnvBuildWarnings(targetPath, mappings) {
330
+ console.error(`!WARN CNOS detected explicit secret env mappings for ${targetPath}.`);
331
+ console.error(`!WARN Writing revealed env artifacts is a security risk and may leak plaintext secrets outside CNOS.`);
332
+ console.error(`!WARN Secret env vars: ${mappings.map((mapping) => mapping.envVar).join(", ")}`);
333
+ }
327
334
  function getSecretEnvMappings(runtime) {
328
335
  return Object.entries(runtime.manifest.envMapping.explicit).filter(([, logicalKey]) => runtime.graph.entries.get(logicalKey)?.namespace === "secret").map(([envVar, logicalKey]) => ({
329
336
  envVar,
330
337
  logicalKey
331
338
  })).sort((left, right) => left.envVar.localeCompare(right.envVar));
332
339
  }
340
+ async function hydrateSecretEnvMappings(runtime, mappings) {
341
+ for (const mapping of mappings) {
342
+ await runtime.refreshSecret(mapping.logicalKey);
343
+ }
344
+ }
345
+ function applyMaskedSecretEnvMappings(env, mappings) {
346
+ const nextEnv = { ...env };
347
+ for (const { envVar } of mappings) {
348
+ if (!(envVar in nextEnv)) {
349
+ nextEnv[envVar] = "****";
350
+ }
351
+ }
352
+ return nextEnv;
353
+ }
333
354
  function resolveGitRoot(cwd) {
334
355
  const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
335
356
  cwd,
@@ -377,19 +398,16 @@ async function assertSecretEnvTargetIsGitIgnored(targetPath, cwd) {
377
398
  }
378
399
  }
379
400
  async function confirmSecretEnvBuild(targetPath, mappings) {
401
+ printSecretEnvBuildWarnings(targetPath, mappings);
380
402
  if (!isInteractiveSession()) {
381
403
  return;
382
404
  }
383
- console.error(`!WARN CNOS is about to write resolved secret values into ${targetPath}.`);
384
- console.error(
385
- `!WARN Secret env vars: ${mappings.map((mapping) => mapping.envVar).join(", ")}`
386
- );
387
405
  const rl = readline.createInterface({
388
406
  input: process.stdin,
389
407
  output: process.stdout
390
408
  });
391
409
  try {
392
- const answer = (await rl.question("Continue writing secrets to this env artifact? [y/N] ")).trim().toLowerCase();
410
+ const answer = (await rl.question("Do you want to continue? [y/N] ")).trim().toLowerCase();
393
411
  if (answer !== "y" && answer !== "yes") {
394
412
  throw new Error("Aborted secret env build.");
395
413
  }
@@ -503,14 +521,13 @@ async function buildEnvProjectionArtifact(to, options = {}, format = "dotenv") {
503
521
  if (revealSecrets && secretMappings.length > 0) {
504
522
  await assertSecretEnvTargetIsGitIgnored(targetPath, basePath);
505
523
  await confirmSecretEnvBuild(targetPath, secretMappings);
524
+ await hydrateSecretEnvMappings(runtime, secretMappings);
506
525
  }
507
- const env = runtime.toEnv({
526
+ let env = runtime.toEnv({
508
527
  includeSecrets: revealSecrets
509
528
  });
510
- for (const { envVar } of secretMappings) {
511
- if (!revealSecrets && !(envVar in env)) {
512
- env[envVar] = "****";
513
- }
529
+ if (!revealSecrets) {
530
+ env = applyMaskedSecretEnvMappings(env, secretMappings);
514
531
  }
515
532
  const output = formatKeyValueMap(env, format);
516
533
  const writtenTargetPath = await writeProjectionFile(to, output, basePath);
@@ -1333,7 +1350,7 @@ async function runDiff(leftProfile, rightProfile, options = {}) {
1333
1350
  }
1334
1351
 
1335
1352
  // src/services/doctor.ts
1336
- import { readdir as readdir2, readFile as readFile3 } from "fs/promises";
1353
+ import { readdir as readdir2, readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
1337
1354
  import path9 from "path";
1338
1355
  import {
1339
1356
  detectLegacyVaultFormat,
@@ -1341,7 +1358,8 @@ import {
1341
1358
  loadManifest as loadManifest3,
1342
1359
  parseYaml as parseYaml2,
1343
1360
  readKeychain,
1344
- resolveSecretStoreRoot
1361
+ resolveSecretStoreRoot,
1362
+ stringifyYaml as stringifyYaml3
1345
1363
  } from "@kitsy/cnos/internal";
1346
1364
 
1347
1365
  // src/services/validation.ts
@@ -1443,6 +1461,7 @@ async function checkSecretSecurity(options, runtime) {
1443
1461
  const warnings = [
1444
1462
  ...legacyDetected.map((entry) => `legacy vault ${entry.vault}: ${entry.path}`),
1445
1463
  ...plaintextFiles.map((file) => `plaintext secret file: ${file}`),
1464
+ ...Object.entries(runtime.manifest.envMapping.explicit).filter(([, logicalKey]) => logicalKey.startsWith("secret.")).map(([envVar, logicalKey]) => `secret env mapping: ${envVar} -> ${logicalKey}`),
1446
1465
  ...keychainWarnings.filter((entry) => !entry.value).map((entry) => `no keychain entry for vault ${entry.vault} (${entry.source})`)
1447
1466
  ];
1448
1467
  return {
@@ -1451,6 +1470,39 @@ async function checkSecretSecurity(options, runtime) {
1451
1470
  details: warnings.length === 0 ? "no legacy vaults, plaintext secret files, or missing keychain entries" : warnings.join("; ")
1452
1471
  };
1453
1472
  }
1473
+ async function repairSecretEnvMappings(options = {}) {
1474
+ const loadedManifest = await loadManifest3({
1475
+ ...options.root ? { root: options.root } : {},
1476
+ ...options.cwd ? { cwd: options.cwd } : {},
1477
+ ...options.processEnv ? { processEnv: options.processEnv } : {},
1478
+ ...options.cacheMode ? { cacheMode: options.cacheMode } : {},
1479
+ ...typeof options.cacheTtlSeconds === "number" ? { cacheTtlSeconds: options.cacheTtlSeconds } : {},
1480
+ ...options.forceRefresh ? { forceRefresh: true } : {}
1481
+ });
1482
+ const explicit = loadedManifest.rawManifest.envMapping?.explicit ?? {};
1483
+ const removed = Object.entries(explicit).filter(([, logicalKey]) => logicalKey.startsWith("secret.")).map(([envVar, logicalKey]) => ({ envVar, logicalKey }));
1484
+ if (removed.length === 0) {
1485
+ return {
1486
+ manifestPath: loadedManifest.manifestPath,
1487
+ removed
1488
+ };
1489
+ }
1490
+ const nextExplicit = Object.fromEntries(
1491
+ Object.entries(explicit).filter(([, logicalKey]) => !logicalKey.startsWith("secret."))
1492
+ );
1493
+ const nextRawManifest = {
1494
+ ...loadedManifest.rawManifest,
1495
+ envMapping: {
1496
+ ...loadedManifest.rawManifest.envMapping ?? {},
1497
+ explicit: nextExplicit
1498
+ }
1499
+ };
1500
+ await writeFile4(loadedManifest.manifestPath, stringifyYaml3(nextRawManifest), "utf8");
1501
+ return {
1502
+ manifestPath: loadedManifest.manifestPath,
1503
+ removed
1504
+ };
1505
+ }
1454
1506
  async function evaluateDoctor(options = {}) {
1455
1507
  const root = resolveFilesystemBasePath(options.root, options.cwd ?? process.cwd());
1456
1508
  const loadedManifest = await loadManifest3({
@@ -1508,15 +1560,22 @@ async function evaluateDoctor(options = {}) {
1508
1560
 
1509
1561
  // src/commands/doctor.ts
1510
1562
  async function runDoctor(options = {}) {
1563
+ const cliArgs = [...options.cliArgs ?? []];
1564
+ const shouldFixSecretEnvMappings = cliArgs.includes("--fix-secret-env-mappings");
1565
+ const repairResult = shouldFixSecretEnvMappings ? await repairSecretEnvMappings(options) : void 0;
1511
1566
  const checks = await evaluateDoctor(options);
1512
1567
  const hasFailures = checks.some((check) => !check.ok);
1513
1568
  if (hasFailures) {
1514
1569
  process.exitCode = 1;
1515
1570
  }
1516
1571
  if (options.json) {
1517
- return printJson(checks);
1572
+ return printJson({
1573
+ ...repairResult ? { repair: repairResult } : {},
1574
+ checks
1575
+ });
1518
1576
  }
1519
- return checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.name}: ${check.details}`).join("\n");
1577
+ const repairLine = repairResult ? repairResult.removed.length > 0 ? `REPAIRED secret-env-mappings: removed ${repairResult.removed.map((entry) => `${entry.envVar} -> ${entry.logicalKey}`).join(", ")}` : "REPAIRED secret-env-mappings: no secret env mappings found" : void 0;
1578
+ return [repairLine, ...checks.map((check) => `${check.ok ? "OK" : "FAIL"} ${check.name}: ${check.details}`)].filter((value) => Boolean(value)).join("\n");
1520
1579
  }
1521
1580
 
1522
1581
  // src/commands/dump.ts
@@ -2092,8 +2151,8 @@ var COMMANDS = [
2092
2151
  {
2093
2152
  id: "promote",
2094
2153
  summary: "Promote shareable config into public or env projection surfaces.",
2095
- usage: "cnos promote <key...> --to <public|env> [--as <ENV_VAR>] [global-options]",
2096
- 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.",
2154
+ usage: "cnos promote <key...> --to <public|env> [--as <ENV_VAR>] [--allow-secret] [global-options]",
2155
+ description: "Adds keys to public.promote or envMapping.explicit in .cnos/cnos.yml. Sensitive or non-shareable namespaces are rejected by default, but secret.* may be mapped to env explicitly when you pass --allow-secret. public never allows secret promotion.",
2097
2156
  options: [
2098
2157
  {
2099
2158
  flag: "--to <public|env>",
@@ -2102,12 +2161,17 @@ var COMMANDS = [
2102
2161
  {
2103
2162
  flag: "--as <ENV_VAR>",
2104
2163
  description: "Required for --to env. Sets the exported env var name for the promoted key."
2164
+ },
2165
+ {
2166
+ flag: "--allow-secret",
2167
+ description: "Allow secret.* only for --to env. This does not permit secret promotion to public."
2105
2168
  }
2106
2169
  ],
2107
2170
  examples: [
2108
2171
  "cnos promote value.flag.auth.upi_enabled --to public",
2109
2172
  "cnos promote flags.upi_enabled --to public",
2110
- "cnos promote value.server.port --to env --as PORT"
2173
+ "cnos promote value.server.port --to env --as PORT",
2174
+ "cnos promote secret.db.password --to env --as POSTGRES_PASSWORD --allow-secret"
2111
2175
  ]
2112
2176
  },
2113
2177
  {
@@ -2133,9 +2197,13 @@ var COMMANDS = [
2133
2197
  {
2134
2198
  id: "secret list",
2135
2199
  summary: "List resolved secrets.",
2136
- usage: "cnos secret list [--vault <name>] [--provider <name>] [global-options]",
2137
- description: "Lists stored secret entries for the selected workspace and profile, optionally filtered by vault or provider.",
2138
- examples: ["cnos secret list --workspace api", "cnos secret list --vault github-ci"]
2200
+ usage: "cnos secret list [--vault <name>] [--provider <name>] [--reveal] [global-options]",
2201
+ description: "Lists secret keys for the selected workspace and profile as masked values by default, or as resolved values when --reveal is supplied. Supports optional vault and provider filtering.",
2202
+ examples: [
2203
+ "cnos secret list --workspace api",
2204
+ "cnos secret list --vault github-ci",
2205
+ "cnos secret list --workspace api --reveal"
2206
+ ]
2139
2207
  },
2140
2208
  {
2141
2209
  id: "secret delete",
@@ -2220,7 +2288,7 @@ var COMMANDS = [
2220
2288
  id: "build env",
2221
2289
  summary: "Build a flat env-file artifact from CNOS.",
2222
2290
  usage: "cnos build env --to <path> [--format <dotenv|docker-env|json|shell|toml|yaml>] [--reveal] [global-options]",
2223
- description: "Builds a deterministic KEY=VALUE artifact for legacy build and runtime workflows. Secret env mappings stay masked by default; use --reveal only when the target env file is gitignored and you intentionally want concrete secret values.",
2291
+ description: "Builds a deterministic KEY=VALUE artifact for legacy build and runtime workflows. Secret env mappings stay masked by default; use --reveal only when the target env file is gitignored and you intentionally want concrete secret values. CNOS prints explicit risk warnings before revealed secret writes.",
2224
2292
  options: [
2225
2293
  {
2226
2294
  flag: "--to <path>",
@@ -2472,9 +2540,9 @@ var COMMANDS = [
2472
2540
  {
2473
2541
  id: "doctor",
2474
2542
  summary: "Run repository and workspace diagnostics.",
2475
- usage: "cnos doctor [global-options]",
2476
- description: "Checks manifest/workspace setup, gitignore coverage, and related diagnostics for the selected workspace.",
2477
- examples: ["cnos doctor", "cnos doctor --workspace api --json"]
2543
+ usage: "cnos doctor [--fix-secret-env-mappings] [global-options]",
2544
+ description: "Checks manifest/workspace setup, gitignore coverage, and related diagnostics for the selected workspace. Secret env mappings are reported as a security risk; use --fix-secret-env-mappings to remove them from envMapping.explicit in one shot.",
2545
+ examples: ["cnos doctor", "cnos doctor --workspace api --json", "cnos doctor --fix-secret-env-mappings"]
2478
2546
  },
2479
2547
  {
2480
2548
  id: "drift",
@@ -2747,7 +2815,7 @@ function runHelpAi(topic, cliArgs = []) {
2747
2815
  import path11 from "path";
2748
2816
 
2749
2817
  // src/services/scaffold.ts
2750
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2818
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
2751
2819
  import path10 from "path";
2752
2820
  function scaffoldManifest(projectName, options = {}) {
2753
2821
  const mode = options.mode ?? "regular";
@@ -2791,7 +2859,7 @@ async function ensureFile(filePath, content) {
2791
2859
  await readFile4(filePath, "utf8");
2792
2860
  return false;
2793
2861
  } catch {
2794
- await writeFile4(filePath, content, "utf8");
2862
+ await writeFile5(filePath, content, "utf8");
2795
2863
  return true;
2796
2864
  }
2797
2865
  }
@@ -2819,7 +2887,7 @@ async function ensureGitignore(root) {
2819
2887
  }
2820
2888
  const prefix = current.trim().length > 0 ? `${current.trimEnd()}
2821
2889
  ` : "";
2822
- await writeFile4(gitignorePath, `${prefix}${missingEntries.join("\n")}
2890
+ await writeFile5(gitignorePath, `${prefix}${missingEntries.join("\n")}
2823
2891
  `, "utf8");
2824
2892
  return true;
2825
2893
  }
@@ -2995,6 +3063,45 @@ async function runInspect(key, options = {}) {
2995
3063
  return printInspect(printable);
2996
3064
  }
2997
3065
 
3066
+ // src/format/printTable.ts
3067
+ function stringifyCell(value) {
3068
+ if (value === void 0 || value === null) {
3069
+ return "";
3070
+ }
3071
+ if (typeof value === "string") {
3072
+ return value;
3073
+ }
3074
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
3075
+ return String(value);
3076
+ }
3077
+ return JSON.stringify(value);
3078
+ }
3079
+ function printTable(rows) {
3080
+ if (rows.length === 0) {
3081
+ return "";
3082
+ }
3083
+ const columns = Array.from(
3084
+ rows.reduce((set, row) => {
3085
+ for (const key of Object.keys(row)) {
3086
+ set.add(key);
3087
+ }
3088
+ return set;
3089
+ }, /* @__PURE__ */ new Set())
3090
+ );
3091
+ const widths = columns.map(
3092
+ (column) => Math.max(
3093
+ column.length,
3094
+ ...rows.map((row) => stringifyCell(row[column]).length)
3095
+ )
3096
+ );
3097
+ const renderRow = (row) => columns.map((column, index) => stringifyCell(row[column]).padEnd(widths[index], " ")).join(" ").trimEnd();
3098
+ return [
3099
+ columns.map((column, index) => column.padEnd(widths[index], " ")).join(" ").trimEnd(),
3100
+ widths.map((width) => "-".repeat(width)).join(" "),
3101
+ ...rows.map(renderRow)
3102
+ ].join("\n");
3103
+ }
3104
+
2998
3105
  // src/format/printValue.ts
2999
3106
  function printValue(value, json = false) {
3000
3107
  if (json) {
@@ -3040,6 +3147,10 @@ function toStoredEntry(namespace, entry, filter = {}) {
3040
3147
  return {
3041
3148
  key: entry.key,
3042
3149
  value: selectedCandidate.value,
3150
+ ...namespace === "secret" ? {
3151
+ vault: selectedCandidate.metadata?.secretRef?.vault ?? "default",
3152
+ provider: selectedCandidate.metadata?.secretRef?.provider ?? "local"
3153
+ } : {},
3043
3154
  ...typeof selectedCandidate.value === "object" && selectedCandidate.value !== null && !Array.isArray(selectedCandidate.value) && "$derive" in selectedCandidate.value ? {
3044
3155
  derived: true
3045
3156
  } : {}
@@ -3050,20 +3161,35 @@ async function listStoredNamespace(namespace, options) {
3050
3161
  ...options,
3051
3162
  ...namespace === "secret" ? { secretResolution: "lazy" } : {}
3052
3163
  });
3053
- return Array.from(runtime.graph.entries.values()).filter((entry) => entry.namespace === namespace).map((entry) => {
3164
+ const revealSecrets = namespace === "secret" && (options.cliArgs?.includes("--reveal") ?? false);
3165
+ const results = [];
3166
+ for (const entry of Array.from(runtime.graph.entries.values()).filter((candidate) => candidate.namespace === namespace)) {
3054
3167
  const stored = toStoredEntry(namespace, entry, options);
3055
3168
  if (!stored) {
3056
- return void 0;
3169
+ continue;
3057
3170
  }
3058
- return {
3171
+ const value = namespace === "secret" ? revealSecrets ? (await runtime.refreshSecret(entry.key), runtime.secret(entry.key.slice("secret.".length))) : maskSecretValue(stored.value) : stored.derived ? runtime.read(entry.key) : stored.value;
3172
+ if (value === void 0 || !matchesPrefix(stored.key, options.prefix)) {
3173
+ continue;
3174
+ }
3175
+ results.push({
3059
3176
  ...stored,
3060
- value: stored.derived ? runtime.read(entry.key) : stored.value
3061
- };
3062
- }).filter((entry) => Boolean(entry)).filter((entry) => entry.value !== void 0).filter((entry) => matchesPrefix(entry.key, options.prefix)).sort((left, right) => left.key.localeCompare(right.key));
3177
+ value
3178
+ });
3179
+ }
3180
+ return results.sort((left, right) => left.key.localeCompare(right.key));
3063
3181
  }
3064
3182
  function listProjectedNamespace(namespace, options) {
3065
- return createRuntimeService(options).then((runtime) => {
3066
- const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? runtime.toEnv() : namespace === "public" ? runtime.toPublicEnv({
3183
+ return createRuntimeService({
3184
+ ...options,
3185
+ ...namespace === "env" ? { secretResolution: "lazy" } : {}
3186
+ }).then(async (runtime) => {
3187
+ const revealSecrets = options.cliArgs?.includes("--reveal") ?? false;
3188
+ const secretMappings = namespace === "env" ? getSecretEnvMappings(runtime) : [];
3189
+ if (namespace === "env" && revealSecrets && secretMappings.length > 0) {
3190
+ await hydrateSecretEnvMappings(runtime, secretMappings);
3191
+ }
3192
+ const projected = namespace === "meta" ? flattenObject2(runtime.toNamespace("meta")) : namespace === "env" ? revealSecrets ? runtime.toEnv({ includeSecrets: true }) : applyMaskedSecretEnvMappings(runtime.toEnv(), secretMappings) : namespace === "public" ? runtime.toPublicEnv({
3067
3193
  ...options.framework ? {
3068
3194
  framework: options.framework
3069
3195
  } : {}
@@ -3129,11 +3255,15 @@ async function runList(args = [], options = {}) {
3129
3255
  const namespace = normalizeNamespace(args[0] ?? consumeOption(cliArgs, "--namespace"));
3130
3256
  const prefix = consumeOption(cliArgs, "--prefix");
3131
3257
  const framework = consumeOption(cliArgs, "--framework");
3258
+ const vault = consumeOption(cliArgs, "--vault");
3259
+ const provider = consumeOption(cliArgs, "--provider");
3132
3260
  const entries = await listConfigEntries(namespace, {
3133
3261
  ...options,
3134
3262
  cliArgs,
3135
3263
  ...prefix ? { prefix } : {},
3136
- ...framework ? { framework } : {}
3264
+ ...framework ? { framework } : {},
3265
+ ...vault ? { vault } : {},
3266
+ ...provider ? { provider } : {}
3137
3267
  });
3138
3268
  if (options.json) {
3139
3269
  return printJson(entries);
@@ -3141,6 +3271,16 @@ async function runList(args = [], options = {}) {
3141
3271
  if (entries.length === 0) {
3142
3272
  return "";
3143
3273
  }
3274
+ if (namespace === "secret") {
3275
+ return printTable(
3276
+ entries.map((entry) => ({
3277
+ key: entry.key,
3278
+ value: printValue(entry.value),
3279
+ vault: entry.vault ?? "default",
3280
+ provider: entry.provider ?? "local"
3281
+ }))
3282
+ );
3283
+ }
3144
3284
  return entries.map((entry) => `${entry.key}=${printValue(entry.value)}${entry.derived ? " (derived)" : ""}`).join("\n");
3145
3285
  }
3146
3286
 
@@ -3603,9 +3743,9 @@ async function runOnboard(options = {}) {
3603
3743
  import path17 from "path";
3604
3744
 
3605
3745
  // src/services/context.ts
3606
- import { readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
3746
+ import { readFile as readFile6, writeFile as writeFile6 } from "fs/promises";
3607
3747
  import path15 from "path";
3608
- import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml3 } from "@kitsy/cnos/internal";
3748
+ import { parseYaml as parseYaml4, stringifyYaml as stringifyYaml4 } from "@kitsy/cnos/internal";
3609
3749
  async function loadCliContext(root = process.cwd()) {
3610
3750
  const filePath = path15.join(path15.resolve(root), ".cnos-workspace.yml");
3611
3751
  try {
@@ -3631,7 +3771,7 @@ async function saveCliContext(options = {}) {
3631
3771
  ...options.profile ? { profile: options.profile } : {},
3632
3772
  ...options.globalRoot ? { globalRoot: options.globalRoot } : {}
3633
3773
  };
3634
- await writeFile5(filePath, stringifyYaml3(next), "utf8");
3774
+ await writeFile6(filePath, stringifyYaml4(next), "utf8");
3635
3775
  return {
3636
3776
  filePath,
3637
3777
  context: next
@@ -3639,9 +3779,9 @@ async function saveCliContext(options = {}) {
3639
3779
  }
3640
3780
 
3641
3781
  // src/services/profiles.ts
3642
- import { mkdir as mkdir6, readdir as readdir4, readFile as readFile7, rm as rm3, writeFile as writeFile6 } from "fs/promises";
3782
+ import { mkdir as mkdir6, readdir as readdir4, readFile as readFile7, rm as rm3, writeFile as writeFile7 } from "fs/promises";
3643
3783
  import path16 from "path";
3644
- import { loadManifest as loadManifest6, parseYaml as parseYaml5, stringifyYaml as stringifyYaml4 } from "@kitsy/cnos/internal";
3784
+ import { loadManifest as loadManifest6, parseYaml as parseYaml5, stringifyYaml as stringifyYaml5 } from "@kitsy/cnos/internal";
3645
3785
  async function resolveProfilesRoot(root = process.cwd()) {
3646
3786
  try {
3647
3787
  const loadedManifest = await loadManifest6({ root });
@@ -3667,7 +3807,7 @@ async function createProfileDefinition(root = process.cwd(), profile, inherit, o
3667
3807
  } : {
3668
3808
  name: profile
3669
3809
  };
3670
- await writeFile6(filePath, stringifyYaml4(document), "utf8");
3810
+ await writeFile7(filePath, stringifyYaml5(document), "utf8");
3671
3811
  return {
3672
3812
  filePath,
3673
3813
  profile,
@@ -3790,11 +3930,11 @@ async function runProfile(args, options = {}) {
3790
3930
 
3791
3931
  // src/commands/promote.ts
3792
3932
  import path18 from "path";
3793
- import { writeFile as writeFile7 } from "fs/promises";
3933
+ import { writeFile as writeFile8 } from "fs/promises";
3794
3934
  import {
3795
3935
  ensureProjectionAllowed,
3796
3936
  loadManifest as loadManifest7,
3797
- stringifyYaml as stringifyYaml5
3937
+ stringifyYaml as stringifyYaml6
3798
3938
  } from "@kitsy/cnos/internal";
3799
3939
  function normalizeTarget(value) {
3800
3940
  if (value === "public" || value === "env") {
@@ -3810,6 +3950,7 @@ async function runPromote(args = [], options = {}) {
3810
3950
  const cliArgs = [...options.cliArgs ?? []];
3811
3951
  const target = normalizeTarget(consumeOption(cliArgs, "--to"));
3812
3952
  const alias = consumeOption(cliArgs, "--as");
3953
+ const allowSecret = cliArgs.includes("--allow-secret");
3813
3954
  const keys = args.filter(Boolean);
3814
3955
  if (keys.length === 0) {
3815
3956
  throw new Error("promote requires at least one logical key");
@@ -3821,6 +3962,8 @@ async function runPromote(args = [], options = {}) {
3821
3962
  if (!alias) {
3822
3963
  throw new Error("promote --to env requires --as <ENV_VAR>");
3823
3964
  }
3965
+ } else if (allowSecret) {
3966
+ throw new Error("--allow-secret is only supported with promote --to env");
3824
3967
  }
3825
3968
  const loadedManifest = await loadManifest7({
3826
3969
  ...options.root ? { root: options.root } : {},
@@ -3832,7 +3975,9 @@ async function runPromote(args = [], options = {}) {
3832
3975
  });
3833
3976
  await assertWritableConfigRoot(`promote ${keys.join(", ")}`, options);
3834
3977
  for (const key of keys) {
3835
- ensureProjectionAllowed(loadedManifest.manifest, key, target);
3978
+ ensureProjectionAllowed(loadedManifest.manifest, key, target, {
3979
+ allowSecretForEnv: allowSecret && target === "env"
3980
+ });
3836
3981
  }
3837
3982
  const rawManifest = {
3838
3983
  ...loadedManifest.rawManifest
@@ -3853,7 +3998,7 @@ async function runPromote(args = [], options = {}) {
3853
3998
  })
3854
3999
  };
3855
4000
  }
3856
- await writeFile7(loadedManifest.manifestPath, stringifyYaml5(rawManifest), "utf8");
4001
+ await writeFile8(loadedManifest.manifestPath, stringifyYaml6(rawManifest), "utf8");
3857
4002
  if (options.json) {
3858
4003
  return printJson({
3859
4004
  target,
@@ -3862,7 +4007,7 @@ async function runPromote(args = [], options = {}) {
3862
4007
  manifestPath: loadedManifest.manifestPath
3863
4008
  });
3864
4009
  }
3865
- 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)}`;
4010
+ return target === "public" ? `promoted ${keys.join(", ")} to public in ${displayPath(loadedManifest.manifestPath, root)}` : `promoted ${keys[0]} to env as ${alias}${allowSecret ? " with secret override" : ""} in ${displayPath(loadedManifest.manifestPath, root)}`;
3866
4011
  }
3867
4012
 
3868
4013
  // src/commands/read.ts
@@ -3992,7 +4137,7 @@ import path21 from "path";
3992
4137
  import path20 from "path";
3993
4138
 
3994
4139
  // src/services/vaults.ts
3995
- import { rm as rm4, writeFile as writeFile8 } from "fs/promises";
4140
+ import { rm as rm4, writeFile as writeFile9 } from "fs/promises";
3996
4141
  import path19 from "path";
3997
4142
  import {
3998
4143
  clearAllVaultSessionKeys,
@@ -4006,7 +4151,7 @@ import {
4006
4151
  resolveSecretStoreRoot as resolveSecretStoreRoot2,
4007
4152
  resolveVaultAuth as resolveVaultAuth2,
4008
4153
  resolveVaultDefinition,
4009
- stringifyYaml as stringifyYaml6,
4154
+ stringifyYaml as stringifyYaml7,
4010
4155
  writeKeychain,
4011
4156
  writeVaultSessionKey
4012
4157
  } from "@kitsy/cnos/internal";
@@ -4055,7 +4200,7 @@ async function createVaultDefinition(name, options = {}) {
4055
4200
  [vault]: vaultDefinition
4056
4201
  }
4057
4202
  };
4058
- await writeFile8(loadedManifest.manifestPath, stringifyYaml6(rawManifest), "utf8");
4203
+ await writeFile9(loadedManifest.manifestPath, stringifyYaml7(rawManifest), "utf8");
4059
4204
  const definition = resolveVaultDefinition({ [vault]: vaultDefinition }, vault);
4060
4205
  if (provider === "local") {
4061
4206
  const auth = await resolveVaultAuth2(vault, vaultDefinition, options.processEnv ?? process.env);
@@ -4121,7 +4266,7 @@ async function removeVaultDefinition(name, options = {}) {
4121
4266
  if (Object.keys(nextVaults).length === 0) {
4122
4267
  delete rawManifest.vaults;
4123
4268
  }
4124
- await writeFile8(loadedManifest.manifestPath, stringifyYaml6(rawManifest), "utf8");
4269
+ await writeFile9(loadedManifest.manifestPath, stringifyYaml7(rawManifest), "utf8");
4125
4270
  const vaultRoot = path19.join(resolveSecretStoreRoot2(options.processEnv), "vaults", vault);
4126
4271
  let removedStore;
4127
4272
  try {
@@ -4337,34 +4482,27 @@ async function runSecret(argsOrPath, options = {}) {
4337
4482
  return runVault(["create", tail[0] ?? "default"], options);
4338
4483
  }
4339
4484
  if (action === "list") {
4340
- const runtime2 = await createRuntimeService({
4341
- ...options,
4342
- secretResolution: "lazy"
4343
- });
4344
4485
  const prefix = consumeOption(cliArgs, "--prefix");
4345
4486
  const vault = consumeOption(cliArgs, "--vault");
4346
4487
  const provider = consumeOption(cliArgs, "--provider");
4347
- const entries = Array.from(runtime2.graph.entries.values()).filter((entry2) => entry2.namespace === "secret").filter((entry2) => !prefix || entry2.key.startsWith(`secret.${prefix}`) || entry2.key.startsWith(prefix)).filter((entry2) => {
4348
- const secretRef2 = entry2.winner.metadata?.secretRef;
4349
- if (vault && secretRef2?.vault !== vault) {
4350
- return false;
4351
- }
4352
- if (provider && secretRef2?.provider !== provider) {
4353
- return false;
4354
- }
4355
- return true;
4356
- }).map((entry2) => {
4357
- const secretRef2 = entry2.winner.metadata?.secretRef;
4358
- return {
4359
- key: entry2.key,
4360
- vault: secretRef2?.vault ?? "default",
4361
- provider: secretRef2?.provider ?? "local"
4362
- };
4363
- }).sort((left, right) => left.key.localeCompare(right.key));
4488
+ const entries = await listConfigEntries("secret", {
4489
+ ...options,
4490
+ cliArgs,
4491
+ ...prefix ? { prefix } : {},
4492
+ ...vault ? { vault } : {},
4493
+ ...provider ? { provider } : {}
4494
+ });
4364
4495
  if (options.json) {
4365
4496
  return printJson(entries);
4366
4497
  }
4367
- return entries.map((entry2) => `${entry2.key} (vault: ${entry2.vault}, provider: ${entry2.provider})`).join("\n");
4498
+ return printTable(
4499
+ entries.map((entry2) => ({
4500
+ key: entry2.key,
4501
+ value: printValue(entry2.value),
4502
+ vault: entry2.vault ?? "default",
4503
+ provider: entry2.provider ?? "local"
4504
+ }))
4505
+ );
4368
4506
  }
4369
4507
  if (action === "set") {
4370
4508
  const secretPath2 = tail[0];
@@ -4486,6 +4624,16 @@ function writeJson(response, statusCode, payload) {
4486
4624
  response.end(`${printJson(payload)}
4487
4625
  `);
4488
4626
  }
4627
+ async function readJsonBody(request) {
4628
+ const chunks = [];
4629
+ for await (const chunk of request) {
4630
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
4631
+ }
4632
+ if (chunks.length === 0) {
4633
+ return {};
4634
+ }
4635
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
4636
+ }
4489
4637
  function maskInspectResult(key, value) {
4490
4638
  if (!key.startsWith("secret.")) {
4491
4639
  return value;
@@ -4517,6 +4665,24 @@ function toRuntimeOptionsFromQuery(baseOptions, searchParams) {
4517
4665
  ...profile ? { profile } : {}
4518
4666
  };
4519
4667
  }
4668
+ function toRuntimeOptionsFromBody(baseOptions, body) {
4669
+ const workspace = typeof body.workspace === "string" ? body.workspace.trim() : "";
4670
+ const profile = typeof body.profile === "string" ? body.profile.trim() : "";
4671
+ return {
4672
+ ...baseOptions,
4673
+ ...workspace ? { workspace } : {},
4674
+ ...profile ? { profile } : {}
4675
+ };
4676
+ }
4677
+ function withUiPassphrase(processEnv, passphrase) {
4678
+ if (!passphrase?.trim()) {
4679
+ return processEnv;
4680
+ }
4681
+ return {
4682
+ ...processEnv ?? process.env,
4683
+ CNOS_SECRET_PASSPHRASE: passphrase.trim()
4684
+ };
4685
+ }
4520
4686
  async function handleSummary(options, searchParams) {
4521
4687
  const runtimeOptions = toRuntimeOptionsFromQuery(options, searchParams);
4522
4688
  const runtime = await createRuntimeService({
@@ -4562,21 +4728,62 @@ async function handleSummary(options, searchParams) {
4562
4728
  vaults: Object.keys(runtime.manifest.vaults)
4563
4729
  };
4564
4730
  }
4731
+ async function handleRevealList(body, options) {
4732
+ const prefix = typeof body.prefix === "string" ? body.prefix.trim() : "";
4733
+ const passphrase = typeof body.passphrase === "string" ? body.passphrase : void 0;
4734
+ const runtimeOptions = toRuntimeOptionsFromBody(options, body);
4735
+ const runtime = await createRuntimeService({
4736
+ ...runtimeOptions,
4737
+ processEnv: withUiPassphrase(options.processEnv, passphrase),
4738
+ secretResolution: "lazy"
4739
+ });
4740
+ const entries = [];
4741
+ for (const entry of Array.from(runtime.graph.entries.values()).filter((candidate) => candidate.namespace === "secret").filter((candidate) => {
4742
+ if (!prefix) {
4743
+ return true;
4744
+ }
4745
+ return candidate.key.startsWith(prefix) || candidate.key.split(".").slice(1).join(".").startsWith(prefix);
4746
+ }).sort((left, right) => left.key.localeCompare(right.key))) {
4747
+ await runtime.refreshSecret(entry.key);
4748
+ entries.push({
4749
+ key: entry.key,
4750
+ value: runtime.read(entry.key),
4751
+ ...typeof entry.winner.value === "object" && entry.winner.value !== null && !Array.isArray(entry.winner.value) && "$derive" in entry.winner.value ? { derived: true } : {}
4752
+ });
4753
+ }
4754
+ return {
4755
+ namespace: "secret",
4756
+ entries
4757
+ };
4758
+ }
4759
+ async function handleRevealInspect(body, options) {
4760
+ const key = typeof body.key === "string" ? body.key.trim() : "";
4761
+ if (!key) {
4762
+ throw new Error("Missing key");
4763
+ }
4764
+ const passphrase = typeof body.passphrase === "string" ? body.passphrase : void 0;
4765
+ const runtimeOptions = toRuntimeOptionsFromBody(options, body);
4766
+ const runtime = await createRuntimeService({
4767
+ ...runtimeOptions,
4768
+ processEnv: withUiPassphrase(options.processEnv, passphrase),
4769
+ ...key.startsWith("secret.") ? { secretResolution: "lazy" } : {}
4770
+ });
4771
+ if (key.startsWith("secret.")) {
4772
+ await runtime.refreshSecret(key);
4773
+ }
4774
+ return runtime.inspect(key);
4775
+ }
4565
4776
  async function handleRequest(request, response, options) {
4566
4777
  const url = new URL(request.url ?? "/", "http://127.0.0.1");
4567
- if (request.method !== "GET") {
4568
- writeJson(response, 405, { error: "Method not allowed" });
4569
- return;
4570
- }
4571
- if (url.pathname === "/api/health") {
4778
+ if (request.method === "GET" && url.pathname === "/api/health") {
4572
4779
  writeJson(response, 200, { ok: true });
4573
4780
  return;
4574
4781
  }
4575
- if (url.pathname === "/api/summary") {
4782
+ if (request.method === "GET" && url.pathname === "/api/summary") {
4576
4783
  writeJson(response, 200, await handleSummary(options, url.searchParams));
4577
4784
  return;
4578
4785
  }
4579
- if (url.pathname === "/api/list") {
4786
+ if (request.method === "GET" && url.pathname === "/api/list") {
4580
4787
  const namespace = url.searchParams.get("namespace") ?? "value";
4581
4788
  const prefix = url.searchParams.get("prefix") ?? void 0;
4582
4789
  const runtimeOptions = toRuntimeOptionsFromQuery(options, url.searchParams);
@@ -4591,7 +4798,7 @@ async function handleRequest(request, response, options) {
4591
4798
  });
4592
4799
  return;
4593
4800
  }
4594
- if (url.pathname === "/api/inspect") {
4801
+ if (request.method === "GET" && url.pathname === "/api/inspect") {
4595
4802
  const key = url.searchParams.get("key");
4596
4803
  if (!key) {
4597
4804
  writeJson(response, 400, { error: "Missing key query parameter" });
@@ -4605,6 +4812,20 @@ async function handleRequest(request, response, options) {
4605
4812
  writeJson(response, 200, maskInspectResult(key, runtime.inspect(key)));
4606
4813
  return;
4607
4814
  }
4815
+ if (request.method === "POST" && url.pathname === "/api/reveal/list") {
4816
+ const body = await readJsonBody(request);
4817
+ writeJson(response, 200, await handleRevealList(body, options));
4818
+ return;
4819
+ }
4820
+ if (request.method === "POST" && url.pathname === "/api/reveal/inspect") {
4821
+ const body = await readJsonBody(request);
4822
+ writeJson(response, 200, await handleRevealInspect(body, options));
4823
+ return;
4824
+ }
4825
+ if (request.method !== "GET" && request.method !== "POST") {
4826
+ writeJson(response, 405, { error: "Method not allowed" });
4827
+ return;
4828
+ }
4608
4829
  writeJson(response, 404, { error: "Not found" });
4609
4830
  }
4610
4831
  function resolveUiUrl(host, port) {
@@ -4684,7 +4905,7 @@ async function runValidate(options = {}) {
4684
4905
  // package.json
4685
4906
  var package_default = {
4686
4907
  name: "@kitsy/cnos-cli",
4687
- version: "1.9.0",
4908
+ version: "1.9.1",
4688
4909
  description: "CLI entry point and developer tooling for CNOS.",
4689
4910
  type: "module",
4690
4911
  main: "./dist/index.js",
@@ -4961,9 +5182,9 @@ async function runWatch(command, options = {}) {
4961
5182
  }
4962
5183
 
4963
5184
  // src/commands/workspace.ts
4964
- import { cp, mkdir as mkdir7, readdir as readdir5, readFile as readFile8, rename, rm as rm5, stat as stat3, writeFile as writeFile9 } from "fs/promises";
5185
+ import { cp, mkdir as mkdir7, readdir as readdir5, readFile as readFile8, rename, rm as rm5, stat as stat3, writeFile as writeFile10 } from "fs/promises";
4965
5186
  import path25 from "path";
4966
- import { loadManifest as loadManifest10, parseYaml as parseYaml6, stringifyYaml as stringifyYaml7 } from "@kitsy/cnos/internal";
5187
+ import { loadManifest as loadManifest10, parseYaml as parseYaml6, stringifyYaml as stringifyYaml8 } from "@kitsy/cnos/internal";
4967
5188
  async function exists2(targetPath) {
4968
5189
  try {
4969
5190
  await stat3(targetPath);
@@ -5002,9 +5223,9 @@ async function mergeWorkspaceRootsIntoStandalone(targetCnosRoot, sourceRoots) {
5002
5223
  async function writeAnchor(packageRoot, manifestRoot, workspace) {
5003
5224
  const relativeRoot = path25.relative(packageRoot, manifestRoot).replace(/\\/g, "/");
5004
5225
  const rootValue = relativeRoot.length === 0 ? "./.cnos" : relativeRoot.startsWith(".") ? relativeRoot : `./${relativeRoot}`;
5005
- await writeFile9(
5226
+ await writeFile10(
5006
5227
  path25.join(packageRoot, ".cnosrc.yml"),
5007
- stringifyYaml7({
5228
+ stringifyYaml8({
5008
5229
  root: rootValue,
5009
5230
  ...workspace ? { workspace } : {}
5010
5231
  }),
@@ -5054,9 +5275,9 @@ async function hasDirectConfigData(cnosRoot) {
5054
5275
  async function updateRootAnchorToWorkspace(packageRoot, workspaceId) {
5055
5276
  const anchorPath = path25.join(packageRoot, ".cnosrc.yml");
5056
5277
  const current = await exists2(anchorPath) ? parseYaml6(await readFile8(anchorPath, "utf8")) : void 0;
5057
- await writeFile9(
5278
+ await writeFile10(
5058
5279
  anchorPath,
5059
- stringifyYaml7({
5280
+ stringifyYaml8({
5060
5281
  root: typeof current?.root === "string" ? current.root : "./.cnos",
5061
5282
  workspace: workspaceId
5062
5283
  }),
@@ -5066,9 +5287,9 @@ async function updateRootAnchorToWorkspace(packageRoot, workspaceId) {
5066
5287
  async function updateWorkspaceContext(packageRoot, workspaceId) {
5067
5288
  const workspacePath = path25.join(packageRoot, ".cnos-workspace.yml");
5068
5289
  const current = await exists2(workspacePath) ? parseYaml6(await readFile8(workspacePath, "utf8")) : void 0;
5069
- await writeFile9(
5290
+ await writeFile10(
5070
5291
  workspacePath,
5071
- stringifyYaml7({
5292
+ stringifyYaml8({
5072
5293
  workspace: workspaceId,
5073
5294
  ...typeof current?.profile === "string" ? { profile: current.profile } : {},
5074
5295
  ...typeof current?.globalRoot === "string" ? { globalRoot: current.globalRoot } : { globalRoot: "~/.cnos" }
@@ -5097,9 +5318,9 @@ async function runDetach(packageRoot, options = {}) {
5097
5318
  const localRoots = runtime.graph.workspace.workspaceRoots.filter((root) => root.scope === "local").map((root) => root.path);
5098
5319
  await mkdir7(targetCnosRoot, { recursive: true });
5099
5320
  await mergeWorkspaceRootsIntoStandalone(targetCnosRoot, localRoots);
5100
- await writeFile9(
5321
+ await writeFile10(
5101
5322
  path25.join(targetCnosRoot, "cnos.yml"),
5102
- stringifyYaml7(createDetachedManifest(loaded.rawManifest)),
5323
+ stringifyYaml8(createDetachedManifest(loaded.rawManifest)),
5103
5324
  "utf8"
5104
5325
  );
5105
5326
  const relativeRoot = path25.relative(packageRoot, loaded.manifestRoot).replace(/\\/g, "/");
@@ -5112,8 +5333,8 @@ async function runDetach(packageRoot, options = {}) {
5112
5333
  workspace: loaded.anchoredWorkspace
5113
5334
  }
5114
5335
  };
5115
- await writeFile9(path25.join(targetCnosRoot, ".detached"), stringifyYaml7(marker), "utf8");
5116
- await writeFile9(path25.join(packageRoot, ".cnosrc.yml"), stringifyYaml7({ root: "./.cnos" }), "utf8");
5336
+ await writeFile10(path25.join(targetCnosRoot, ".detached"), stringifyYaml8(marker), "utf8");
5337
+ await writeFile10(path25.join(packageRoot, ".cnosrc.yml"), stringifyYaml8({ root: "./.cnos" }), "utf8");
5117
5338
  if (options.json) {
5118
5339
  return printJson({
5119
5340
  packageRoot,
@@ -5160,7 +5381,7 @@ async function runAttach(packageRoot, options = {}) {
5160
5381
  items[workspaceId] = items[workspaceId] ?? {};
5161
5382
  workspaces.items = items;
5162
5383
  rawManifest.workspaces = workspaces;
5163
- await writeFile9(path25.join(parentLoaded.manifestRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
5384
+ await writeFile10(path25.join(parentLoaded.manifestRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
5164
5385
  const archivePath = path25.join(packageRoot, ".cnos.detached.bak");
5165
5386
  await rm5(archivePath, { recursive: true, force: true });
5166
5387
  await rename(childCnosRoot, archivePath);
@@ -5242,7 +5463,7 @@ async function runEnable(manifestCwd, packageRoot, options = {}) {
5242
5463
  base: {}
5243
5464
  };
5244
5465
  rawManifest.workspaces = rawWorkspaces;
5245
- await writeFile9(path25.join(cnosRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
5466
+ await writeFile10(path25.join(cnosRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
5246
5467
  await updateRootAnchorToWorkspace(packageRoot, "base");
5247
5468
  await updateWorkspaceContext(packageRoot, "base");
5248
5469
  await ensureGitignore(path25.dirname(cnosRoot));
@@ -5295,7 +5516,7 @@ async function runAddOrScaffold(action, workspaceId, manifestCwd, packageRoot, o
5295
5516
  rawManifest.workspaces = rawWorkspaces;
5296
5517
  const workspaceRoot = path25.join(cnosRoot, "workspaces", workspaceId);
5297
5518
  const created = await ensureWorkspaceLayout(cnosRoot, workspaceId);
5298
- await writeFile9(path25.join(cnosRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
5519
+ await writeFile10(path25.join(cnosRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
5299
5520
  await ensureGitignore(path25.dirname(cnosRoot));
5300
5521
  await writeAnchor(packageRoot, cnosRoot, workspaceId);
5301
5522
  await updateWorkspaceContext(packageRoot, workspaceId);
@@ -5339,7 +5560,7 @@ async function runRemove(workspaceId, manifestCwd, options = {}) {
5339
5560
  delete rawItems[workspaceId];
5340
5561
  rawWorkspaces.items = rawItems;
5341
5562
  rawManifest.workspaces = rawWorkspaces;
5342
- await writeFile9(path25.join(loaded.manifestRoot, "cnos.yml"), stringifyYaml7(rawManifest), "utf8");
5563
+ await writeFile10(path25.join(loaded.manifestRoot, "cnos.yml"), stringifyYaml8(rawManifest), "utf8");
5343
5564
  await rm5(path25.join(loaded.manifestRoot, "workspaces", workspaceId), { recursive: true, force: true });
5344
5565
  if (options.json) {
5345
5566
  return printJson({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitsy/cnos-cli",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "CLI entry point and developer tooling for CNOS.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -37,8 +37,8 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "smol-toml": "^1.4.2",
40
- "@kitsy/cnos": "1.9.0",
41
- "@kitsy/cnos-ui": "1.9.0"
40
+ "@kitsy/cnos-ui": "1.9.1",
41
+ "@kitsy/cnos": "1.9.1"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm --dts",