@treeseed/cli 0.8.13 → 0.8.15

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.
@@ -7,6 +7,26 @@ import { createMarketClientForInvocation, marketAuthRoot } from "./market-utils.
7
7
  function sleep(ms) {
8
8
  return new Promise((resolve) => setTimeout(resolve, ms));
9
9
  }
10
+ function localWebApprovalUrlFromApiUrl(value, profileId) {
11
+ const explicit = process.env.TREESEED_SITE_URL?.trim() || process.env.BETTER_AUTH_URL?.trim();
12
+ if (explicit && profileId === "local") {
13
+ const url2 = new URL(value);
14
+ return new URL(`${url2.pathname}${url2.search}${url2.hash}`, explicit.replace(/\/+$/u, "")).toString();
15
+ }
16
+ const url = new URL(value);
17
+ if (profileId === "local" && (url.hostname === "127.0.0.1" || url.hostname === "localhost") && url.port === "3000") {
18
+ url.port = "4321";
19
+ return url.toString();
20
+ }
21
+ return value;
22
+ }
23
+ function approvalUrlForDisplay(value, profileId) {
24
+ try {
25
+ return localWebApprovalUrlFromApiUrl(value, profileId);
26
+ } catch {
27
+ return value;
28
+ }
29
+ }
10
30
  const handleAuthLogin = async (invocation, context) => {
11
31
  try {
12
32
  const tenantRoot = marketAuthRoot(context);
@@ -15,8 +35,9 @@ const handleAuthLogin = async (invocation, context) => {
15
35
  clientName: "treeseed-cli",
16
36
  scopes: ["auth:me", "market"]
17
37
  });
38
+ const approvalUrl = approvalUrlForDisplay(started.verificationUriComplete, profile.id);
18
39
  if (context.outputFormat !== "json") {
19
- context.write(`Open ${started.verificationUriComplete}`, "stdout");
40
+ context.write(`Open ${approvalUrl}`, "stdout");
20
41
  context.write(`User code: ${started.userCode}`, "stdout");
21
42
  context.write("Waiting for approval...", "stdout");
22
43
  }
@@ -42,6 +42,7 @@ function resolveCoreDevEntrypoint(cwd) {
42
42
  }
43
43
  const handleDev = async (invocation, context) => {
44
44
  try {
45
+ const managerMode = invocation.commandName === "dev:manager";
45
46
  const feedback = typeof invocation.args.feedback === "string" ? invocation.args.feedback : void 0;
46
47
  const watch = feedback !== "off";
47
48
  const passthroughArgs = [];
@@ -56,8 +57,14 @@ const handleDev = async (invocation, context) => {
56
57
  passthroughArgs.push(flag);
57
58
  }
58
59
  };
59
- forwardStringOption("surface", "--surface");
60
- forwardStringOption("surfaces", "--surfaces");
60
+ if (managerMode) {
61
+ const explicitSurfaces = typeof invocation.args.surfaces === "string" && invocation.args.surfaces.trim() ? invocation.args.surfaces.trim() : typeof invocation.args.surface === "string" && invocation.args.surface.trim() ? invocation.args.surface.trim() : null;
62
+ const surfaces = explicitSurfaces ?? (invocation.args.withWorker === true ? "manager,worker" : "manager");
63
+ passthroughArgs.push("--surfaces", surfaces);
64
+ } else {
65
+ forwardStringOption("surface", "--surface");
66
+ forwardStringOption("surfaces", "--surfaces");
67
+ }
61
68
  forwardStringOption("host", "--host");
62
69
  forwardStringOption("port", "--port");
63
70
  forwardStringOption("apiHost", "--api-host");
@@ -69,6 +76,19 @@ const handleDev = async (invocation, context) => {
69
76
  forwardBooleanOption("plan", "--plan");
70
77
  forwardBooleanOption("reset", "--reset");
71
78
  forwardBooleanOption("json", "--json");
79
+ const docsAutomationMode = typeof invocation.args.docsAutomation === "string" ? invocation.args.docsAutomation.trim() : "";
80
+ const workdayId = typeof invocation.args.workdayId === "string" ? invocation.args.workdayId.trim() : "";
81
+ const capacityBudget = typeof invocation.args.capacityBudget === "string" ? invocation.args.capacityBudget.trim() : "";
82
+ const approvalPolicy = typeof invocation.args.approvalPolicy === "string" ? invocation.args.approvalPolicy.trim() : "";
83
+ const devManagerEnv = managerMode ? {
84
+ TREESEED_DOCS_AUTOMATION_MODE: docsAutomationMode || "on",
85
+ ...workdayId ? { TREESEED_WORKDAY_ID: workdayId } : {},
86
+ ...capacityBudget ? {
87
+ TREESEED_CAPACITY_BUDGET: capacityBudget,
88
+ TREESEED_WORKDAY_TASK_CREDIT_BUDGET: capacityBudget
89
+ } : {},
90
+ TREESEED_APPROVAL_POLICY: approvalPolicy || "manual"
91
+ } : {};
72
92
  const workspaceRoot = findNearestTreeseedWorkspaceRoot(context.cwd);
73
93
  const workspaceLinksMode = typeof invocation.args.workspaceLinks === "string" ? invocation.args.workspaceLinks : void 0;
74
94
  const workspaceLinks = workspaceRoot ? ensureLocalWorkspaceLinks(workspaceRoot, { env: context.env, mode: workspaceLinksMode }) : null;
@@ -82,7 +102,7 @@ const handleDev = async (invocation, context) => {
82
102
  env: resolveTreeseedLaunchEnvironment({
83
103
  tenantRoot: context.cwd,
84
104
  scope: "local",
85
- baseEnv: { ...process.env, ...context.env ?? {} }
105
+ baseEnv: { ...process.env, ...context.env ?? {}, ...devManagerEnv }
86
106
  }),
87
107
  stdio: "inherit"
88
108
  });
@@ -91,10 +111,18 @@ const handleDev = async (invocation, context) => {
91
111
  suppressJsonResult: invocation.args.json === true,
92
112
  report: {
93
113
  command: "dev",
114
+ alias: managerMode ? "dev:manager" : invocation.commandName,
94
115
  ok: (result.status ?? 1) === 0,
95
116
  watch,
96
117
  executable: resolved.command,
97
118
  args,
119
+ docsAutomation: managerMode ? {
120
+ mode: docsAutomationMode || "on",
121
+ workdayId: workdayId || null,
122
+ capacityBudget: capacityBudget || null,
123
+ approvalPolicy: approvalPolicy || "manual",
124
+ withWorker: invocation.args.withWorker === true
125
+ } : void 0,
98
126
  workspaceLinks
99
127
  }
100
128
  };
@@ -0,0 +1,2 @@
1
+ import type { TreeseedCommandHandler } from '../types.js';
2
+ export declare const handleSeed: TreeseedCommandHandler;
@@ -0,0 +1,321 @@
1
+ import { pathToFileURL } from "node:url";
2
+ import { dirname, resolve } from "node:path";
3
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
+ import { formatSeedDiagnostics, formatSeedPlan, loadAndPlanSeed } from "@treeseed/sdk/seeds";
5
+ import { MarketApiError } from "@treeseed/sdk/market-client";
6
+ import { createMarketClientForInvocation, marketAuthRoot, marketSelector } from "./market-utils.js";
7
+ import { resolveMarketProfile, resolveMarketSession } from "@treeseed/sdk/market-client";
8
+ async function loadLocalSeedModule(projectRoot) {
9
+ const apiModulePath = resolve(projectRoot, "src", "lib", "market", "seeds", "local-api.js");
10
+ const moduleUrl = pathToFileURL(existsSync(apiModulePath) ? apiModulePath : resolve(projectRoot, "src", "lib", "market", "seeds", "apply.js")).href;
11
+ return await import(moduleUrl);
12
+ }
13
+ function requireLocalSeedSession(invocation, context) {
14
+ const selector = marketSelector(invocation) ?? "local";
15
+ const profile = resolveMarketProfile(selector);
16
+ const session = resolveMarketSession(marketAuthRoot(context), profile.id);
17
+ if (!session?.accessToken) {
18
+ throw new Error(`Not logged in to market "${profile.id}". Run treeseed auth:login --market ${profile.id}.`);
19
+ }
20
+ return { profile, session };
21
+ }
22
+ function seedRequestBody(invocation) {
23
+ return {
24
+ ...typeof invocation.args.environments === "string" ? { environments: invocation.args.environments.split(",").map((entry) => entry.trim()).filter(Boolean) } : {},
25
+ ...typeof invocation.args.approvalRequest === "string" ? { approvalRequestId: invocation.args.approvalRequest } : {}
26
+ };
27
+ }
28
+ function planFromRemotePayload(payload) {
29
+ return {
30
+ ok: payload.ok !== false,
31
+ seed: String(payload.seed ?? ""),
32
+ version: 1,
33
+ mode: payload.mode === "apply" ? "apply" : "plan",
34
+ environments: Array.isArray(payload.environments) ? payload.environments : [],
35
+ summary: payload.summary,
36
+ actions: Array.isArray(payload.actions) ? payload.actions : [],
37
+ diagnostics: Array.isArray(payload.diagnostics) ? payload.diagnostics : [],
38
+ manifestPath: ""
39
+ };
40
+ }
41
+ function remoteSeedResult(payload, command, exitCode = 0) {
42
+ const plan = planFromRemotePayload(payload);
43
+ const result = payload.result && typeof payload.result === "object" ? payload.result : null;
44
+ return {
45
+ exitCode,
46
+ stdout: exitCode === 0 ? [
47
+ ...formatSeedPlan(plan),
48
+ ...result ? [
49
+ "",
50
+ "Apply:",
51
+ ` created: ${plan.summary.create}`,
52
+ ` updated: ${plan.summary.update}`,
53
+ ` unchanged: ${plan.summary.unchanged}`,
54
+ ` skipped: ${plan.summary.skip}`,
55
+ ` failed: ${plan.summary.error}`,
56
+ ...formatCapacityProviderKeyNotes(result)
57
+ ] : []
58
+ ] : [],
59
+ stderr: exitCode === 0 ? [] : [typeof payload.error === "string" ? payload.error : "Seed operation failed."],
60
+ report: {
61
+ ...payload,
62
+ command,
63
+ ok: exitCode === 0
64
+ }
65
+ };
66
+ }
67
+ function formatCapacityProviderKeyNotes(result) {
68
+ const capacityProviderKeys = result.capacityProviderKeys && typeof result.capacityProviderKeys === "object" ? result.capacityProviderKeys : null;
69
+ const created = Array.isArray(capacityProviderKeys?.created) ? capacityProviderKeys.created : [];
70
+ if (created.length === 0) return [];
71
+ return [
72
+ "",
73
+ "Provider security codes:",
74
+ ...created.map((entry) => ` ${String(entry.providerName ?? entry.providerKey ?? entry.providerId)} (${String(entry.keyPrefix ?? "new key")}): ${String(entry.plaintextKey ?? "created")}`),
75
+ " Copy these now. TreeSeed will not show the plaintext codes again."
76
+ ];
77
+ }
78
+ function remoteSeedError(error, command) {
79
+ if (error instanceof MarketApiError) {
80
+ const payload = error.payload && typeof error.payload === "object" ? error.payload : { error: error.message };
81
+ const blocked = error.status === 409 || payload.result?.blocked === true;
82
+ const auth = error.status === 401 || error.status === 403;
83
+ return remoteSeedResult(payload, command, blocked ? 2 : auth ? 4 : 3);
84
+ }
85
+ if (error instanceof Error && /not logged in|authentication|permission denied/iu.test(error.message)) {
86
+ return {
87
+ exitCode: 4,
88
+ stderr: [error.message],
89
+ report: {
90
+ command,
91
+ ok: false,
92
+ error: error.message
93
+ }
94
+ };
95
+ }
96
+ throw error;
97
+ }
98
+ function writeSeedExportOutput(context, outputPath, yaml) {
99
+ const destination = resolve(context.cwd, outputPath);
100
+ mkdirSync(dirname(destination), { recursive: true });
101
+ writeFileSync(destination, yaml, "utf8");
102
+ return destination;
103
+ }
104
+ async function handleSeedExport(invocation, context) {
105
+ const seedName = invocation.positionals[1];
106
+ const team = typeof invocation.args.team === "string" ? invocation.args.team.trim() : "";
107
+ if (!seedName || !team) {
108
+ return {
109
+ exitCode: 1,
110
+ stderr: ["Usage: treeseed seed export <name> --team <team> [--output seeds/exported.yaml]"],
111
+ report: {
112
+ command: "seed export",
113
+ ok: false,
114
+ error: !seedName ? "Missing required export seed name." : "Missing required --team."
115
+ }
116
+ };
117
+ }
118
+ const body = {
119
+ name: seedName,
120
+ ...typeof invocation.args.environments === "string" ? { environments: invocation.args.environments.split(",").map((entry) => entry.trim()).filter(Boolean) } : {},
121
+ ...invocation.args.includePrivate === true ? { includePrivate: true } : {},
122
+ ...invocation.args.includeArtifacts === true ? { includeArtifacts: true } : {}
123
+ };
124
+ let payload;
125
+ try {
126
+ if (typeof invocation.args.market === "string" || typeof invocation.args.host === "string") {
127
+ const { client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
128
+ payload = await client.exportSeed(team, body);
129
+ } else {
130
+ const { session } = requireLocalSeedSession(invocation, context);
131
+ const localModule = await loadLocalSeedModule(context.cwd);
132
+ const exporter = localModule.exportLocalSeedViaApiFromCli ?? localModule.exportSeedFromCli;
133
+ if (typeof exporter !== "function") {
134
+ throw new Error("Local seed export service is not available in this market project.");
135
+ }
136
+ payload = await exporter({
137
+ projectRoot: context.cwd,
138
+ seedName,
139
+ team,
140
+ environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
141
+ includePrivate: invocation.args.includePrivate === true,
142
+ includeArtifacts: invocation.args.includeArtifacts === true,
143
+ accessToken: session.accessToken,
144
+ env: context.env
145
+ });
146
+ }
147
+ } catch (error) {
148
+ return remoteSeedError(error, "seed export");
149
+ }
150
+ const yaml = typeof payload.yaml === "string" ? payload.yaml : "";
151
+ const outputPath = typeof invocation.args.output === "string" && invocation.args.output.trim() ? invocation.args.output.trim() : null;
152
+ const writtenPath = outputPath ? writeSeedExportOutput(context, outputPath, yaml) : null;
153
+ const ok = payload.ok !== false;
154
+ return {
155
+ exitCode: ok ? 0 : 1,
156
+ stdout: context.outputFormat === "json" ? [] : writtenPath ? [`Exported seed ${String(payload.seed ?? seedName)} to ${writtenPath}.`] : yaml.trimEnd().split("\n"),
157
+ stderr: ok ? [] : ["Seed export failed."],
158
+ report: {
159
+ ...payload,
160
+ command: "seed export",
161
+ outputPath: writtenPath
162
+ }
163
+ };
164
+ }
165
+ const handleSeed = async (invocation, context) => {
166
+ if (invocation.positionals[0] === "export") {
167
+ return handleSeedExport(invocation, context);
168
+ }
169
+ const seedName = invocation.positionals[0];
170
+ if (!seedName) {
171
+ return {
172
+ exitCode: 1,
173
+ stderr: ["Usage: treeseed seed <name> [--environments local,staging,prod] [--plan|--validate]"],
174
+ report: {
175
+ command: "seed",
176
+ ok: false,
177
+ error: "Missing required seed name."
178
+ }
179
+ };
180
+ }
181
+ const wantsApply = invocation.args.apply === true;
182
+ const wantsValidate = invocation.args.validate === true;
183
+ const mode = wantsApply ? "apply" : wantsValidate ? "validate" : "plan";
184
+ const planned = loadAndPlanSeed({
185
+ projectRoot: context.cwd,
186
+ seedName,
187
+ environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
188
+ mode
189
+ });
190
+ if (!planned.plan) {
191
+ return {
192
+ exitCode: 1,
193
+ stderr: formatSeedDiagnostics(planned.diagnostics),
194
+ report: {
195
+ command: "seed",
196
+ ok: false,
197
+ seed: seedName,
198
+ mode,
199
+ manifestPath: planned.manifestPath,
200
+ diagnostics: planned.diagnostics
201
+ }
202
+ };
203
+ }
204
+ const localAuth = planned.plan.environments.every((environment) => environment === "local") && !wantsValidate ? (() => {
205
+ try {
206
+ return requireLocalSeedSession(invocation, context);
207
+ } catch (error) {
208
+ return { error };
209
+ }
210
+ })() : null;
211
+ if (localAuth && "error" in localAuth) {
212
+ return remoteSeedError(localAuth.error, "seed");
213
+ }
214
+ if (!wantsApply && !wantsValidate && planned.plan.environments.length === 1 && planned.plan.environments[0] === "local") {
215
+ try {
216
+ const localModule = await loadLocalSeedModule(context.cwd);
217
+ const localPlanner = localModule.planLocalSeedViaApiFromCli ?? localModule.planLocalSeedFromCli;
218
+ if (typeof localPlanner === "function") {
219
+ const localPlanned = await localPlanner({
220
+ projectRoot: context.cwd,
221
+ seedName,
222
+ environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
223
+ mode,
224
+ accessToken: localAuth?.session.accessToken,
225
+ env: context.env
226
+ });
227
+ if (localPlanned.plan) {
228
+ planned.plan = localPlanned.plan;
229
+ }
230
+ }
231
+ } catch (error) {
232
+ if (error instanceof Error && /not logged in|authentication|permission denied/iu.test(error.message)) {
233
+ return remoteSeedError(error, "seed");
234
+ }
235
+ planned.plan.diagnostics.push({
236
+ severity: "warning",
237
+ code: "seed.local_state_unavailable",
238
+ message: "Local current state could not be loaded; falling back to manifest-only planning.",
239
+ path: "local"
240
+ });
241
+ }
242
+ }
243
+ if (wantsApply) {
244
+ if (planned.plan.environments.some((environment) => environment !== "local")) {
245
+ try {
246
+ const { client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
247
+ const payload = await client.applySeed(seedName, seedRequestBody(invocation));
248
+ return remoteSeedResult(payload, "seed");
249
+ } catch (error) {
250
+ return remoteSeedError(error, "seed");
251
+ }
252
+ }
253
+ const localModule = await loadLocalSeedModule(context.cwd);
254
+ const runner = localModule.applyLocalSeedViaApiFromCli ?? localModule.applyLocalSeedFromCli;
255
+ if (typeof runner !== "function") {
256
+ throw new Error("Local seed apply service is not available in this market project.");
257
+ }
258
+ const applied = await runner({
259
+ projectRoot: context.cwd,
260
+ seedName,
261
+ environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
262
+ plan: planned.plan,
263
+ accessToken: localAuth?.session.accessToken,
264
+ env: context.env
265
+ });
266
+ const message = "Local seed apply completed.";
267
+ return {
268
+ exitCode: 0,
269
+ stdout: context.outputFormat === "json" ? [] : [
270
+ ...formatSeedPlan(applied.plan),
271
+ "",
272
+ "Apply:",
273
+ ` created: ${applied.plan.summary.create}`,
274
+ ` updated: ${applied.plan.summary.update}`,
275
+ ` unchanged: ${applied.plan.summary.unchanged}`,
276
+ ` skipped: ${applied.plan.summary.skip}`,
277
+ ` failed: ${applied.plan.summary.error}`,
278
+ ...formatCapacityProviderKeyNotes(applied.result)
279
+ ],
280
+ report: {
281
+ ...applied.plan,
282
+ ok: true,
283
+ command: "seed",
284
+ result: {
285
+ message,
286
+ ...applied.result
287
+ }
288
+ }
289
+ };
290
+ }
291
+ if (wantsValidate) {
292
+ return {
293
+ exitCode: 0,
294
+ stdout: [`Seed ${planned.plan.seed} is valid for environments: ${planned.plan.environments.join(", ")}.`],
295
+ report: {
296
+ ...planned.plan,
297
+ command: "seed"
298
+ }
299
+ };
300
+ }
301
+ if (planned.plan.environments.some((environment) => environment !== "local")) {
302
+ try {
303
+ const { client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
304
+ const payload = await client.planSeed(seedName, seedRequestBody(invocation));
305
+ return remoteSeedResult(payload, "seed");
306
+ } catch (error) {
307
+ return remoteSeedError(error, "seed");
308
+ }
309
+ }
310
+ return {
311
+ exitCode: 0,
312
+ stdout: formatSeedPlan(planned.plan),
313
+ report: {
314
+ ...planned.plan,
315
+ command: "seed"
316
+ }
317
+ };
318
+ };
319
+ export {
320
+ handleSeed
321
+ };
@@ -30,13 +30,21 @@ const DEV_RUNTIME_OPTIONS = [
30
30
  { name: "managerPort", flags: "--manager-port <port>", description: "Port used for the local manager service URL.", kind: "string" },
31
31
  { name: "setup", flags: "--setup <mode>", description: "Control automatic local runtime setup.", kind: "enum", values: ["auto", "check", "off"] },
32
32
  { name: "feedback", flags: "--feedback <mode>", description: "Control live feedback, service restarts, and browser reload stamps.", kind: "enum", values: ["live", "restart", "off"] },
33
- { name: "open", flags: "--open <mode>", description: "Control whether dev opens the browser after readiness.", kind: "enum", values: ["auto", "on", "off"] },
33
+ { name: "open", flags: "--open <mode>", description: "Control whether dev opens the browser after readiness. Defaults to off; use --open on to launch it.", kind: "enum", values: ["auto", "on", "off"] },
34
34
  { name: "plan", flags: "--plan", description: "Print the dev runtime plan and exit without starting services.", kind: "boolean" },
35
35
  { name: "reset", flags: "--reset", description: "Clear local dev runtime state before setup, migrations, and service startup.", kind: "boolean" },
36
36
  { name: "json", flags: "--json", description: "Emit structured JSON or newline-delimited dev events.", kind: "boolean" },
37
37
  { name: "watch", flags: "--watch", description: "Enable live watch behavior. `dev` defaults to live feedback; this remains for compatibility.", kind: "boolean" },
38
38
  { name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] }
39
39
  ];
40
+ const DEV_MANAGER_OPTIONS = [
41
+ ...DEV_RUNTIME_OPTIONS,
42
+ { name: "docsAutomation", flags: "--docs-automation <mode>", description: "Control governed documentation automation for the local manager.", kind: "enum", values: ["on", "dry-run", "off"] },
43
+ { name: "withWorker", flags: "--with-worker", description: "Run the local worker alongside the manager in the same dev supervisor.", kind: "boolean" },
44
+ { name: "workdayId", flags: "--workday-id <id>", description: "Start or resume a specific local documentation automation workday.", kind: "string" },
45
+ { name: "capacityBudget", flags: "--capacity-budget <credits>", description: "Override the local workday task credit budget.", kind: "string" },
46
+ { name: "approvalPolicy", flags: "--approval-policy <policy>", description: "Set the local docs automation approval policy.", kind: "enum", values: ["manual", "low-risk-auto"] }
47
+ ];
40
48
  function genericWorkflowPosition(spec) {
41
49
  if (spec.group === "Workflow") {
42
50
  if (spec.name === "switch") return "start work";
@@ -718,6 +726,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
718
726
  longSummary: ["Auth:login authenticates the CLI against the configured Treeseed API so later provider-aware and remote-aware workflows can run without missing-credential failures."],
719
727
  examples: [
720
728
  example("treeseed auth:login", "Log in with the default host", "Authenticate the CLI against the configured default Treeseed API host."),
729
+ example("treeseed auth:login --market local", "Log in to local dev", "Authenticate against the local Treeseed API at TREESEED_MARKET_API_BASE_URL or http://127.0.0.1:3000."),
721
730
  example("treeseed auth:login --host production", "Target a specific host id", "Override the configured default host for this login session."),
722
731
  example("treeseed auth:login --json", "Automate auth workflows", "Emit structured auth results where supported for scripts and agents.")
723
732
  ]
@@ -1163,7 +1172,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1163
1172
  })],
1164
1173
  ["dev", command({
1165
1174
  options: DEV_RUNTIME_OPTIONS,
1166
- examples: ["treeseed dev", "treeseed dev --reset", "treeseed dev --reset --plan --json", "treeseed dev --surfaces web,api --plan --json", "treeseed dev --surface web --port 4322 --open off"],
1175
+ examples: ["treeseed dev", "treeseed dev --reset", "treeseed dev --reset --plan --json", "treeseed dev --surfaces web,api --plan --json", "treeseed dev --surface web --port 4322"],
1167
1176
  help: {
1168
1177
  longSummary: [
1169
1178
  "Dev starts the unified local Treeseed runtime as a foreground supervisor so you can work against the integrated web, API, manager, worker, and supporting local surfaces.",
@@ -1173,6 +1182,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1173
1182
  "Run from the tenant or workspace root you want to develop.",
1174
1183
  "Use `--plan --json` when you want to inspect selected surfaces, commands, setup steps, readiness checks, watched paths, and restart policy without starting services.",
1175
1184
  "Use `--reset` when you want a fresh local D1 database, Mailpit inbox, generated worker bundle, and Wrangler temp output without deleting configuration.",
1185
+ "Dev prints the local web URL after readiness; it does not open a browser unless you pass `--open on` or `--open auto`.",
1176
1186
  "Keep the foreground process running while you test. Press Ctrl+C to stop the supervised stack and free the local ports."
1177
1187
  ],
1178
1188
  examples: [
@@ -1183,12 +1193,13 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1183
1193
  example("treeseed dev --surfaces web,api,worker --plan --json", "Inspect selected surfaces", "Plan a comma-separated set of local surfaces without starting the supervisor."),
1184
1194
  example("treeseed dev --surface api --plan --json", "Inspect the API surface", "Plan the local API runtime without the web UI."),
1185
1195
  example("treeseed dev --surfaces integrated,agents", "Opt into agents diagnostics", "Start the integrated local platform plus the agents diagnostic loop."),
1186
- example("treeseed dev --surface web --port 4322 --open off", "Run only the web surface", "Start the Astro UI on a specific port without opening a browser."),
1196
+ example("treeseed dev --surface web --port 4322", "Run only the web surface", "Start the Astro UI on a specific port and print the URL when ready."),
1197
+ example("treeseed dev --open on", "Open the browser explicitly", "Start the integrated runtime and launch the local web URL after readiness."),
1187
1198
  example("trsd dev", "Use the short alias", "Start the same local runtime through the shorter entrypoint."),
1188
1199
  example("treeseed dev --json", "Stream dev events", "Emit newline-delimited events while the long-running dev process supervises local services.")
1189
1200
  ],
1190
1201
  outcomes: [
1191
- "Starts the selected local surfaces, waits for readiness, and then remains attached as the live supervisor.",
1202
+ "Starts the selected local surfaces, waits for readiness, prints the local URL, and then remains attached as the live supervisor.",
1192
1203
  "Restarts required crashed surfaces with capped exponential backoff and keeps setup/readiness failures alive for retry.",
1193
1204
  "Stops watchers first and then terminates service process groups when the foreground command exits."
1194
1205
  ]
@@ -1196,6 +1207,27 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1196
1207
  executionMode: "handler",
1197
1208
  handlerName: "dev"
1198
1209
  })],
1210
+ ["dev:manager", command({
1211
+ options: DEV_MANAGER_OPTIONS,
1212
+ examples: ["treeseed dev:manager", "treeseed dev:manager --with-worker", "treeseed dev:manager --docs-automation dry-run --plan --json"],
1213
+ help: {
1214
+ longSummary: [
1215
+ "Dev:manager starts the governed local documentation automation manager through the same integrated dev supervisor used by `treeseed dev`.",
1216
+ "It selects the manager surface by default, can supervise a local worker with `--with-worker`, and passes docs automation policy into the existing manager and worker runtime."
1217
+ ],
1218
+ examples: [
1219
+ example("treeseed dev:manager", "Start the local manager", "Run the governed workday manager in the foreground."),
1220
+ example("treeseed dev:manager --with-worker", "Start manager plus worker", "Run scheduling and task execution in the same local supervision session."),
1221
+ example("treeseed dev:manager --docs-automation dry-run --plan --json", "Inspect dry-run manager mode", "Show the manager plan and docs automation settings without starting the supervisor.")
1222
+ ],
1223
+ outcomes: [
1224
+ "Starts or plans the existing manager surface, with optional worker supervision.",
1225
+ "Keeps manual approval as the local default and forwards docs automation mode, workday id, capacity budget, and approval policy through runtime environment variables."
1226
+ ]
1227
+ },
1228
+ executionMode: "handler",
1229
+ handlerName: "dev"
1230
+ })],
1199
1231
  ["dev:watch", command({
1200
1232
  options: DEV_RUNTIME_OPTIONS,
1201
1233
  examples: ["treeseed dev:watch", "treeseed dev:watch --json"],
@@ -1265,6 +1297,75 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1265
1297
  ["starlight:patch", command({ examples: ["treeseed starlight:patch"], executionMode: "adapter" })]
1266
1298
  ]);
1267
1299
  const CLI_ONLY_OPERATION_SPECS = [
1300
+ {
1301
+ id: "seed.plan",
1302
+ name: "seed",
1303
+ aliases: [],
1304
+ group: "Validation",
1305
+ summary: "Validate and plan declarative Treeseed environment seeds.",
1306
+ description: "Load a seed manifest from seeds/<name>.yaml, validate references and environment targeting, produce a deterministic plan, apply governed seeds through the market store/API, or export a team portfolio to YAML.",
1307
+ provider: "default",
1308
+ related: ["status", "config", "capacity", "projects"],
1309
+ usage: "treeseed seed <name> [--environments local,staging,prod] [--plan|--validate|--apply] [--json]\n treeseed seed export <name> --team <team> [--output <path>] [--json]",
1310
+ arguments: [{ name: "name", description: "Seed manifest name under the project seeds directory, or `export <name>` for portfolio export.", required: true }],
1311
+ options: [
1312
+ { name: "environments", flags: "--environments <list>", description: "Comma-separated environments to select from the manifest.", kind: "string" },
1313
+ { name: "plan", flags: "--plan", description: "Generate a deterministic plan without applying it. This is the Phase 1 default.", kind: "boolean" },
1314
+ { name: "validate", flags: "--validate", description: "Validate the manifest and selected environments without printing plan actions.", kind: "boolean" },
1315
+ { name: "apply", flags: "--apply", description: "Apply local seeds directly or governed staging/production seeds through the market API.", kind: "boolean" },
1316
+ { name: "market", flags: "--market <market>", description: "Market profile or URL for staging and production seed operations.", kind: "string" },
1317
+ { name: "host", flags: "--host <host>", description: "Compatibility alias for --market.", kind: "string" },
1318
+ { name: "approvalRequest", flags: "--approval-request <id>", description: "Approved production seed apply request id.", kind: "string" },
1319
+ { name: "team", flags: "--team <team>", description: "Team slug, name, or id for seed export.", kind: "string" },
1320
+ { name: "output", flags: "--output <path>", description: "Write exported seed YAML to this path.", kind: "string" },
1321
+ { name: "includePrivate", flags: "--include-private", description: "Include private catalog products in seed export when authorized.", kind: "boolean" },
1322
+ { name: "includeArtifacts", flags: "--include-artifacts", description: "Include catalog artifact version references in seed export.", kind: "boolean" },
1323
+ { name: "yes", flags: "--yes", description: "Future-compatible non-interactive confirmation flag for local seed apply.", kind: "boolean" },
1324
+ { name: "strict", flags: "--strict", description: "Reserved for stricter future diagnostics; Phase 1 validation is already strict.", kind: "boolean" },
1325
+ { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
1326
+ ],
1327
+ examples: [
1328
+ "treeseed seed treeseed --validate",
1329
+ "treeseed seed treeseed --environments local --plan",
1330
+ "trsd seed treeseed --environments prod --plan --json",
1331
+ "trsd seed export treeseed --team treeseed --include-artifacts --json"
1332
+ ],
1333
+ help: {
1334
+ workflowPosition: "validate",
1335
+ longSummary: [
1336
+ "Seed validates a declarative market portfolio manifest and produces a deterministic reconciliation plan.",
1337
+ "Phase 5 also exports an existing team portfolio into a reusable YAML seed bundle without embedding secrets or artifact bytes."
1338
+ ],
1339
+ whenToUse: [
1340
+ "Use this when a TreeSeed workspace needs a repeatable description of teams, projects, repositories, capacity providers, grants, and work policies.",
1341
+ "Use `--json` when an agent or CI check needs the same plan in a stable machine-readable shape."
1342
+ ],
1343
+ beforeYouRun: [
1344
+ "Run from a Treeseed project containing the requested `seeds/<name>.yaml` manifest.",
1345
+ "Choose the target environments explicitly when reviewing staging or production resources."
1346
+ ],
1347
+ outcomes: [
1348
+ "Prints validation diagnostics, a deterministic plan, an apply summary, or an exported seed manifest.",
1349
+ "Mutates the local market store for local applies, or the selected authenticated market for staging applies and approved production applies."
1350
+ ],
1351
+ automationNotes: [
1352
+ "Agents may run validation and planning safely. Production apply requires an approved seed approval request.",
1353
+ "Skipped resources are omitted from human plan output but included in JSON actions for review."
1354
+ ],
1355
+ warnings: [
1356
+ "Do not put raw secrets in seed manifests; validation rejects secret-looking fields and values.",
1357
+ "`--apply --environments prod` is blocked until a matching approval request is approved."
1358
+ ],
1359
+ relatedDetails: [
1360
+ related("projects", "Use `projects` after Phase 2 apply work lands to inspect created projects through the market API."),
1361
+ related("capacity", "Use `capacity` to inspect existing provider and grant state outside the seed planner.")
1362
+ ]
1363
+ },
1364
+ helpVisible: true,
1365
+ helpFeatured: true,
1366
+ executionMode: "handler",
1367
+ handlerName: "seed"
1368
+ },
1268
1369
  {
1269
1370
  id: "audit.hosting",
1270
1371
  name: "audit",
@@ -10,6 +10,7 @@ export declare const COMMAND_HANDLERS: {
10
10
  readonly status: import("./operations-types.js").TreeseedCommandHandler;
11
11
  readonly ci: import("./operations-types.js").TreeseedCommandHandler;
12
12
  readonly dev: import("./operations-types.js").TreeseedCommandHandler;
13
+ readonly 'dev:manager': import("./operations-types.js").TreeseedCommandHandler;
13
14
  readonly 'dev:watch': import("./operations-types.js").TreeseedCommandHandler;
14
15
  readonly doctor: import("./operations-types.js").TreeseedCommandHandler;
15
16
  readonly rollback: import("./operations-types.js").TreeseedCommandHandler;
@@ -40,6 +41,7 @@ export declare const COMMAND_HANDLERS: {
40
41
  readonly 'secrets:rotate-passphrase': import("./operations-types.js").TreeseedCommandHandler;
41
42
  readonly 'secrets:rotate-machine-key': import("./operations-types.js").TreeseedCommandHandler;
42
43
  readonly audit: import("./operations-types.js").TreeseedCommandHandler;
44
+ readonly seed: import("./operations-types.js").TreeseedCommandHandler;
43
45
  };
44
46
  export declare const TRESEED_COMMAND_SPECS: TreeseedCommandSpec[];
45
47
  export declare function findCommandSpec(name: string | null | undefined): import("./operations-types.js").TreeseedOperationSpec | null;
@@ -42,6 +42,7 @@ import { handleResume } from "./handlers/resume.js";
42
42
  import { handleRecover } from "./handlers/recover.js";
43
43
  import { handleWorkspace } from "./handlers/workspace.js";
44
44
  import { handleAudit } from "./handlers/audit.js";
45
+ import { handleSeed } from "./handlers/seed.js";
45
46
  const workspaceCommand = (name) => `workspace${":"}${name}`;
46
47
  const COMMAND_HANDLERS = {
47
48
  init: handleInit,
@@ -53,6 +54,7 @@ const COMMAND_HANDLERS = {
53
54
  status: handleStatus,
54
55
  ci: handleCi,
55
56
  dev: handleDev,
57
+ "dev:manager": handleDev,
56
58
  "dev:watch": handleDev,
57
59
  doctor: handleDoctor,
58
60
  rollback: handleRollback,
@@ -85,7 +87,8 @@ const COMMAND_HANDLERS = {
85
87
  "secrets:migrate-key": handleSecretsMigrateKey,
86
88
  "secrets:rotate-passphrase": handleSecretsRotatePassphrase,
87
89
  "secrets:rotate-machine-key": handleSecretsRotateMachineKey,
88
- audit: handleAudit
90
+ audit: handleAudit,
91
+ seed: handleSeed
89
92
  };
90
93
  const TRESEED_COMMAND_SPECS = TRESEED_OPERATION_SPECS;
91
94
  function findCommandSpec(name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.8.13",
3
+ "version": "0.8.15",
4
4
  "description": "Operator-facing Treeseed CLI package.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -31,7 +31,7 @@
31
31
  "setup:ci": "npm ci",
32
32
  "build": "npm run build:dist",
33
33
  "lint": "npm run build:dist",
34
- "test": "npm run build:dist && node --test --test-concurrency=1 ./scripts/treeseed-help.test.mjs ./scripts/wrapper-package.test.mjs",
34
+ "test": "npm run build:dist && node --test --test-concurrency=1 ./scripts/treeseed-help.test.mjs ./scripts/seed.test.mjs ./scripts/wrapper-package.test.mjs",
35
35
  "build:dist": "node ./scripts/run-ts.mjs ./scripts/build-dist.ts",
36
36
  "prepare": "node ./scripts/prepare.mjs",
37
37
  "prepack": "npm run build:dist",
@@ -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.8.13",
48
+ "@treeseed/sdk": "0.8.15",
49
49
  "ink": "^7.0.0",
50
50
  "react": "^19.2.5"
51
51
  },