@treeseed/cli 0.10.18 → 0.10.21
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.
- package/README.md +23 -0
- package/dist/cli/handlers/config-ui.d.ts +3 -0
- package/dist/cli/handlers/config-ui.js +6 -0
- package/dist/cli/handlers/config.js +2 -1
- package/dist/cli/handlers/dev.js +14 -1
- package/dist/cli/handlers/init.js +35 -4
- package/dist/cli/handlers/projects.js +133 -1
- package/dist/cli/handlers/template.js +90 -0
- package/dist/cli/operations-registry.js +198 -14
- package/dist/cli/runtime.js +26 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ The main workflow commands exposed by the current CLI are:
|
|
|
45
45
|
- `treeseed tasks [--json]`
|
|
46
46
|
- `treeseed switch <branch-name> [--preview]`
|
|
47
47
|
- `treeseed dev`
|
|
48
|
+
- `treeseed dev start|status|logs|stop|restart`
|
|
48
49
|
- `treeseed save [--preview] [--plan] "<commit message>"`
|
|
49
50
|
- `treeseed stage "<resolution message>"`
|
|
50
51
|
- `treeseed close "<close reason>"`
|
|
@@ -65,6 +66,7 @@ treeseed config
|
|
|
65
66
|
treeseed switch feature/search-improvements --plan
|
|
66
67
|
treeseed switch feature/search-improvements --preview
|
|
67
68
|
treeseed dev
|
|
69
|
+
treeseed dev start --web-runtime local
|
|
68
70
|
treeseed save --preview "feat: add search filters"
|
|
69
71
|
treeseed stage "feat: add search filters"
|
|
70
72
|
treeseed release --patch
|
|
@@ -72,6 +74,27 @@ treeseed recover
|
|
|
72
74
|
treeseed status --json
|
|
73
75
|
```
|
|
74
76
|
|
|
77
|
+
## Development Server Instances
|
|
78
|
+
|
|
79
|
+
`treeseed dev` remains the foreground local runtime supervisor. It delegates to `@treeseed/core`, starts the Market web/API/control-plane development surface, streams output in the active terminal, and exits when the shell-owned process is stopped.
|
|
80
|
+
|
|
81
|
+
Managed dev instances use subcommands:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
treeseed dev start --web-runtime local --json
|
|
85
|
+
treeseed dev status --json
|
|
86
|
+
treeseed dev status --all --json
|
|
87
|
+
treeseed dev logs --follow
|
|
88
|
+
treeseed dev stop --json
|
|
89
|
+
treeseed dev restart --web-runtime local --json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Managed instances are scoped to the current physical worktree. The core runtime writes `.treeseed/dev/instances/<scope>.json`, `.treeseed/dev/pids/<scope>.pid`, and `.treeseed/logs/dev-<scope>.jsonl` in that worktree. A repository-family index under the git common dir makes sibling worktree instances discoverable to humans and AI agents.
|
|
93
|
+
|
|
94
|
+
`--force` replaces only the current worktree instance. `--force-conflicts` is the explicit cross-worktree port-owner escape hatch. Additional worktrees receive stable alternate port blocks and worktree-specific local PostgreSQL/Mailpit names, so many agents can run development sessions in the same repository family.
|
|
95
|
+
|
|
96
|
+
For the complete architecture, see the root workspace document `docs/local-dev-instances.md`.
|
|
97
|
+
|
|
75
98
|
## Agent-Safe Workflow
|
|
76
99
|
|
|
77
100
|
Use planning mode before any destructive or multi-repo mutation:
|
|
@@ -21,6 +21,9 @@ type ConfigEntry = {
|
|
|
21
21
|
purposes: string[];
|
|
22
22
|
storage: 'shared' | 'scoped';
|
|
23
23
|
validation?: ConfigValidation;
|
|
24
|
+
sourceRequirement?: string;
|
|
25
|
+
sourceHostType?: string | null;
|
|
26
|
+
sourceProvider?: string | null;
|
|
24
27
|
scope: Exclude<ConfigScope, 'all'>;
|
|
25
28
|
sharedScopes: Array<Exclude<ConfigScope, 'all'>>;
|
|
26
29
|
required: boolean;
|
|
@@ -336,6 +336,9 @@ function buildStartupDetailLines(step, draftValue) {
|
|
|
336
336
|
`Applies to: ${step.scopes.join(", ")}`,
|
|
337
337
|
`Required in: ${step.requiredScopes.join(", ")}`,
|
|
338
338
|
`Storage: ${step.entry.storage}`,
|
|
339
|
+
...step.entry.sourceRequirement ? [
|
|
340
|
+
`Host source: ${step.entry.sourceRequirement}${step.entry.sourceProvider ? ` (${step.entry.sourceProvider})` : ""}${step.entry.sourceHostType ? ` / ${step.entry.sourceHostType}` : ""}`
|
|
341
|
+
] : [],
|
|
339
342
|
"",
|
|
340
343
|
`Current value: ${formatDisplayValue(step, step.currentValue, "(unset)")}`,
|
|
341
344
|
`Suggested value: ${formatDisplayValue(step, step.suggestedValue, "(none)")}`,
|
|
@@ -356,6 +359,9 @@ function buildFullDetailLines(page, draftValue) {
|
|
|
356
359
|
`Scope: ${page.scopes.join(", ")}`,
|
|
357
360
|
`Storage: ${page.entry.storage} | ${page.required ? "required" : "optional"}`,
|
|
358
361
|
`Group: ${page.entry.group}`,
|
|
362
|
+
...page.entry.sourceRequirement ? [
|
|
363
|
+
`Host source: ${page.entry.sourceRequirement}${page.entry.sourceProvider ? ` (${page.entry.sourceProvider})` : ""}${page.entry.sourceHostType ? ` / ${page.entry.sourceHostType}` : ""}`
|
|
364
|
+
] : [],
|
|
359
365
|
"",
|
|
360
366
|
`Current: ${formatDisplayValue(page, page.currentValue, "(unset)")}`,
|
|
361
367
|
`Suggested: ${formatDisplayValue(page, page.suggestedValue, "(none)")}`,
|
|
@@ -32,7 +32,8 @@ function formatPrintEnvReports(payload) {
|
|
|
32
32
|
lines.push(`Resolved environment values for ${report.scope}`);
|
|
33
33
|
lines.push(payload.secretsRevealed ? "Secrets are shown." : "Secret values are masked.");
|
|
34
34
|
for (const entry of report.environment?.entries ?? []) {
|
|
35
|
-
|
|
35
|
+
const hostSource = entry.sourceRequirement ? ` requirement=${entry.sourceRequirement}${entry.sourceProvider ? ` provider=${entry.sourceProvider}` : ""}${entry.sourceHostType ? ` hostType=${entry.sourceHostType}` : ""}` : "";
|
|
36
|
+
lines.push(`${entry.id}=${entry.displayValue} (${entry.source}${hostSource})`);
|
|
36
37
|
}
|
|
37
38
|
lines.push("");
|
|
38
39
|
lines.push(`Provider connection checks for ${report.scope}`);
|
package/dist/cli/handlers/dev.js
CHANGED
|
@@ -52,6 +52,11 @@ const handleDev = async (invocation, context) => {
|
|
|
52
52
|
}
|
|
53
53
|
const feedback = typeof invocation.args.feedback === "string" ? invocation.args.feedback : void 0;
|
|
54
54
|
const watch = feedback !== "off";
|
|
55
|
+
const subcommand = typeof invocation.positionals[0] === "string" ? invocation.positionals[0] : "";
|
|
56
|
+
const managedSubcommands = /* @__PURE__ */ new Set(["start", "status", "logs", "stop", "restart"]);
|
|
57
|
+
if (subcommand && !managedSubcommands.has(subcommand)) {
|
|
58
|
+
return fail(`Unknown dev subcommand "${subcommand}". Use start, status, logs, stop, or restart.`);
|
|
59
|
+
}
|
|
55
60
|
const passthroughArgs = ["--surfaces", "web,api"];
|
|
56
61
|
const forwardStringOption = (name, flag) => {
|
|
57
62
|
const value = invocation.args[name];
|
|
@@ -75,6 +80,9 @@ const handleDev = async (invocation, context) => {
|
|
|
75
80
|
forwardBooleanOption("plan", "--plan");
|
|
76
81
|
forwardBooleanOption("reset", "--reset");
|
|
77
82
|
forwardBooleanOption("force", "--force");
|
|
83
|
+
forwardBooleanOption("forceConflicts", "--force-conflicts");
|
|
84
|
+
forwardBooleanOption("all", "--all");
|
|
85
|
+
forwardBooleanOption("follow", "--follow");
|
|
78
86
|
forwardBooleanOption("json", "--json");
|
|
79
87
|
const workspaceRoot = findNearestTreeseedWorkspaceRoot(context.cwd);
|
|
80
88
|
const workspaceLinksMode = typeof invocation.args.workspaceLinks === "string" ? invocation.args.workspaceLinks : void 0;
|
|
@@ -83,7 +91,12 @@ const handleDev = async (invocation, context) => {
|
|
|
83
91
|
context.write(`[workspace][link] Linked ${workspaceLinks.created.length} local workspace package paths.`, "stdout");
|
|
84
92
|
}
|
|
85
93
|
const resolved = resolveCoreDevEntrypoint(context.cwd);
|
|
86
|
-
const args =
|
|
94
|
+
const args = [
|
|
95
|
+
...resolved.args,
|
|
96
|
+
...subcommand ? [subcommand] : [],
|
|
97
|
+
...passthroughArgs,
|
|
98
|
+
...watch && !subcommand ? ["--watch"] : []
|
|
99
|
+
];
|
|
87
100
|
const result = context.spawn(resolved.command, args, {
|
|
88
101
|
cwd: context.cwd,
|
|
89
102
|
env: resolveTreeseedLaunchEnvironment({
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { TREESEED_DEFAULT_STARTER_TEMPLATE_ID } from "@treeseed/sdk";
|
|
1
2
|
import { TreeseedOperationsSdk } from "@treeseed/sdk/operations";
|
|
2
3
|
import { guidedResult } from "./utils.js";
|
|
3
4
|
const operations = new TreeseedOperationsSdk();
|
|
5
|
+
function normalizeRepeatable(value) {
|
|
6
|
+
return Array.isArray(value) ? value.map(String) : typeof value === "string" ? [value] : [];
|
|
7
|
+
}
|
|
4
8
|
const handleInit = async (invocation, context) => {
|
|
5
9
|
const directory = invocation.positionals[0];
|
|
6
10
|
const result = await operations.execute({
|
|
@@ -13,7 +17,8 @@ const handleInit = async (invocation, context) => {
|
|
|
13
17
|
siteUrl: invocation.args.siteUrl,
|
|
14
18
|
contactEmail: invocation.args.contactEmail,
|
|
15
19
|
repo: invocation.args.repo,
|
|
16
|
-
discord: invocation.args.discord
|
|
20
|
+
discord: invocation.args.discord,
|
|
21
|
+
hostBindingSpecs: normalizeRepeatable(invocation.args.host)
|
|
17
22
|
}
|
|
18
23
|
}, {
|
|
19
24
|
cwd: context.cwd,
|
|
@@ -31,13 +36,35 @@ const handleInit = async (invocation, context) => {
|
|
|
31
36
|
report: result.payload
|
|
32
37
|
};
|
|
33
38
|
}
|
|
39
|
+
const payload = result.payload;
|
|
40
|
+
const hostSummaries = Array.isArray(payload?.hostBindingSummaries) ? payload.hostBindingSummaries : [];
|
|
41
|
+
const configWrites = Array.isArray(payload?.hostBindingConfig?.configWrites) ? payload.hostBindingConfig.configWrites : [];
|
|
42
|
+
const environmentWrites = Array.isArray(payload?.hostBindingConfig?.environmentWrites) ? payload.hostBindingConfig.environmentWrites : [];
|
|
34
43
|
return guidedResult({
|
|
35
44
|
command: "init",
|
|
36
45
|
summary: "Treeseed init completed successfully.",
|
|
37
|
-
facts: [
|
|
46
|
+
facts: [
|
|
47
|
+
{ label: "Directory", value: directory ?? "(current directory)" },
|
|
48
|
+
{ label: "Template", value: String(payload?.template ?? TREESEED_DEFAULT_STARTER_TEMPLATE_ID) },
|
|
49
|
+
{ label: "Host bindings", value: hostSummaries.length > 0 ? hostSummaries.length : "(none)" }
|
|
50
|
+
],
|
|
51
|
+
sections: [
|
|
52
|
+
...hostSummaries.length > 0 ? [{
|
|
53
|
+
title: "Host Bindings",
|
|
54
|
+
lines: hostSummaries.map((summary) => `${summary.requirementKey}: ${summary.mode}${summary.provider ? ` ${summary.provider}` : ""}${summary.alias ? ` (${summary.alias})` : ""}`)
|
|
55
|
+
}] : [],
|
|
56
|
+
...configWrites.length > 0 ? [{
|
|
57
|
+
title: "Config Writes",
|
|
58
|
+
lines: configWrites.map((write) => `${write.target} ${write.path} <- ${write.provider ?? "template"}`)
|
|
59
|
+
}] : [],
|
|
60
|
+
...environmentWrites.length > 0 ? [{
|
|
61
|
+
title: "Environment Entries",
|
|
62
|
+
lines: environmentWrites.map((entry) => `${entry.env}: ${entry.sensitivity} from ${entry.requirementKey}${entry.sourceProvider ? ` (${entry.sourceProvider})` : ""}`)
|
|
63
|
+
}] : []
|
|
64
|
+
],
|
|
38
65
|
nextSteps: [
|
|
39
66
|
`cd ${directory}`,
|
|
40
|
-
|
|
67
|
+
`treeseed template show ${String(payload?.template ?? TREESEED_DEFAULT_STARTER_TEMPLATE_ID)}`,
|
|
41
68
|
"treeseed sync --check",
|
|
42
69
|
"treeseed doctor",
|
|
43
70
|
"treeseed config --environment local",
|
|
@@ -45,7 +72,11 @@ const handleInit = async (invocation, context) => {
|
|
|
45
72
|
],
|
|
46
73
|
report: {
|
|
47
74
|
directory: directory ?? null,
|
|
48
|
-
template:
|
|
75
|
+
template: payload?.template ?? null,
|
|
76
|
+
hostBindings: payload?.hostBindings ?? {},
|
|
77
|
+
hostBindingPlans: payload?.hostBindingPlans ?? null,
|
|
78
|
+
hostBindingSummaries: hostSummaries,
|
|
79
|
+
hostBindingConfig: payload?.hostBindingConfig ?? null
|
|
49
80
|
}
|
|
50
81
|
});
|
|
51
82
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MarketApiError } from "@treeseed/sdk/market-client";
|
|
2
|
+
import { parseProjectLaunchHostBindingSpecs } from "@treeseed/sdk";
|
|
2
3
|
import { fail, guidedResult } from "./utils.js";
|
|
3
4
|
import { createMarketClientForInvocation } from "./market-utils.js";
|
|
4
5
|
const DEPLOYMENT_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled", "timed_out"]);
|
|
@@ -39,8 +40,10 @@ function projectUsage(action) {
|
|
|
39
40
|
return "Usage: treeseed projects deployments <project-id>";
|
|
40
41
|
case "deployment":
|
|
41
42
|
return "Usage: treeseed projects deployment <project-id> <deployment-id>";
|
|
43
|
+
case "hosts":
|
|
44
|
+
return "Usage: treeseed projects hosts [audit|replace|resync|rotate] <project-id> [--host <requirement=provider:host-id|managed>]";
|
|
42
45
|
default:
|
|
43
|
-
return "Usage: treeseed projects [list|access|deploy|publish|monitor|deployments|deployment]";
|
|
46
|
+
return "Usage: treeseed projects [list|access|hosts|deploy|publish|monitor|deployments|deployment]";
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
function authFailure(error) {
|
|
@@ -106,6 +109,55 @@ function deploymentLine(deployment) {
|
|
|
106
109
|
deploymentUrl(deployment)
|
|
107
110
|
].filter(Boolean).join(" ");
|
|
108
111
|
}
|
|
112
|
+
function normalizeRepeatable(value) {
|
|
113
|
+
if (Array.isArray(value)) return value.map(String).filter((entry) => entry.trim());
|
|
114
|
+
return typeof value === "string" && value.trim() ? [value.trim()] : [];
|
|
115
|
+
}
|
|
116
|
+
function hostBindingLine(entry) {
|
|
117
|
+
const binding = entry?.binding ?? {};
|
|
118
|
+
const audit = entry?.audit ?? {};
|
|
119
|
+
return [
|
|
120
|
+
entry.requirementKey,
|
|
121
|
+
entry.required ? "required" : "optional",
|
|
122
|
+
entry.type,
|
|
123
|
+
binding.provider ?? "(none)",
|
|
124
|
+
binding.hostId ?? binding.managedHostKey ?? "(not selected)",
|
|
125
|
+
audit.status ?? "ok"
|
|
126
|
+
].filter(Boolean).join(" ");
|
|
127
|
+
}
|
|
128
|
+
function operationFacts(projectId, response) {
|
|
129
|
+
const operation = response.operation ?? null;
|
|
130
|
+
return [
|
|
131
|
+
{ label: "Project", value: projectId },
|
|
132
|
+
{ label: "Operation", value: operation?.id ?? null },
|
|
133
|
+
{ label: "Status", value: operation?.status ?? null },
|
|
134
|
+
{ label: "Poll", value: operation?.pollUrl ?? null }
|
|
135
|
+
].filter((fact) => fact.value != null && fact.value !== "");
|
|
136
|
+
}
|
|
137
|
+
function hostBindingForMarket(parsed, requirementKey) {
|
|
138
|
+
const summary = parsed.summaries.find((entry) => entry.requirementKey === requirementKey) ?? parsed.omitted.find((entry) => entry.requirementKey === requirementKey);
|
|
139
|
+
const binding = parsed.hostBindings[requirementKey];
|
|
140
|
+
if (summary?.mode === "none") {
|
|
141
|
+
return {
|
|
142
|
+
requirementKey,
|
|
143
|
+
requirementKind: "host",
|
|
144
|
+
type: summary.type,
|
|
145
|
+
provider: summary.provider ?? "",
|
|
146
|
+
hostId: null,
|
|
147
|
+
managedHostKey: null,
|
|
148
|
+
mode: "none",
|
|
149
|
+
selectedBy: "user"
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (!binding || !summary) return null;
|
|
153
|
+
return {
|
|
154
|
+
...binding,
|
|
155
|
+
hostId: summary.mode === "team_owned" ? summary.alias : null,
|
|
156
|
+
managedHostKey: summary.mode === "treeseed_managed" ? summary.alias === "managed" ? binding.managedHostKey : summary.alias : null,
|
|
157
|
+
displayName: summary.displayName,
|
|
158
|
+
environmentScopes: binding.environmentScopes?.filter((scope) => scope !== "local") ?? ["staging", "prod"]
|
|
159
|
+
};
|
|
160
|
+
}
|
|
109
161
|
function waitExitCode(status) {
|
|
110
162
|
if (status === "succeeded") return 0;
|
|
111
163
|
if (status === "timed_out") return 4;
|
|
@@ -226,6 +278,86 @@ const handleProjects = async (invocation, context) => {
|
|
|
226
278
|
report: { marketId: profile.id, access: redact(response.payload) }
|
|
227
279
|
});
|
|
228
280
|
}
|
|
281
|
+
if (action === "hosts") {
|
|
282
|
+
const subaction = ["audit", "replace", "resync", "rotate"].includes(String(invocation.positionals[1])) ? String(invocation.positionals[1]) : "list";
|
|
283
|
+
const projectId = subaction === "list" ? invocation.positionals[1] : invocation.positionals[2];
|
|
284
|
+
if (!projectId) return fail(projectUsage(action));
|
|
285
|
+
if (subaction === "list") {
|
|
286
|
+
const response2 = await client.projectHosts(projectId);
|
|
287
|
+
const view2 = response2.payload.view ?? {};
|
|
288
|
+
return guidedResult({
|
|
289
|
+
command: "projects",
|
|
290
|
+
summary: "Treeseed project host bindings",
|
|
291
|
+
facts: [
|
|
292
|
+
{ label: "Project", value: projectId },
|
|
293
|
+
{ label: "Status", value: view2.summary?.status ?? "ok" },
|
|
294
|
+
{ label: "Requirements", value: view2.summary?.total ?? 0 }
|
|
295
|
+
],
|
|
296
|
+
sections: [{
|
|
297
|
+
title: "Host requirements",
|
|
298
|
+
lines: (view2.requirements ?? []).map(hostBindingLine)
|
|
299
|
+
}],
|
|
300
|
+
report: { marketId: profile.id, projectId, hosts: redact(response2.payload) }
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
if (subaction === "audit") {
|
|
304
|
+
const response2 = await client.auditProjectHosts(projectId, {
|
|
305
|
+
idempotencyKey: stringArg(invocation, "idempotencyKey")
|
|
306
|
+
});
|
|
307
|
+
const view2 = response2.payload.view ?? {};
|
|
308
|
+
return guidedResult({
|
|
309
|
+
command: "projects",
|
|
310
|
+
summary: "Treeseed project host audit",
|
|
311
|
+
facts: [
|
|
312
|
+
{ label: "Project", value: projectId },
|
|
313
|
+
{ label: "Status", value: view2.summary?.status ?? "ok" },
|
|
314
|
+
{ label: "Warnings", value: view2.summary?.warnings ?? 0 },
|
|
315
|
+
{ label: "Blocked", value: view2.summary?.blocked ?? 0 }
|
|
316
|
+
],
|
|
317
|
+
sections: [{
|
|
318
|
+
title: "Diagnostics",
|
|
319
|
+
lines: (view2.diagnostics ?? []).map((entry) => `${entry.status} ${entry.requirementKey ?? ""} ${entry.message}`)
|
|
320
|
+
}],
|
|
321
|
+
report: { marketId: profile.id, projectId, audit: redact(response2.payload) }
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const hostSpecs = normalizeRepeatable(invocation.args.host);
|
|
325
|
+
const hostSnapshot = await client.projectHosts(projectId);
|
|
326
|
+
const launchRequirements = hostSnapshot.payload.launchRequirements ?? null;
|
|
327
|
+
let requirementKey = stringArg(invocation, "requirement");
|
|
328
|
+
let hostBinding = null;
|
|
329
|
+
if (subaction === "replace") {
|
|
330
|
+
if (hostSpecs.length !== 1) return fail("Host replacement requires exactly one --host <requirement=provider:host-id|managed> spec.");
|
|
331
|
+
try {
|
|
332
|
+
const parsed = parseProjectLaunchHostBindingSpecs({ specs: hostSpecs, launchRequirements });
|
|
333
|
+
requirementKey = requirementKey ?? parsed.summaries[0]?.requirementKey ?? parsed.omitted[0]?.requirementKey ?? null;
|
|
334
|
+
if (!requirementKey) return fail("Host replacement could not determine a launch requirement key.");
|
|
335
|
+
hostBinding = hostBindingForMarket(parsed, requirementKey);
|
|
336
|
+
if (!hostBinding) return fail("Host replacement could not normalize the selected host binding.");
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (!requirementKey) return fail(`${subaction} requires --requirement <key>.`);
|
|
342
|
+
const body = {
|
|
343
|
+
...hostBinding ? { hostBinding } : {},
|
|
344
|
+
...stringArg(invocation, "sensitivePassphrase") ? { sensitivePassphrase: stringArg(invocation, "sensitivePassphrase") } : {},
|
|
345
|
+
...stringArg(invocation, "idempotencyKey") ? { idempotencyKey: stringArg(invocation, "idempotencyKey") } : {}
|
|
346
|
+
};
|
|
347
|
+
const response = subaction === "replace" ? await client.replaceProjectHost(projectId, requirementKey, body) : subaction === "resync" ? await client.resyncProjectHost(projectId, requirementKey, body) : await client.rotateProjectHost(projectId, requirementKey, body);
|
|
348
|
+
const view = response.payload.view ?? {};
|
|
349
|
+
return guidedResult({
|
|
350
|
+
command: "projects",
|
|
351
|
+
summary: `Treeseed project host ${subaction} queued`,
|
|
352
|
+
facts: operationFacts(projectId, response),
|
|
353
|
+
sections: [{
|
|
354
|
+
title: "Host requirements",
|
|
355
|
+
lines: (view.requirements ?? []).map(hostBindingLine)
|
|
356
|
+
}],
|
|
357
|
+
nextSteps: response.operation?.id ? [`trsd projects hosts ${projectId}`, `trsd operations ${response.operation.id}`] : [`trsd projects hosts ${projectId}`],
|
|
358
|
+
report: { marketId: profile.id, projectId, response: redact(response) }
|
|
359
|
+
});
|
|
360
|
+
}
|
|
229
361
|
if (action === "connect") {
|
|
230
362
|
return fail("Use treeseed config --connect-market --market-project-id <project-id> for project pairing.");
|
|
231
363
|
}
|
|
@@ -9,6 +9,50 @@ import {
|
|
|
9
9
|
import { guidedResult } from "./utils.js";
|
|
10
10
|
import { marketAuthRoot, marketSelector } from "./market-utils.js";
|
|
11
11
|
const operations = new TreeseedOperationsSdk();
|
|
12
|
+
function requirementStatus(required) {
|
|
13
|
+
return required === true ? "required" : "optional";
|
|
14
|
+
}
|
|
15
|
+
function providerList(requirement) {
|
|
16
|
+
return Array.isArray(requirement.compatibleProviders) && requirement.compatibleProviders.length > 0 ? requirement.compatibleProviders.join(", ") : "any provider";
|
|
17
|
+
}
|
|
18
|
+
function renderLaunchRequirementSections(template) {
|
|
19
|
+
const launchRequirements = template.launchRequirements;
|
|
20
|
+
if (!launchRequirements) {
|
|
21
|
+
return [{ title: "Launch Requirements", lines: ["No launch requirements declared."] }];
|
|
22
|
+
}
|
|
23
|
+
const hosts = Array.isArray(launchRequirements.hosts) ? launchRequirements.hosts : [];
|
|
24
|
+
const resources = Array.isArray(launchRequirements.resources) ? launchRequirements.resources : [];
|
|
25
|
+
const secrets = Array.isArray(launchRequirements.secrets) ? launchRequirements.secrets : [];
|
|
26
|
+
const configurableRequirements = [...hosts, ...resources];
|
|
27
|
+
const configWrites = configurableRequirements.flatMap((requirement) => Array.isArray(requirement.configWrites) ? requirement.configWrites.map((write) => `${requirement.key}: ${write.target}.${write.path} <- ${write.valueFrom}${write.writeWhen ? ` (${write.writeWhen})` : ""}`) : []);
|
|
28
|
+
const environmentWrites = configurableRequirements.flatMap((requirement) => Array.isArray(requirement.environmentWrites) ? requirement.environmentWrites.map((write) => `${requirement.key}: ${write.env} -> ${(write.targets ?? []).join(", ") || "config"} [${(write.scopes ?? []).join(", ") || "template scopes"}]`) : []);
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
title: "Required Hosts",
|
|
32
|
+
lines: hosts.filter((host) => host.required === true).map((host) => `${host.key}: ${host.type} via ${providerList(host)} - ${host.purpose ?? host.displayName}`)
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
title: "Optional Hosts",
|
|
36
|
+
lines: hosts.filter((host) => host.required !== true).map((host) => `${host.key}: ${host.type} via ${providerList(host)} - ${host.purpose ?? host.displayName}`)
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
title: "Resources",
|
|
40
|
+
lines: resources.length > 0 ? resources.map((resource) => `${resource.key}: ${resource.type} ${requirementStatus(resource.required)} via ${providerList(resource)}`) : ["No resource lifecycle requirements in this phase."]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
title: "Secrets",
|
|
44
|
+
lines: secrets.length > 0 ? secrets.map((secret) => `${secret.key}: ${secret.env} ${requirementStatus(secret.required)} -> ${(secret.targets ?? []).join(", ")}`) : ["No standalone secret requirements declared."]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
title: "Config Writes",
|
|
48
|
+
lines: configWrites.length > 0 ? configWrites : ["No config writes declared."]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
title: "Environment Targets",
|
|
52
|
+
lines: environmentWrites.length > 0 ? environmentWrites : ["No host-derived environment targets declared."]
|
|
53
|
+
}
|
|
54
|
+
].filter((section) => section.lines.length > 0);
|
|
55
|
+
}
|
|
12
56
|
const handleTemplate = async (invocation, context) => {
|
|
13
57
|
if (invocation.positionals[0] === "search" || invocation.positionals[0] === "install" || typeof invocation.args.market === "string") {
|
|
14
58
|
const action = invocation.positionals[0] ?? "search";
|
|
@@ -78,6 +122,52 @@ const handleTemplate = async (invocation, context) => {
|
|
|
78
122
|
outputFormat: context.outputFormat,
|
|
79
123
|
transport: "cli"
|
|
80
124
|
});
|
|
125
|
+
if (context.outputFormat === "json" || !result.ok) {
|
|
126
|
+
return {
|
|
127
|
+
exitCode: result.exitCode ?? (result.ok ? 0 : 1),
|
|
128
|
+
stdout: result.stdout,
|
|
129
|
+
stderr: result.stderr,
|
|
130
|
+
report: result.payload
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const payload = result.payload;
|
|
134
|
+
if (payload?.action === "show" && payload.template) {
|
|
135
|
+
const template = payload.template;
|
|
136
|
+
return guidedResult({
|
|
137
|
+
command: "template",
|
|
138
|
+
summary: `Template ${template.id} is ready to scaffold.`,
|
|
139
|
+
facts: [
|
|
140
|
+
{ label: "Name", value: template.displayName ?? template.id },
|
|
141
|
+
{ label: "Status", value: template.status ?? "(unknown)" },
|
|
142
|
+
{ label: "Version", value: template.templateVersion ?? "(unversioned)" },
|
|
143
|
+
{ label: "Fulfillment", value: template.fulfillmentMode ?? "(unknown)" }
|
|
144
|
+
],
|
|
145
|
+
sections: renderLaunchRequirementSections(template),
|
|
146
|
+
report: payload
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (payload?.action === "list" && Array.isArray(payload.templates)) {
|
|
150
|
+
return guidedResult({
|
|
151
|
+
command: "template",
|
|
152
|
+
summary: "Treeseed starter templates",
|
|
153
|
+
sections: [{
|
|
154
|
+
title: "Templates",
|
|
155
|
+
lines: payload.templates.map((template) => `${template.id} ${template.displayName ?? template.id} ${template.status ?? "unknown"}`)
|
|
156
|
+
}],
|
|
157
|
+
report: payload
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (payload?.action === "validate") {
|
|
161
|
+
return guidedResult({
|
|
162
|
+
command: "template",
|
|
163
|
+
summary: "Template validation completed.",
|
|
164
|
+
sections: [{
|
|
165
|
+
title: "Validated",
|
|
166
|
+
lines: Array.isArray(payload.validated) ? payload.validated.map(String) : []
|
|
167
|
+
}],
|
|
168
|
+
report: payload
|
|
169
|
+
});
|
|
170
|
+
}
|
|
81
171
|
return {
|
|
82
172
|
exitCode: result.exitCode ?? (result.ok ? 0 : 1),
|
|
83
173
|
stdout: result.stdout,
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
findTreeseedOperation as findSdkOperation,
|
|
3
3
|
TRESEED_OPERATION_SPECS as SDK_OPERATION_SPECS
|
|
4
4
|
} from "@treeseed/sdk/operations";
|
|
5
|
+
import { TREESEED_DEFAULT_STARTER_TEMPLATE_ID } from "@treeseed/sdk";
|
|
5
6
|
function command(overlay) {
|
|
6
7
|
return overlay;
|
|
7
8
|
}
|
|
@@ -31,10 +32,53 @@ const DEV_RUNTIME_OPTIONS = [
|
|
|
31
32
|
{ 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"] },
|
|
32
33
|
{ name: "plan", flags: "--plan", description: "Print the dev runtime plan and exit without starting services.", kind: "boolean" },
|
|
33
34
|
{ name: "reset", flags: "--reset", description: "Clear local dev runtime state before setup, migrations, and service startup.", kind: "boolean" },
|
|
34
|
-
{ name: "force", flags: "--force", description: "
|
|
35
|
+
{ name: "force", flags: "--force", description: "Replace the current worktree dev instance before startup.", kind: "boolean" },
|
|
36
|
+
{ name: "forceConflicts", flags: "--force-conflicts", description: "Allow managed dev start to stop sibling worktree port owners when explicit ports conflict.", kind: "boolean" },
|
|
37
|
+
{ name: "all", flags: "--all", description: "Apply managed dev status or stop to all worktrees in the repository family.", kind: "boolean" },
|
|
38
|
+
{ name: "follow", flags: "--follow", description: "Follow managed dev logs when supported.", kind: "boolean" },
|
|
35
39
|
{ name: "json", flags: "--json", description: "Emit structured JSON or newline-delimited dev events.", kind: "boolean" },
|
|
36
40
|
{ name: "workspaceLinks", flags: "--workspace-links <mode>", description: "Control local workspace package links.", kind: "enum", values: ["auto", "off"] }
|
|
37
41
|
];
|
|
42
|
+
const DEV_STATUS_OPTIONS = DEV_RUNTIME_OPTIONS.filter((option) => ["all", "json"].includes(option.name));
|
|
43
|
+
const DEV_LOGS_OPTIONS = DEV_RUNTIME_OPTIONS.filter((option) => ["follow", "json"].includes(option.name));
|
|
44
|
+
const DEV_STOP_OPTIONS = DEV_RUNTIME_OPTIONS.filter((option) => ["all", "json"].includes(option.name));
|
|
45
|
+
const DEV_START_OPTIONS = DEV_RUNTIME_OPTIONS.filter((option) => !["all", "follow"].includes(option.name));
|
|
46
|
+
function devManagedHelpCommand(subcommand, spec) {
|
|
47
|
+
return {
|
|
48
|
+
id: `dev.${subcommand}`,
|
|
49
|
+
name: `dev ${subcommand}`,
|
|
50
|
+
aliases: [],
|
|
51
|
+
group: "Local Development",
|
|
52
|
+
summary: spec.summary,
|
|
53
|
+
description: spec.description,
|
|
54
|
+
provider: "default",
|
|
55
|
+
related: ["dev"],
|
|
56
|
+
usage: spec.usage,
|
|
57
|
+
options: spec.options,
|
|
58
|
+
examples: spec.examples,
|
|
59
|
+
helpVisible: false,
|
|
60
|
+
helpFeatured: false,
|
|
61
|
+
executionMode: "handler",
|
|
62
|
+
handlerName: "dev",
|
|
63
|
+
help: {
|
|
64
|
+
workflowPosition: "managed dev instance",
|
|
65
|
+
longSummary: [spec.description],
|
|
66
|
+
whenToUse: spec.whenToUse,
|
|
67
|
+
beforeYouRun: spec.beforeYouRun,
|
|
68
|
+
outcomes: spec.outcomes,
|
|
69
|
+
examples: spec.examples,
|
|
70
|
+
automationNotes: [
|
|
71
|
+
"These managed dev subcommands use the same `dev` handler and core supervisor as foreground `treeseed dev`.",
|
|
72
|
+
"Use `--json` when another process needs stable instance records, ports, URLs, PIDs, ready checks, or log paths."
|
|
73
|
+
],
|
|
74
|
+
warnings: spec.warnings ?? [],
|
|
75
|
+
relatedDetails: [
|
|
76
|
+
related("dev", "Use `dev` without a subcommand for the foreground supervisor.")
|
|
77
|
+
],
|
|
78
|
+
seeAlso: ["dev"]
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
38
82
|
function genericWorkflowPosition(spec) {
|
|
39
83
|
if (spec.group === "Workflow") {
|
|
40
84
|
if (spec.name === "switch") return "start work";
|
|
@@ -848,14 +892,14 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
848
892
|
{ name: "version", flags: "--version <version>", description: "Artifact version for market template install.", kind: "string" },
|
|
849
893
|
{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
|
|
850
894
|
],
|
|
851
|
-
examples: ["treeseed template", "treeseed template list",
|
|
895
|
+
examples: ["treeseed template", "treeseed template list", `treeseed template show ${TREESEED_DEFAULT_STARTER_TEMPLATE_ID}`, "treeseed template validate"],
|
|
852
896
|
help: {
|
|
853
897
|
longSummary: [
|
|
854
898
|
"Template exposes local starter catalog actions and market-backed search/install actions. Market search/install uses an integrated catalog from central and configured specialized markets, with every result labeled by source market."
|
|
855
899
|
],
|
|
856
900
|
examples: [
|
|
857
901
|
example("treeseed template", "Default to the catalog list", "Show the available starters without specifying an action."),
|
|
858
|
-
example(
|
|
902
|
+
example(`treeseed template show ${TREESEED_DEFAULT_STARTER_TEMPLATE_ID}`, "Inspect a single starter", "View the details of one starter template."),
|
|
859
903
|
example("treeseed template validate", "Validate the current template set", "Run template validation to confirm the catalog is internally consistent.")
|
|
860
904
|
]
|
|
861
905
|
},
|
|
@@ -881,15 +925,19 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
881
925
|
["init", command({
|
|
882
926
|
arguments: [{ name: "directory", description: "Target directory for the new tenant.", required: true }],
|
|
883
927
|
options: [
|
|
884
|
-
{ name: "template", flags: "--template <starter-id>", description:
|
|
928
|
+
{ name: "template", flags: "--template <starter-id>", description: `Select the starter template id to generate. Defaults to ${TREESEED_DEFAULT_STARTER_TEMPLATE_ID}.`, kind: "string" },
|
|
885
929
|
{ name: "name", flags: "--name <site-name>", description: "Override the generated site name.", kind: "string" },
|
|
886
930
|
{ name: "slug", flags: "--slug <slug>", description: "Override the generated package and tenant slug.", kind: "string" },
|
|
887
931
|
{ name: "siteUrl", flags: "--site-url <url>", description: "Set the initial public site URL.", kind: "string" },
|
|
888
932
|
{ name: "contactEmail", flags: "--contact-email <email>", description: "Set the site contact address.", kind: "string" },
|
|
889
933
|
{ name: "repo", flags: "--repo <url>", description: "Set the repository URL.", kind: "string" },
|
|
890
|
-
{ name: "discord", flags: "--discord <url>", description: "Set the Discord/community URL.", kind: "string" }
|
|
934
|
+
{ name: "discord", flags: "--discord <url>", description: "Set the Discord/community URL.", kind: "string" },
|
|
935
|
+
{ name: "host", flags: "--host <requirement=provider:alias>", description: "Bind a template launch requirement locally. Repeat for multiple requirements, or use requirement=none for optional hosts.", kind: "string", repeatable: true }
|
|
936
|
+
],
|
|
937
|
+
examples: [
|
|
938
|
+
`treeseed init docs-site --template ${TREESEED_DEFAULT_STARTER_TEMPLATE_ID} --name "Docs Site" --site-url https://docs.example.com`,
|
|
939
|
+
`treeseed init docs-site --template ${TREESEED_DEFAULT_STARTER_TEMPLATE_ID} --host sourceRepository=github:acme --host publicWeb=cloudflare:managed`
|
|
891
940
|
],
|
|
892
|
-
examples: ['treeseed init docs-site --template starter-basic --name "Docs Site" --site-url https://docs.example.com'],
|
|
893
941
|
notes: ["Runs outside an existing repo or from any branch."],
|
|
894
942
|
help: {
|
|
895
943
|
workflowPosition: "create workspace",
|
|
@@ -909,7 +957,8 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
909
957
|
"Seeds the project metadata fields requested through the CLI flags."
|
|
910
958
|
],
|
|
911
959
|
examples: [
|
|
912
|
-
example(
|
|
960
|
+
example(`treeseed init docs-site --template ${TREESEED_DEFAULT_STARTER_TEMPLATE_ID} --name "Docs Site" --site-url https://docs.example.com`, "Create a starter site", "Scaffold a new tenant using the default starter and explicit branding metadata."),
|
|
961
|
+
example(`treeseed init docs-site --template ${TREESEED_DEFAULT_STARTER_TEMPLATE_ID} --host sourceRepository=github:acme --host publicWeb=cloudflare:managed`, "Bind launch hosts locally", "Apply host-derived starter config during scaffold without calling Market inventory APIs."),
|
|
913
962
|
example("treeseed init workbench --slug workbench --contact-email ops@example.com", "Control project identity fields", "Initialize a tenant while overriding slug and contact metadata at creation time."),
|
|
914
963
|
example("treeseed init docs-site --repo https://github.com/example/docs-site --discord https://discord.gg/example", "Seed community and repository metadata", "Attach repository and community URLs during project initialization.")
|
|
915
964
|
],
|
|
@@ -1167,15 +1216,18 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
1167
1216
|
})],
|
|
1168
1217
|
["dev", command({
|
|
1169
1218
|
options: DEV_RUNTIME_OPTIONS,
|
|
1170
|
-
|
|
1219
|
+
arguments: [{ name: "subcommand", description: "Optional managed instance action: start, status, logs, stop, or restart.", required: false }],
|
|
1220
|
+
examples: ["treeseed dev", "treeseed dev start", "treeseed dev status --all --json", "treeseed dev logs", "treeseed dev stop", "treeseed dev --web-runtime local --plan --json"],
|
|
1171
1221
|
help: {
|
|
1172
1222
|
longSummary: [
|
|
1173
|
-
"Dev starts the local Treeseed Market web/API/runtime services
|
|
1223
|
+
"Dev starts or manages the local Treeseed Market web/API/runtime services.",
|
|
1224
|
+
"Without a subcommand, dev runs as the existing foreground supervisor.",
|
|
1174
1225
|
"Capacity provider lifecycle is package-owned and runs through `treeseed capacity ...`, not through `treeseed dev`."
|
|
1175
1226
|
],
|
|
1176
1227
|
beforeYouRun: [
|
|
1177
1228
|
"Run from the tenant or workspace root you want to develop.",
|
|
1178
1229
|
"From the Market repo root, dev automatically starts the local Market API, managed local PostgreSQL, and Market operations runner alongside the web UI.",
|
|
1230
|
+
"Use `dev start` for worktree-scoped background instance management that AI agents can discover and stop later.",
|
|
1179
1231
|
"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.",
|
|
1180
1232
|
"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.",
|
|
1181
1233
|
"Dev prints the local web URL after readiness; it does not open a browser unless you pass `--open on` or `--open auto`.",
|
|
@@ -1183,6 +1235,10 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
1183
1235
|
],
|
|
1184
1236
|
examples: [
|
|
1185
1237
|
example("treeseed dev", "Start local Market development", "Run web, API, managed PostgreSQL setup, and the Market operations runner locally."),
|
|
1238
|
+
example("treeseed dev start", "Start a managed worktree instance", "Launch the same dev runtime detached, write worktree-local PID/log/state files, and return after readiness."),
|
|
1239
|
+
example("treeseed dev status --all --json", "Inspect all worktree instances", "List managed dev instances discovered through the repository-family index."),
|
|
1240
|
+
example("treeseed dev logs", "Show managed dev logs", "Print the current worktree managed dev log file."),
|
|
1241
|
+
example("treeseed dev stop", "Stop managed dev", "Stop only the current worktree managed dev instance."),
|
|
1186
1242
|
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."),
|
|
1187
1243
|
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."),
|
|
1188
1244
|
example("treeseed dev --plan --json", "Inspect the runtime plan", "Emit a structured plan with setup steps, commands, ports, URLs, readiness checks, and watch entries."),
|
|
@@ -1251,7 +1307,129 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
|
|
|
1251
1307
|
})],
|
|
1252
1308
|
["starlight:patch", command({ examples: ["treeseed starlight:patch"], executionMode: "adapter" })]
|
|
1253
1309
|
]);
|
|
1310
|
+
const DEV_MANAGED_OPERATION_SPECS = [
|
|
1311
|
+
devManagedHelpCommand("start", {
|
|
1312
|
+
summary: "Start a detached worktree-scoped dev instance.",
|
|
1313
|
+
description: "Start launches the same Market web/API/runner runtime as foreground `dev`, but detaches it, writes worktree-local instance state, captures logs, waits for readiness, and returns a summary.",
|
|
1314
|
+
usage: "treeseed dev start [--web-runtime local|provider|auto] [--port <port>] [--api-port <port>] [--force] [--force-conflicts] [--json]",
|
|
1315
|
+
options: DEV_START_OPTIONS,
|
|
1316
|
+
whenToUse: [
|
|
1317
|
+
"Use this when you want the local Market runtime to keep running after the shell command exits.",
|
|
1318
|
+
"Use it for AI-agent workflows where later commands need discoverable PID, port, URL, and log state."
|
|
1319
|
+
],
|
|
1320
|
+
beforeYouRun: [
|
|
1321
|
+
"Run from the worktree that should own the managed instance.",
|
|
1322
|
+
"Use `--web-runtime local` for fast Astro hot reload.",
|
|
1323
|
+
"Use `--force` to replace only the current worktree instance. Use `--force-conflicts` only when you intentionally want to stop sibling worktree port owners."
|
|
1324
|
+
],
|
|
1325
|
+
outcomes: [
|
|
1326
|
+
"Writes `.treeseed/dev/instances/web-api.json`, `.treeseed/dev/pids/web-api.pid`, and `.treeseed/logs/dev-web-api.jsonl` in the current worktree.",
|
|
1327
|
+
"Returns after readiness with URLs, ports, PID, process group, log path, and ready-check state."
|
|
1328
|
+
],
|
|
1329
|
+
examples: [
|
|
1330
|
+
example("treeseed dev start --web-runtime local --force", "Start the current worktree instance", "Launch the managed background runtime with local web hot reload and replace any stale current-worktree owner."),
|
|
1331
|
+
example("treeseed dev start --port 4322 --api-port 3002 --json", "Start on explicit ports", "Pin web and API ports and emit a structured instance summary."),
|
|
1332
|
+
example("trsd dev start --web-runtime local", "Use the short alias", "Start the same managed runtime through the shorter entrypoint.")
|
|
1333
|
+
]
|
|
1334
|
+
}),
|
|
1335
|
+
devManagedHelpCommand("status", {
|
|
1336
|
+
summary: "Inspect managed dev instance state.",
|
|
1337
|
+
description: "Status reads worktree-local managed dev state, checks process and readiness health, repairs stale records opportunistically, and can discover sibling worktree instances through the repository-family index.",
|
|
1338
|
+
usage: "treeseed dev status [--all] [--json]",
|
|
1339
|
+
options: DEV_STATUS_OPTIONS,
|
|
1340
|
+
whenToUse: [
|
|
1341
|
+
"Use this before starting a new managed instance to see whether one is already running.",
|
|
1342
|
+
"Use `--all` when multiple worktrees or AI agents may be running sibling instances for the same repository family."
|
|
1343
|
+
],
|
|
1344
|
+
beforeYouRun: [
|
|
1345
|
+
"Run from the worktree you want to inspect.",
|
|
1346
|
+
"Use `--json` for automation that needs the exact status, PID, ports, URLs, stale reason, or sibling conflicts."
|
|
1347
|
+
],
|
|
1348
|
+
outcomes: [
|
|
1349
|
+
"Prints ready, starting, degraded, stopped, or stale state for the current worktree instance.",
|
|
1350
|
+
"With `--all`, includes instances discovered from sibling worktrees through the shared repository-family index."
|
|
1351
|
+
],
|
|
1352
|
+
examples: [
|
|
1353
|
+
example("treeseed dev status", "Inspect current worktree", "Show the managed instance state for the current worktree."),
|
|
1354
|
+
example("treeseed dev status --all", "Inspect sibling worktrees", "List managed instances discoverable across the repository family."),
|
|
1355
|
+
example("treeseed dev status --json", "Read status programmatically", "Emit machine-readable instance state for agents or scripts.")
|
|
1356
|
+
]
|
|
1357
|
+
}),
|
|
1358
|
+
devManagedHelpCommand("logs", {
|
|
1359
|
+
summary: "Read managed dev logs.",
|
|
1360
|
+
description: "Logs prints the current worktree managed dev log in human-readable form, rendering structured dev events as concise text and optionally following the stable log file.",
|
|
1361
|
+
usage: "treeseed dev logs [--follow] [--json]",
|
|
1362
|
+
options: DEV_LOGS_OPTIONS,
|
|
1363
|
+
whenToUse: [
|
|
1364
|
+
"Use this when `dev status` reports stale, degraded, or stopped and you need to see what happened.",
|
|
1365
|
+
"Use `--follow` while iterating locally to watch the background runtime without reattaching to the supervisor process."
|
|
1366
|
+
],
|
|
1367
|
+
beforeYouRun: [
|
|
1368
|
+
"Run from the worktree that owns the managed instance.",
|
|
1369
|
+
"Default human output shows a recent, readable tail. Use the reported log path if you need the full historical file."
|
|
1370
|
+
],
|
|
1371
|
+
outcomes: [
|
|
1372
|
+
"Prints the current managed log tail from `.treeseed/logs/dev-web-api.jsonl`.",
|
|
1373
|
+
"With `--follow`, continues streaming appended log lines until interrupted."
|
|
1374
|
+
],
|
|
1375
|
+
examples: [
|
|
1376
|
+
example("treeseed dev logs", "Show recent managed logs", "Print the current worktree managed dev log tail in human-readable form."),
|
|
1377
|
+
example("treeseed dev logs --follow", "Follow background runtime logs", "Stream appended log entries while the managed instance runs."),
|
|
1378
|
+
example("treeseed dev status --json", "Find the log path", "Use status when automation needs the authoritative log file location.")
|
|
1379
|
+
]
|
|
1380
|
+
}),
|
|
1381
|
+
devManagedHelpCommand("stop", {
|
|
1382
|
+
summary: "Stop managed dev instances.",
|
|
1383
|
+
description: "Stop terminates the current worktree managed dev process group and leaves sibling worktree instances alone unless `--all` is explicitly provided.",
|
|
1384
|
+
usage: "treeseed dev stop [--all] [--json]",
|
|
1385
|
+
options: DEV_STOP_OPTIONS,
|
|
1386
|
+
whenToUse: [
|
|
1387
|
+
"Use this when you are done with the current worktree background runtime.",
|
|
1388
|
+
"Use `--all` only when intentionally cleaning up every discoverable managed instance in the repository family."
|
|
1389
|
+
],
|
|
1390
|
+
beforeYouRun: [
|
|
1391
|
+
"Run from the worktree whose instance should be stopped.",
|
|
1392
|
+
"Prefer `dev status --all` first when several agents may be working in sibling worktrees."
|
|
1393
|
+
],
|
|
1394
|
+
outcomes: [
|
|
1395
|
+
"Stops only the owned process group for the current worktree by default.",
|
|
1396
|
+
"Marks stopped or stale records so later `dev start` and `dev status` calls see accurate state."
|
|
1397
|
+
],
|
|
1398
|
+
warnings: [
|
|
1399
|
+
"`--all` can interrupt sibling worktree sessions owned by other humans or agents."
|
|
1400
|
+
],
|
|
1401
|
+
examples: [
|
|
1402
|
+
example("treeseed dev stop", "Stop current worktree instance", "Terminate only the current managed dev process group."),
|
|
1403
|
+
example("treeseed dev stop --all", "Stop repository-family instances", "Stop all discoverable managed instances for the repository family."),
|
|
1404
|
+
example("treeseed dev stop --json", "Stop from automation", "Emit a structured stop summary.")
|
|
1405
|
+
]
|
|
1406
|
+
}),
|
|
1407
|
+
devManagedHelpCommand("restart", {
|
|
1408
|
+
summary: "Restart a managed dev instance.",
|
|
1409
|
+
description: "Restart stops the current worktree managed dev instance, then starts it again with the same managed-start semantics, state files, log path, readiness checks, and port ownership rules.",
|
|
1410
|
+
usage: "treeseed dev restart [--web-runtime local|provider|auto] [--port <port>] [--api-port <port>] [--force] [--force-conflicts] [--json]",
|
|
1411
|
+
options: DEV_START_OPTIONS,
|
|
1412
|
+
whenToUse: [
|
|
1413
|
+
"Use this after changing runtime configuration, package links, or local service state that needs a fresh supervisor.",
|
|
1414
|
+
"Use it when the managed instance is degraded and a clean stop/start is preferable to inspecting every child process manually."
|
|
1415
|
+
],
|
|
1416
|
+
beforeYouRun: [
|
|
1417
|
+
"Run from the worktree that owns the managed instance.",
|
|
1418
|
+
"Pass the same runtime options you would use with `dev start` when you want to change ports or web runtime mode during restart."
|
|
1419
|
+
],
|
|
1420
|
+
outcomes: [
|
|
1421
|
+
"Stops the current worktree managed instance and starts a new one.",
|
|
1422
|
+
"Returns the new PID, process group, ports, URLs, log path, and readiness result."
|
|
1423
|
+
],
|
|
1424
|
+
examples: [
|
|
1425
|
+
example("treeseed dev restart --web-runtime local --force", "Restart local hot-reload runtime", "Replace the current worktree managed instance and wait for readiness."),
|
|
1426
|
+
example("treeseed dev restart --port 4322 --api-port 3002 --json", "Restart on new ports", "Restart with explicit web/API ports and emit a structured summary."),
|
|
1427
|
+
example("trsd dev restart", "Use the short alias", "Restart through the shorter entrypoint.")
|
|
1428
|
+
]
|
|
1429
|
+
})
|
|
1430
|
+
];
|
|
1254
1431
|
const CLI_ONLY_OPERATION_SPECS = [
|
|
1432
|
+
...DEV_MANAGED_OPERATION_SPECS,
|
|
1255
1433
|
{
|
|
1256
1434
|
id: "seed.plan",
|
|
1257
1435
|
name: "seed",
|
|
@@ -1498,11 +1676,11 @@ const CLI_ONLY_OPERATION_SPECS = [
|
|
|
1498
1676
|
name: "projects",
|
|
1499
1677
|
aliases: [],
|
|
1500
1678
|
group: "Utilities",
|
|
1501
|
-
summary: "Inspect projects and
|
|
1502
|
-
description: "List market projects, inspect access, and queue
|
|
1679
|
+
summary: "Inspect projects, project hosts, and web deployment operations from the selected market.",
|
|
1680
|
+
description: "List market projects, inspect access and host bindings, and queue project host or web deployment operations through the Market API.",
|
|
1503
1681
|
provider: "default",
|
|
1504
1682
|
related: ["market", "teams", "config"],
|
|
1505
|
-
usage: "treeseed projects [list|access|deploy|publish|monitor|deployments|deployment]",
|
|
1683
|
+
usage: "treeseed projects [list|access|hosts|deploy|publish|monitor|deployments|deployment]",
|
|
1506
1684
|
arguments: [
|
|
1507
1685
|
{ name: "action", description: "Projects action.", required: false },
|
|
1508
1686
|
{ name: "project-id", description: "Project id for deployment and access actions.", required: false },
|
|
@@ -1518,6 +1696,9 @@ const CLI_ONLY_OPERATION_SPECS = [
|
|
|
1518
1696
|
{ name: "dryRun", flags: "--dry-run", description: "Queue a dry-run deployment request when supported.", kind: "boolean" },
|
|
1519
1697
|
{ name: "reason", flags: "--reason <text>", description: "Presentation-safe reason stored on the deployment request.", kind: "string" },
|
|
1520
1698
|
{ name: "idempotencyKey", flags: "--idempotency-key <key>", description: "Deterministic idempotency key for the deployment request.", kind: "string" },
|
|
1699
|
+
{ name: "requirement", flags: "--requirement <key>", description: "Launch host requirement key for project host resync or rotate operations.", kind: "string" },
|
|
1700
|
+
{ name: "host", flags: "--host <requirement=provider:host-id>", description: "Replacement host binding for `projects hosts replace`. Use requirement=provider:managed for a managed host or requirement=none for optional hosts.", kind: "string", repeatable: true },
|
|
1701
|
+
{ name: "sensitivePassphrase", flags: "--sensitive-passphrase <passphrase>", description: "One-time unlock passphrase for team-owned host credentials. Avoid this in shell history; prefer interactive UI when possible.", kind: "string" },
|
|
1521
1702
|
{ name: "yes", flags: "--yes", description: "Required confirmation for production deploy and publish actions.", kind: "boolean" },
|
|
1522
1703
|
{ name: "limit", flags: "--limit <count>", description: "Maximum number of deployments to list.", kind: "string" },
|
|
1523
1704
|
{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
|
|
@@ -1525,14 +1706,17 @@ const CLI_ONLY_OPERATION_SPECS = [
|
|
|
1525
1706
|
examples: [
|
|
1526
1707
|
"treeseed projects list",
|
|
1527
1708
|
"treeseed projects access project_123",
|
|
1709
|
+
"treeseed projects hosts project_123",
|
|
1710
|
+
"treeseed projects hosts audit project_123",
|
|
1711
|
+
"treeseed projects hosts replace project_123 --host publicWeb=cloudflare:web-host-123",
|
|
1528
1712
|
"treeseed projects deploy project_123 --environment staging --wait",
|
|
1529
1713
|
"treeseed projects publish project_123 --environment prod --yes",
|
|
1530
1714
|
"treeseed projects deployments project_123 --json",
|
|
1531
1715
|
"treeseed projects deployment project_123 dep_123"
|
|
1532
1716
|
],
|
|
1533
1717
|
help: {
|
|
1534
|
-
longSummary: ["Projects reads project, access, and deployment state from the selected market API using the SDK market client."],
|
|
1535
|
-
whenToUse: ["Use this to inspect projects, queue staging or production web deployment operations, and inspect the same
|
|
1718
|
+
longSummary: ["Projects reads project, host binding, access, and deployment state from the selected market API using the SDK market client."],
|
|
1719
|
+
whenToUse: ["Use this to inspect projects, audit or replace launch host bindings, queue staging or production web deployment operations, and inspect the same state shown in the Market UI."],
|
|
1536
1720
|
beforeYouRun: ["Authenticate to the market with `treeseed auth:login --market <selector>` and know the project id before queueing deployment work."],
|
|
1537
1721
|
automationNotes: ["Use `--json` to capture project lists, deployment records, events, and wait results for automation."],
|
|
1538
1722
|
warnings: ["Production deploy and publish require `--yes`; without it the CLI exits before calling the API."]
|
package/dist/cli/runtime.js
CHANGED
|
@@ -22,6 +22,28 @@ function shouldRenderCommandHelp(spec, argv) {
|
|
|
22
22
|
const treeseedArgs = separatorIndex >= 0 ? argv.slice(0, separatorIndex) : argv;
|
|
23
23
|
return treeseedArgs.some(isHelpFlag);
|
|
24
24
|
}
|
|
25
|
+
function resolveNestedDevHelpTarget(argv) {
|
|
26
|
+
const managedSubcommands = /* @__PURE__ */ new Set(["start", "status", "logs", "stop", "restart"]);
|
|
27
|
+
const subcommand = argv.find((arg) => !arg.startsWith("-") && managedSubcommands.has(arg));
|
|
28
|
+
return subcommand ? `dev ${subcommand}` : null;
|
|
29
|
+
}
|
|
30
|
+
function resolveCommandHelpTarget(spec, argv) {
|
|
31
|
+
if (spec.name === "dev") {
|
|
32
|
+
return resolveNestedDevHelpTarget(argv) ?? spec.name;
|
|
33
|
+
}
|
|
34
|
+
return spec.name;
|
|
35
|
+
}
|
|
36
|
+
function resolveExplicitHelpTarget(args) {
|
|
37
|
+
const helpArgs = args.filter((arg) => !arg.startsWith("-"));
|
|
38
|
+
if (helpArgs.length === 0) return null;
|
|
39
|
+
for (let length = Math.min(3, helpArgs.length); length > 0; length -= 1) {
|
|
40
|
+
const candidate = helpArgs.slice(0, length).join(" ");
|
|
41
|
+
if (findTreeseedOperation(candidate)) {
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return helpArgs[0] ?? null;
|
|
46
|
+
}
|
|
25
47
|
function isNoColorFlag(value) {
|
|
26
48
|
return value === "--no-color";
|
|
27
49
|
}
|
|
@@ -303,13 +325,14 @@ class TreeseedOperationsSdk {
|
|
|
303
325
|
return writeTreeseedResult({ exitCode: 1, stderr: [lines.join("\n")] }, context);
|
|
304
326
|
}
|
|
305
327
|
if (shouldRenderCommandHelp(spec, argv)) {
|
|
328
|
+
const helpTarget = resolveCommandHelpTarget(spec, argv);
|
|
306
329
|
if (shouldUseInkHelp(context)) {
|
|
307
|
-
const helpExitCode = await renderTreeseedHelpInk(
|
|
330
|
+
const helpExitCode = await renderTreeseedHelpInk(helpTarget, context);
|
|
308
331
|
if (typeof helpExitCode === "number") {
|
|
309
332
|
return helpExitCode;
|
|
310
333
|
}
|
|
311
334
|
}
|
|
312
|
-
context.write(renderTreeseedHelp(
|
|
335
|
+
context.write(renderTreeseedHelp(helpTarget), "stdout");
|
|
313
336
|
return 0;
|
|
314
337
|
}
|
|
315
338
|
return spec.executionMode === "adapter" ? this.executeAdapter(spec, argv, context) : spec.executionMode === "delegate" ? this.executeAgents(argv, context) : this.executeHandler(spec, argv, context);
|
|
@@ -318,7 +341,7 @@ class TreeseedOperationsSdk {
|
|
|
318
341
|
const context = createTreeseedCommandContext(overrides);
|
|
319
342
|
const [firstArg, ...restArgs] = argv;
|
|
320
343
|
if (!firstArg || isHelpFlag(firstArg) || firstArg === "help") {
|
|
321
|
-
const commandName = firstArg === "help" ? restArgs
|
|
344
|
+
const commandName = firstArg === "help" ? resolveExplicitHelpTarget(restArgs) : null;
|
|
322
345
|
if (shouldUseInkHelp(context)) {
|
|
323
346
|
const helpExitCode = await renderTreeseedHelpInk(commandName, context);
|
|
324
347
|
if (typeof helpExitCode === "number") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeseed/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.21",
|
|
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": "github:treeseed-ai/sdk#0.10.
|
|
48
|
+
"@treeseed/sdk": "github:treeseed-ai/sdk#0.10.27",
|
|
49
49
|
"ink": "^7.0.0",
|
|
50
50
|
"react": "^19.2.5"
|
|
51
51
|
},
|