@treeseed/cli 0.8.19 → 0.9.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.
@@ -1,144 +1,288 @@
1
- import { guidedResult } from "./utils.js";
2
- import { createMarketClientForInvocation } from "./market-utils.js";
3
- function required(value, message) {
4
- if (typeof value === "string" && value.trim()) return value.trim();
5
- throw new Error(message);
6
- }
7
- const handleCapacity = async (invocation, context) => {
8
- const action = invocation.positionals[0] ?? "status";
9
- const { profile, client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
10
- const teamId = typeof invocation.args.team === "string" ? invocation.args.team : typeof profile.teamId === "string" ? profile.teamId : null;
11
- if (action === "status") {
12
- const team = required(teamId, "Usage: treeseed capacity status --team <team-id>");
13
- const response = await client.teamCapacity(team);
14
- const summary = response.payload.summary;
1
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { spawnSync } from "node:child_process";
3
+ import { resolve } from "node:path";
4
+ import { resolveCapacityProviderLaunchEnvironment } from "@treeseed/sdk/capacity-provider";
5
+ import { resolveMarketProfile } from "@treeseed/sdk/market-client";
6
+ import { findNearestTreeseedRoot, findNearestTreeseedWorkspaceRoot } from "@treeseed/sdk/workflow-support";
7
+ import { fail, guidedResult } from "./utils.js";
8
+ const ENTRYPOINT_RELATIVE_PATH = ["dist", "provider", "entrypoint.js"];
9
+ const COMPOSE_FILE_NAME = "compose.capacity-provider.yml";
10
+ const DEFAULT_PROJECT_NAME = "treeseed-capacity-provider";
11
+ const DEFAULT_HOST_DATA_DIR = ".treeseed/local-capacity-provider/data";
12
+ const PROVIDER_LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set(["build", "up", "down", "restart", "logs", "status", "test-local"]);
13
+ const PROVIDER_ENTRYPOINT_ACTIONS = /* @__PURE__ */ new Set(["doctor", "register", "plan"]);
14
+ function stringArg(invocation, name) {
15
+ const value = invocation.args[name];
16
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
17
+ }
18
+ function boolArg(invocation, name) {
19
+ return invocation.args[name] === true;
20
+ }
21
+ function readPackageName(packageRoot) {
22
+ const packageJsonPath = resolve(packageRoot, "package.json");
23
+ if (!existsSync(packageJsonPath)) return null;
24
+ try {
25
+ const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
26
+ return parsed.name ?? null;
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ function agentEntrypoint(packageRoot) {
32
+ return resolve(packageRoot, ...ENTRYPOINT_RELATIVE_PATH);
33
+ }
34
+ function resolveAgentPackageRoot(invocation, context) {
35
+ const explicitRoot = stringArg(invocation, "agentPackageRoot");
36
+ if (explicitRoot) {
37
+ return resolve(context.cwd, explicitRoot);
38
+ }
39
+ if (readPackageName(context.cwd) === "@treeseed/agent") {
40
+ return context.cwd;
41
+ }
42
+ const workspaceRoot = findNearestTreeseedWorkspaceRoot(context.cwd);
43
+ const workspaceAgentRoot = workspaceRoot ? resolve(workspaceRoot, "packages", "agent") : null;
44
+ if (workspaceAgentRoot && existsSync(resolve(workspaceAgentRoot, "package.json"))) {
45
+ return workspaceAgentRoot;
46
+ }
47
+ const nearestProjectRoot = findNearestTreeseedRoot(context.cwd);
48
+ const projectAgentRoot = nearestProjectRoot ? resolve(nearestProjectRoot, "packages", "agent") : null;
49
+ if (projectAgentRoot && existsSync(resolve(projectAgentRoot, "package.json"))) {
50
+ return projectAgentRoot;
51
+ }
52
+ const installedRoot = resolve(context.cwd, "node_modules", "@treeseed", "agent");
53
+ if (existsSync(resolve(installedRoot, "package.json"))) {
54
+ return installedRoot;
55
+ }
56
+ return null;
57
+ }
58
+ function resolveAgentPackage(invocation, context, options = {}) {
59
+ const packageRoot = resolveAgentPackageRoot(invocation, context);
60
+ if (!packageRoot) {
61
+ throw new Error(
62
+ "Unable to locate @treeseed/agent. Build the workspace package, install @treeseed/agent, or pass --agent-package-root."
63
+ );
64
+ }
65
+ const entrypointPath = agentEntrypoint(packageRoot);
66
+ if (options.requireEntrypoint !== false && !existsSync(entrypointPath)) {
67
+ throw new Error(
68
+ `Missing provider runtime at ${entrypointPath}. Run npm -w packages/agent run build:dist or pass --agent-package-root to a built package.`
69
+ );
70
+ }
71
+ const composeFilePath = resolve(packageRoot, COMPOSE_FILE_NAME);
72
+ return { packageRoot, entrypointPath, composeFilePath };
73
+ }
74
+ function providerSelector(invocation) {
75
+ return stringArg(invocation, "provider") ?? "local";
76
+ }
77
+ function environmentSelector(invocation) {
78
+ return stringArg(invocation, "environment") ?? "local";
79
+ }
80
+ function resolveMarket(invocation) {
81
+ return resolveMarketProfile(stringArg(invocation, "market") ?? "local");
82
+ }
83
+ function resolveTenantRoot(context, agentPackageRoot) {
84
+ return findNearestTreeseedRoot(context.cwd) ?? (readPackageName(agentPackageRoot) === "@treeseed/agent" ? agentPackageRoot : context.cwd);
85
+ }
86
+ function defaultHostDataDir(context) {
87
+ const tenantRoot = findNearestTreeseedRoot(context.cwd) ?? context.cwd;
88
+ return resolve(tenantRoot, DEFAULT_HOST_DATA_DIR);
89
+ }
90
+ function providerProjectName(invocation) {
91
+ const provider = providerSelector(invocation).replace(/[^A-Za-z0-9_.-]+/gu, "-").replace(/^-+|-+$/gu, "") || "local";
92
+ return `${DEFAULT_PROJECT_NAME}-${provider}`;
93
+ }
94
+ function composeCommandArgs(composeFilePath, projectName, action) {
95
+ const base = ["compose", "-f", composeFilePath, "-p", projectName];
96
+ switch (action) {
97
+ case "up":
98
+ return [...base, "up", "-d"];
99
+ case "down":
100
+ return [...base, "down"];
101
+ case "restart":
102
+ return [...base, "restart"];
103
+ case "logs":
104
+ return [...base, "logs", "--tail", "200"];
105
+ case "status":
106
+ return [...base, "ps"];
107
+ default:
108
+ return base;
109
+ }
110
+ }
111
+ function lifecycleActionRequiresConnection(action) {
112
+ return action === "up" || action === "restart";
113
+ }
114
+ function runLifecycleAction(action, invocation, context) {
115
+ const agentPackage = resolveAgentPackage(invocation, context, { requireEntrypoint: action !== "build" });
116
+ if (action !== "build" && action !== "test-local" && !existsSync(agentPackage.composeFilePath)) {
117
+ return fail(`Missing ${COMPOSE_FILE_NAME} in ${agentPackage.packageRoot}. Build or reinstall @treeseed/agent with Phase 3 container assets.`);
118
+ }
119
+ if (action === "build" || action === "test-local") {
120
+ const script = action === "build" ? "capacity-provider:build" : "capacity-provider:test-local";
121
+ const result2 = context.spawn("npm", ["run", script], {
122
+ cwd: agentPackage.packageRoot,
123
+ env: context.env,
124
+ stdio: "inherit"
125
+ });
15
126
  return guidedResult({
16
- command: "capacity",
17
- summary: "Team helper capacity status",
127
+ command: `capacity ${action}`,
128
+ summary: result2.status === 0 ? `Capacity provider ${action === "build" ? "image build" : "container smoke test"} completed.` : `Capacity provider ${action === "build" ? "image build" : "container smoke test"} failed.`,
18
129
  facts: [
19
- { label: "Market", value: profile.id },
20
- { label: "Team", value: team },
21
- { label: "Monthly remaining", value: summary?.monthlyRemainingCredits },
22
- { label: "Daily remaining", value: summary?.dailyRemainingCredits },
23
- { label: "Active providers", value: summary?.activeProviderCount }
130
+ { label: "Agent package", value: agentPackage.packageRoot },
131
+ { label: "Script", value: script },
132
+ { label: "Exit code", value: result2.status ?? 1 }
24
133
  ],
25
- report: { marketId: profile.id, teamId: team, capacity: response.payload }
134
+ exitCode: result2.status ?? 1,
135
+ report: {
136
+ action,
137
+ agentPackageRoot: agentPackage.packageRoot,
138
+ script
139
+ }
26
140
  });
27
141
  }
28
- if (action === "providers") {
29
- const subcommand = invocation.positionals[1] ?? "list";
30
- const team = required(teamId, "Usage: treeseed capacity providers list --team <team-id>");
31
- if (subcommand === "list") {
32
- const response = await client.teamCapacity(team);
33
- const providers = response.payload.providers ?? [];
34
- return guidedResult({
35
- command: "capacity",
36
- summary: `Found ${providers.length} helper capacity provider${providers.length === 1 ? "" : "s"}.`,
37
- sections: [{
38
- title: "Providers",
39
- lines: providers.map((provider) => `${provider.id} ${provider.name} ${provider.status} workers=${provider.maxConcurrentWorkers ?? 0}`)
40
- }],
41
- report: { marketId: profile.id, teamId: team, providers }
42
- });
43
- }
44
- if (subcommand === "connect") {
45
- const response = await client.launchManagedCapacityProvider(team, { launchSource: "cli" });
46
- return guidedResult({
47
- command: "capacity",
48
- summary: "TreeSeed-managed helper capacity is connected.",
49
- facts: [
50
- { label: "Provider", value: response.payload.provider?.id },
51
- { label: "Security code prefix", value: response.payload.apiKey?.keyPrefix }
52
- ],
53
- report: { marketId: profile.id, teamId: team, result: response.payload }
54
- });
142
+ const diagnostic = boolArg(invocation, "diagnostic") || action === "test-local";
143
+ const market = resolveMarket(invocation);
144
+ const hostDataDirInput = stringArg(invocation, "dataDir") ?? context.env.TREESEED_PROVIDER_HOST_DATA_DIR ?? defaultHostDataDir(context);
145
+ const resolvedHostDataDir = resolve(context.cwd, hostDataDirInput);
146
+ const tenantRoot = resolveTenantRoot(context, agentPackage.packageRoot);
147
+ const launch = resolveCapacityProviderLaunchEnvironment({
148
+ tenantRoot,
149
+ scope: environmentSelector(invocation),
150
+ env: context.env,
151
+ diagnostic,
152
+ requireConnection: lifecycleActionRequiresConnection(action),
153
+ overrides: {
154
+ TREESEED_MARKET_URL: market.baseUrl,
155
+ TREESEED_MARKET_ID: market.id,
156
+ TREESEED_PROVIDER_HOST_DATA_DIR: resolvedHostDataDir,
157
+ TREESEED_PROVIDER_ENVIRONMENT: providerSelector(invocation),
158
+ ...diagnostic ? { TREESEED_PROVIDER_STARTUP_MODE: "diagnostic" } : {}
55
159
  }
56
- if (subcommand === "keys") {
57
- const keyAction = invocation.positionals[2] ?? "reset";
58
- const providerId = required(invocation.args.provider, "Usage: treeseed capacity providers keys reset --provider <provider-id>");
59
- if (keyAction === "reset") {
60
- const response = await client.resetCapacityProviderApiKey(providerId, { name: "Provider security code" });
61
- return guidedResult({
62
- command: "capacity",
63
- summary: "Provider security code was reset.",
64
- facts: [
65
- { label: "Provider", value: providerId },
66
- { label: "Prefix", value: response.payload.key?.keyPrefix },
67
- { label: "Security access code", value: response.payload.plaintextKey }
68
- ],
69
- report: { marketId: profile.id, providerId, result: response.payload }
70
- });
71
- }
72
- if (keyAction === "revoke") {
73
- const keyId = required(invocation.args.key, "Usage: treeseed capacity providers keys revoke --provider <provider-id> --key <key-id>");
74
- const response = await client.revokeCapacityProviderApiKey(providerId, keyId);
75
- return guidedResult({
76
- command: "capacity",
77
- summary: "Provider security code was revoked.",
78
- facts: [
79
- { label: "Provider", value: providerId },
80
- { label: "Key", value: keyId }
81
- ],
82
- report: { marketId: profile.id, providerId, keyId, result: response.payload }
83
- });
160
+ });
161
+ const hostDataDir = resolvedHostDataDir;
162
+ mkdirSync(hostDataDir, { recursive: true });
163
+ const projectName = providerProjectName(invocation);
164
+ const args = composeCommandArgs(agentPackage.composeFilePath, projectName, action);
165
+ const result = context.spawn("docker", args, {
166
+ cwd: agentPackage.packageRoot,
167
+ env: {
168
+ ...context.env,
169
+ ...launch.env
170
+ },
171
+ stdio: "inherit"
172
+ });
173
+ return guidedResult({
174
+ command: `capacity ${action}`,
175
+ summary: result.status === 0 ? `Capacity provider ${action} completed${diagnostic ? " in diagnostic mode" : ""}.` : `Capacity provider ${action} failed.`,
176
+ facts: [
177
+ { label: "Market", value: `${market.id} (${market.baseUrl})` },
178
+ { label: "Provider", value: providerSelector(invocation) },
179
+ { label: "Mode", value: diagnostic ? "diagnostic" : "live" },
180
+ { label: "Compose project", value: projectName },
181
+ { label: "Agent package", value: agentPackage.packageRoot },
182
+ { label: "Data directory", value: hostDataDir },
183
+ { label: "Exit code", value: result.status ?? 1 }
184
+ ],
185
+ sections: [
186
+ {
187
+ title: "Environment",
188
+ lines: Object.entries(launch.redactedEnv).sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${key}=${value}`)
84
189
  }
190
+ ],
191
+ exitCode: result.status ?? 1,
192
+ report: {
193
+ action,
194
+ agentPackageRoot: agentPackage.packageRoot,
195
+ composeFile: agentPackage.composeFilePath,
196
+ composeProject: projectName,
197
+ market: { id: market.id, baseUrl: market.baseUrl },
198
+ provider: providerSelector(invocation),
199
+ diagnostic,
200
+ redactedEnv: launch.redactedEnv
85
201
  }
86
- return { exitCode: 1, stderr: [`Unknown capacity providers action: ${subcommand}`] };
87
- }
88
- if (action === "grants") {
89
- const subcommand = invocation.positionals[1] ?? "list";
90
- const team = required(teamId, "Usage: treeseed capacity grants list --team <team-id>");
91
- if (subcommand === "list") {
92
- const response = await client.capacityGrants(team);
93
- return guidedResult({
94
- command: "capacity",
95
- summary: `Found ${response.payload.length} capacity grant${response.payload.length === 1 ? "" : "s"}.`,
96
- sections: [{
97
- title: "Grants",
98
- lines: response.payload.map((grant) => `${grant.id} ${grant.grantScope} daily=${grant.dailyCreditLimit ?? "policy"} monthly=${grant.monthlyCreditLimit ?? "policy"}`)
99
- }],
100
- report: { marketId: profile.id, teamId: team, grants: response.payload }
101
- });
202
+ });
203
+ }
204
+ function invokeProviderEntrypoint(action, invocation, context) {
205
+ const agentPackage = resolveAgentPackage(invocation, context);
206
+ const market = resolveMarket(invocation);
207
+ const args = [agentPackage.entrypointPath, action, "--market", market.id, "--provider", providerSelector(invocation)];
208
+ if (boolArg(invocation, "dryRun") || action === "doctor" || action === "plan") {
209
+ args.push("--dry-run");
210
+ }
211
+ if (context.outputFormat === "json" || boolArg(invocation, "json")) {
212
+ args.push("--json");
213
+ }
214
+ const result = spawnSync(process.execPath, args, {
215
+ cwd: agentPackage.packageRoot,
216
+ env: {
217
+ ...context.env,
218
+ TREESEED_MARKET_URL: market.baseUrl,
219
+ TREESEED_MARKET_ID: market.id,
220
+ TREESEED_PROVIDER_ENVIRONMENT: providerSelector(invocation)
221
+ },
222
+ encoding: "utf8"
223
+ });
224
+ const stdout = result.stdout.trim();
225
+ const stderr = result.stderr.trim();
226
+ let report = null;
227
+ if (stdout.startsWith("{")) {
228
+ try {
229
+ report = JSON.parse(stdout);
230
+ } catch {
231
+ report = null;
102
232
  }
103
- if (subcommand === "create") {
104
- const providerId = required(invocation.args.provider, "Usage: treeseed capacity grants create --team <team-id> --provider <provider-id>");
105
- const response = await client.createCapacityGrant(team, {
106
- capacityProviderId: providerId,
107
- projectId: typeof invocation.args.project === "string" ? invocation.args.project : null,
108
- environment: typeof invocation.args.environment === "string" ? invocation.args.environment : null,
109
- dailyCreditLimit: Number(invocation.args.daily ?? 25),
110
- monthlyCreditLimit: Number(invocation.args.monthly ?? 500),
111
- overflowPolicy: typeof invocation.args.overflow === "string" ? invocation.args.overflow : "approval_required"
112
- });
113
- return guidedResult({
114
- command: "capacity",
115
- summary: "Capacity grant created.",
116
- facts: [
117
- { label: "Grant", value: response.payload.id },
118
- { label: "Provider", value: providerId }
119
- ],
120
- report: { marketId: profile.id, teamId: team, grant: response.payload }
121
- });
233
+ }
234
+ if (context.outputFormat === "json") {
235
+ return {
236
+ exitCode: result.status ?? 1,
237
+ stdout: stdout ? [stdout] : [],
238
+ stderr: stderr ? [stderr] : [],
239
+ report: report ?? {
240
+ ok: result.status === 0,
241
+ action,
242
+ stdout,
243
+ stderr,
244
+ agentPackageRoot: agentPackage.packageRoot
245
+ }
246
+ };
247
+ }
248
+ return guidedResult({
249
+ command: `capacity ${action}`,
250
+ summary: result.status === 0 ? `Capacity provider ${action} completed.` : `Capacity provider ${action} failed.`,
251
+ facts: [
252
+ { label: "Market", value: `${market.id} (${market.baseUrl})` },
253
+ { label: "Provider", value: providerSelector(invocation) },
254
+ { label: "Agent package", value: agentPackage.packageRoot },
255
+ { label: "Exit code", value: result.status ?? 1 }
256
+ ],
257
+ sections: [
258
+ { title: "Output", lines: stdout ? stdout.split(/\r?\n/u) : [] },
259
+ { title: "Errors", lines: stderr ? stderr.split(/\r?\n/u) : [] }
260
+ ],
261
+ exitCode: result.status ?? 1,
262
+ report: report ?? {
263
+ ok: result.status === 0,
264
+ action,
265
+ agentPackageRoot: agentPackage.packageRoot
266
+ }
267
+ });
268
+ }
269
+ const handleCapacity = (invocation, context) => {
270
+ const action = invocation.positionals[0] ?? "doctor";
271
+ if (PROVIDER_LIFECYCLE_ACTIONS.has(action)) {
272
+ try {
273
+ return runLifecycleAction(action, invocation, context);
274
+ } catch (error) {
275
+ return fail(error instanceof Error ? error.message : String(error));
122
276
  }
123
- return { exitCode: 1, stderr: [`Unknown capacity grants action: ${subcommand}`] };
124
277
  }
125
- if (action === "enqueue") {
126
- const projectId = required(invocation.args.project, "Usage: treeseed capacity enqueue --project <project-id> --task-kind <kind>");
127
- const response = await client.enqueueAgentTask(projectId, {
128
- taskKind: typeof invocation.args.taskKind === "string" ? invocation.args.taskKind : typeof invocation.args.task === "string" ? invocation.args.task : "proposal.draft",
129
- environment: typeof invocation.args.environment === "string" ? invocation.args.environment : "staging"
130
- });
131
- return guidedResult({
132
- command: "capacity",
133
- summary: "Budgeted agent task enqueued.",
134
- facts: [
135
- { label: "Task", value: response.payload.task?.id },
136
- { label: "Reserved credits", value: response.payload.reservation?.reservedCredits }
137
- ],
138
- report: { marketId: profile.id, projectId, result: response.payload }
139
- });
278
+ if (PROVIDER_ENTRYPOINT_ACTIONS.has(action)) {
279
+ try {
280
+ return invokeProviderEntrypoint(action, invocation, context);
281
+ } catch (error) {
282
+ return fail(error instanceof Error ? error.message : String(error));
283
+ }
140
284
  }
141
- return { exitCode: 1, stderr: [`Unknown capacity action: ${action}`] };
285
+ return fail(`Unknown capacity action "${action}". Use doctor, register, plan, build, up, down, restart, logs, status, or test-local.`);
142
286
  };
143
287
  export {
144
288
  handleCapacity
@@ -3,6 +3,7 @@ import { createRequire } from "node:module";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { ensureLocalWorkspaceLinks, findNearestTreeseedWorkspaceRoot, resolveTreeseedLaunchEnvironment } from "@treeseed/sdk/workflow-support";
5
5
  import { workflowErrorResult } from "./workflow.js";
6
+ import { fail } from "./utils.js";
6
7
  const require2 = createRequire(import.meta.url);
7
8
  function resolveCoreDevEntrypoint(cwd) {
8
9
  const workspacePackageJsonPath = resolve(cwd, "packages", "core", "package.json");
@@ -42,10 +43,16 @@ function resolveCoreDevEntrypoint(cwd) {
42
43
  }
43
44
  const handleDev = async (invocation, context) => {
44
45
  try {
45
- const managerMode = invocation.commandName === "dev:manager";
46
+ if (invocation.commandName !== "dev") {
47
+ return fail("`trsd dev` only starts the Market web/API dev runtime. Use `trsd capacity ...` for capacity provider lifecycle commands.");
48
+ }
49
+ const removedOptions = ["surface", "surfaces", "withWorker"].filter((name) => invocation.args[name] !== void 0);
50
+ if (removedOptions.length > 0) {
51
+ return fail(`\`trsd dev\` no longer accepts ${removedOptions.map((name) => `--${name.replace(/[A-Z]/gu, (char) => `-${char.toLowerCase()}`)}`).join(", ")}. It always starts fixed Market web/API surfaces; use \`trsd capacity ...\` for providers.`);
52
+ }
46
53
  const feedback = typeof invocation.args.feedback === "string" ? invocation.args.feedback : void 0;
47
54
  const watch = feedback !== "off";
48
- const passthroughArgs = [];
55
+ const passthroughArgs = ["--surfaces", "web,api"];
49
56
  const forwardStringOption = (name, flag) => {
50
57
  const value = invocation.args[name];
51
58
  if (typeof value === "string" && value.trim().length > 0) {
@@ -57,38 +64,18 @@ const handleDev = async (invocation, context) => {
57
64
  passthroughArgs.push(flag);
58
65
  }
59
66
  };
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
- }
68
67
  forwardStringOption("host", "--host");
69
68
  forwardStringOption("port", "--port");
69
+ forwardStringOption("webRuntime", "--web-runtime");
70
70
  forwardStringOption("apiHost", "--api-host");
71
71
  forwardStringOption("apiPort", "--api-port");
72
- forwardStringOption("managerPort", "--manager-port");
73
72
  forwardStringOption("setup", "--setup");
74
73
  forwardStringOption("feedback", "--feedback");
75
74
  forwardStringOption("open", "--open");
76
75
  forwardBooleanOption("plan", "--plan");
77
76
  forwardBooleanOption("reset", "--reset");
77
+ forwardBooleanOption("force", "--force");
78
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
- } : {};
92
79
  const workspaceRoot = findNearestTreeseedWorkspaceRoot(context.cwd);
93
80
  const workspaceLinksMode = typeof invocation.args.workspaceLinks === "string" ? invocation.args.workspaceLinks : void 0;
94
81
  const workspaceLinks = workspaceRoot ? ensureLocalWorkspaceLinks(workspaceRoot, { env: context.env, mode: workspaceLinksMode }) : null;
@@ -102,7 +89,8 @@ const handleDev = async (invocation, context) => {
102
89
  env: resolveTreeseedLaunchEnvironment({
103
90
  tenantRoot: context.cwd,
104
91
  scope: "local",
105
- baseEnv: { ...process.env, ...context.env ?? {}, ...devManagerEnv }
92
+ baseEnv: { ...process.env, ...context.env ?? {} },
93
+ overrides: {}
106
94
  }),
107
95
  stdio: "inherit"
108
96
  });
@@ -111,18 +99,11 @@ const handleDev = async (invocation, context) => {
111
99
  suppressJsonResult: invocation.args.json === true,
112
100
  report: {
113
101
  command: "dev",
114
- alias: managerMode ? "dev:manager" : invocation.commandName,
102
+ alias: invocation.commandName,
115
103
  ok: (result.status ?? 1) === 0,
116
104
  watch,
117
105
  executable: resolved.command,
118
106
  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,
126
107
  workspaceLinks
127
108
  }
128
109
  };
@@ -2,21 +2,57 @@ import { pathToFileURL } from "node:url";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
4
  import { formatSeedDiagnostics, formatSeedPlan, loadAndPlanSeed } from "@treeseed/sdk/seeds";
5
+ import { persistCapacityProviderConnectionToTreeseedConfig } from "@treeseed/sdk/capacity-provider";
5
6
  import { MarketApiError } from "@treeseed/sdk/market-client";
6
7
  import { createMarketClientForInvocation, marketAuthRoot, marketSelector } from "./market-utils.js";
7
- import { resolveMarketProfile, resolveMarketSession } from "@treeseed/sdk/market-client";
8
+ import { MarketClient, resolveMarketProfile, resolveMarketSession, setMarketSession } from "@treeseed/sdk/market-client";
8
9
  async function loadLocalSeedModule(projectRoot) {
9
10
  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);
11
+ const applyModulePath = resolve(projectRoot, "src", "lib", "market", "seeds", "apply.js");
12
+ const applyModule = await import(pathToFileURL(applyModulePath).href);
13
+ if (!existsSync(apiModulePath)) {
14
+ return applyModule;
15
+ }
16
+ const apiModule = await import(pathToFileURL(apiModulePath).href);
17
+ return {
18
+ ...applyModule,
19
+ ...apiModule
20
+ };
21
+ }
22
+ function sessionExpiresSoon(session) {
23
+ if (!session.expiresAt) return false;
24
+ const expiresAt = Date.parse(session.expiresAt);
25
+ return Number.isFinite(expiresAt) && expiresAt <= Date.now() + 6e4;
12
26
  }
13
- function requireLocalSeedSession(invocation, context) {
27
+ async function requireLocalSeedSession(invocation, context) {
14
28
  const selector = marketSelector(invocation) ?? "local";
15
29
  const profile = resolveMarketProfile(selector);
16
- const session = resolveMarketSession(marketAuthRoot(context), profile.id);
30
+ const tenantRoot = marketAuthRoot(context);
31
+ const session = resolveMarketSession(tenantRoot, profile.id);
17
32
  if (!session?.accessToken) {
18
33
  throw new Error(`Not logged in to market "${profile.id}". Run treeseed auth:login --market ${profile.id}.`);
19
34
  }
35
+ if (sessionExpiresSoon(session) && session.refreshToken) {
36
+ try {
37
+ const refreshed = await new MarketClient({ profile, userAgent: "treeseed-cli" }).refreshToken({ refreshToken: session.refreshToken });
38
+ if (refreshed.ok) {
39
+ const nextSession = {
40
+ marketId: profile.id,
41
+ accessToken: refreshed.accessToken,
42
+ refreshToken: refreshed.refreshToken,
43
+ expiresAt: refreshed.expiresAt,
44
+ principal: refreshed.principal
45
+ };
46
+ setMarketSession(tenantRoot, nextSession);
47
+ return { profile, session: nextSession };
48
+ }
49
+ } catch {
50
+ throw new Error(`Login for market "${profile.id}" expired. Run treeseed auth:login --market ${profile.id}.`);
51
+ }
52
+ }
53
+ if (sessionExpiresSoon(session)) {
54
+ throw new Error(`Login for market "${profile.id}" expired. Run treeseed auth:login --market ${profile.id}.`);
55
+ }
20
56
  return { profile, session };
21
57
  }
22
58
  function seedRequestBody(invocation) {
@@ -70,11 +106,44 @@ function formatCapacityProviderKeyNotes(result) {
70
106
  if (created.length === 0) return [];
71
107
  return [
72
108
  "",
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."
109
+ "Provider connection:",
110
+ ...created.map((entry) => ` ${String(entry.providerName ?? entry.providerKey ?? entry.providerId)} (${String(entry.keyPrefix ?? "new key")}): stored in encrypted Treeseed config`),
111
+ " Use `treeseed capacity up --market local --provider local` to launch the provider."
76
112
  ];
77
113
  }
114
+ function redactLocalCapacityProviderKeys(result) {
115
+ const capacityProviderKeys = result.capacityProviderKeys && typeof result.capacityProviderKeys === "object" ? result.capacityProviderKeys : null;
116
+ if (!capacityProviderKeys) return result;
117
+ return {
118
+ ...result,
119
+ capacityProviderKeys: {
120
+ ...capacityProviderKeys,
121
+ created: Array.isArray(capacityProviderKeys.created) ? capacityProviderKeys.created.map((entry) => {
122
+ if (!entry || typeof entry !== "object") return entry;
123
+ const { plaintextKey: _plaintextKey, ...safeEntry } = entry;
124
+ return {
125
+ ...safeEntry,
126
+ storedInTreeseedConfig: true
127
+ };
128
+ }) : []
129
+ }
130
+ };
131
+ }
132
+ function storeLocalCapacityProviderConnection(input) {
133
+ const capacityProviderKeys = input.result.capacityProviderKeys && typeof input.result.capacityProviderKeys === "object" ? input.result.capacityProviderKeys : null;
134
+ const created = Array.isArray(capacityProviderKeys?.created) ? capacityProviderKeys.created : [];
135
+ const first = created.find((entry) => typeof entry.plaintextKey === "string" && String(entry.plaintextKey).length > 0);
136
+ if (!first) return null;
137
+ return persistCapacityProviderConnectionToTreeseedConfig({
138
+ tenantRoot: input.context.cwd,
139
+ scope: "local",
140
+ marketUrl: input.profile.baseUrl,
141
+ marketId: input.profile.id,
142
+ apiKey: String(first.plaintextKey),
143
+ providerHostDataDir: ".treeseed/local-capacity-provider/data",
144
+ providerEnvironment: "local"
145
+ });
146
+ }
78
147
  function remoteSeedError(error, command) {
79
148
  if (error instanceof MarketApiError) {
80
149
  const payload = error.payload && typeof error.payload === "object" ? error.payload : { error: error.message };
@@ -127,7 +196,7 @@ async function handleSeedExport(invocation, context) {
127
196
  const { client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
128
197
  payload = await client.exportSeed(team, body);
129
198
  } else {
130
- const { session } = requireLocalSeedSession(invocation, context);
199
+ const { session } = await requireLocalSeedSession(invocation, context);
131
200
  const localModule = await loadLocalSeedModule(context.cwd);
132
201
  const exporter = localModule.exportLocalSeedViaApiFromCli ?? localModule.exportSeedFromCli;
133
202
  if (typeof exporter !== "function") {
@@ -201,27 +270,16 @@ const handleSeed = async (invocation, context) => {
201
270
  }
202
271
  };
203
272
  }
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
273
  if (!wantsApply && !wantsValidate && planned.plan.environments.length === 1 && planned.plan.environments[0] === "local") {
215
274
  try {
216
275
  const localModule = await loadLocalSeedModule(context.cwd);
217
- const localPlanner = localModule.planLocalSeedViaApiFromCli ?? localModule.planLocalSeedFromCli;
276
+ const localPlanner = localModule.planLocalSeedFromCli ?? localModule.planLocalSeedViaApiFromCli;
218
277
  if (typeof localPlanner === "function") {
219
278
  const localPlanned = await localPlanner({
220
279
  projectRoot: context.cwd,
221
280
  seedName,
222
281
  environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
223
282
  mode,
224
- accessToken: localAuth?.session.accessToken,
225
283
  env: context.env
226
284
  });
227
285
  if (localPlanned.plan) {
@@ -250,6 +308,12 @@ const handleSeed = async (invocation, context) => {
250
308
  return remoteSeedError(error, "seed");
251
309
  }
252
310
  }
311
+ let localAuth;
312
+ try {
313
+ localAuth = await requireLocalSeedSession(invocation, context);
314
+ } catch (error) {
315
+ return remoteSeedError(error, "seed");
316
+ }
253
317
  const localModule = await loadLocalSeedModule(context.cwd);
254
318
  const runner = localModule.applyLocalSeedViaApiFromCli ?? localModule.applyLocalSeedFromCli;
255
319
  if (typeof runner !== "function") {
@@ -263,6 +327,26 @@ const handleSeed = async (invocation, context) => {
263
327
  accessToken: localAuth?.session.accessToken,
264
328
  env: context.env
265
329
  });
330
+ let providerConnection = null;
331
+ try {
332
+ providerConnection = storeLocalCapacityProviderConnection({
333
+ context,
334
+ profile: localAuth.profile,
335
+ result: applied.result
336
+ });
337
+ } catch (error) {
338
+ const message2 = `Unable to store local capacity provider connection in encrypted Treeseed config. ${error instanceof Error ? error.message : String(error)}`;
339
+ return {
340
+ exitCode: 4,
341
+ stderr: [message2],
342
+ report: {
343
+ command: "seed",
344
+ ok: false,
345
+ error: message2
346
+ }
347
+ };
348
+ }
349
+ const safeResult = redactLocalCapacityProviderKeys(applied.result);
266
350
  const message = "Local seed apply completed.";
267
351
  return {
268
352
  exitCode: 0,
@@ -275,7 +359,7 @@ const handleSeed = async (invocation, context) => {
275
359
  ` unchanged: ${applied.plan.summary.unchanged}`,
276
360
  ` skipped: ${applied.plan.summary.skip}`,
277
361
  ` failed: ${applied.plan.summary.error}`,
278
- ...formatCapacityProviderKeyNotes(applied.result)
362
+ ...formatCapacityProviderKeyNotes(safeResult)
279
363
  ],
280
364
  report: {
281
365
  ...applied.plan,
@@ -283,7 +367,14 @@ const handleSeed = async (invocation, context) => {
283
367
  command: "seed",
284
368
  result: {
285
369
  message,
286
- ...applied.result
370
+ ...safeResult,
371
+ ...providerConnection ? {
372
+ capacityProviderConnection: {
373
+ scope: providerConnection.scope,
374
+ writtenKeys: providerConnection.writtenKeys,
375
+ redactedEnv: providerConnection.redactedEnv
376
+ }
377
+ } : {}
287
378
  }
288
379
  }
289
380
  };
@@ -21,30 +21,20 @@ function related(name, why) {
21
21
  return { name, why };
22
22
  }
23
23
  const DEV_RUNTIME_OPTIONS = [
24
- { name: "surface", flags: "--surface <surface>", description: "Select one local dev surface to run. Compatibility alias for --surfaces.", kind: "enum", values: ["integrated", "web", "api", "manager", "worker", "agents", "services"] },
25
- { name: "surfaces", flags: "--surfaces <surfaces>", description: "Select comma-separated local dev surfaces to run.", kind: "string" },
26
24
  { name: "host", flags: "--host <host>", description: "Host for the web dev server.", kind: "string" },
27
25
  { name: "port", flags: "--port <port>", description: "Port for the web dev server.", kind: "string" },
26
+ { name: "webRuntime", flags: "--web-runtime <mode>", description: "Choose the local web runtime. Use local for Astro hot reload or provider for provider parity.", kind: "enum", values: ["auto", "local", "provider"] },
28
27
  { name: "apiHost", flags: "--api-host <host>", description: "Host used to construct the local API URL.", kind: "string" },
29
28
  { name: "apiPort", flags: "--api-port <port>", description: "Port for the local API server.", kind: "string" },
30
- { name: "managerPort", flags: "--manager-port <port>", description: "Port used for the local manager service URL.", kind: "string" },
31
29
  { name: "setup", flags: "--setup <mode>", description: "Control automatic local runtime setup.", kind: "enum", values: ["auto", "check", "off"] },
32
30
  { name: "feedback", flags: "--feedback <mode>", description: "Control live feedback, service restarts, and browser reload stamps.", kind: "enum", values: ["live", "restart", "off"] },
33
31
  { 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
32
  { name: "plan", flags: "--plan", description: "Print the dev runtime plan and exit without starting services.", kind: "boolean" },
35
33
  { name: "reset", flags: "--reset", description: "Clear local dev runtime state before setup, migrations, and service startup.", kind: "boolean" },
34
+ { name: "force", flags: "--force", description: "Terminate overlapping Treeseed dev runtimes and listeners on required local dev ports before startup.", kind: "boolean" },
36
35
  { name: "json", flags: "--json", description: "Emit structured JSON or newline-delimited dev events.", kind: "boolean" },
37
- { name: "watch", flags: "--watch", description: "Enable live watch behavior. `dev` defaults to live feedback; this remains for compatibility.", kind: "boolean" },
38
36
  { name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] }
39
37
  ];
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
- ];
48
38
  function genericWorkflowPosition(spec) {
49
39
  if (spec.group === "Workflow") {
50
40
  if (spec.name === "switch") return "start work";
@@ -1172,34 +1162,32 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1172
1162
  })],
1173
1163
  ["dev", command({
1174
1164
  options: DEV_RUNTIME_OPTIONS,
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"],
1165
+ examples: ["treeseed dev", "treeseed dev --reset", "treeseed dev --reset --plan --json", "treeseed dev --web-runtime local --plan --json", "treeseed dev --port 4322"],
1176
1166
  help: {
1177
1167
  longSummary: [
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.",
1179
- "The command keeps streaming logs and dev events until you press Ctrl+C or receive SIGTERM. Required surface failures are restarted with backoff instead of ending the supervisor."
1168
+ "Dev starts the local Treeseed Market web/API runtime as a foreground supervisor.",
1169
+ "Capacity provider lifecycle is package-owned and runs through `treeseed capacity ...`, not through `treeseed dev`."
1180
1170
  ],
1181
1171
  beforeYouRun: [
1182
1172
  "Run from the tenant or workspace root you want to develop.",
1183
- "Use `--plan --json` when you want to inspect selected surfaces, commands, setup steps, readiness checks, watched paths, and restart policy without starting services.",
1173
+ "Use `--plan --json` when you want to inspect fixed web/API commands, setup steps, readiness checks, watched paths, and restart policy without starting services.",
1184
1174
  "Use `--reset` when you want a fresh local D1 database, Mailpit inbox, generated worker bundle, and Wrangler temp output without deleting configuration.",
1185
1175
  "Dev prints the local web URL after readiness; it does not open a browser unless you pass `--open on` or `--open auto`.",
1186
1176
  "Keep the foreground process running while you test. Press Ctrl+C to stop the supervised stack and free the local ports."
1187
1177
  ],
1188
1178
  examples: [
1189
- example("treeseed dev", "Start integrated local development", "Run web, API, manager, and worker locally and keep supervising them in the foreground."),
1179
+ example("treeseed dev", "Start local Market development", "Run web and API locally and keep supervising them in the foreground."),
1190
1180
  example("treeseed dev --reset", "Start from a fresh local runtime", "Clear disposable local dev state, rerun setup and D1 migrations, then start the dev supervisor."),
1191
1181
  example("treeseed dev --reset --plan --json", "Inspect reset actions", "Emit the reset, setup, readiness, command, and watch plan without deleting local state or starting services."),
1192
1182
  example("treeseed dev --plan --json", "Inspect the runtime plan", "Emit a structured plan with setup steps, commands, ports, URLs, readiness checks, and watch entries."),
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."),
1194
- example("treeseed dev --surface api --plan --json", "Inspect the API surface", "Plan the local API runtime without the web UI."),
1195
- example("treeseed dev --surfaces integrated,agents", "Opt into agents diagnostics", "Start the integrated local platform plus the agents diagnostic loop."),
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."),
1183
+ example("treeseed dev --web-runtime local --plan --json", "Inspect local web runtime", "Plan fixed web/API startup using the local Astro web runtime."),
1184
+ example("treeseed dev --port 4322", "Change the web port", "Start the fixed web/API runtime with the Astro UI on a specific port."),
1197
1185
  example("treeseed dev --open on", "Open the browser explicitly", "Start the integrated runtime and launch the local web URL after readiness."),
1198
1186
  example("trsd dev", "Use the short alias", "Start the same local runtime through the shorter entrypoint."),
1199
1187
  example("treeseed dev --json", "Stream dev events", "Emit newline-delimited events while the long-running dev process supervises local services.")
1200
1188
  ],
1201
1189
  outcomes: [
1202
- "Starts the selected local surfaces, waits for readiness, prints the local URL, and then remains attached as the live supervisor.",
1190
+ "Starts fixed local web/API surfaces, waits for readiness, prints the local URL, and then remains attached as the live supervisor.",
1203
1191
  "Restarts required crashed surfaces with capped exponential backoff and keeps setup/readiness failures alive for retry.",
1204
1192
  "Stops watchers first and then terminates service process groups when the foreground command exits."
1205
1193
  ]
@@ -1207,45 +1195,6 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1207
1195
  executionMode: "handler",
1208
1196
  handlerName: "dev"
1209
1197
  })],
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
- })],
1231
- ["dev:watch", command({
1232
- options: DEV_RUNTIME_OPTIONS,
1233
- examples: ["treeseed dev:watch", "treeseed dev:watch --json"],
1234
- help: {
1235
- longSummary: [
1236
- "Dev:watch is a compatibility alias for foreground dev supervision with live feedback enabled.",
1237
- "It stays attached to the terminal and cleans up supervised services on Ctrl+C just like `dev`."
1238
- ],
1239
- examples: [
1240
- example("treeseed dev:watch", "Start watch mode", "Run the local runtime with watch and rebuild behavior enabled in the foreground."),
1241
- example("trsd dev:watch", "Use the short alias", "Start the same watch-mode runtime through the shorter entrypoint."),
1242
- example("treeseed dev:watch --feedback restart", "Restart services without browser reload", "Use watcher-driven service restarts while leaving browser refresh to your own tooling."),
1243
- example("treeseed dev:watch --help", "Inspect watch help", "Read the help surface before starting a longer watch session.")
1244
- ]
1245
- },
1246
- executionMode: "handler",
1247
- handlerName: "dev"
1248
- })],
1249
1198
  ["build", command({ examples: ["treeseed build"], help: { longSummary: ["Build runs the tenant build path and produces the generated output for the current project."], examples: [example("treeseed build", "Build the tenant", "Run the packaged build flow for the current project."), example("trsd build", "Use the short alias", "Run the same build through the shorter entrypoint."), example("treeseed build && treeseed export", "Build before packaging context", "Produce build artifacts first and then capture a code export if needed.")] }, executionMode: "adapter" })],
1250
1199
  ["check", command({ examples: ["treeseed check"], help: { longSummary: ["Check runs the project validation path against the current tenant and shared fixture model."], examples: [example("treeseed check", "Validate the tenant", "Run the project check flow."), example("trsd check", "Use the short alias", "Run the same validation via the short entrypoint."), example("treeseed check && treeseed doctor", "Pair validation with diagnostics", "Follow failed checks with the broader doctor surface.")] }, executionMode: "adapter" })],
1251
1200
  ["preview", command({ examples: ["treeseed preview"], help: { longSummary: ["Preview serves the built tenant output locally so you can inspect the built site rather than the live dev runtime."], examples: [example("treeseed preview", "Preview the built site", "Run the packaged preview flow for the built tenant."), example("treeseed preview -- --help", "Forward preview help", "Pass through additional args when the preview runtime supports them."), example("treeseed build && treeseed preview", "Build then preview", "Generate the build output first and then serve it locally.")] }, executionMode: "adapter", buildAdapterInput: PASS_THROUGH_ARGS })],
@@ -1571,36 +1520,39 @@ const CLI_ONLY_OPERATION_SPECS = [
1571
1520
  name: "capacity",
1572
1521
  aliases: [],
1573
1522
  group: "Utilities",
1574
- summary: "Manage team helper capacity from the selected market.",
1575
- description: "Inspect team helper credits, connect managed providers, rotate provider security codes, manage grants, and enqueue budgeted agent tasks.",
1523
+ summary: "Operate the package-owned capacity provider runtime.",
1524
+ description: "Run provider diagnostics, registration, and planning through the built @treeseed/agent capacity provider entrypoint.",
1576
1525
  provider: "default",
1577
1526
  related: ["teams", "projects", "agents"],
1578
- usage: "treeseed capacity [status|providers|grants|enqueue]",
1527
+ usage: "treeseed capacity [doctor|register|plan|up|down|restart|logs|status|build|test-local]",
1579
1528
  arguments: [{ name: "action", description: "Capacity action.", required: false }],
1580
1529
  options: [
1581
1530
  { name: "market", flags: "--market <id-or-url>", description: "Select a configured market id or direct market API URL.", kind: "string" },
1582
- { name: "team", flags: "--team <team-id>", description: "Team id for capacity status and grants.", kind: "string" },
1583
- { name: "provider", flags: "--provider <provider-id>", description: "Capacity provider id for grant or key actions.", kind: "string" },
1584
- { name: "project", flags: "--project <project-id>", description: "Project id for grant allocation or agent enqueue.", kind: "string" },
1585
- { name: "key", flags: "--key <key-id>", description: "Provider security code id to revoke.", kind: "string" },
1586
- { name: "taskKind", flags: "--task-kind <kind>", description: "Agent task signature for budgeted enqueue.", kind: "string" },
1587
- { name: "environment", flags: "--environment <environment>", description: "Project environment for grants or enqueue.", kind: "enum", values: ["local", "staging", "prod"] },
1588
- { name: "daily", flags: "--daily <credits>", description: "Daily credit limit for a created grant.", kind: "string" },
1589
- { name: "monthly", flags: "--monthly <credits>", description: "Monthly credit limit for a created grant.", kind: "string" },
1531
+ { name: "provider", flags: "--provider <provider-id>", description: "Provider id or local provider selector for diagnostics.", kind: "string" },
1532
+ { name: "environment", flags: "--environment <scope>", description: "Treeseed config scope used when resolving encrypted provider launch values.", kind: "enum", values: ["local", "staging", "prod"] },
1533
+ { name: "dataDir", flags: "--data-dir <path>", description: "Host data directory mounted into the provider container at /data.", kind: "string" },
1534
+ { name: "agentPackageRoot", flags: "--agent-package-root <path>", description: "Path to a built @treeseed/agent package root.", kind: "string" },
1535
+ { name: "diagnostic", flags: "--diagnostic", description: "Start lifecycle commands without live Market registration or provider credentials.", kind: "boolean" },
1536
+ { name: "dryRun", flags: "--dry-run", description: "Ask the provider runtime to render deterministic output without provider secrets or Market mutation.", kind: "boolean" },
1590
1537
  { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
1591
1538
  ],
1592
1539
  examples: [
1593
- "treeseed capacity status --team team_123",
1594
- "treeseed capacity providers connect --team team_123",
1595
- "treeseed capacity providers keys reset --provider provider_123",
1596
- "treeseed capacity grants create --team team_123 --provider provider_123 --daily 25",
1597
- "treeseed capacity enqueue --project project_123 --task-kind proposal.draft"
1540
+ "treeseed capacity doctor --market local --provider local",
1541
+ "treeseed capacity register --market local --provider local --dry-run --json",
1542
+ "treeseed capacity plan --market local --provider local --dry-run --json",
1543
+ "treeseed capacity build",
1544
+ "treeseed capacity up --market local --provider local",
1545
+ "treeseed capacity up --market local --provider local --diagnostic",
1546
+ "treeseed capacity status --market local --provider local",
1547
+ "treeseed capacity logs --market local --provider local",
1548
+ "treeseed capacity down --market local --provider local",
1549
+ "treeseed capacity test-local"
1598
1550
  ],
1599
1551
  help: {
1600
- longSummary: ["Capacity talks to the market control plane so technical stewards can inspect helper credits, provider security codes, and budgeted agent task routing without opening the browser."],
1601
- whenToUse: ["Use this after connecting to a market when provider health, budget allocation, or budgeted agent enqueue needs to be checked from a terminal."],
1602
- beforeYouRun: ["Authenticate to the selected market and know the team, project, or provider id for the action you are taking."],
1603
- automationNotes: ["Use `--json` when capturing provider keys, grant ids, task ids, or dashboard summaries in automation."]
1552
+ longSummary: ["Capacity invokes the package-owned @treeseed/agent provider runtime for local diagnostics, registration previews, portfolio planning, and Docker/Compose lifecycle commands."],
1553
+ whenToUse: ["Use this when validating a self-hosted capacity provider install, checking provider runtime output, or running the local provider stack."],
1554
+ beforeYouRun: ["Build @treeseed/agent first, or pass --agent-package-root to a built package. Use `trsd config` for encrypted provider values; capacity commands do not write plaintext env files."],
1555
+ automationNotes: ["Use `--json` for stable provider runtime output. `up` runs live local provider mode by default; pass `--diagnostic` or use `test-local` for diagnostics without provider secrets."]
1604
1556
  },
1605
1557
  helpVisible: true,
1606
1558
  helpFeatured: false,
@@ -10,8 +10,6 @@ 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;
14
- readonly 'dev:watch': import("./operations-types.js").TreeseedCommandHandler;
15
13
  readonly doctor: import("./operations-types.js").TreeseedCommandHandler;
16
14
  readonly rollback: import("./operations-types.js").TreeseedCommandHandler;
17
15
  readonly template: import("./operations-types.js").TreeseedCommandHandler;
@@ -54,8 +54,6 @@ const COMMAND_HANDLERS = {
54
54
  status: handleStatus,
55
55
  ci: handleCi,
56
56
  dev: handleDev,
57
- "dev:manager": handleDev,
58
- "dev:watch": handleDev,
59
57
  doctor: handleDoctor,
60
58
  rollback: handleRollback,
61
59
  template: handleTemplate,
@@ -351,6 +351,7 @@ function commandNeedsProjectRoot(spec) {
351
351
  "market",
352
352
  "teams",
353
353
  "projects",
354
+ "capacity",
354
355
  "packs",
355
356
  "template"
356
357
  ])).has(spec.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.8.19",
3
+ "version": "0.9.3",
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.8.19",
48
+ "@treeseed/sdk": "github:treeseed-ai/sdk#0.10.6",
49
49
  "ink": "^7.0.0",
50
50
  "react": "^19.2.5"
51
51
  },