@treeseed/cli 0.4.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,30 @@
1
1
  import type { TreeseedOperationSpec } from './operations-types.ts';
2
+ export type TreeseedHelpEntryAccent = 'command' | 'flag' | 'argument' | 'example' | 'alias' | 'related';
3
+ export type TreeseedHelpEntry = {
4
+ label: string;
5
+ summary?: string;
6
+ accent?: TreeseedHelpEntryAccent;
7
+ required?: boolean;
8
+ targetCommand?: string;
9
+ };
10
+ export type TreeseedHelpSection = {
11
+ id: string;
12
+ title: string;
13
+ entries?: TreeseedHelpEntry[];
14
+ lines?: string[];
15
+ };
16
+ export type TreeseedHelpView = {
17
+ kind: 'top' | 'command' | 'unknown';
18
+ title: string;
19
+ subtitle?: string;
20
+ badge?: string;
21
+ sidebarTitle: string;
22
+ sections: TreeseedHelpSection[];
23
+ statusPrimary: string;
24
+ statusSecondary: string;
25
+ exitCode: number;
26
+ };
2
27
  export declare function renderUsage(spec: TreeseedOperationSpec): string;
3
28
  export declare function suggestTreeseedCommands(input: string): string[];
29
+ export declare function buildTreeseedHelpView(commandName?: string | null): TreeseedHelpView;
4
30
  export declare function renderTreeseedHelp(commandName?: string | null): string;
package/dist/cli/help.js CHANGED
@@ -7,6 +7,88 @@ const GROUP_ORDER = [
7
7
  "Utilities",
8
8
  "Passthrough"
9
9
  ];
10
+ function formatExecutionMode(spec) {
11
+ switch (spec.executionMode) {
12
+ case "handler":
13
+ return "cli handler";
14
+ case "adapter":
15
+ return "sdk adapter";
16
+ case "delegate":
17
+ return `delegated to ${spec.delegateTo ?? "runtime"}`;
18
+ default:
19
+ return spec.executionMode;
20
+ }
21
+ }
22
+ function formatOptionSummary(spec, flags, description) {
23
+ const option = (spec.options ?? []).find((candidate) => candidate.flags === flags);
24
+ if (!option) {
25
+ return description;
26
+ }
27
+ const traits = [];
28
+ if (option.kind === "enum" && (option.values?.length ?? 0) > 0) {
29
+ traits.push(`values: ${(option.values ?? []).join(", ")}`);
30
+ }
31
+ if (option.repeatable) {
32
+ traits.push("repeatable");
33
+ }
34
+ if (traits.length === 0) {
35
+ return description;
36
+ }
37
+ return `${description} (${traits.join("; ")})`;
38
+ }
39
+ function automationNotes(spec) {
40
+ if ((spec.help?.automationNotes?.length ?? 0) > 0) {
41
+ return spec.help?.automationNotes ?? [];
42
+ }
43
+ const lines = [
44
+ `Execution path: ${formatExecutionMode(spec)}.`,
45
+ `Provider: ${spec.provider}.`
46
+ ];
47
+ if ((spec.options ?? []).some((option) => option.name === "json")) {
48
+ lines.push("Machine use: supports --json output for scripts and agents.");
49
+ } else {
50
+ lines.push("Machine use: human-oriented command with no dedicated --json surface.");
51
+ }
52
+ return lines;
53
+ }
54
+ function normalizeStructuredExamples(spec) {
55
+ if ((spec.help?.examples?.length ?? 0) > 0) {
56
+ return spec.help?.examples ?? [];
57
+ }
58
+ return (spec.examples ?? []).map((entry, index) => typeof entry === "string" ? {
59
+ command: entry,
60
+ title: `Example ${index + 1}`,
61
+ description: `Run ${spec.name} with a representative argument set.`
62
+ } : entry);
63
+ }
64
+ function detailMap(details) {
65
+ return new Map((details ?? []).map((entry) => [entry.name, entry.detail]));
66
+ }
67
+ function relatedMap(details) {
68
+ return new Map((details ?? []).map((entry) => [entry.name, entry.why]));
69
+ }
70
+ function commandSectionEntries(spec) {
71
+ const entries = [
72
+ { label: "Command path", summary: `treeseed ${spec.name}` },
73
+ { label: "Group", summary: spec.group },
74
+ { label: "Workflow position", summary: spec.help?.workflowPosition ?? "general" },
75
+ { label: "Execution", summary: formatExecutionMode(spec) },
76
+ { label: "Provider", summary: spec.provider },
77
+ { label: "Aliases", summary: spec.aliases.length > 0 ? spec.aliases.join(", ") : "none" }
78
+ ];
79
+ return entries;
80
+ }
81
+ function exampleEntries(spec) {
82
+ return normalizeStructuredExamples(spec).flatMap((entry) => [
83
+ {
84
+ label: entry.command,
85
+ summary: `${entry.title}: ${entry.description}`,
86
+ accent: "example"
87
+ },
88
+ ...entry.result ? [{ label: "Result", summary: entry.result }] : [],
89
+ ...entry.why ? [{ label: "Why", summary: entry.why }] : []
90
+ ]);
91
+ }
10
92
  function levenshtein(left, right) {
11
93
  const rows = Array.from({ length: left.length + 1 }, () => new Array(right.length + 1).fill(0));
12
94
  for (let i = 0; i <= left.length; i += 1) rows[i][0] = i;
@@ -28,12 +110,46 @@ function groupedCommands() {
28
110
  const groups = /* @__PURE__ */ new Map();
29
111
  for (const group of GROUP_ORDER) groups.set(group, []);
30
112
  for (const spec of TRESEED_OPERATION_SPECS) {
113
+ if (spec.helpVisible === false) continue;
31
114
  const entries = groups.get(spec.group) ?? [];
32
115
  entries.push(spec);
33
116
  groups.set(spec.group, entries);
34
117
  }
118
+ for (const entries of groups.values()) {
119
+ entries.sort((left, right) => left.name.localeCompare(right.name));
120
+ }
35
121
  return groups;
36
122
  }
123
+ function specEntries(specs) {
124
+ return specs.map((spec) => ({
125
+ label: spec.name,
126
+ summary: spec.help?.workflowPosition ? `${spec.help.workflowPosition} \u2022 ${spec.summary}` : spec.summary,
127
+ accent: "command",
128
+ targetCommand: spec.name
129
+ }));
130
+ }
131
+ function sectionLines(lines) {
132
+ return (lines ?? []).filter((line) => line.trim().length > 0);
133
+ }
134
+ function entryLines(entries) {
135
+ if (entries.length === 0) {
136
+ return [];
137
+ }
138
+ const labelWidth = Math.max(
139
+ 0,
140
+ Math.min(
141
+ 28,
142
+ entries.reduce((max, entry) => Math.max(max, entry.label.length), 0)
143
+ )
144
+ );
145
+ return entries.map((entry) => {
146
+ if (!entry.summary) {
147
+ return ` ${entry.label}`;
148
+ }
149
+ const spacer = entry.label.length < labelWidth ? " ".repeat(labelWidth - entry.label.length) : " ";
150
+ return ` ${entry.label}${spacer} ${entry.summary}`;
151
+ });
152
+ }
37
153
  function renderUsage(spec) {
38
154
  if (spec.usage) return spec.usage;
39
155
  const args = (spec.arguments ?? []).map((arg) => arg.required ? `<${arg.name}>` : `[${arg.name}]`);
@@ -45,110 +161,235 @@ function suggestTreeseedCommands(input) {
45
161
  if (!normalized) return [];
46
162
  return listTreeseedOperationNames().map((name) => ({ name, score: levenshtein(normalized, name) })).sort((left, right) => left.score - right.score || left.name.localeCompare(right.name)).slice(0, 3).map((entry) => entry.name);
47
163
  }
48
- function renderTreeseedHelp(commandName) {
164
+ function buildTreeseedHelpView(commandName) {
49
165
  if (!commandName) {
50
166
  const groups = groupedCommands();
51
- const primaryWorkflow = ["init", "status", "config", "tasks", "switch", "dev", "save", "close", "stage", "release", "destroy"].map((name) => findTreeseedOperation(name)).filter((spec2) => Boolean(spec2));
52
- const workflowGuidance = ["doctor", "rollback"].map((name) => findTreeseedOperation(name)).filter((spec2) => Boolean(spec2));
53
- const sections2 = [
54
- "Treeseed CLI",
55
- "Thin command wrapper over the Treeseed SDK workflow/operations surface plus the agent runtime namespace.",
56
- "",
57
- "Usage",
58
- " treeseed <command> [args...]",
59
- " treeseed help [command]",
60
- "",
61
- formatSection("Primary Workflow", primaryWorkflow.map((command) => {
62
- const spacer = command.name.length < 18 ? " ".repeat(18 - command.name.length) : " ";
63
- return ` ${command.name}${spacer}${command.summary}`;
64
- })),
65
- "",
66
- formatSection("Workflow Support", workflowGuidance.map((command) => {
67
- const spacer = command.name.length < 18 ? " ".repeat(18 - command.name.length) : " ";
68
- return ` ${command.name}${spacer}${command.summary}`;
69
- })),
70
- "",
71
- ...GROUP_ORDER.filter((group) => group !== "Workflow").map((group) => formatSection(group, (groups.get(group) ?? []).map((command) => {
72
- const spacer = command.name.length < 18 ? " ".repeat(18 - command.name.length) : " ";
73
- return ` ${command.name}${spacer}${command.summary}`;
74
- }))).filter(Boolean),
75
- "",
76
- formatSection("Common Flows", [
77
- " treeseed status",
78
- " treeseed config",
79
- " treeseed switch feature/my-change --preview",
80
- " treeseed dev",
81
- ' treeseed save "feat: describe your change"',
82
- ' treeseed stage "feat: describe the resolution"',
83
- " treeseed release --patch"
84
- ]),
85
- "",
86
- formatSection("Help", [
87
- " treeseed --help",
88
- " treeseed help stage",
89
- " treeseed stage --help",
90
- " treeseed agents --help"
91
- ]),
92
- "",
93
- "Notes",
94
- " - Workspace-only commands must be run inside a Treeseed workspace; the CLI will resolve the project root from ancestor directories when possible.",
95
- " - Use `treeseed status`, `treeseed config`, `treeseed switch`, `treeseed save`, `treeseed stage`, and `treeseed release` for development and release work.",
96
- ' - Use `treeseed close "reason"` to archive a task without merging; it creates a deprecated resurrection tag before deleting the branch.',
97
- " - Use `treeseed agents <command>` for agent runtime inspection and execution.",
98
- " - Use `--json` on guidance and main workflow commands when an AI agent or script needs machine-readable output."
99
- ];
100
- return sections2.filter(Boolean).join("\n");
101
- }
102
- if (commandName === "agents") {
103
- return [
104
- "agents Run the Treeseed agent runtime namespace.",
105
- "",
106
- "Usage",
107
- " treeseed agents <command>",
108
- " treeseed agents --help",
109
- "",
110
- "Notes",
111
- " - Delegates to the integrated `@treeseed/core` agent runtime.",
112
- " - Use `treeseed agents --help` to list supported agent subcommands."
113
- ].join("\n");
167
+ const featuredCommands = TRESEED_OPERATION_SPECS.filter((spec2) => spec2.helpVisible !== false && spec2.helpFeatured).sort((left, right) => left.name.localeCompare(right.name));
168
+ const sections2 = [];
169
+ if (featuredCommands.length > 0) {
170
+ sections2.push({
171
+ id: "featured",
172
+ title: "Featured Commands",
173
+ entries: specEntries(featuredCommands)
174
+ });
175
+ }
176
+ for (const group of GROUP_ORDER) {
177
+ const entries = groups.get(group) ?? [];
178
+ if (entries.length === 0) continue;
179
+ sections2.push({
180
+ id: `group:${group}`,
181
+ title: group,
182
+ entries: specEntries(entries)
183
+ });
184
+ }
185
+ sections2.push({
186
+ id: "help",
187
+ title: "Help",
188
+ lines: [
189
+ "treeseed --help",
190
+ "treeseed help <command>",
191
+ "treeseed <command> --help"
192
+ ]
193
+ });
194
+ sections2.push({
195
+ id: "notes",
196
+ title: "Notes",
197
+ lines: [
198
+ "Workspace-only commands must be run inside a Treeseed workspace; the CLI resolves the project root from ancestor directories when possible.",
199
+ "Help text is generated from the CLI command registry.",
200
+ "Use --json on supported workflow and utility commands when an AI agent or script needs machine-readable output."
201
+ ]
202
+ });
203
+ return {
204
+ kind: "top",
205
+ title: "Treeseed CLI",
206
+ subtitle: "Command surface over the Treeseed SDK workflow operations, local adapters, and delegated runtime namespaces.",
207
+ badge: `${listTreeseedOperationNames().length} commands`,
208
+ sidebarTitle: "Sections",
209
+ sections: sections2,
210
+ statusPrimary: "Up/Down selects a section. PgUp/PgDn scroll. Enter, q, or Esc exits help.",
211
+ statusSecondary: "All commands, groups, and summaries are derived from the registry.",
212
+ exitCode: 0
213
+ };
114
214
  }
115
215
  const spec = findTreeseedOperation(commandName);
116
216
  if (!spec) {
117
217
  const suggestions = suggestTreeseedCommands(commandName);
118
- const lines = [`Unknown treeseed command: ${commandName}`];
119
- if (suggestions.length > 0) {
120
- lines.push(`Did you mean: ${suggestions.map((item) => `\`${item}\``).join(", ")}?`);
218
+ return {
219
+ kind: "unknown",
220
+ title: `Unknown treeseed command: ${commandName}`,
221
+ subtitle: suggestions.length > 0 ? `Closest matches: ${suggestions.map((item) => `\`${item}\``).join(", ")}` : "No close registry matches were found.",
222
+ badge: "Unknown command",
223
+ sidebarTitle: "Next Steps",
224
+ sections: [
225
+ ...suggestions.length > 0 ? [{
226
+ id: "suggestions",
227
+ title: "Suggestions",
228
+ entries: suggestions.map((item) => ({ label: item, summary: "View this command help.", accent: "command", targetCommand: item }))
229
+ }] : [],
230
+ {
231
+ id: "help",
232
+ title: "Help",
233
+ lines: ["Run `treeseed help` to see the full command list."]
234
+ }
235
+ ],
236
+ statusPrimary: "Enter, q, or Esc exits help.",
237
+ statusSecondary: "Unknown-command suggestions come from the same registry used by parsing and execution.",
238
+ exitCode: 1
239
+ };
240
+ }
241
+ const sections = [
242
+ ...(spec.help?.longSummary?.length ?? 0) > 0 || spec.description ? [{
243
+ id: "overview",
244
+ title: "Overview",
245
+ lines: spec.help?.longSummary ?? [spec.description ?? spec.summary]
246
+ }] : [],
247
+ ...(spec.help?.whenToUse?.length ?? 0) > 0 ? [{
248
+ id: "when-to-use",
249
+ title: "When To Use",
250
+ lines: spec.help?.whenToUse ?? []
251
+ }] : [],
252
+ ...(spec.help?.beforeYouRun?.length ?? 0) > 0 ? [{
253
+ id: "before-you-run",
254
+ title: "Before You Run",
255
+ lines: spec.help?.beforeYouRun ?? []
256
+ }] : [],
257
+ {
258
+ id: "command",
259
+ title: "Command",
260
+ entries: commandSectionEntries(spec)
261
+ },
262
+ {
263
+ id: "usage",
264
+ title: "Usage",
265
+ lines: [renderUsage(spec)]
121
266
  }
122
- lines.push("Run `treeseed help` to see the full command list.");
123
- return lines.join("\n");
267
+ ];
268
+ if ((spec.arguments ?? []).length > 0) {
269
+ const argumentDetails = detailMap(spec.help?.argumentDetails);
270
+ sections.push({
271
+ id: "arguments",
272
+ title: "Arguments",
273
+ entries: (spec.arguments ?? []).map((arg) => ({
274
+ label: arg.required ? `<${arg.name}>` : `[${arg.name}]`,
275
+ summary: `${arg.description} (${arg.required ? "required" : "optional"})${argumentDetails.get(arg.name) ? ` ${argumentDetails.get(arg.name)}` : ""}`,
276
+ accent: "argument",
277
+ required: arg.required
278
+ }))
279
+ });
280
+ } else {
281
+ sections.push({
282
+ id: "arguments",
283
+ title: "Arguments",
284
+ lines: ["This command does not take positional arguments."]
285
+ });
124
286
  }
125
- const formatOptions = (spec.options ?? []).map((option) => {
126
- const spacer = option.flags.length < 28 ? " ".repeat(28 - option.flags.length) : " ";
127
- return ` ${option.flags}${spacer}${option.description}`;
128
- });
129
- const formatArguments = (spec.arguments ?? []).map((arg) => {
130
- const rendered = arg.required ? `<${arg.name}>` : `[${arg.name}]`;
131
- return ` ${rendered} ${arg.description}`;
287
+ if ((spec.options ?? []).length > 0) {
288
+ const optionDetails = detailMap(spec.help?.optionDetails);
289
+ sections.push({
290
+ id: "options",
291
+ title: "Options",
292
+ entries: (spec.options ?? []).map((option) => ({
293
+ label: option.flags,
294
+ summary: `${formatOptionSummary(spec, option.flags, option.description)}${optionDetails.get(option.flags) ? ` ${optionDetails.get(option.flags)}` : ""}`,
295
+ accent: "flag"
296
+ }))
297
+ });
298
+ } else {
299
+ sections.push({
300
+ id: "options",
301
+ title: "Options",
302
+ lines: ["This command does not define CLI options."]
303
+ });
304
+ }
305
+ if (spec.aliases.length > 0) {
306
+ sections.push({
307
+ id: "aliases",
308
+ title: "Aliases",
309
+ entries: spec.aliases.map((alias) => ({ label: alias, accent: "alias", targetCommand: alias }))
310
+ });
311
+ }
312
+ if (normalizeStructuredExamples(spec).length > 0) {
313
+ sections.push({
314
+ id: "examples",
315
+ title: "Examples",
316
+ entries: exampleEntries(spec)
317
+ });
318
+ }
319
+ if ((spec.help?.outcomes?.length ?? 0) > 0 || (spec.notes?.length ?? 0) > 0) {
320
+ sections.push({
321
+ id: "behavior",
322
+ title: "Behavior",
323
+ lines: [...spec.help?.outcomes ?? [], ...spec.notes ?? []]
324
+ });
325
+ }
326
+ sections.push({
327
+ id: "automation",
328
+ title: "Automation",
329
+ lines: automationNotes(spec)
132
330
  });
331
+ if ((spec.help?.warnings?.length ?? 0) > 0) {
332
+ sections.push({
333
+ id: "warnings",
334
+ title: "Warnings",
335
+ lines: spec.help?.warnings ?? []
336
+ });
337
+ }
338
+ if ((spec.related ?? []).length > 0) {
339
+ const relatedDetails = relatedMap(spec.help?.relatedDetails);
340
+ sections.push({
341
+ id: "related",
342
+ title: "Related",
343
+ entries: (spec.related ?? []).map((related) => ({
344
+ label: related,
345
+ summary: relatedDetails.get(related) ?? "Related command.",
346
+ accent: "related",
347
+ targetCommand: related
348
+ }))
349
+ });
350
+ }
351
+ if ((spec.help?.seeAlso?.length ?? 0) > 0) {
352
+ sections.push({
353
+ id: "see-also",
354
+ title: "See Also",
355
+ entries: (spec.help?.seeAlso ?? []).filter((name, index, entries) => entries.indexOf(name) === index && !spec.related.includes(name)).map((name) => ({
356
+ label: name,
357
+ summary: `Additional next step from the ${spec.name} reference surface.`,
358
+ accent: "related",
359
+ targetCommand: name
360
+ }))
361
+ });
362
+ }
363
+ return {
364
+ kind: "command",
365
+ title: `${spec.name} ${spec.summary}`,
366
+ subtitle: spec.help?.workflowPosition ? `${spec.group} \u2022 ${spec.help.workflowPosition}` : spec.group,
367
+ badge: spec.provider,
368
+ sidebarTitle: spec.name,
369
+ sections,
370
+ statusPrimary: "Up/Down selects a section. PgUp/PgDn scroll. Enter opens linked commands. b/[ goes back. f/] goes forward.",
371
+ statusSecondary: "This view is generated from the same command registry used by parsing and execution.",
372
+ exitCode: 0
373
+ };
374
+ }
375
+ function renderTreeseedHelp(commandName) {
376
+ const view = buildTreeseedHelpView(commandName);
133
377
  const sections = [
134
- `${spec.name} ${spec.summary}`,
135
- spec.description,
378
+ view.title,
379
+ view.subtitle ?? "",
136
380
  "",
137
- formatSection("Usage", [` ${renderUsage(spec)}`]),
138
- formatArguments.length > 0 ? `
139
- ${formatSection("Arguments", formatArguments)}` : "",
140
- formatOptions.length > 0 ? `
141
- ${formatSection("Options", formatOptions)}` : "",
142
- (spec.examples ?? []).length > 0 ? `
143
- ${formatSection("Examples", spec.examples.map((example) => ` ${example}`))}` : "",
144
- (spec.notes ?? []).length > 0 ? `
145
- ${formatSection("Notes", spec.notes.map((note) => ` - ${note}`))}` : "",
146
- (spec.related ?? []).length > 0 ? `
147
- Related: ${spec.related.map((item) => `\`${item}\``).join(", ")}` : ""
148
- ];
149
- return sections.filter(Boolean).join("\n");
381
+ ...view.sections.flatMap((section) => {
382
+ const lines = [
383
+ ...entryLines(section.entries ?? []),
384
+ ...sectionLines(section.lines).map((line) => ` ${line}`)
385
+ ];
386
+ return lines.length > 0 ? [formatSection(section.title, lines), ""] : [];
387
+ })
388
+ ].filter(Boolean);
389
+ return sections.join("\n").trimEnd();
150
390
  }
151
391
  export {
392
+ buildTreeseedHelpView,
152
393
  renderTreeseedHelp,
153
394
  renderUsage,
154
395
  suggestTreeseedCommands