@treeseed/cli 0.6.35 → 0.6.36

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.
@@ -0,0 +1,2 @@
1
+ import type { TreeseedCommandHandler } from '../types.js';
2
+ export declare const handleTagsCleanup: TreeseedCommandHandler;
@@ -0,0 +1,42 @@
1
+ import { guidedResult } from "./utils.js";
2
+ import { createWorkflowSdk, renderWorkflowNextSteps, workflowErrorResult } from "./workflow.js";
3
+ function parseIncludePackages(value) {
4
+ if (typeof value !== "string" || value.trim().length === 0) return void 0;
5
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
6
+ }
7
+ const handleTagsCleanup = async (invocation, context) => {
8
+ try {
9
+ const result = await createWorkflowSdk(context).tagsCleanup({
10
+ includePackages: parseIncludePackages(invocation.args.includePackages),
11
+ branchScope: typeof invocation.args.branchScope === "string" ? invocation.args.branchScope : void 0,
12
+ plan: invocation.args.plan === true || invocation.args.dryRun === true,
13
+ dryRun: invocation.args.dryRun === true
14
+ });
15
+ const payload = result.payload;
16
+ return guidedResult({
17
+ command: invocation.commandName || "tags:cleanup",
18
+ summary: result.executionMode === "plan" ? "Treeseed dev tag cleanup plan ready." : "Treeseed dev tag cleanup completed.",
19
+ facts: [
20
+ { label: "Status", value: payload.status ?? (result.executionMode === "plan" ? "planned" : "completed") },
21
+ { label: "Branch scope", value: payload.branchScope ?? "all" },
22
+ { label: "Included packages", value: (payload.includePackages ?? []).join(", ") || "all" },
23
+ { label: "Candidate tags", value: String(payload.candidateCount ?? 0) },
24
+ { label: "Cleaned tags", value: String(payload.cleanedCount ?? 0) },
25
+ { label: "Skipped tags", value: String(payload.skippedCount ?? 0) }
26
+ ],
27
+ sections: [
28
+ {
29
+ title: "Repositories",
30
+ lines: (payload.repos ?? []).map((repo) => `- ${repo.name ?? "repo"}: candidates ${repo.candidateCount ?? 0}, cleaned ${repo.cleanedCount ?? 0}, skipped ${repo.skippedCount ?? 0}`)
31
+ }
32
+ ],
33
+ nextSteps: renderWorkflowNextSteps(result),
34
+ report: result
35
+ });
36
+ } catch (error) {
37
+ return workflowErrorResult(error);
38
+ }
39
+ };
40
+ export {
41
+ handleTagsCleanup
42
+ };
@@ -0,0 +1,2 @@
1
+ import type { TreeseedCommandHandler } from '../types.js';
2
+ export declare const handleToolWrapper: TreeseedCommandHandler;
@@ -0,0 +1,77 @@
1
+ import {
2
+ createTreeseedManagedToolEnv,
3
+ resolveTreeseedLaunchEnvironment,
4
+ resolveTreeseedToolCommand
5
+ } from "@treeseed/sdk/workflow-support";
6
+ import { workflowErrorResult } from "./workflow.js";
7
+ const WRAPPED_TOOLS = /* @__PURE__ */ new Set(["gh", "railway", "wrangler"]);
8
+ const ENVIRONMENT_SCOPES = /* @__PURE__ */ new Set(["local", "staging", "prod"]);
9
+ function wrappedToolName(value) {
10
+ if (WRAPPED_TOOLS.has(value)) {
11
+ return value;
12
+ }
13
+ throw new Error(`Unsupported Treeseed tool wrapper: ${value}`);
14
+ }
15
+ function wrapperScope(value) {
16
+ if (typeof value === "string" && ENVIRONMENT_SCOPES.has(value)) {
17
+ return value;
18
+ }
19
+ return "staging";
20
+ }
21
+ const handleToolWrapper = (invocation, context) => {
22
+ try {
23
+ const toolName = wrappedToolName(invocation.commandName);
24
+ const scope = wrapperScope(invocation.args.environment);
25
+ const launchEnv = resolveTreeseedLaunchEnvironment({
26
+ tenantRoot: context.cwd,
27
+ scope,
28
+ baseEnv: { ...process.env, ...context.env ?? {} }
29
+ });
30
+ const managedEnv = createTreeseedManagedToolEnv({
31
+ ...process.env,
32
+ ...context.env ?? {},
33
+ ...launchEnv,
34
+ TREESEED_ACTIVE_ENVIRONMENT: scope
35
+ });
36
+ const resolved = resolveTreeseedToolCommand(toolName, { env: managedEnv });
37
+ if (!resolved) {
38
+ return {
39
+ exitCode: 1,
40
+ stderr: [
41
+ `Treeseed managed tool \`${toolName}\` is not installed or could not be resolved.`,
42
+ "Run `npx trsd install --json` and retry the wrapper command."
43
+ ],
44
+ report: {
45
+ command: toolName,
46
+ ok: false,
47
+ scope,
48
+ error: `Unable to resolve ${toolName}.`
49
+ }
50
+ };
51
+ }
52
+ const targetArgs = invocation.positionals;
53
+ const result = context.spawn(resolved.command, [...resolved.argsPrefix, ...targetArgs], {
54
+ cwd: context.cwd,
55
+ env: managedEnv,
56
+ stdio: "inherit"
57
+ });
58
+ return {
59
+ exitCode: result.status ?? 1,
60
+ suppressJsonResult: true,
61
+ report: {
62
+ command: toolName,
63
+ ok: (result.status ?? 1) === 0,
64
+ scope,
65
+ executable: resolved.command,
66
+ binaryPath: resolved.binaryPath,
67
+ argsPrefix: resolved.argsPrefix,
68
+ args: targetArgs
69
+ }
70
+ };
71
+ } catch (error) {
72
+ return workflowErrorResult(error);
73
+ }
74
+ };
75
+ export {
76
+ handleToolWrapper
77
+ };
@@ -114,7 +114,7 @@ function genericWarnings(spec) {
114
114
  if (spec.name === "release") {
115
115
  warnings.push("Release operations assume staging is the source of truth for what should move to production. Treat version bumps and promotion as deliberate release events.");
116
116
  }
117
- if (spec.executionMode === "passthrough") {
117
+ if (spec.group === "Passthrough") {
118
118
  warnings.push("This command forwards to another CLI surface. Flags after `--` or positional forwarding may follow the target tool semantics rather than Treeseed-specific semantics.");
119
119
  }
120
120
  return warnings;
@@ -148,6 +148,9 @@ function mergeHelpSpec(metadata, overlay, spec) {
148
148
  };
149
149
  }
150
150
  const PASS_THROUGH_ARGS = (invocation) => ({ args: invocation.rawArgs });
151
+ const TOOL_WRAPPER_OPTIONS = [
152
+ { name: "environment", flags: "--environment <scope>", description: "Treeseed environment scope used to decrypt and inject provider credentials.", kind: "enum", values: ["local", "staging", "prod"] }
153
+ ];
151
154
  const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
152
155
  ["status", command({
153
156
  options: [
@@ -456,6 +459,43 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
456
459
  executionMode: "handler",
457
460
  handlerName: "stage"
458
461
  })],
462
+ ["tags:cleanup", command({
463
+ usage: "treeseed tags:cleanup [--plan|--dry-run] [--include-packages <names>] [--branch-scope <staging|preview|all>] [--json]",
464
+ options: [
465
+ { name: "plan", flags: "--plan", description: "Compute stale dev tag cleanup without deleting tags.", kind: "boolean" },
466
+ { name: "dryRun", flags: "--dry-run", description: "Alias for --plan.", kind: "boolean" },
467
+ { name: "includePackages", flags: "--include-packages <names>", description: "Comma-separated package names to inspect.", kind: "string" },
468
+ { name: "branchScope", flags: "--branch-scope <scope>", description: "Limit cleanup to staging tags, preview tags, or all dev branch tags.", kind: "enum", values: ["staging", "preview", "all"] },
469
+ { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
470
+ ],
471
+ examples: ["treeseed tags:cleanup --plan", "treeseed tags:cleanup --branch-scope staging", "treeseed tags:cleanup --include-packages @treeseed/sdk,@treeseed/core --json"],
472
+ notes: ["Only deletes Treeseed-managed package dev tags older than each package current version line."],
473
+ help: {
474
+ workflowPosition: "maintain workspace",
475
+ longSummary: [
476
+ "Tags cleanup removes stale Treeseed-managed package dev tags from staging and preview branch saves."
477
+ ],
478
+ whenToUse: [
479
+ "Use this when old staging or preview package tags have accumulated after many saves and releases.",
480
+ "Use `--plan` first to inspect exactly which tags would be removed."
481
+ ],
482
+ outcomes: [
483
+ "Plans or deletes stale local and origin package dev tags.",
484
+ "Preserves current-version dev tags, active dependency references, stable release tags, and non-Treeseed tags."
485
+ ],
486
+ examples: [
487
+ example("treeseed tags:cleanup --plan", "Inspect cleanup", "Show stale tag candidates without deleting anything."),
488
+ example("treeseed tags:cleanup --branch-scope preview", "Clean preview tags", "Delete stale non-staging branch dev tags while leaving staging dev tags alone."),
489
+ example("treeseed tags:cleanup --json", "Automate cleanup", "Emit structured per-repository cleanup counts and skipped reasons.")
490
+ ],
491
+ relatedDetails: [
492
+ related("release", "Release also runs safe dev tag cleanup after successful stable promotion."),
493
+ related("status", "Use `status` to inspect workspace alignment before maintenance.")
494
+ ]
495
+ },
496
+ executionMode: "handler",
497
+ handlerName: "tags:cleanup"
498
+ })],
459
499
  ["resume", command({
460
500
  arguments: [{ name: "run-id", description: "Interrupted workflow run id to resume.", required: true }],
461
501
  options: [{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }],
@@ -1217,6 +1257,78 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1217
1257
  ["starlight:patch", command({ examples: ["treeseed starlight:patch"], executionMode: "adapter" })]
1218
1258
  ]);
1219
1259
  const CLI_ONLY_OPERATION_SPECS = [
1260
+ {
1261
+ id: "tools.gh",
1262
+ name: "gh",
1263
+ aliases: [],
1264
+ group: "Passthrough",
1265
+ summary: "Run the managed GitHub CLI with Treeseed environment credentials.",
1266
+ description: "Decrypt Treeseed machine configuration for the selected environment and pass it to the managed GitHub CLI.",
1267
+ provider: "default",
1268
+ related: ["tools", "install", "config"],
1269
+ usage: "treeseed gh [--environment staging] -- <gh-args>",
1270
+ arguments: [{ name: "args", description: "Arguments forwarded to GitHub CLI.", required: false }],
1271
+ options: TOOL_WRAPPER_OPTIONS,
1272
+ examples: ["treeseed gh --environment staging -- run list --limit 5", "treeseed gh -- repo view"],
1273
+ help: {
1274
+ longSummary: ["The GitHub wrapper resolves the Treeseed-managed `gh` executable, decrypts scoped machine configuration, and passes the resulting GitHub token only to the child process environment."],
1275
+ whenToUse: ["Use this when provider auth lives in Treeseed machine config rather than your shell environment."],
1276
+ beforeYouRun: ["Run from a Treeseed project. Use `--environment staging` unless you intentionally need local or production credentials."],
1277
+ automationNotes: ["Use `--` before target CLI flags when a flag could be parsed by Treeseed itself. The wrapper does not print decrypted secrets."]
1278
+ },
1279
+ helpVisible: true,
1280
+ helpFeatured: false,
1281
+ executionMode: "handler",
1282
+ handlerName: "gh"
1283
+ },
1284
+ {
1285
+ id: "tools.railway",
1286
+ name: "railway",
1287
+ aliases: [],
1288
+ group: "Passthrough",
1289
+ summary: "Run the managed Railway CLI with Treeseed environment credentials.",
1290
+ description: "Decrypt Treeseed machine configuration for the selected environment and pass it to the managed Railway CLI.",
1291
+ provider: "default",
1292
+ related: ["tools", "install", "config"],
1293
+ usage: "treeseed railway [--environment staging] -- <railway-args>",
1294
+ arguments: [{ name: "args", description: "Arguments forwarded to Railway CLI.", required: false }],
1295
+ options: TOOL_WRAPPER_OPTIONS,
1296
+ examples: ["treeseed railway --environment staging -- whoami", "treeseed railway --environment staging -- status"],
1297
+ help: {
1298
+ longSummary: ["The Railway wrapper resolves the Treeseed-managed Railway executable, decrypts scoped machine configuration, and passes the resulting Railway token only to the child process environment."],
1299
+ whenToUse: ["Use this to debug Railway projects and service builds with the same decrypted `RAILWAY_API_TOKEN` Treeseed deploys use."],
1300
+ beforeYouRun: ["Run from a Treeseed project. Use `--environment staging` when inspecting staging deployments."],
1301
+ automationNotes: ["Use `--` before target CLI flags when a flag could be parsed by Treeseed itself. The wrapper does not print decrypted secrets."]
1302
+ },
1303
+ helpVisible: true,
1304
+ helpFeatured: false,
1305
+ executionMode: "handler",
1306
+ handlerName: "railway"
1307
+ },
1308
+ {
1309
+ id: "tools.wrangler",
1310
+ name: "wrangler",
1311
+ aliases: [],
1312
+ group: "Passthrough",
1313
+ summary: "Run the managed Wrangler CLI with Treeseed environment credentials.",
1314
+ description: "Decrypt Treeseed machine configuration for the selected environment and pass it to the managed Wrangler CLI.",
1315
+ provider: "default",
1316
+ related: ["tools", "install", "config"],
1317
+ usage: "treeseed wrangler [--environment staging] -- <wrangler-args>",
1318
+ arguments: [{ name: "args", description: "Arguments forwarded to Wrangler CLI.", required: false }],
1319
+ options: TOOL_WRAPPER_OPTIONS,
1320
+ examples: ["treeseed wrangler --environment staging -- whoami", "treeseed wrangler --environment staging -- d1 list"],
1321
+ help: {
1322
+ longSummary: ["The Wrangler wrapper resolves the Treeseed-managed Wrangler executable, decrypts scoped machine configuration, and passes the resulting Cloudflare token and account settings only to the child process environment."],
1323
+ whenToUse: ["Use this to debug Cloudflare resources with the same decrypted `CLOUDFLARE_API_TOKEN` and account settings Treeseed deploys use."],
1324
+ beforeYouRun: ["Run from a Treeseed project. Use `--environment staging` when inspecting staging resources."],
1325
+ automationNotes: ["Use `--` before target CLI flags when a flag could be parsed by Treeseed itself. The wrapper does not print decrypted secrets."]
1326
+ },
1327
+ helpVisible: true,
1328
+ helpFeatured: false,
1329
+ executionMode: "handler",
1330
+ handlerName: "wrangler"
1331
+ },
1220
1332
  {
1221
1333
  id: "market.registry",
1222
1334
  name: "market",
@@ -21,6 +21,10 @@ function parseTreeseedInvocation(command, argv) {
21
21
  const [flag, inlineValue] = current.split("=", 2);
22
22
  const spec = byFlag.get(flag);
23
23
  if (!spec) {
24
+ if (command.group === "Passthrough") {
25
+ positionals.push(current);
26
+ continue;
27
+ }
24
28
  throw new Error(`Unknown option: ${flag}`);
25
29
  }
26
30
  if (spec.kind === "boolean") {
@@ -18,6 +18,7 @@ export declare const COMMAND_HANDLERS: {
18
18
  readonly tasks: import("./operations-types.js").TreeseedCommandHandler;
19
19
  readonly switch: import("./operations-types.js").TreeseedCommandHandler;
20
20
  readonly stage: import("./operations-types.js").TreeseedCommandHandler;
21
+ readonly 'tags:cleanup': import("./operations-types.js").TreeseedCommandHandler;
21
22
  readonly resume: import("./operations-types.js").TreeseedCommandHandler;
22
23
  readonly recover: import("./operations-types.js").TreeseedCommandHandler;
23
24
  readonly export: import("./operations-types.js").TreeseedCommandHandler;
@@ -28,6 +29,9 @@ export declare const COMMAND_HANDLERS: {
28
29
  readonly teams: import("./operations-types.js").TreeseedCommandHandler;
29
30
  readonly projects: import("./operations-types.js").TreeseedCommandHandler;
30
31
  readonly packs: import("./operations-types.js").TreeseedCommandHandler;
32
+ readonly gh: import("./operations-types.js").TreeseedCommandHandler;
33
+ readonly railway: import("./operations-types.js").TreeseedCommandHandler;
34
+ readonly wrangler: import("./operations-types.js").TreeseedCommandHandler;
31
35
  readonly 'secrets:status': import("./operations-types.js").TreeseedCommandHandler;
32
36
  readonly 'secrets:unlock': import("./operations-types.js").TreeseedCommandHandler;
33
37
  readonly 'secrets:lock': import("./operations-types.js").TreeseedCommandHandler;
@@ -23,6 +23,7 @@ import { handleMarket } from "./handlers/market.js";
23
23
  import { handleTeams } from "./handlers/teams.js";
24
24
  import { handleProjects } from "./handlers/projects.js";
25
25
  import { handlePacks } from "./handlers/packs.js";
26
+ import { handleToolWrapper } from "./handlers/tool-wrapper.js";
26
27
  import {
27
28
  handleSecretsLock,
28
29
  handleSecretsMigrateKey,
@@ -34,6 +35,7 @@ import {
34
35
  import { handleTasks } from "./handlers/tasks.js";
35
36
  import { handleSwitch } from "./handlers/switch.js";
36
37
  import { handleStage } from "./handlers/stage.js";
38
+ import { handleTagsCleanup } from "./handlers/tags-cleanup.js";
37
39
  import { handleExport } from "./handlers/export.js";
38
40
  import { handleResume } from "./handlers/resume.js";
39
41
  import { handleRecover } from "./handlers/recover.js";
@@ -57,6 +59,7 @@ const COMMAND_HANDLERS = {
57
59
  tasks: handleTasks,
58
60
  switch: handleSwitch,
59
61
  stage: handleStage,
62
+ "tags:cleanup": handleTagsCleanup,
60
63
  resume: handleResume,
61
64
  recover: handleRecover,
62
65
  [workspaceCommand("status")]: handleWorkspace,
@@ -70,6 +73,9 @@ const COMMAND_HANDLERS = {
70
73
  teams: handleTeams,
71
74
  projects: handleProjects,
72
75
  packs: handlePacks,
76
+ gh: handleToolWrapper,
77
+ railway: handleToolWrapper,
78
+ wrangler: handleToolWrapper,
73
79
  "secrets:status": handleSecretsStatus,
74
80
  "secrets:unlock": handleSecretsUnlock,
75
81
  "secrets:lock": handleSecretsLock,
@@ -14,6 +14,14 @@ const require2 = createRequire(import.meta.url);
14
14
  function isHelpFlag(value) {
15
15
  return value === "--help" || value === "-h";
16
16
  }
17
+ function shouldRenderCommandHelp(spec, argv) {
18
+ if (spec.group !== "Passthrough") {
19
+ return argv.some(isHelpFlag);
20
+ }
21
+ const separatorIndex = argv.indexOf("--");
22
+ const treeseedArgs = separatorIndex >= 0 ? argv.slice(0, separatorIndex) : argv;
23
+ return treeseedArgs.some(isHelpFlag);
24
+ }
17
25
  function isNoColorFlag(value) {
18
26
  return value === "--no-color";
19
27
  }
@@ -294,7 +302,7 @@ class TreeseedOperationsSdk {
294
302
  lines.push("Run `treeseed help` to see the full command list.");
295
303
  return writeTreeseedResult({ exitCode: 1, stderr: [lines.join("\n")] }, context);
296
304
  }
297
- if (argv.some(isHelpFlag)) {
305
+ if (shouldRenderCommandHelp(spec, argv)) {
298
306
  if (shouldUseInkHelp(context)) {
299
307
  const helpExitCode = await renderTreeseedHelpInk(spec.name, context);
300
308
  if (typeof helpExitCode === "number") {
@@ -376,7 +384,7 @@ async function executeTreeseedCommand(commandName, argv, context) {
376
384
  if (!spec) {
377
385
  return cliOperationsSdk.executeOperation({ commandName, argv: cleanArgv }, commandContext);
378
386
  }
379
- if (cleanArgv.some(isHelpFlag)) {
387
+ if (shouldRenderCommandHelp(spec, cleanArgv)) {
380
388
  return cliOperationsSdk.executeOperation({ commandName, argv: cleanArgv }, commandContext);
381
389
  }
382
390
  const resolved = resolveTreeseedCommandCwd(spec, commandContext.cwd);
@@ -402,7 +410,7 @@ async function runTreeseedCli(argv, overrides = {}) {
402
410
  if (!spec) {
403
411
  return cliOperationsSdk.run(cleanArgv, { ...overrides, colorEnabled });
404
412
  }
405
- if (cleanArgv.slice(1).some(isHelpFlag)) {
413
+ if (shouldRenderCommandHelp(spec, cleanArgv.slice(1))) {
406
414
  return cliOperationsSdk.run(cleanArgv, { ...overrides, colorEnabled });
407
415
  }
408
416
  const baseCwd = overrides.cwd ?? process.cwd();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.6.35",
3
+ "version": "0.6.36",
4
4
  "description": "Operator-facing Treeseed CLI package.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -45,7 +45,7 @@
45
45
  "release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@treeseed/sdk": "0.6.39",
48
+ "@treeseed/sdk": "0.6.40",
49
49
  "ink": "^7.0.0",
50
50
  "react": "^19.2.5"
51
51
  },