@crafter/cli-tree 0.1.0

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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +328 -0
  3. package/dist/archaeology/cache.d.ts +11 -0
  4. package/dist/archaeology/delegate.d.ts +43 -0
  5. package/dist/archaeology/index.d.ts +12 -0
  6. package/dist/archaeology/index.js +61 -0
  7. package/dist/archaeology/index.js.map +9 -0
  8. package/dist/archaeology/llm.d.ts +1 -0
  9. package/dist/archaeology/merge.d.ts +3 -0
  10. package/dist/archaeology/orchestrator.d.ts +25 -0
  11. package/dist/archaeology/prompts.d.ts +13 -0
  12. package/dist/archaeology/types.d.ts +101 -0
  13. package/dist/archaeology/validate.d.ts +18 -0
  14. package/dist/chunk-57gtsvhb.js +434 -0
  15. package/dist/chunk-57gtsvhb.js.map +16 -0
  16. package/dist/chunk-5aahbfr2.js +293 -0
  17. package/dist/chunk-5aahbfr2.js.map +10 -0
  18. package/dist/chunk-pkfpaae1.js +678 -0
  19. package/dist/chunk-pkfpaae1.js.map +15 -0
  20. package/dist/chunk-q4se2rwe.js +181 -0
  21. package/dist/chunk-q4se2rwe.js.map +14 -0
  22. package/dist/chunk-v5w3w6bd.js +168 -0
  23. package/dist/chunk-v5w3w6bd.js.map +11 -0
  24. package/dist/chunk-ykze151b.js +770 -0
  25. package/dist/chunk-ykze151b.js.map +16 -0
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +433 -0
  28. package/dist/cli.js.map +10 -0
  29. package/dist/encoders/ansi.d.ts +2 -0
  30. package/dist/encoders/html.d.ts +10 -0
  31. package/dist/encoders/string.d.ts +2 -0
  32. package/dist/flow/encode.d.ts +5 -0
  33. package/dist/flow/index.d.ts +8 -0
  34. package/dist/flow/index.js +25 -0
  35. package/dist/flow/index.js.map +9 -0
  36. package/dist/flow/layout.d.ts +30 -0
  37. package/dist/flow/parse.d.ts +2 -0
  38. package/dist/flow/render.d.ts +3 -0
  39. package/dist/flow/types.d.ts +42 -0
  40. package/dist/flow/validate.d.ts +3 -0
  41. package/dist/flow/yaml.d.ts +4 -0
  42. package/dist/grid.d.ts +14 -0
  43. package/dist/index.d.ts +7 -0
  44. package/dist/index.js +21 -0
  45. package/dist/index.js.map +9 -0
  46. package/dist/miner/history.d.ts +6 -0
  47. package/dist/miner/index.d.ts +18 -0
  48. package/dist/miner/index.js +38 -0
  49. package/dist/miner/index.js.map +9 -0
  50. package/dist/miner/sessions.d.ts +3 -0
  51. package/dist/miner/stats.d.ts +2 -0
  52. package/dist/miner/suggest.d.ts +11 -0
  53. package/dist/miner/transitions.d.ts +6 -0
  54. package/dist/miner/types.d.ts +46 -0
  55. package/dist/miner/workflows.d.ts +11 -0
  56. package/dist/parse.d.ts +3 -0
  57. package/dist/render.d.ts +3 -0
  58. package/dist/types.d.ts +39 -0
  59. package/package.json +85 -0
  60. package/skill/SKILL.md +263 -0
  61. package/skill/evals/evals.json +26 -0
  62. package/skill/install.sh +38 -0
  63. package/skill/references/archaeology-guide.md +157 -0
  64. package/skill/references/skill-template.md +120 -0
  65. package/src/archaeology/cache.ts +107 -0
  66. package/src/archaeology/delegate.ts +113 -0
  67. package/src/archaeology/index.ts +48 -0
  68. package/src/archaeology/llm.ts +10 -0
  69. package/src/archaeology/merge.ts +155 -0
  70. package/src/archaeology/orchestrator.ts +185 -0
  71. package/src/archaeology/prompts.ts +178 -0
  72. package/src/archaeology/types.ts +139 -0
  73. package/src/archaeology/validate.ts +157 -0
  74. package/src/cli.ts +451 -0
  75. package/src/encoders/ansi.ts +32 -0
  76. package/src/encoders/html.ts +78 -0
  77. package/src/encoders/string.ts +20 -0
  78. package/src/flow/encode.ts +21 -0
  79. package/src/flow/index.ts +15 -0
  80. package/src/flow/layout.ts +150 -0
  81. package/src/flow/parse.ts +100 -0
  82. package/src/flow/render.ts +186 -0
  83. package/src/flow/types.ts +45 -0
  84. package/src/flow/validate.ts +111 -0
  85. package/src/flow/yaml.ts +235 -0
  86. package/src/grid.ts +59 -0
  87. package/src/index.ts +24 -0
  88. package/src/miner/history.ts +156 -0
  89. package/src/miner/index.ts +76 -0
  90. package/src/miner/sessions.ts +39 -0
  91. package/src/miner/stats.ts +43 -0
  92. package/src/miner/suggest.ts +101 -0
  93. package/src/miner/transitions.ts +62 -0
  94. package/src/miner/types.ts +45 -0
  95. package/src/miner/workflows.ts +96 -0
  96. package/src/parse.ts +321 -0
  97. package/src/render.ts +182 -0
  98. package/src/types.ts +62 -0
  99. package/workflows/docker-deploy.yml +42 -0
  100. package/workflows/docker-parallel.yml +36 -0
  101. package/workflows/gh-issue-to-pr.yml +48 -0
  102. package/workflows/git-pr-flow.yml +36 -0
  103. package/workflows/kubectl-rollout.yml +37 -0
  104. package/workflows/npm-publish.yml +42 -0
@@ -0,0 +1,178 @@
1
+ import type { EnrichedCLINode } from "./types";
2
+
3
+ export interface PromptContext {
4
+ cli: string;
5
+ version?: string;
6
+ knownCommands?: string[];
7
+ knownTree?: EnrichedCLINode;
8
+ }
9
+
10
+ export function surveyPrompt(ctx: PromptContext): string {
11
+ return `You are a CLI archaeologist. Your task is to produce a hypothesis map of the \`${ctx.cli}\` CLI based on your training knowledge.
12
+
13
+ ${ctx.version ? `Version: ${ctx.version}\n` : ""}Do not invoke the CLI yet. This is survey phase — hypothesize what families of commands exist and what subcommands belong to each family.
14
+
15
+ Output STRICT JSON matching this schema:
16
+
17
+ \`\`\`json
18
+ {
19
+ "cli": "${ctx.cli}",
20
+ "description": "<one-line description of the CLI>",
21
+ "families": [
22
+ {
23
+ "name": "<family name, e.g. 'container management'>",
24
+ "description": "<one line>",
25
+ "subcommands": ["<subcommand-name>", ...]
26
+ }
27
+ ],
28
+ "topLevelFlags": ["--global-flag-1", "--global-flag-2"]
29
+ }
30
+ \`\`\`
31
+
32
+ Rules:
33
+ - Group related subcommands into families (container, image, network, auth, config, etc).
34
+ - List ONLY subcommand names — no descriptions in this phase.
35
+ - Include hidden/experimental commands you know about.
36
+ - topLevelFlags = flags that work on the root (e.g. --version, --help, --config).
37
+ - Output ONLY the JSON, no prose before or after.`;
38
+ }
39
+
40
+ export function excavationPrompt(ctx: PromptContext, subcommandPath: string[]): string {
41
+ const cmd = [ctx.cli, ...subcommandPath].join(" ");
42
+ return `You are a CLI archaeologist, currently excavating the \`${cmd}\` subcommand.
43
+
44
+ You will validate your hypothesis by running the real CLI. Use the Bash tool to run:
45
+
46
+ \`${cmd} --help\`
47
+
48
+ Then parse its output and produce a structured description of this subcommand.
49
+
50
+ Output STRICT JSON matching this schema:
51
+
52
+ \`\`\`json
53
+ {
54
+ "command": ${JSON.stringify([ctx.cli, ...subcommandPath])},
55
+ "exists": true|false,
56
+ "description": "<description from help output>",
57
+ "subcommands": [
58
+ { "name": "<sub>", "description": "<desc>" }
59
+ ],
60
+ "flags": [
61
+ {
62
+ "name": "flag-name",
63
+ "short": "f",
64
+ "type": "boolean|string|number|enum",
65
+ "description": "<desc>",
66
+ "required": false
67
+ }
68
+ ],
69
+ "args": [
70
+ { "name": "input", "required": true, "variadic": false }
71
+ ]
72
+ }
73
+ \`\`\`
74
+
75
+ Rules:
76
+ - If the command does not exist or --help fails, set exists: false and return empty arrays.
77
+ - Extract every flag and arg from the help output.
78
+ - Do NOT invent anything — only what is in the actual help text.
79
+ - Output ONLY the JSON.`;
80
+ }
81
+
82
+ export function deepDivePrompt(ctx: PromptContext, subcommandPath: string[]): string {
83
+ const cmd = [ctx.cli, ...subcommandPath].join(" ");
84
+ return `You are a CLI archaeologist. You already excavated \`${cmd}\` from --help.
85
+
86
+ Now dig deeper: based on your training knowledge, what flags exist for this command that are NOT documented in --help? Focus on:
87
+ - Hidden flags (work but undocumented)
88
+ - Experimental flags (marked as experimental in source code)
89
+ - Deprecated flags (still functional but hidden)
90
+ - Flags that only appear in man pages or shell completion scripts
91
+
92
+ Output STRICT JSON:
93
+
94
+ \`\`\`json
95
+ {
96
+ "command": ${JSON.stringify([ctx.cli, ...subcommandPath])},
97
+ "hiddenFlags": [
98
+ {
99
+ "name": "flag-name",
100
+ "short": "f",
101
+ "type": "boolean|string|number|enum",
102
+ "hidden": true,
103
+ "description": "<what it does>",
104
+ "source": "source-code|man-page|completion|community-known"
105
+ }
106
+ ],
107
+ "deprecatedFlags": [
108
+ {
109
+ "name": "old-flag",
110
+ "description": "Deprecated in favor of --new-flag",
111
+ "replacement": "new-flag"
112
+ }
113
+ ],
114
+ "examples": [
115
+ "${cmd} --example-use-case"
116
+ ]
117
+ }
118
+ \`\`\`
119
+
120
+ Rules:
121
+ - Only suggest flags you are confident about. Confidence > 70%.
122
+ - Prefer flags you have seen in real usage, PRs, issues, or man pages.
123
+ - If unsure, omit. This phase is about quality, not quantity.
124
+ - Output ONLY the JSON.`;
125
+ }
126
+
127
+ export function cartographyPrompt(ctx: PromptContext, subcommandPath: string[]): string {
128
+ const cmd = [ctx.cli, ...subcommandPath].join(" ");
129
+ return `You are a CLI archaeologist mapping flag interactions for \`${cmd}\`.
130
+
131
+ Identify constraints between flags:
132
+ - Mutually exclusive (cannot use together)
133
+ - Requires (using A requires B)
134
+ - Implies (using A sets B automatically)
135
+
136
+ Output STRICT JSON:
137
+
138
+ \`\`\`json
139
+ {
140
+ "command": ${JSON.stringify([ctx.cli, ...subcommandPath])},
141
+ "constraints": [
142
+ {
143
+ "type": "mutually-exclusive|requires|implies",
144
+ "flags": ["--flag-a", "--flag-b"],
145
+ "description": "<human explanation>"
146
+ }
147
+ ]
148
+ }
149
+ \`\`\`
150
+
151
+ Rules:
152
+ - Only list constraints you are confident about.
153
+ - Reference real flags from the --help output, not hypothetical ones.
154
+ - Output ONLY the JSON.`;
155
+ }
156
+
157
+ export function validationInstructions(cli: string): string {
158
+ return `You are validating an archaeology result for \`${cli}\`.
159
+
160
+ For each command/flag proposed by the LLM survey phase, you must:
161
+
162
+ 1. Run \`<cli> <subcommand> --help\` via the Bash tool.
163
+ 2. Parse the real help output.
164
+ 3. Mark each flag as:
165
+ - "verified" — appears in --help, can confirm it exists
166
+ - "rejected" — does NOT appear in --help, LLM hallucinated
167
+ - "unconfirmed" — not in --help but LLM may still be correct (hidden flags)
168
+
169
+ Return a validation report. Flags marked "rejected" must be removed from the final tree.
170
+ Flags marked "unconfirmed" are kept only if --include-unverified is set.`;
171
+ }
172
+
173
+ export function extractJsonFromLlmResponse(response: string): unknown {
174
+ const fenced = response.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
175
+ const raw = fenced ? fenced[1]! : response;
176
+ const trimmed = raw.trim();
177
+ return JSON.parse(trimmed);
178
+ }
@@ -0,0 +1,139 @@
1
+ import type { CLINode, Flag, Arg } from "../types";
2
+
3
+ export type SourceKind =
4
+ | "help"
5
+ | "llm-survey"
6
+ | "llm-deep-dive"
7
+ | "llm-cartography"
8
+ | "llm-validated"
9
+ | "man"
10
+ | "completion"
11
+ | "user";
12
+
13
+ export interface Source {
14
+ type: SourceKind;
15
+ confidence: number;
16
+ verifiedAt?: string;
17
+ note?: string;
18
+ }
19
+
20
+ export interface EnrichedFlag extends Flag {
21
+ hidden?: boolean;
22
+ deprecated?: boolean;
23
+ experimental?: boolean;
24
+ source?: Source;
25
+ examples?: string[];
26
+ excludes?: string[];
27
+ requires?: string[];
28
+ }
29
+
30
+ export interface EnrichedArg extends Arg {
31
+ source?: Source;
32
+ }
33
+
34
+ export interface Example {
35
+ command: string;
36
+ description?: string;
37
+ source?: Source;
38
+ }
39
+
40
+ export interface FlagConstraint {
41
+ type: "mutually-exclusive" | "requires" | "implies";
42
+ flags: string[];
43
+ description?: string;
44
+ }
45
+
46
+ export interface EnrichedCLINode {
47
+ name: string;
48
+ description?: string;
49
+ aliases?: string[];
50
+ hidden?: boolean;
51
+ deprecated?: boolean;
52
+ experimental?: boolean;
53
+ subcommands?: EnrichedCLINode[];
54
+ flags?: EnrichedFlag[];
55
+ args?: EnrichedArg[];
56
+ constraints?: FlagConstraint[];
57
+ examples?: Example[];
58
+ source?: Source;
59
+ }
60
+
61
+ export interface SurveyHypothesis {
62
+ cli: string;
63
+ description: string;
64
+ families: Array<{
65
+ name: string;
66
+ description: string;
67
+ subcommands: string[];
68
+ }>;
69
+ topLevelFlags: string[];
70
+ }
71
+
72
+ export interface DeepDiveProposal {
73
+ command: string[];
74
+ flags: Array<{
75
+ name: string;
76
+ short?: string;
77
+ type: "boolean" | "string" | "number" | "enum";
78
+ hidden?: boolean;
79
+ deprecated?: boolean;
80
+ description?: string;
81
+ }>;
82
+ examples: string[];
83
+ }
84
+
85
+ export interface ValidationVerdict {
86
+ command: string[];
87
+ exists: boolean;
88
+ verifiedFlags: string[];
89
+ rejectedFlags: string[];
90
+ unconfirmedFlags: string[];
91
+ rawHelp?: string;
92
+ error?: string;
93
+ }
94
+
95
+ export interface ArchaeologyCache {
96
+ cli: string;
97
+ cliVersion?: string;
98
+ createdAt: string;
99
+ tree: EnrichedCLINode;
100
+ phases: {
101
+ survey: boolean;
102
+ excavation: boolean;
103
+ deepDive: boolean;
104
+ cartography: boolean;
105
+ };
106
+ }
107
+
108
+ export interface ArchaeologyOptions {
109
+ cacheDir?: string;
110
+ skipCache?: boolean;
111
+ includeUnverified?: boolean;
112
+ maxDepth?: number;
113
+ phases?: {
114
+ survey?: boolean;
115
+ excavation?: boolean;
116
+ deepDive?: boolean;
117
+ cartography?: boolean;
118
+ };
119
+ }
120
+
121
+ export function toCLINode(node: EnrichedCLINode): CLINode {
122
+ const base: CLINode = { name: node.name };
123
+ if (node.description) base.description = node.description;
124
+ if (node.aliases) base.aliases = node.aliases;
125
+ if (node.subcommands) base.subcommands = node.subcommands.map(toCLINode);
126
+ if (node.flags) base.flags = node.flags.map(f => ({ ...f }));
127
+ if (node.args) base.args = node.args.map(a => ({ ...a }));
128
+ return base;
129
+ }
130
+
131
+ export function fromCLINode(node: CLINode, source: Source = { type: "help", confidence: 1 }): EnrichedCLINode {
132
+ const base: EnrichedCLINode = { name: node.name, source };
133
+ if (node.description) base.description = node.description;
134
+ if (node.aliases) base.aliases = node.aliases;
135
+ if (node.subcommands) base.subcommands = node.subcommands.map(s => fromCLINode(s, source));
136
+ if (node.flags) base.flags = node.flags.map(f => ({ ...f, source }));
137
+ if (node.args) base.args = node.args.map(a => ({ ...a, source }));
138
+ return base;
139
+ }
@@ -0,0 +1,157 @@
1
+ import { parseHelp } from "../parse";
2
+ import type { CLINode } from "../types";
3
+ import type { ValidationVerdict, EnrichedCLINode, SurveyHypothesis, DeepDiveProposal } from "./types";
4
+
5
+ export async function runHelpCommand(binary: string, subcommandPath: string[]): Promise<string | null> {
6
+ try {
7
+ const proc = Bun.spawn([binary, ...subcommandPath, "--help"], {
8
+ stdout: "pipe",
9
+ stderr: "pipe",
10
+ });
11
+
12
+ const timeout = setTimeout(() => proc.kill(), 5000);
13
+
14
+ const [stdout, stderr] = await Promise.all([
15
+ new Response(proc.stdout).text(),
16
+ new Response(proc.stderr).text(),
17
+ ]);
18
+
19
+ clearTimeout(timeout);
20
+ await proc.exited;
21
+
22
+ const output = stdout || stderr;
23
+ return output.trim() ? output : null;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ export async function validateSubcommandExists(
30
+ binary: string,
31
+ subcommandPath: string[],
32
+ ): Promise<boolean> {
33
+ const output = await runHelpCommand(binary, subcommandPath);
34
+ if (!output) return false;
35
+
36
+ const lower = output.toLowerCase();
37
+ const hasError =
38
+ /unknown (command|subcommand)|command not found|no such command/.test(lower) ||
39
+ /is not a \w+ command/.test(lower) ||
40
+ /invalid (command|subcommand)/.test(lower) ||
41
+ /error: unrecognized/.test(lower);
42
+ return !hasError;
43
+ }
44
+
45
+ export async function validateSurvey(
46
+ binary: string,
47
+ survey: SurveyHypothesis,
48
+ ): Promise<{ confirmed: string[]; rejected: string[] }> {
49
+ const confirmed: string[] = [];
50
+ const rejected: string[] = [];
51
+
52
+ const allSubs = survey.families.flatMap(f => f.subcommands);
53
+ const unique = Array.from(new Set(allSubs));
54
+
55
+ const BATCH = 5;
56
+ for (let i = 0; i < unique.length; i += BATCH) {
57
+ const batch = unique.slice(i, i + BATCH);
58
+ const results = await Promise.all(
59
+ batch.map(async sub => ({ sub, exists: await validateSubcommandExists(binary, [sub]) })),
60
+ );
61
+ for (const { sub, exists } of results) {
62
+ if (exists) confirmed.push(sub);
63
+ else rejected.push(sub);
64
+ }
65
+ }
66
+
67
+ return { confirmed, rejected };
68
+ }
69
+
70
+ export async function excavateCommand(
71
+ binary: string,
72
+ subcommandPath: string[],
73
+ ): Promise<CLINode | null> {
74
+ const output = await runHelpCommand(binary, subcommandPath);
75
+ if (!output) return null;
76
+
77
+ const name = subcommandPath[subcommandPath.length - 1] ?? binary;
78
+ return parseHelp(name, output);
79
+ }
80
+
81
+ export async function validateDeepDive(
82
+ binary: string,
83
+ proposal: DeepDiveProposal,
84
+ ): Promise<ValidationVerdict> {
85
+ const subcommandPath = proposal.command.slice(1);
86
+ const realHelp = await runHelpCommand(binary, subcommandPath);
87
+
88
+ if (!realHelp) {
89
+ return {
90
+ command: proposal.command,
91
+ exists: false,
92
+ verifiedFlags: [],
93
+ rejectedFlags: proposal.flags.map(f => f.name),
94
+ unconfirmedFlags: [],
95
+ error: "Command did not produce help output",
96
+ };
97
+ }
98
+
99
+ const parsed = parseHelp(subcommandPath[subcommandPath.length - 1] ?? binary, realHelp);
100
+ const realFlagNames = new Set(parsed.flags?.map(f => f.name) ?? []);
101
+
102
+ const verifiedFlags: string[] = [];
103
+ const rejectedFlags: string[] = [];
104
+ const unconfirmedFlags: string[] = [];
105
+
106
+ for (const flag of proposal.flags) {
107
+ if (realFlagNames.has(flag.name)) {
108
+ verifiedFlags.push(flag.name);
109
+ } else if (flag.hidden || flag.deprecated) {
110
+ unconfirmedFlags.push(flag.name);
111
+ } else {
112
+ rejectedFlags.push(flag.name);
113
+ }
114
+ }
115
+
116
+ return {
117
+ command: proposal.command,
118
+ exists: true,
119
+ verifiedFlags,
120
+ rejectedFlags,
121
+ unconfirmedFlags,
122
+ rawHelp: realHelp,
123
+ };
124
+ }
125
+
126
+ export interface ValidationSummary {
127
+ totalProposed: number;
128
+ verified: number;
129
+ rejected: number;
130
+ unconfirmed: number;
131
+ hallucinations: string[];
132
+ }
133
+
134
+ export function summarizeValidation(verdicts: ValidationVerdict[]): ValidationSummary {
135
+ let verified = 0;
136
+ let rejected = 0;
137
+ let unconfirmed = 0;
138
+ const hallucinations: string[] = [];
139
+
140
+ for (const v of verdicts) {
141
+ verified += v.verifiedFlags.length;
142
+ rejected += v.rejectedFlags.length;
143
+ unconfirmed += v.unconfirmedFlags.length;
144
+
145
+ for (const flag of v.rejectedFlags) {
146
+ hallucinations.push(`${v.command.join(" ")} --${flag}`);
147
+ }
148
+ }
149
+
150
+ return {
151
+ totalProposed: verified + rejected + unconfirmed,
152
+ verified,
153
+ rejected,
154
+ unconfirmed,
155
+ hallucinations,
156
+ };
157
+ }