@treeseed/cli 0.6.1 → 0.6.3

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.
@@ -17,6 +17,13 @@ function normalizeConfigScopes(value) {
17
17
  }
18
18
  return ["local", "staging", "prod"].filter((scope) => requested.includes(scope));
19
19
  }
20
+ function normalizeBootstrapSystems(system, systems) {
21
+ const values = [
22
+ ...Array.isArray(system) ? system.map(String) : typeof system === "string" ? [system] : [],
23
+ ...typeof systems === "string" ? systems.split(",") : Array.isArray(systems) ? systems.flatMap((value) => String(value).split(",")) : []
24
+ ].map((value) => value.trim()).filter(Boolean);
25
+ return values.length > 0 ? values : void 0;
26
+ }
20
27
  function formatPrintEnvReports(payload) {
21
28
  const lines = [];
22
29
  for (const report of payload.reports ?? []) {
@@ -110,6 +117,8 @@ function renderConfigResult(commandName, result) {
110
117
  const configContext = payload.context;
111
118
  const configReadiness = configContext?.configReadinessByScope?.local ?? {};
112
119
  const readinessByScope = payload.result?.readinessByScope ?? {};
120
+ const bootstrapSystemsByScope = payload.result?.bootstrapSystemsByScope ?? payload.bootstrapSystemsByScope ?? {};
121
+ const skippedSystems = Object.values(bootstrapSystemsByScope).flatMap((entry) => Array.isArray(entry?.skipped) ? entry.skipped : []);
113
122
  const resourceInventoryByScope = payload.result?.resourceInventoryByScope ?? payload.resourceInventoryByScope ?? {};
114
123
  const secretSession = payload.secretSession;
115
124
  const sharedStorageMigrations = payload.result?.sharedStorageMigrations;
@@ -122,6 +131,8 @@ function renderConfigResult(commandName, result) {
122
131
  { label: "Mode", value: payload.mode },
123
132
  { label: "Scopes", value: Array.isArray(payload.scopes) ? payload.scopes.join(", ") : "(none)" },
124
133
  { label: "Sync", value: payload.sync ?? "all" },
134
+ { label: "Bootstrap systems", value: Object.values(bootstrapSystemsByScope).flatMap((entry) => entry?.runnable ?? []).filter((value, index, all) => all.indexOf(value) === index).join(", ") || "(none)" },
135
+ { label: "Skipped systems", value: skippedSystems.map((entry) => entry.system).filter((value, index, all) => all.indexOf(value) === index).join(", ") || "(none)" },
125
136
  { label: "Safe repairs", value: Array.isArray(payload.repairs) ? payload.repairs.length : 0 },
126
137
  { label: "Machine config", value: payload.configPath },
127
138
  { label: "Machine key", value: payload.keyPath },
@@ -169,6 +180,7 @@ const handleConfig = async (invocation, context) => {
169
180
  });
170
181
  const scopes = normalizeConfigScopes(invocation.args.environment);
171
182
  const sync = invocation.args.sync;
183
+ const systems = normalizeBootstrapSystems(invocation.args.system, invocation.args.systems);
172
184
  const interactive = context.outputFormat !== "json" && context.interactiveUi !== false && process.stdin.isTTY && process.stdout.isTTY;
173
185
  const nonInteractive = invocation.args.nonInteractive === true || context.outputFormat === "json";
174
186
  const operationalMode = invocation.args.printEnvOnly === true || invocation.args.rotateMachineKey === true || invocation.args.connectMarket === true || invocation.args.bootstrap === true;
@@ -245,6 +257,9 @@ const handleConfig = async (invocation, context) => {
245
257
  context.write("Applying config updates, validating environments, and syncing managed providers...", "stdout");
246
258
  const result2 = await workflow.config({
247
259
  environment: scopes,
260
+ systems,
261
+ skipUnavailable: invocation.args.skipUnavailable === true ? true : void 0,
262
+ bootstrapExecution: invocation.args.bootstrapSequential === true ? "sequential" : "parallel",
248
263
  sync,
249
264
  printEnv: invocation.args.printEnv === true,
250
265
  showSecrets: invocation.args.showSecrets === true,
@@ -256,6 +271,9 @@ const handleConfig = async (invocation, context) => {
256
271
  }
257
272
  const result = await workflow.config({
258
273
  environment: invocation.args.environment,
274
+ systems,
275
+ skipUnavailable: invocation.args.skipUnavailable === true ? true : void 0,
276
+ bootstrapExecution: invocation.args.bootstrapSequential === true ? "sequential" : "parallel",
259
277
  sync,
260
278
  bootstrap: invocation.args.bootstrap === true,
261
279
  preflight: invocation.args.preflight === true,
package/dist/cli/help.js CHANGED
@@ -197,7 +197,8 @@ function buildTreeseedHelpView(commandName) {
197
197
  lines: [
198
198
  "Workspace-only commands must be run inside a Treeseed workspace; the CLI resolves the project root from ancestor directories when possible.",
199
199
  "Help text is generated from the CLI command registry.",
200
- "Use --json on supported workflow and utility commands when an AI agent or script needs machine-readable output."
200
+ "Use --json on supported workflow and utility commands when an AI agent or script needs machine-readable output.",
201
+ "Use --no-color, NO_COLOR, or TREESEED_NO_COLOR=1 to disable CLI color output globally."
201
202
  ]
202
203
  });
203
204
  return {
@@ -703,6 +703,10 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
703
703
  { name: "environment", flags: "--environment <scope>", description: "Select all environments or limit configuration to local, staging, or prod. Defaults to all.", kind: "enum", repeatable: true, values: ["all", "local", "staging", "prod"] },
704
704
  { name: "sync", flags: "--sync <mode>", description: "Sync hosted secrets/variables to GitHub, Cloudflare, Railway, or all providers. Defaults to all.", kind: "enum", values: ["none", "github", "cloudflare", "railway", "all"] },
705
705
  { name: "bootstrap", flags: "--bootstrap", description: "Skip the editor and run platform reconciliation/bootstrap from the currently saved required values.", kind: "boolean" },
706
+ { name: "system", flags: "--system <system>", description: "Limit bootstrap to a system group. Repeatable. Values: all, github, data, web, api, agents.", kind: "enum", repeatable: true, values: ["all", "github", "data", "web", "api", "agents"] },
707
+ { name: "systems", flags: "--systems <systems>", description: "Comma-separated bootstrap system groups. Values: all, github, data, web, api, agents.", kind: "string" },
708
+ { name: "skipUnavailable", flags: "--skip-unavailable", description: "Skip selected bootstrap systems whose required provider credentials are not configured.", kind: "boolean" },
709
+ { name: "bootstrapSequential", flags: "--bootstrap-sequential", description: "Run bootstrap DAG tasks sequentially for ordered debugging logs.", kind: "boolean" },
706
710
  { name: "preflight", flags: "--preflight", description: "Inspect bootstrap verification readiness and planned checks without mutating provider resources.", kind: "boolean" },
707
711
  { name: "nonInteractive", flags: "--non-interactive", description: "Apply resolved values without opening the interactive UI. Required for non-TTY automation unless using an operational mode such as --print-env-only.", kind: "boolean" },
708
712
  { name: "installMissingTooling", flags: "--install-missing-tooling", description: "Install missing config verification tooling such as `gh-act` during the run instead of only reporting it.", kind: "boolean" },
@@ -721,7 +725,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
721
725
  { name: "rotateRunnerToken", flags: "--rotate-runner-token", description: "Rotate the project runner credential while pairing the local hybrid repo.", kind: "boolean" },
722
726
  { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
723
727
  ],
724
- examples: ["treeseed config", "treeseed config --full --mouse", "treeseed config --environment all", "treeseed config --environment staging --bootstrap", "treeseed config --environment staging --bootstrap --preflight", "treeseed config --environment local --sync none", "treeseed config --environment local --sync none --non-interactive", "treeseed config --environment staging --print-env-only --show-secrets", "treeseed config --rotate-machine-key", "treeseed config --connect-market --market-project-id kc_proj_123"],
728
+ examples: ["treeseed config", "treeseed config --full --mouse", "treeseed config --environment all", "treeseed config --environment staging --bootstrap", "treeseed config --environment staging --bootstrap --system web", "treeseed config --environment staging --bootstrap --preflight", "treeseed config --environment local --sync none", "treeseed config --environment local --sync none --non-interactive", "treeseed config --environment staging --print-env-only --show-secrets", "treeseed config --rotate-machine-key", "treeseed config --connect-market --market-project-id kc_proj_123"],
725
729
  notes: ["Does not create branch preview deployments. Use `treeseed switch <branch> --preview` for that."],
726
730
  help: {
727
731
  workflowPosition: "configure runtime",
@@ -748,6 +752,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
748
752
  example("treeseed config --full", "Open the advanced editor directly", "Skip the startup wizard and go straight to the full configuration surface."),
749
753
  example("treeseed config --full --mouse", "Opt into mouse capture for the editor", "Keep the keyboard-first defaults unless you explicitly want click and wheel interaction inside the config UI."),
750
754
  example("treeseed config --environment staging --bootstrap", "Bootstrap infrastructure from saved config", "Skip the editor and run reconciliation/bootstrap when the required values are already configured."),
755
+ example("treeseed config --environment staging --bootstrap --system web", "Bootstrap only the hosted hub", "Provision data and web systems while leaving optional API and agent Railway services alone."),
751
756
  example("treeseed config --environment staging --bootstrap --preflight", "Inspect bootstrap verification readiness", "Show the resolved units, verification capabilities, and planned reconcile actions without mutating provider resources."),
752
757
  example("treeseed config --environment local --sync none", "Edit local values without provider sync", "Limit the session to local values and avoid hosted synchronization while iterating locally."),
753
758
  example("treeseed config --environment local --sync none --non-interactive", "Apply deterministic local config in automation", "Use the resolved current and suggested values without opening the interactive UI."),
@@ -761,6 +766,10 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
761
766
  detail("--environment <scope>", "Filter configuration to `all`, `local`, `staging`, or `prod`."),
762
767
  detail("--sync <mode>", "Choose which provider surfaces should receive synchronized values after local updates are applied."),
763
768
  detail("--bootstrap", "Skip the editor and run the reconcile/bootstrap path from the values already stored in machine config."),
769
+ detail("--system <system>", "Limit bootstrap to a stable system group. Repeat it to select multiple groups."),
770
+ detail("--systems <systems>", "Comma-separated form of --system for automation."),
771
+ detail("--skip-unavailable", "Skip selected systems whose provider credentials are missing instead of failing the run."),
772
+ detail("--bootstrap-sequential", "Run bootstrap DAG tasks one at a time to preserve log order for debugging or LLM review."),
764
773
  detail("--preflight", "When combined with `--bootstrap`, inspect verification readiness and planned reconcile actions without mutating provider resources."),
765
774
  detail("--non-interactive", "Apply resolved values without opening the interactive editor. Use this for automation when you do not want `--json` output."),
766
775
  detail("--install-missing-tooling", "Allow config to install missing verification helpers such as the GitHub `gh-act` extension instead of only reporting them."),
@@ -71,6 +71,7 @@ export type TreeseedCommandContext = {
71
71
  spawn: TreeseedSpawner;
72
72
  outputFormat?: 'human' | 'json';
73
73
  interactiveUi?: boolean;
74
+ colorEnabled?: boolean;
74
75
  prompt?: TreeseedPromptHandler;
75
76
  confirm?: TreeseedConfirmHandler;
76
77
  };
@@ -1,4 +1,5 @@
1
1
  import type { TreeseedCommandContext, TreeseedHandlerResolver, TreeseedOperationRequest, TreeseedOperationResult, TreeseedOperationSpec } from './operations-types.ts';
2
+ export declare function colorizeTreeseedCliOutput(output: string, colorEnabled?: boolean): string;
2
3
  export declare function createTreeseedCommandContext(overrides?: Partial<TreeseedCommandContext>): TreeseedCommandContext;
3
4
  export declare function writeTreeseedResult(result: TreeseedOperationResult | {
4
5
  exitCode?: number;
@@ -14,6 +14,54 @@ const require2 = createRequire(import.meta.url);
14
14
  function isHelpFlag(value) {
15
15
  return value === "--help" || value === "-h";
16
16
  }
17
+ function isNoColorFlag(value) {
18
+ return value === "--no-color";
19
+ }
20
+ function stripGlobalFlags(argv) {
21
+ return argv.filter((value) => !isNoColorFlag(value));
22
+ }
23
+ function resolveColorEnabled(argv, env, override) {
24
+ if (typeof override === "boolean") {
25
+ return override;
26
+ }
27
+ if (argv.some(isNoColorFlag)) {
28
+ return false;
29
+ }
30
+ if (env.NO_COLOR !== void 0 || env.TREESEED_NO_COLOR === "1" || env.TREESEED_NO_COLOR === "true") {
31
+ return false;
32
+ }
33
+ return true;
34
+ }
35
+ function colorCodeForBootstrapSystem(system) {
36
+ switch (system) {
37
+ case "github":
38
+ return "35;1";
39
+ case "data":
40
+ return "34;1";
41
+ case "web":
42
+ return "36;1";
43
+ case "api":
44
+ return "32;1";
45
+ case "agents":
46
+ return "33;1";
47
+ case "skip":
48
+ return "90;1";
49
+ default:
50
+ return "37;1";
51
+ }
52
+ }
53
+ function colorizeTreeseedCliOutput(output, colorEnabled = true) {
54
+ if (!colorEnabled) {
55
+ return output;
56
+ }
57
+ return output.replace(/^((?:\[[^\]]+\]){3,4})(\s|$)/u, (match, prefix, suffix) => {
58
+ const segments = [...prefix.matchAll(/\[([^\]]+)\]/gu)].map((entry) => entry[1]);
59
+ const system = segments[1] ?? "";
60
+ const stage = segments[segments.length - 1] ?? "";
61
+ const code = /fail|error/iu.test(stage) ? "31;1" : /skip/iu.test(stage) ? "90;1" : colorCodeForBootstrapSystem(system);
62
+ return `\x1B[${code}m${prefix}\x1B[0m${suffix}`;
63
+ });
64
+ }
17
65
  function defaultWrite(output, stream = "stdout") {
18
66
  if (!output) return;
19
67
  (stream === "stderr" ? process.stderr : process.stdout).write(`${output}
@@ -68,13 +116,16 @@ function formatValidationError(spec, errors) {
68
116
  ].join("\n");
69
117
  }
70
118
  function createTreeseedCommandContext(overrides = {}) {
119
+ const colorEnabled = resolveColorEnabled([], overrides.env ?? process.env, overrides.colorEnabled);
120
+ const rawWrite = overrides.write ?? defaultWrite;
71
121
  return {
72
122
  cwd: overrides.cwd ?? process.cwd(),
73
123
  env: overrides.env ?? process.env,
74
- write: overrides.write ?? defaultWrite,
124
+ write: overrides.write ? rawWrite : ((output, stream) => rawWrite(colorizeTreeseedCliOutput(output, colorEnabled), stream)),
75
125
  spawn: overrides.spawn ?? defaultSpawn,
76
126
  outputFormat: overrides.outputFormat ?? "human",
77
127
  interactiveUi: overrides.interactiveUi ?? overrides.write == null,
128
+ colorEnabled,
78
129
  prompt: overrides.prompt,
79
130
  confirm: overrides.confirm
80
131
  };
@@ -292,14 +343,19 @@ const cliOperationsSdk = new TreeseedOperationsSdk({
292
343
  resolveHandler: (handlerName) => COMMAND_HANDLERS[handlerName] ?? null
293
344
  });
294
345
  async function executeTreeseedCommand(commandName, argv, context) {
346
+ const cleanArgv = stripGlobalFlags(argv);
347
+ const commandContext = {
348
+ ...context,
349
+ colorEnabled: resolveColorEnabled(argv, context.env ?? process.env, context.colorEnabled)
350
+ };
295
351
  const spec = cliOperationsSdk.findOperation(commandName);
296
352
  if (!spec) {
297
- return cliOperationsSdk.executeOperation({ commandName, argv }, context);
353
+ return cliOperationsSdk.executeOperation({ commandName, argv: cleanArgv }, commandContext);
298
354
  }
299
- if (argv.some(isHelpFlag)) {
300
- return cliOperationsSdk.executeOperation({ commandName, argv }, context);
355
+ if (cleanArgv.some(isHelpFlag)) {
356
+ return cliOperationsSdk.executeOperation({ commandName, argv: cleanArgv }, commandContext);
301
357
  }
302
- const resolved = resolveTreeseedCommandCwd(spec, context.cwd);
358
+ const resolved = resolveTreeseedCommandCwd(spec, commandContext.cwd);
303
359
  if (commandNeedsProjectRoot(spec) && !resolved.resolvedProjectRoot) {
304
360
  return writeTreeseedResult({
305
361
  exitCode: 1,
@@ -307,21 +363,23 @@ async function executeTreeseedCommand(commandName, argv, context) {
307
363
  report: {
308
364
  command: spec.name,
309
365
  ok: false,
310
- error: `No ancestor containing treeseed.site.yaml was found from ${context.cwd}.`,
366
+ error: `No ancestor containing treeseed.site.yaml was found from ${commandContext.cwd}.`,
311
367
  hint: `treeseed help ${spec.name}`
312
368
  }
313
- }, { ...context, outputFormat: argv.includes("--json") ? "json" : context.outputFormat ?? "human" });
369
+ }, { ...commandContext, outputFormat: cleanArgv.includes("--json") ? "json" : commandContext.outputFormat ?? "human" });
314
370
  }
315
- return cliOperationsSdk.executeOperation({ commandName, argv }, { ...context, cwd: resolved.cwd });
371
+ return cliOperationsSdk.executeOperation({ commandName, argv: cleanArgv }, { ...commandContext, cwd: resolved.cwd });
316
372
  }
317
373
  async function runTreeseedCli(argv, overrides = {}) {
318
- const [firstArg] = argv;
374
+ const cleanArgv = stripGlobalFlags(argv);
375
+ const colorEnabled = resolveColorEnabled(argv, overrides.env ?? process.env, overrides.colorEnabled);
376
+ const [firstArg] = cleanArgv;
319
377
  const spec = firstArg ? cliOperationsSdk.findOperation(firstArg) : null;
320
378
  if (!spec) {
321
- return cliOperationsSdk.run(argv, overrides);
379
+ return cliOperationsSdk.run(cleanArgv, { ...overrides, colorEnabled });
322
380
  }
323
- if (argv.slice(1).some(isHelpFlag)) {
324
- return cliOperationsSdk.run(argv, overrides);
381
+ if (cleanArgv.slice(1).some(isHelpFlag)) {
382
+ return cliOperationsSdk.run(cleanArgv, { ...overrides, colorEnabled });
325
383
  }
326
384
  const baseCwd = overrides.cwd ?? process.cwd();
327
385
  const resolved = resolveTreeseedCommandCwd(spec, baseCwd);
@@ -337,14 +395,16 @@ async function runTreeseedCli(argv, overrides = {}) {
337
395
  }
338
396
  }, createTreeseedCommandContext({
339
397
  ...overrides,
340
- outputFormat: argv.includes("--json") ? "json" : overrides.outputFormat ?? "human"
398
+ colorEnabled,
399
+ outputFormat: cleanArgv.includes("--json") ? "json" : overrides.outputFormat ?? "human"
341
400
  }));
342
401
  }
343
- const contextOverrides = commandNeedsProjectRoot(spec) && resolved.resolvedProjectRoot ? { ...overrides, cwd: resolved.cwd } : overrides;
344
- return cliOperationsSdk.run(argv, contextOverrides);
402
+ const contextOverrides = commandNeedsProjectRoot(spec) && resolved.resolvedProjectRoot ? { ...overrides, cwd: resolved.cwd, colorEnabled } : { ...overrides, colorEnabled };
403
+ return cliOperationsSdk.run(cleanArgv, contextOverrides);
345
404
  }
346
405
  export {
347
406
  TreeseedOperationsSdk,
407
+ colorizeTreeseedCliOutput,
348
408
  createTreeseedCommandContext,
349
409
  executeTreeseedCommand,
350
410
  resolveTreeseedCommandCwd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Operator-facing Treeseed CLI package.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -44,7 +44,7 @@
44
44
  "release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
45
45
  },
46
46
  "dependencies": {
47
- "@treeseed/sdk": "^0.6.1",
47
+ "@treeseed/sdk": "^0.6.3",
48
48
  "ink": "^7.0.0",
49
49
  "react": "^19.2.5"
50
50
  },