@treeseed/cli 0.10.5 → 0.10.7

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.
@@ -4,6 +4,7 @@ import { resolve } from "node:path";
4
4
  import { resolveCapacityProviderLaunchEnvironment } from "@treeseed/sdk/capacity-provider";
5
5
  import { resolveMarketProfile } from "@treeseed/sdk/market-client";
6
6
  import { findNearestTreeseedRoot, findNearestTreeseedWorkspaceRoot } from "@treeseed/sdk/workflow-support";
7
+ import { createMarketClientForInvocation } from "./market-utils.js";
7
8
  import { fail, guidedResult } from "./utils.js";
8
9
  const ENTRYPOINT_RELATIVE_PATH = ["dist", "provider", "entrypoint.js"];
9
10
  const COMPOSE_FILE_NAME = "compose.capacity-provider.yml";
@@ -11,6 +12,7 @@ const DEFAULT_PROJECT_NAME = "treeseed-capacity-provider";
11
12
  const DEFAULT_HOST_DATA_DIR = ".treeseed/local-capacity-provider/data";
12
13
  const PROVIDER_LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set(["build", "up", "down", "restart", "logs", "status", "test-local"]);
13
14
  const PROVIDER_ENTRYPOINT_ACTIONS = /* @__PURE__ */ new Set(["doctor", "register", "plan"]);
15
+ const MARKET_CAPACITY_ACTIONS = /* @__PURE__ */ new Set(["migrate"]);
14
16
  function stringArg(invocation, name) {
15
17
  const value = invocation.args[name];
16
18
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
@@ -18,6 +20,24 @@ function stringArg(invocation, name) {
18
20
  function boolArg(invocation, name) {
19
21
  return invocation.args[name] === true;
20
22
  }
23
+ function numberArg(invocation, name) {
24
+ const value = invocation.args[name];
25
+ if (typeof value === "number" && Number.isFinite(value)) return value;
26
+ if (typeof value === "string" && value.trim().length > 0 && Number.isFinite(Number(value))) return Number(value);
27
+ return null;
28
+ }
29
+ function formatNumber(value, digits = 2) {
30
+ if (value === null || value === void 0 || value === "") return "n/a";
31
+ const numeric = Number(value);
32
+ if (!Number.isFinite(numeric)) return String(value);
33
+ return numeric.toLocaleString("en-US", { maximumFractionDigits: digits });
34
+ }
35
+ function recordValue(record, key) {
36
+ return record && typeof record === "object" && key in record ? record[key] : void 0;
37
+ }
38
+ function marketRequest(client, path, options = {}) {
39
+ return client.request(path, options);
40
+ }
21
41
  function readPackageName(packageRoot) {
22
42
  const packageJsonPath = resolve(packageRoot, "package.json");
23
43
  if (!existsSync(packageJsonPath)) return null;
@@ -108,6 +128,212 @@ function composeCommandArgs(composeFilePath, projectName, action) {
108
128
  return base;
109
129
  }
110
130
  }
131
+ function nativeBudgetSummaryLines(report) {
132
+ const budgets = recordValue(report, "budgets");
133
+ const nativeCapacity = recordValue(budgets, "nativeCapacity") ?? recordValue(budgets, "native_capacity");
134
+ const executionProviders = recordValue(nativeCapacity, "executionProviders") ?? recordValue(nativeCapacity, "execution_providers");
135
+ if (!Array.isArray(executionProviders)) return [];
136
+ return executionProviders.flatMap((provider) => {
137
+ const name = recordValue(provider, "name") ?? recordValue(provider, "id") ?? "execution provider";
138
+ const kind = recordValue(provider, "kind") ?? "custom";
139
+ const nativeUnit = recordValue(provider, "nativeUnit") ?? recordValue(provider, "native_unit") ?? "native unit";
140
+ const workers = recordValue(provider, "maxConcurrentWorkers") ?? recordValue(provider, "max_concurrent_workers");
141
+ const limits = recordValue(provider, "nativeLimits") ?? recordValue(provider, "native_limits");
142
+ const lines = [`${name}: ${kind}, ${nativeUnit}${workers ? `, workers ${workers}` : ""}`];
143
+ if (Array.isArray(limits)) {
144
+ for (const limit of limits) {
145
+ lines.push(` ${recordValue(limit, "scope") ?? recordValue(limit, "limitScope") ?? "limit"}: ${formatNumber(recordValue(limit, "limitAmount") ?? recordValue(limit, "limit_amount"))} ${recordValue(limit, "nativeUnit") ?? nativeUnit}, reserve ${formatNumber(recordValue(limit, "reserveBufferPercent") ?? recordValue(limit, "reserve_buffer_percent"))}%`);
146
+ }
147
+ }
148
+ return lines;
149
+ });
150
+ }
151
+ function derivedCapacityLines(plan) {
152
+ const derivedCapacity = recordValue(plan, "derivedCapacity");
153
+ const entries = recordValue(derivedCapacity, "entries");
154
+ if (!Array.isArray(entries) || entries.length === 0) {
155
+ return ["No derived native capacity entries are available yet."];
156
+ }
157
+ return entries.map((entry) => [
158
+ `${recordValue(entry, "executionProviderKind") ?? "provider"}:${recordValue(entry, "nativeUnit") ?? "native"}`,
159
+ `limit ${formatNumber(recordValue(entry, "configuredNativeLimit"))}`,
160
+ `observed ${formatNumber(recordValue(entry, "observedNativeRemaining"))}`,
161
+ `reserved ${formatNumber(recordValue(entry, "activeReservedNativeAmount"))}`,
162
+ `reserve ${formatNumber(recordValue(entry, "reserveBufferPercent"))}%`,
163
+ `conversion ${formatNumber(recordValue(entry, "nativeUnitsPerCredit"))} native/credit`,
164
+ `derived ${formatNumber(recordValue(entry, "derivedAvailableCredits"))} credits`,
165
+ `confidence ${recordValue(entry, "confidence") ?? "unknown"}`
166
+ ].join(" | "));
167
+ }
168
+ function grantAllocationLines(plan) {
169
+ const grants = recordValue(plan, "grants");
170
+ if (!Array.isArray(grants) || grants.length === 0) return [];
171
+ return grants.map((grant) => [
172
+ `${recordValue(grant, "grantScope") ?? "grant"} ${recordValue(grant, "environment") ?? "all"}`,
173
+ `allocation ${formatNumber(recordValue(grant, "portfolioAllocationPercent"))}%`,
174
+ `reserve pool ${formatNumber(recordValue(grant, "reservePoolPercent"))}%`,
175
+ `max daily project credits ${formatNumber(recordValue(grant, "maxDailyProjectCredits"))}`,
176
+ `overflow ${recordValue(grant, "overflowPolicy") ?? "soft_grant"}`,
177
+ `emergency ${recordValue(grant, "emergencyOverride") === true ? "on" : "off"}`
178
+ ].join(" | "));
179
+ }
180
+ async function runProjectCapacityPlan(invocation, context) {
181
+ const projectId = stringArg(invocation, "project");
182
+ if (!projectId) return fail("Missing --project. Use `trsd capacity plan --project <project-id> --environment local`.");
183
+ const { profile, client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
184
+ const environment = environmentSelector(invocation);
185
+ const response = await marketRequest(
186
+ client,
187
+ `/v1/projects/${encodeURIComponent(projectId)}/capacity-plan?environment=${encodeURIComponent(environment)}`,
188
+ { requireAuth: true }
189
+ );
190
+ const plan = response.payload;
191
+ return guidedResult({
192
+ command: "capacity plan",
193
+ summary: `Capacity plan for project ${projectId} in ${environment}.`,
194
+ facts: [
195
+ { label: "Market", value: `${profile.id} (${profile.baseUrl})` },
196
+ { label: "Project", value: projectId },
197
+ { label: "Environment", value: environment },
198
+ { label: "Derived credits", value: formatNumber(recordValue(recordValue(plan, "derivedCapacity"), "totalDerivedAvailableCredits")) }
199
+ ],
200
+ sections: [
201
+ { title: "Native projection", lines: derivedCapacityLines(plan) },
202
+ { title: "Allocation grants", lines: grantAllocationLines(plan) }
203
+ ],
204
+ report: { action: "plan", projectId, environment, market: { id: profile.id, baseUrl: profile.baseUrl }, plan }
205
+ });
206
+ }
207
+ function providerMatcher(selector) {
208
+ return (provider) => {
209
+ const id = String(recordValue(provider, "id") ?? "");
210
+ const name = String(recordValue(provider, "name") ?? "");
211
+ return id === selector || name === selector;
212
+ };
213
+ }
214
+ function migrationMissingFields(invocation) {
215
+ const missing = [];
216
+ if (!stringArg(invocation, "team")) missing.push("--team");
217
+ if (!stringArg(invocation, "provider")) missing.push("--provider");
218
+ if (!stringArg(invocation, "kind")) missing.push("--kind");
219
+ if (!stringArg(invocation, "nativeUnit")) missing.push("--native-unit");
220
+ if (numberArg(invocation, "limit") === null) missing.push("--limit");
221
+ return missing;
222
+ }
223
+ async function runMigrateToDerived(invocation, context) {
224
+ if (!boolArg(invocation, "toDerived")) {
225
+ return fail("Missing --to-derived. Phase 8 supports `trsd capacity migrate --to-derived`.");
226
+ }
227
+ const missing = migrationMissingFields(invocation);
228
+ const example = "trsd capacity migrate --to-derived --team team_123 --provider provider_123 --kind codex_subscription --native-unit wall_minute --limit 480 --scope daily --reset-cadence daily --quota-visibility opaque --reserve-buffer-percent 20 --max-concurrent-workers 4 --project project_123 --portfolio-allocation-percent 100 --dry-run";
229
+ if (missing.length > 0) {
230
+ return fail(`Missing native capacity facts: ${missing.join(", ")}.
231
+ Example: ${example}`);
232
+ }
233
+ const teamId = stringArg(invocation, "team");
234
+ const providerSelectorValue = stringArg(invocation, "provider");
235
+ const kind = stringArg(invocation, "kind");
236
+ const nativeUnit = stringArg(invocation, "nativeUnit");
237
+ const limitAmount = numberArg(invocation, "limit");
238
+ const scope = stringArg(invocation, "scope") ?? "daily";
239
+ const resetCadence = stringArg(invocation, "resetCadence") ?? "daily";
240
+ const quotaVisibility = stringArg(invocation, "quotaVisibility") ?? "opaque";
241
+ const reserveBufferPercent = numberArg(invocation, "reserveBufferPercent") ?? 20;
242
+ const maxConcurrentWorkers = Math.max(1, Math.floor(numberArg(invocation, "maxConcurrentWorkers") ?? 1));
243
+ const environment = environmentSelector(invocation);
244
+ const projectId = stringArg(invocation, "project");
245
+ const allocationPercent = numberArg(invocation, "portfolioAllocationPercent");
246
+ const dryRun = boolArg(invocation, "dryRun");
247
+ const { profile, client } = createMarketClientForInvocation(invocation, context, { requireAuth: !dryRun });
248
+ const providerList = dryRun ? { payload: [{ id: providerSelectorValue, name: providerSelectorValue }] } : await marketRequest(
249
+ client,
250
+ `/v1/teams/${encodeURIComponent(teamId)}/capacity-providers`,
251
+ { requireAuth: true }
252
+ );
253
+ const provider = providerList.payload.find(providerMatcher(providerSelectorValue));
254
+ if (!provider) return fail(`Capacity provider "${providerSelectorValue}" was not found in team ${teamId}.`);
255
+ const providerId = String(recordValue(provider, "id"));
256
+ const executionProvider = {
257
+ name: `${kind.replace(/_/gu, " ")} ${nativeUnit}`,
258
+ kind,
259
+ nativeUnit,
260
+ quotaVisibility,
261
+ maxConcurrentWorkers,
262
+ resetCadence,
263
+ nativeLimits: [{
264
+ scope,
265
+ nativeUnit,
266
+ limitAmount,
267
+ reserveBufferPercent,
268
+ resetCadence,
269
+ confidence: "estimated",
270
+ source: "operator_migration"
271
+ }],
272
+ metadata: {
273
+ source: "trsd capacity migrate --to-derived",
274
+ staticCreditBudgetsPreservedAs: "hybrid_fallback_cap"
275
+ }
276
+ };
277
+ const grant = allocationPercent === null ? null : {
278
+ capacityProviderId: providerId,
279
+ teamId,
280
+ projectId,
281
+ environment,
282
+ grantScope: projectId ? "project" : "team",
283
+ portfolioAllocationPercent: allocationPercent,
284
+ overflowPolicy: "soft_grant",
285
+ metadata: {
286
+ source: "trsd capacity migrate --to-derived"
287
+ }
288
+ };
289
+ if (!dryRun) {
290
+ await marketRequest(client, `/v1/teams/${encodeURIComponent(teamId)}/capacity-providers/${encodeURIComponent(providerId)}`, {
291
+ method: "PATCH",
292
+ body: {
293
+ name: String(recordValue(provider, "name") ?? providerId),
294
+ creditBudgetMode: "hybrid"
295
+ },
296
+ requireAuth: true
297
+ });
298
+ await marketRequest(client, `/v1/teams/${encodeURIComponent(teamId)}/capacity-providers/${encodeURIComponent(providerId)}/execution-providers`, {
299
+ method: "POST",
300
+ body: executionProvider,
301
+ requireAuth: true
302
+ });
303
+ if (grant) {
304
+ await marketRequest(client, `/v1/teams/${encodeURIComponent(teamId)}/capacity-grants`, {
305
+ method: "POST",
306
+ body: grant,
307
+ requireAuth: true
308
+ });
309
+ }
310
+ }
311
+ return guidedResult({
312
+ command: "capacity migrate",
313
+ summary: dryRun ? "Dry run: derived native capacity migration plan." : "Derived native capacity migration applied.",
314
+ facts: [
315
+ { label: "Market", value: `${profile.id} (${profile.baseUrl})` },
316
+ { label: "Team", value: teamId },
317
+ { label: "Provider", value: providerId },
318
+ { label: "Native limit", value: `${formatNumber(limitAmount)} ${nativeUnit} / ${scope}` },
319
+ { label: "Reserve buffer", value: `${formatNumber(reserveBufferPercent)}%` },
320
+ { label: "Allocation percent", value: allocationPercent === null ? null : `${formatNumber(allocationPercent)}%` },
321
+ { label: "Dry run", value: dryRun }
322
+ ],
323
+ sections: [
324
+ { title: "Execution provider", lines: [`${executionProvider.name}: ${kind}, ${nativeUnit}, ${maxConcurrentWorkers} workers, ${quotaVisibility} quota visibility`] },
325
+ ...grant ? [{ title: "Allocation grant", lines: [`${grant.grantScope} ${projectId ?? teamId} in ${environment}: ${formatNumber(allocationPercent)}%`] }] : []
326
+ ],
327
+ report: {
328
+ action: "migrate",
329
+ dryRun,
330
+ teamId,
331
+ providerId,
332
+ executionProvider,
333
+ grant
334
+ }
335
+ });
336
+ }
111
337
  function lifecycleActionRequiresConnection(action) {
112
338
  return action === "up" || action === "restart";
113
339
  }
@@ -256,6 +482,7 @@ function invokeProviderEntrypoint(action, invocation, context) {
256
482
  ],
257
483
  sections: [
258
484
  { title: "Output", lines: stdout ? stdout.split(/\r?\n/u) : [] },
485
+ { title: "Native budget file", lines: nativeBudgetSummaryLines(report) },
259
486
  { title: "Errors", lines: stderr ? stderr.split(/\r?\n/u) : [] }
260
487
  ],
261
488
  exitCode: result.status ?? 1,
@@ -268,6 +495,21 @@ function invokeProviderEntrypoint(action, invocation, context) {
268
495
  }
269
496
  const handleCapacity = (invocation, context) => {
270
497
  const action = invocation.positionals[0] ?? "doctor";
498
+ if (action === "plan" && stringArg(invocation, "project")) {
499
+ try {
500
+ return runProjectCapacityPlan(invocation, context);
501
+ } catch (error) {
502
+ return fail(error instanceof Error ? error.message : String(error));
503
+ }
504
+ }
505
+ if (MARKET_CAPACITY_ACTIONS.has(action)) {
506
+ try {
507
+ if (action === "migrate") return runMigrateToDerived(invocation, context);
508
+ return fail(`Unknown capacity action "${action}".`);
509
+ } catch (error) {
510
+ return fail(error instanceof Error ? error.message : String(error));
511
+ }
512
+ }
271
513
  if (PROVIDER_LIFECYCLE_ACTIONS.has(action)) {
272
514
  try {
273
515
  return runLifecycleAction(action, invocation, context);
@@ -282,7 +524,7 @@ const handleCapacity = (invocation, context) => {
282
524
  return fail(error instanceof Error ? error.message : String(error));
283
525
  }
284
526
  }
285
- return fail(`Unknown capacity action "${action}". Use doctor, register, plan, build, up, down, restart, logs, status, or test-local.`);
527
+ return fail(`Unknown capacity action "${action}". Use doctor, register, plan, migrate, build, up, down, restart, logs, status, or test-local.`);
286
528
  };
287
529
  export {
288
530
  handleCapacity
@@ -44,11 +44,11 @@ function resolveCoreDevEntrypoint(cwd) {
44
44
  const handleDev = async (invocation, context) => {
45
45
  try {
46
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.");
47
+ return fail("`trsd dev` starts the Market web/API/dev-runner runtime. Use `trsd capacity ...` for capacity provider lifecycle commands.");
48
48
  }
49
49
  const removedOptions = ["surface", "surfaces", "withWorker"].filter((name) => invocation.args[name] !== void 0);
50
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.`);
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/dev-runner surfaces; use \`trsd capacity ...\` for providers.`);
52
52
  }
53
53
  const feedback = typeof invocation.args.feedback === "string" ? invocation.args.feedback : void 0;
54
54
  const watch = feedback !== "off";
@@ -1,47 +1,402 @@
1
- import { guidedResult } from "./utils.js";
1
+ import { MarketApiError } from "@treeseed/sdk/market-client";
2
+ import { fail, guidedResult } from "./utils.js";
2
3
  import { createMarketClientForInvocation } from "./market-utils.js";
4
+ const DEPLOYMENT_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "timed_out"]);
5
+ const FORBIDDEN_OUTPUT_FIELDS = /* @__PURE__ */ new Set([
6
+ "capacityProviderId",
7
+ "laneId",
8
+ "grantId",
9
+ "workerPoolId",
10
+ "runtimeHostId",
11
+ "railwayServiceId",
12
+ "runnerToken"
13
+ ]);
14
+ const ACTIONS = {
15
+ deploy: "deploy_web",
16
+ publish: "publish_content",
17
+ monitor: "monitor"
18
+ };
19
+ function stringArg(invocation, key) {
20
+ const value = invocation.args[key];
21
+ return typeof value === "string" && value.trim() ? value.trim() : null;
22
+ }
23
+ function boolArg(invocation, key) {
24
+ return invocation.args[key] === true;
25
+ }
26
+ function environmentArg(invocation) {
27
+ const value = stringArg(invocation, "environment") ?? "staging";
28
+ return value === "prod" ? "prod" : "staging";
29
+ }
30
+ function projectUsage(action) {
31
+ switch (action) {
32
+ case "deploy":
33
+ return "Usage: treeseed projects deploy <project-id> --environment staging|prod";
34
+ case "publish":
35
+ return "Usage: treeseed projects publish <project-id> --environment staging|prod";
36
+ case "monitor":
37
+ return "Usage: treeseed projects monitor <project-id> --environment staging|prod";
38
+ case "deployments":
39
+ return "Usage: treeseed projects deployments <project-id>";
40
+ case "deployment":
41
+ return "Usage: treeseed projects deployment <project-id> <deployment-id>";
42
+ default:
43
+ return "Usage: treeseed projects [list|access|deploy|publish|monitor|deployments|deployment]";
44
+ }
45
+ }
46
+ function authFailure(error) {
47
+ if (error instanceof MarketApiError && [401, 403].includes(error.status)) {
48
+ return fail(error.message, 2);
49
+ }
50
+ const message = error instanceof Error ? error.message : String(error);
51
+ if (/not logged in|unauthori[sz]ed|forbidden/iu.test(message)) {
52
+ return fail(message, 2);
53
+ }
54
+ return null;
55
+ }
56
+ function deploymentApiExitCode(error) {
57
+ if (error instanceof MarketApiError) {
58
+ if ([401, 403].includes(error.status)) return 2;
59
+ const payload = error.payload;
60
+ const code = payload?.error?.code ?? payload?.code;
61
+ if (code === "operation_not_retryable" || code === "operation_not_cancellable") return 1;
62
+ }
63
+ return 1;
64
+ }
65
+ function redact(value) {
66
+ if (Array.isArray(value)) return value.map((item) => redact(item));
67
+ if (!value || typeof value !== "object") return value;
68
+ return Object.fromEntries(Object.entries(value).filter(([key]) => !FORBIDDEN_OUTPUT_FIELDS.has(key)).filter(([key]) => !/(?:secret|token|password|apiKey|privateKey)/iu.test(key)).map(([key, entry]) => [key, redact(entry)]));
69
+ }
70
+ function text(value, fallback = "") {
71
+ return typeof value === "string" && value.trim() ? value.trim() : fallback;
72
+ }
73
+ function actionLabel(action) {
74
+ switch (action) {
75
+ case "deploy_web":
76
+ return "deploy_web";
77
+ case "publish_content":
78
+ return "publish_content";
79
+ case "monitor":
80
+ return "monitor";
81
+ default:
82
+ return text(action, "deployment");
83
+ }
84
+ }
85
+ function deploymentUrl(deployment) {
86
+ return text(deployment?.target?.url, text(deployment?.target?.previewUrl, ""));
87
+ }
88
+ function workflowUrl(deployment) {
89
+ return text(deployment?.externalWorkflow?.url, text(deployment?.externalWorkflow?.htmlUrl, text(deployment?.externalWorkflow?.runUrl, "")));
90
+ }
91
+ function inspectCommand(projectId, deploymentId) {
92
+ return `trsd projects deployment ${projectId} ${deploymentId}`;
93
+ }
94
+ function retryCommand(projectId, deploymentId) {
95
+ return `trsd projects deployment retry ${projectId} ${deploymentId}`;
96
+ }
97
+ function deploymentLine(deployment) {
98
+ return [
99
+ deployment.id,
100
+ deployment.environment,
101
+ actionLabel(deployment.action),
102
+ deployment.status,
103
+ deployment.monitor?.status ? `monitor=${deployment.monitor.status}` : "",
104
+ deployment.completedAt ?? deployment.finishedAt ?? deployment.updatedAt ?? "",
105
+ workflowUrl(deployment),
106
+ deploymentUrl(deployment)
107
+ ].filter(Boolean).join(" ");
108
+ }
109
+ function waitExitCode(status) {
110
+ if (status === "succeeded") return 0;
111
+ if (status === "timed_out") return 4;
112
+ if (status === "cancelled") return 5;
113
+ return 3;
114
+ }
115
+ function monitorExitCode(deployment, fallback) {
116
+ if (deployment?.monitor?.status === "failed") return 3;
117
+ if (["healthy", "degraded", "unknown"].includes(String(deployment?.monitor?.status))) return 0;
118
+ return fallback;
119
+ }
120
+ function delay(ms) {
121
+ return new Promise((resolve) => setTimeout(resolve, ms));
122
+ }
123
+ async function waitForDeployment(input) {
124
+ const started = Date.now();
125
+ let current = (await input.client.projectDeployment(input.projectId, input.deploymentId)).payload;
126
+ while (!DEPLOYMENT_TERMINAL_STATUSES.has(String(current.status))) {
127
+ if (Date.now() - started >= input.timeoutSeconds * 1e3) {
128
+ return {
129
+ exitCode: 4,
130
+ deployment: current,
131
+ timedOut: true
132
+ };
133
+ }
134
+ await delay(input.pollIntervalMs);
135
+ current = (await input.client.projectDeployment(input.projectId, input.deploymentId)).payload;
136
+ }
137
+ return {
138
+ exitCode: waitExitCode(String(current.status)),
139
+ deployment: current,
140
+ timedOut: false
141
+ };
142
+ }
143
+ function timeoutSeconds(invocation) {
144
+ const value = Number(stringArg(invocation, "timeoutSeconds") ?? 300);
145
+ return Number.isFinite(value) && value > 0 ? value : 300;
146
+ }
147
+ function pollIntervalMs(invocation) {
148
+ const value = Number(stringArg(invocation, "pollIntervalMs") ?? 1e3);
149
+ return Number.isFinite(value) && value > 0 ? value : 1e3;
150
+ }
151
+ function deploymentRequestBody(invocation, action, environment) {
152
+ const body = {
153
+ environment,
154
+ action,
155
+ source: "cli"
156
+ };
157
+ const reason = stringArg(invocation, "reason");
158
+ const idempotencyKey = stringArg(invocation, "idempotencyKey");
159
+ if (boolArg(invocation, "dryRun")) body.dryRun = true;
160
+ if (reason) body.reason = reason;
161
+ if (idempotencyKey) body.idempotencyKey = idempotencyKey;
162
+ if (environment === "prod" && action !== "monitor") body.confirmProduction = true;
163
+ return body;
164
+ }
165
+ function monitorFacts(deployment) {
166
+ const monitor = deployment?.monitor && typeof deployment.monitor === "object" ? deployment.monitor : null;
167
+ if (!monitor) return [];
168
+ return [
169
+ { label: "Monitor", value: monitor.status ?? null },
170
+ { label: "Checked", value: monitor.checkedAt ?? null },
171
+ { label: "Checks", value: Array.isArray(monitor.checks) ? `${monitor.checks.filter((check) => check.status === "passed").length} passed, ${monitor.checks.filter((check) => check.status === "warning").length} warnings, ${monitor.checks.filter((check) => check.status === "failed").length} failed` : null }
172
+ ];
173
+ }
174
+ function monitorSection(deployment) {
175
+ const checks = Array.isArray(deployment?.monitor?.checks) ? deployment.monitor.checks : [];
176
+ if (checks.length === 0) return [];
177
+ return [{
178
+ title: "Monitor checks",
179
+ lines: checks.map((check) => [
180
+ check.status ?? "skipped",
181
+ check.key ?? check.label ?? "check",
182
+ check.summary ?? "",
183
+ check.inspectCommand ?? check.url ?? ""
184
+ ].filter(Boolean).join(" "))
185
+ }];
186
+ }
3
187
  const handleProjects = async (invocation, context) => {
4
188
  const action = invocation.positionals[0] ?? "list";
5
- const { profile, client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
6
- if (action === "list") {
7
- const teamId = typeof invocation.args.team === "string" ? invocation.args.team : null;
8
- const response = await client.projects(teamId);
9
- return guidedResult({
10
- command: "projects",
11
- summary: "Treeseed market projects",
12
- sections: [{
13
- title: "Projects",
14
- lines: response.payload.map((project) => `${project.id} ${project.name ?? project.slug} team=${project.teamId}`)
15
- }],
16
- report: { marketId: profile.id, teamId, projects: response.payload }
17
- });
189
+ let market;
190
+ try {
191
+ market = createMarketClientForInvocation(invocation, context, { requireAuth: true });
192
+ } catch (error) {
193
+ return authFailure(error) ?? fail(error instanceof Error ? error.message : String(error), 1);
18
194
  }
19
- if (action === "access") {
20
- const projectId = invocation.positionals[1];
21
- if (!projectId) return { exitCode: 1, stderr: ["Usage: treeseed projects access <project-id>"] };
22
- const response = await client.projectAccess(projectId);
195
+ const { profile, client } = market;
196
+ try {
197
+ if (action === "list") {
198
+ const teamId = typeof invocation.args.team === "string" ? invocation.args.team : null;
199
+ const response = await client.projects(teamId);
200
+ return guidedResult({
201
+ command: "projects",
202
+ summary: "Treeseed market projects",
203
+ sections: [{
204
+ title: "Projects",
205
+ lines: response.payload.map((project) => `${project.id} ${project.name ?? project.slug} team=${project.teamId}`)
206
+ }],
207
+ report: { marketId: profile.id, teamId, projects: redact(response.payload) }
208
+ });
209
+ }
210
+ if (action === "access") {
211
+ const projectId = invocation.positionals[1];
212
+ if (!projectId) return fail(projectUsage(action));
213
+ const response = await client.projectAccess(projectId);
214
+ return guidedResult({
215
+ command: "projects",
216
+ summary: "Treeseed market project access",
217
+ facts: [
218
+ { label: "Project", value: response.payload.projectId },
219
+ { label: "Staging admin", value: response.payload.team.summary.canAdminStaging },
220
+ { label: "Production admin", value: response.payload.team.summary.canAdminProduction }
221
+ ],
222
+ sections: [{
223
+ title: "Environments",
224
+ lines: response.payload.environments.map((entry) => `${entry.environment}: ${entry.role}`)
225
+ }],
226
+ report: { marketId: profile.id, access: redact(response.payload) }
227
+ });
228
+ }
229
+ if (action === "connect") {
230
+ return fail("Use treeseed config --connect-market --market-project-id <project-id> for project pairing.");
231
+ }
232
+ if (action in ACTIONS) {
233
+ const projectId = invocation.positionals[1];
234
+ if (!projectId) return fail(projectUsage(action));
235
+ const environment = environmentArg(invocation);
236
+ const deploymentAction = ACTIONS[action];
237
+ if (environment === "prod" && deploymentAction !== "monitor" && !boolArg(invocation, "yes")) {
238
+ return fail(`Production ${action} requires --yes and was not queued.`);
239
+ }
240
+ const response = await client.createProjectWebDeployment(projectId, deploymentRequestBody(invocation, deploymentAction, environment));
241
+ let deployment = response.deployment;
242
+ let waitResult = null;
243
+ if (boolArg(invocation, "wait")) {
244
+ waitResult = await waitForDeployment({
245
+ client,
246
+ projectId,
247
+ deploymentId: deployment.id,
248
+ timeoutSeconds: timeoutSeconds(invocation),
249
+ pollIntervalMs: pollIntervalMs(invocation)
250
+ });
251
+ deployment = waitResult.deployment;
252
+ }
253
+ const exitCode = monitorExitCode(deployment, waitResult?.exitCode ?? 0);
254
+ const summary = waitResult ? waitResult.timedOut ? "Treeseed project deployment wait timed out" : deployment.status === "succeeded" ? "Treeseed project deployment completed" : `Treeseed project deployment ${deployment.status}` : "Treeseed project deployment queued";
255
+ const nextSteps = [
256
+ inspectCommand(projectId, deployment.id),
257
+ ...["failed", "timed_out", "cancelled"].includes(deployment.status) ? [retryCommand(projectId, deployment.id)] : []
258
+ ];
259
+ return guidedResult({
260
+ command: "projects",
261
+ summary,
262
+ exitCode,
263
+ facts: [
264
+ { label: "Project", value: projectId },
265
+ { label: "Environment", value: deployment.environment },
266
+ { label: "Action", value: deployment.action },
267
+ { label: "Deployment", value: deployment.id },
268
+ { label: "Operation", value: deployment.platformOperationId ?? response.operation?.id ?? null },
269
+ { label: "Status", value: deployment.status },
270
+ { label: "URL", value: deploymentUrl(deployment) || null },
271
+ { label: "Workflow", value: workflowUrl(deployment) || null },
272
+ ...monitorFacts(deployment)
273
+ ],
274
+ sections: monitorSection(deployment),
275
+ nextSteps,
276
+ report: {
277
+ marketId: profile.id,
278
+ projectId,
279
+ deployment: redact(deployment),
280
+ operation: redact(response.operation),
281
+ pollUrl: response.pollUrl,
282
+ eventsUrl: response.eventsUrl,
283
+ stateUrl: response.stateUrl,
284
+ wait: waitResult ? { timedOut: waitResult.timedOut, exitCode } : null
285
+ }
286
+ });
287
+ }
288
+ if (action === "deployments") {
289
+ const projectId = invocation.positionals[1];
290
+ if (!projectId) return fail(projectUsage(action));
291
+ const response = await client.projectDeployments(projectId, {
292
+ environment: stringArg(invocation, "environment"),
293
+ limit: stringArg(invocation, "limit")
294
+ });
295
+ return guidedResult({
296
+ command: "projects",
297
+ summary: "Treeseed project deployments",
298
+ sections: [{
299
+ title: "Deployments",
300
+ lines: response.payload.map(deploymentLine)
301
+ }],
302
+ report: { marketId: profile.id, projectId, deployments: redact(response.payload) }
303
+ });
304
+ }
305
+ if (action === "deployment") {
306
+ const subaction = invocation.positionals[1];
307
+ const projectId = ["retry", "resume", "cancel"].includes(String(subaction)) ? invocation.positionals[2] : invocation.positionals[1];
308
+ const deploymentId = ["retry", "resume", "cancel"].includes(String(subaction)) ? invocation.positionals[3] : invocation.positionals[2];
309
+ if (!projectId || !deploymentId) return fail(projectUsage(action));
310
+ if (subaction === "retry") {
311
+ const response = await client.retryProjectDeployment(projectId, deploymentId, {
312
+ ...stringArg(invocation, "idempotencyKey") ? { idempotencyKey: stringArg(invocation, "idempotencyKey") } : {}
313
+ });
314
+ return guidedResult({
315
+ command: "projects",
316
+ summary: "Treeseed project deployment retry queued",
317
+ facts: [
318
+ { label: "Original deployment", value: response.originalDeployment.id },
319
+ { label: "Retry deployment", value: response.retryDeployment.id },
320
+ { label: "Operation", value: response.operation?.id ?? response.retryDeployment.platformOperationId },
321
+ { label: "Status", value: response.retryDeployment.status }
322
+ ],
323
+ nextSteps: [inspectCommand(projectId, response.retryDeployment.id)],
324
+ report: { marketId: profile.id, projectId, originalDeployment: redact(response.originalDeployment), retryDeployment: redact(response.retryDeployment), operation: redact(response.operation) }
325
+ });
326
+ }
327
+ if (subaction === "resume") {
328
+ try {
329
+ const response = await client.resumeProjectDeployment(projectId, deploymentId);
330
+ return guidedResult({
331
+ command: "projects",
332
+ summary: "Treeseed project deployment resume queued",
333
+ report: { marketId: profile.id, projectId, response: redact(response) }
334
+ });
335
+ } catch (error) {
336
+ const message = error instanceof Error ? error.message : String(error);
337
+ return guidedResult({
338
+ command: "projects",
339
+ summary: message,
340
+ exitCode: deploymentApiExitCode(error),
341
+ stderr: [message],
342
+ report: { marketId: profile.id, projectId, deploymentId, ok: false, error: message }
343
+ });
344
+ }
345
+ }
346
+ if (subaction === "cancel") {
347
+ const response = await client.cancelProjectDeployment(projectId, deploymentId);
348
+ const exitCode = response.deployment.status === "cancelled" ? 5 : 0;
349
+ return guidedResult({
350
+ command: "projects",
351
+ summary: response.deployment.status === "cancelled" ? "Treeseed project deployment cancelled" : "Treeseed project deployment cancellation requested",
352
+ exitCode,
353
+ facts: [
354
+ { label: "Deployment", value: response.deployment.id },
355
+ { label: "Status", value: response.deployment.status },
356
+ { label: "Cancellation", value: response.cancellation }
357
+ ],
358
+ report: { marketId: profile.id, projectId, deployment: redact(response.deployment), cancellation: response.cancellation }
359
+ });
360
+ }
361
+ const [deploymentResponse, eventsResponse] = await Promise.all([
362
+ client.projectDeployment(projectId, deploymentId),
363
+ client.projectDeploymentEvents(projectId, deploymentId)
364
+ ]);
365
+ const deployment = deploymentResponse.payload;
366
+ return guidedResult({
367
+ command: "projects",
368
+ summary: "Treeseed project deployment",
369
+ facts: [
370
+ { label: "Project", value: projectId },
371
+ { label: "Deployment", value: deployment.id },
372
+ { label: "Environment", value: deployment.environment },
373
+ { label: "Action", value: deployment.action },
374
+ { label: "Status", value: deployment.status },
375
+ { label: "URL", value: deploymentUrl(deployment) || null },
376
+ { label: "Workflow", value: workflowUrl(deployment) || null },
377
+ ...monitorFacts(deployment)
378
+ ],
379
+ sections: [{
380
+ title: "Events",
381
+ lines: eventsResponse.payload.map((event) => `${event.sequence} ${event.kind} ${event.status ?? ""} ${event.message}`)
382
+ }, ...monitorSection(deployment)],
383
+ nextSteps: ["failed", "timed_out", "cancelled"].includes(deployment.status) ? [retryCommand(projectId, deployment.id)] : [],
384
+ report: { marketId: profile.id, projectId, deployment: redact(deployment), events: redact(eventsResponse.payload) }
385
+ });
386
+ }
387
+ return fail(`Unknown projects action: ${action}`);
388
+ } catch (error) {
389
+ const auth = authFailure(error);
390
+ if (auth) return auth;
391
+ const message = error instanceof Error ? error.message : String(error);
23
392
  return guidedResult({
24
393
  command: "projects",
25
- summary: "Treeseed market project access",
26
- facts: [
27
- { label: "Project", value: response.payload.projectId },
28
- { label: "Staging admin", value: response.payload.team.summary.canAdminStaging },
29
- { label: "Production admin", value: response.payload.team.summary.canAdminProduction }
30
- ],
31
- sections: [{
32
- title: "Environments",
33
- lines: response.payload.environments.map((entry) => `${entry.environment}: ${entry.role}`)
34
- }],
35
- report: { marketId: profile.id, access: response.payload }
394
+ summary: message,
395
+ exitCode: deploymentApiExitCode(error),
396
+ stderr: [message],
397
+ report: { marketId: profile.id, ok: false, error: message }
36
398
  });
37
399
  }
38
- if (action === "connect") {
39
- return {
40
- exitCode: 1,
41
- stderr: ["Use treeseed config --connect-market --market-project-id <project-id> for project pairing."]
42
- };
43
- }
44
- return { exitCode: 1, stderr: [`Unknown projects action: ${action}`] };
45
400
  };
46
401
  export {
47
402
  handleProjects
@@ -1168,29 +1168,30 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1168
1168
  examples: ["treeseed dev", "treeseed dev --reset", "treeseed dev --reset --plan --json", "treeseed dev --web-runtime local --plan --json", "treeseed dev --port 4322"],
1169
1169
  help: {
1170
1170
  longSummary: [
1171
- "Dev starts the local Treeseed Market web/API runtime as a foreground supervisor.",
1171
+ "Dev starts the local Treeseed Market web/API/runtime services as a foreground supervisor.",
1172
1172
  "Capacity provider lifecycle is package-owned and runs through `treeseed capacity ...`, not through `treeseed dev`."
1173
1173
  ],
1174
1174
  beforeYouRun: [
1175
1175
  "Run from the tenant or workspace root you want to develop.",
1176
- "Use `--plan --json` when you want to inspect fixed web/API commands, setup steps, readiness checks, watched paths, and restart policy without starting services.",
1177
- "Use `--reset` when you want a fresh local D1 database, Mailpit inbox, generated worker bundle, and Wrangler temp output without deleting configuration.",
1176
+ "From the Market repo root, dev automatically starts the local Market API, managed local PostgreSQL, and Market operations runner alongside the web UI.",
1177
+ "Use `--plan --json` when you want to inspect fixed web/API/runner commands, setup steps, readiness checks, watched paths, and restart policy without starting services.",
1178
+ "Use `--reset` when you want a fresh local D1 database, Market PostgreSQL state, Mailpit inbox, generated worker bundle, and Wrangler temp output without deleting configuration.",
1178
1179
  "Dev prints the local web URL after readiness; it does not open a browser unless you pass `--open on` or `--open auto`.",
1179
1180
  "Keep the foreground process running while you test. Press Ctrl+C to stop the supervised stack and free the local ports."
1180
1181
  ],
1181
1182
  examples: [
1182
- example("treeseed dev", "Start local Market development", "Run web and API locally and keep supervising them in the foreground."),
1183
- 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."),
1183
+ example("treeseed dev", "Start local Market development", "Run web, API, managed PostgreSQL setup, and the Market operations runner locally."),
1184
+ example("treeseed dev --reset", "Start from a fresh local runtime", "Clear disposable local dev state, rerun setup and database migrations, then start the dev supervisor."),
1184
1185
  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."),
1185
1186
  example("treeseed dev --plan --json", "Inspect the runtime plan", "Emit a structured plan with setup steps, commands, ports, URLs, readiness checks, and watch entries."),
1186
- example("treeseed dev --web-runtime local --plan --json", "Inspect local web runtime", "Plan fixed web/API startup using the local Astro web runtime."),
1187
- example("treeseed dev --port 4322", "Change the web port", "Start the fixed web/API runtime with the Astro UI on a specific port."),
1187
+ example("treeseed dev --web-runtime local --plan --json", "Inspect local web runtime", "Plan fixed web/API/runner startup using the local Astro web runtime."),
1188
+ example("treeseed dev --port 4322", "Change the web port", "Start the fixed web/API/runner runtime with the Astro UI on a specific port."),
1188
1189
  example("treeseed dev --open on", "Open the browser explicitly", "Start the integrated runtime and launch the local web URL after readiness."),
1189
1190
  example("trsd dev", "Use the short alias", "Start the same local runtime through the shorter entrypoint."),
1190
1191
  example("treeseed dev --json", "Stream dev events", "Emit newline-delimited events while the long-running dev process supervises local services.")
1191
1192
  ],
1192
1193
  outcomes: [
1193
- "Starts fixed local web/API surfaces, waits for readiness, prints the local URL, and then remains attached as the live supervisor.",
1194
+ "Starts fixed local web/API/runner surfaces, waits for readiness, prints the local URL, and then remains attached as the live supervisor.",
1194
1195
  "Restarts required crashed surfaces with capped exponential backoff and keeps setup/readiness failures alive for retry.",
1195
1196
  "Stops watchers first and then terminates service process groups when the foreground command exits."
1196
1197
  ]
@@ -1495,23 +1496,44 @@ const CLI_ONLY_OPERATION_SPECS = [
1495
1496
  name: "projects",
1496
1497
  aliases: [],
1497
1498
  group: "Utilities",
1498
- summary: "Inspect projects and access controls from the selected market.",
1499
- description: "List market projects and inspect staging/production access through the market API client.",
1499
+ summary: "Inspect projects and run web deployment operations from the selected market.",
1500
+ description: "List market projects, inspect access, and queue or inspect project web deployment operations through the Market API.",
1500
1501
  provider: "default",
1501
1502
  related: ["market", "teams", "config"],
1502
- usage: "treeseed projects [list|access|connect]",
1503
- arguments: [{ name: "action", description: "Projects action.", required: false }],
1503
+ usage: "treeseed projects [list|access|deploy|publish|monitor|deployments|deployment]",
1504
+ arguments: [
1505
+ { name: "action", description: "Projects action.", required: false },
1506
+ { name: "project-id", description: "Project id for deployment and access actions.", required: false },
1507
+ { name: "deployment-id", description: "Deployment id for deployment detail, retry, resume, or cancel.", required: false }
1508
+ ],
1504
1509
  options: [
1505
1510
  { name: "market", flags: "--market <id-or-url>", description: "Select a configured market id or direct market API URL.", kind: "string" },
1506
1511
  { name: "team", flags: "--team <team-id>", description: "Limit project list to a team.", kind: "string" },
1512
+ { name: "environment", flags: "--environment <environment>", description: "Deployment environment for project web actions.", kind: "enum", values: ["staging", "prod"] },
1513
+ { name: "wait", flags: "--wait", description: "Poll the queued deployment until it reaches a terminal state.", kind: "boolean" },
1514
+ { name: "timeoutSeconds", flags: "--timeout-seconds <seconds>", description: "Maximum seconds to wait before returning timeout.", kind: "string" },
1515
+ { name: "pollIntervalMs", flags: "--poll-interval-ms <milliseconds>", description: "Polling interval for --wait.", kind: "string" },
1516
+ { name: "dryRun", flags: "--dry-run", description: "Queue a dry-run deployment request when supported.", kind: "boolean" },
1517
+ { name: "reason", flags: "--reason <text>", description: "Presentation-safe reason stored on the deployment request.", kind: "string" },
1518
+ { name: "idempotencyKey", flags: "--idempotency-key <key>", description: "Deterministic idempotency key for the deployment request.", kind: "string" },
1519
+ { name: "yes", flags: "--yes", description: "Required confirmation for production deploy and publish actions.", kind: "boolean" },
1520
+ { name: "limit", flags: "--limit <count>", description: "Maximum number of deployments to list.", kind: "string" },
1507
1521
  { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
1508
1522
  ],
1509
- examples: ["treeseed projects list", "treeseed projects access project_123"],
1523
+ examples: [
1524
+ "treeseed projects list",
1525
+ "treeseed projects access project_123",
1526
+ "treeseed projects deploy project_123 --environment staging --wait",
1527
+ "treeseed projects publish project_123 --environment prod --yes",
1528
+ "treeseed projects deployments project_123 --json",
1529
+ "treeseed projects deployment project_123 dep_123"
1530
+ ],
1510
1531
  help: {
1511
- longSummary: ["Projects reads project and environment access data from the selected market API using the SDK market client."],
1512
- whenToUse: ["Use this to confirm which market projects you can see and whether your account has staging or production admin access."],
1513
- beforeYouRun: ["Authenticate to the market and know the project id when inspecting a single project access summary."],
1514
- automationNotes: ["Use `--json` to capture project lists and access summaries for automation."]
1532
+ longSummary: ["Projects reads project, access, and deployment state from the selected market API using the SDK market client."],
1533
+ whenToUse: ["Use this to inspect projects, queue staging or production web deployment operations, and inspect the same deployment state shown in the Market UI."],
1534
+ beforeYouRun: ["Authenticate to the market with `treeseed auth:login --market <selector>` and know the project id before queueing deployment work."],
1535
+ automationNotes: ["Use `--json` to capture project lists, deployment records, events, and wait results for automation."],
1536
+ warnings: ["Production deploy and publish require `--yes`; without it the CLI exits before calling the API."]
1515
1537
  },
1516
1538
  helpVisible: true,
1517
1539
  helpFeatured: false,
@@ -1523,16 +1545,28 @@ const CLI_ONLY_OPERATION_SPECS = [
1523
1545
  name: "capacity",
1524
1546
  aliases: [],
1525
1547
  group: "Utilities",
1526
- summary: "Operate the package-owned capacity provider runtime.",
1527
- description: "Run provider diagnostics, registration, and planning through the built @treeseed/agent capacity provider entrypoint.",
1548
+ summary: "Inspect capacity plans and operate the package-owned capacity provider runtime.",
1549
+ description: "Read Market capacity plans, migrate static providers to derived native capacity, and run provider diagnostics through the built @treeseed/agent entrypoint.",
1528
1550
  provider: "default",
1529
1551
  related: ["teams", "projects", "agents"],
1530
- usage: "treeseed capacity [doctor|register|plan|up|down|restart|logs|status|build|test-local]",
1552
+ usage: "treeseed capacity [doctor|register|plan|migrate|up|down|restart|logs|status|build|test-local]",
1531
1553
  arguments: [{ name: "action", description: "Capacity action.", required: false }],
1532
1554
  options: [
1533
1555
  { name: "market", flags: "--market <id-or-url>", description: "Select a configured market id or direct market API URL.", kind: "string" },
1534
1556
  { name: "provider", flags: "--provider <provider-id>", description: "Provider id or local provider selector for diagnostics.", kind: "string" },
1557
+ { name: "team", flags: "--team <team-id>", description: "Team id for Market capacity migration.", kind: "string" },
1558
+ { name: "project", flags: "--project <project-id>", description: "Project id for Market capacity plan or allocation migration.", kind: "string" },
1535
1559
  { name: "environment", flags: "--environment <scope>", description: "Treeseed config scope used when resolving encrypted provider launch values.", kind: "enum", values: ["local", "staging", "prod"] },
1560
+ { name: "toDerived", flags: "--to-derived", description: "Migrate a static provider to derived native capacity facts.", kind: "boolean" },
1561
+ { name: "kind", flags: "--kind <provider-kind>", description: "Execution provider kind such as codex_subscription or openrouter.", kind: "string" },
1562
+ { name: "nativeUnit", flags: "--native-unit <unit>", description: "Native unit humans can forecast, such as wall_minute, usd, or billable_token.", kind: "string" },
1563
+ { name: "limit", flags: "--limit <amount>", description: "Native limit amount for the selected scope.", kind: "string" },
1564
+ { name: "scope", flags: "--scope <scope>", description: "Native limit scope, usually daily or monthly.", kind: "string" },
1565
+ { name: "resetCadence", flags: "--reset-cadence <cadence>", description: "Native limit reset cadence.", kind: "string" },
1566
+ { name: "quotaVisibility", flags: "--quota-visibility <mode>", description: "Whether quota remaining is visible, sampled, or opaque.", kind: "string" },
1567
+ { name: "reserveBufferPercent", flags: "--reserve-buffer-percent <percent>", description: "Native reserve buffer percentage.", kind: "string" },
1568
+ { name: "maxConcurrentWorkers", flags: "--max-concurrent-workers <count>", description: "Maximum workers this execution provider can run concurrently.", kind: "string" },
1569
+ { name: "portfolioAllocationPercent", flags: "--portfolio-allocation-percent <percent>", description: "Optional project/team allocation percent to create during migration.", kind: "string" },
1536
1570
  { name: "dataDir", flags: "--data-dir <path>", description: "Host data directory mounted into the provider container at /data.", kind: "string" },
1537
1571
  { name: "agentPackageRoot", flags: "--agent-package-root <path>", description: "Path to a built @treeseed/agent package root.", kind: "string" },
1538
1572
  { name: "diagnostic", flags: "--diagnostic", description: "Start lifecycle commands without live Market registration or provider credentials.", kind: "boolean" },
@@ -1541,8 +1575,10 @@ const CLI_ONLY_OPERATION_SPECS = [
1541
1575
  ],
1542
1576
  examples: [
1543
1577
  "treeseed capacity doctor --market local --provider local",
1578
+ "treeseed capacity plan --market local --project project_123 --environment local",
1544
1579
  "treeseed capacity register --market local --provider local --dry-run --json",
1545
1580
  "treeseed capacity plan --market local --provider local --dry-run --json",
1581
+ "treeseed capacity migrate --to-derived --market local --team team_123 --provider provider_123 --kind codex_subscription --native-unit wall_minute --limit 480 --scope daily --dry-run",
1546
1582
  "treeseed capacity build",
1547
1583
  "treeseed capacity up --market local --provider local",
1548
1584
  "treeseed capacity up --market local --provider local --diagnostic",
@@ -1552,8 +1588,8 @@ const CLI_ONLY_OPERATION_SPECS = [
1552
1588
  "treeseed capacity test-local"
1553
1589
  ],
1554
1590
  help: {
1555
- longSummary: ["Capacity invokes the package-owned @treeseed/agent provider runtime for local diagnostics, registration previews, portfolio planning, and Docker/Compose lifecycle commands."],
1556
- whenToUse: ["Use this when validating a self-hosted capacity provider install, checking provider runtime output, or running the local provider stack."],
1591
+ longSummary: ["Capacity reads Market project capacity plans, migrates provider setup toward derived native facts, and invokes the package-owned @treeseed/agent provider runtime for local diagnostics and Docker/Compose lifecycle commands."],
1592
+ whenToUse: ["Use this when inspecting native-to-credit projection, validating a self-hosted capacity provider install, checking provider runtime output, or running the local provider stack."],
1557
1593
  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."],
1558
1594
  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."]
1559
1595
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.10.5",
3
+ "version": "0.10.7",
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/seed.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/projects-deploy.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": "github:treeseed-ai/sdk#0.10.11",
48
+ "@treeseed/sdk": "github:treeseed-ai/sdk#0.10.13",
49
49
  "ink": "^7.0.0",
50
50
  "react": "^19.2.5"
51
51
  },