@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.
- package/LICENSE +21 -0
- package/README.md +328 -0
- package/dist/archaeology/cache.d.ts +11 -0
- package/dist/archaeology/delegate.d.ts +43 -0
- package/dist/archaeology/index.d.ts +12 -0
- package/dist/archaeology/index.js +61 -0
- package/dist/archaeology/index.js.map +9 -0
- package/dist/archaeology/llm.d.ts +1 -0
- package/dist/archaeology/merge.d.ts +3 -0
- package/dist/archaeology/orchestrator.d.ts +25 -0
- package/dist/archaeology/prompts.d.ts +13 -0
- package/dist/archaeology/types.d.ts +101 -0
- package/dist/archaeology/validate.d.ts +18 -0
- package/dist/chunk-57gtsvhb.js +434 -0
- package/dist/chunk-57gtsvhb.js.map +16 -0
- package/dist/chunk-5aahbfr2.js +293 -0
- package/dist/chunk-5aahbfr2.js.map +10 -0
- package/dist/chunk-pkfpaae1.js +678 -0
- package/dist/chunk-pkfpaae1.js.map +15 -0
- package/dist/chunk-q4se2rwe.js +181 -0
- package/dist/chunk-q4se2rwe.js.map +14 -0
- package/dist/chunk-v5w3w6bd.js +168 -0
- package/dist/chunk-v5w3w6bd.js.map +11 -0
- package/dist/chunk-ykze151b.js +770 -0
- package/dist/chunk-ykze151b.js.map +16 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +433 -0
- package/dist/cli.js.map +10 -0
- package/dist/encoders/ansi.d.ts +2 -0
- package/dist/encoders/html.d.ts +10 -0
- package/dist/encoders/string.d.ts +2 -0
- package/dist/flow/encode.d.ts +5 -0
- package/dist/flow/index.d.ts +8 -0
- package/dist/flow/index.js +25 -0
- package/dist/flow/index.js.map +9 -0
- package/dist/flow/layout.d.ts +30 -0
- package/dist/flow/parse.d.ts +2 -0
- package/dist/flow/render.d.ts +3 -0
- package/dist/flow/types.d.ts +42 -0
- package/dist/flow/validate.d.ts +3 -0
- package/dist/flow/yaml.d.ts +4 -0
- package/dist/grid.d.ts +14 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +9 -0
- package/dist/miner/history.d.ts +6 -0
- package/dist/miner/index.d.ts +18 -0
- package/dist/miner/index.js +38 -0
- package/dist/miner/index.js.map +9 -0
- package/dist/miner/sessions.d.ts +3 -0
- package/dist/miner/stats.d.ts +2 -0
- package/dist/miner/suggest.d.ts +11 -0
- package/dist/miner/transitions.d.ts +6 -0
- package/dist/miner/types.d.ts +46 -0
- package/dist/miner/workflows.d.ts +11 -0
- package/dist/parse.d.ts +3 -0
- package/dist/render.d.ts +3 -0
- package/dist/types.d.ts +39 -0
- package/package.json +85 -0
- package/skill/SKILL.md +263 -0
- package/skill/evals/evals.json +26 -0
- package/skill/install.sh +38 -0
- package/skill/references/archaeology-guide.md +157 -0
- package/skill/references/skill-template.md +120 -0
- package/src/archaeology/cache.ts +107 -0
- package/src/archaeology/delegate.ts +113 -0
- package/src/archaeology/index.ts +48 -0
- package/src/archaeology/llm.ts +10 -0
- package/src/archaeology/merge.ts +155 -0
- package/src/archaeology/orchestrator.ts +185 -0
- package/src/archaeology/prompts.ts +178 -0
- package/src/archaeology/types.ts +139 -0
- package/src/archaeology/validate.ts +157 -0
- package/src/cli.ts +451 -0
- package/src/encoders/ansi.ts +32 -0
- package/src/encoders/html.ts +78 -0
- package/src/encoders/string.ts +20 -0
- package/src/flow/encode.ts +21 -0
- package/src/flow/index.ts +15 -0
- package/src/flow/layout.ts +150 -0
- package/src/flow/parse.ts +100 -0
- package/src/flow/render.ts +186 -0
- package/src/flow/types.ts +45 -0
- package/src/flow/validate.ts +111 -0
- package/src/flow/yaml.ts +235 -0
- package/src/grid.ts +59 -0
- package/src/index.ts +24 -0
- package/src/miner/history.ts +156 -0
- package/src/miner/index.ts +76 -0
- package/src/miner/sessions.ts +39 -0
- package/src/miner/stats.ts +43 -0
- package/src/miner/suggest.ts +101 -0
- package/src/miner/transitions.ts +62 -0
- package/src/miner/types.ts +45 -0
- package/src/miner/workflows.ts +96 -0
- package/src/parse.ts +321 -0
- package/src/render.ts +182 -0
- package/src/types.ts +62 -0
- package/workflows/docker-deploy.yml +42 -0
- package/workflows/docker-parallel.yml +36 -0
- package/workflows/gh-issue-to-pr.yml +48 -0
- package/workflows/git-pr-flow.yml +36 -0
- package/workflows/kubectl-rollout.yml +37 -0
- package/workflows/npm-publish.yml +42 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { parseHelpRecursive, parseHelp } from "./parse";
|
|
3
|
+
import { printTree, treeToString, treeToHtml } from "./index";
|
|
4
|
+
import type { TreeOptions } from "./types";
|
|
5
|
+
import { parseWorkflow, validateWorkflow, flowToAnsi, flowToString, flowToHtml } from "./flow";
|
|
6
|
+
import type { FlowRenderOptions } from "./flow/types";
|
|
7
|
+
import { mineCli } from "./miner";
|
|
8
|
+
import { runArchaeology, NullDelegate } from "./archaeology";
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const subcommand = args[0];
|
|
12
|
+
|
|
13
|
+
const HELP_SUBCOMMANDS = new Set(["flow", "mine", "archaeology"]);
|
|
14
|
+
const isHelpFlag = args.includes("--help") || args.includes("-h");
|
|
15
|
+
|
|
16
|
+
if (!subcommand || (isHelpFlag && !HELP_SUBCOMMANDS.has(subcommand ?? ""))) {
|
|
17
|
+
printMainHelp();
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (isHelpFlag) {
|
|
22
|
+
if (subcommand === "flow") printFlowHelp();
|
|
23
|
+
else if (subcommand === "mine") printMineHelp();
|
|
24
|
+
else if (subcommand === "archaeology") printArchaeologyHelp();
|
|
25
|
+
else printMainHelp();
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (subcommand === "flow") {
|
|
30
|
+
await runFlow(args.slice(1));
|
|
31
|
+
} else if (subcommand === "mine") {
|
|
32
|
+
await runMine(args.slice(1));
|
|
33
|
+
} else if (subcommand === "archaeology") {
|
|
34
|
+
await runArchaeologyCmd(args.slice(1));
|
|
35
|
+
} else {
|
|
36
|
+
await runTree(args);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function printMainHelp() {
|
|
40
|
+
console.log(`
|
|
41
|
+
clitree — Visualize and explore any CLI
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
clitree <binary> [options] Render command tree from --help
|
|
45
|
+
clitree flow <file> [options] Render a workflow YAML as a DAG
|
|
46
|
+
clitree mine <binary> [options] Mine shell history for workflows
|
|
47
|
+
clitree archaeology <binary> [options] Full analysis: tree + mining + (LLM)
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
clitree docker # basic tree
|
|
51
|
+
clitree mine git # "what workflows do I repeat with git?"
|
|
52
|
+
clitree archaeology bun # tree + mining + LLM archaeology
|
|
53
|
+
|
|
54
|
+
Subcommands:
|
|
55
|
+
tree Parse --help output (default; passing a binary name invokes this)
|
|
56
|
+
flow Render a workflow YAML file as an ASCII DAG
|
|
57
|
+
mine Discover workflows from your shell history
|
|
58
|
+
archaeology Run full analysis (tree + mine + LLM proposals when available)
|
|
59
|
+
|
|
60
|
+
Help for a subcommand: clitree <subcommand> --help
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printMineHelp() {
|
|
65
|
+
console.log(`
|
|
66
|
+
cli-tree mine — Mine your shell history for CLI workflows
|
|
67
|
+
|
|
68
|
+
Usage:
|
|
69
|
+
cli-tree mine <binary> [options]
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
cli-tree mine git
|
|
73
|
+
cli-tree mine docker --min-support 5
|
|
74
|
+
cli-tree mine bun --format json > bun-flows.json
|
|
75
|
+
|
|
76
|
+
Options:
|
|
77
|
+
--min-support <n> Minimum occurrences to count as a workflow (default: 3)
|
|
78
|
+
--history-path <p> Custom shell history path (default: ~/.zsh_history)
|
|
79
|
+
--format <fmt> Output format: ansi (default), json
|
|
80
|
+
--max-paths <n> Max workflows to show (default: 10)
|
|
81
|
+
-h, --help Show this help
|
|
82
|
+
`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function printArchaeologyHelp() {
|
|
86
|
+
console.log(`
|
|
87
|
+
cli-tree archaeology — Full CLI analysis: tree + mining + LLM archaeology
|
|
88
|
+
|
|
89
|
+
Usage:
|
|
90
|
+
cli-tree archaeology <binary> [options]
|
|
91
|
+
|
|
92
|
+
When run from the command line without a delegate, only the deterministic
|
|
93
|
+
phases run (help parsing + shell history mining). LLM archaeology requires
|
|
94
|
+
running inside the clitree skill (see ./skill/SKILL.md).
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
cli-tree archaeology git # help + history
|
|
98
|
+
cli-tree archaeology docker --no-cache # bypass cache
|
|
99
|
+
cli-tree archaeology bun --format json # JSON output
|
|
100
|
+
|
|
101
|
+
Options:
|
|
102
|
+
--no-cache Skip cache, always re-run
|
|
103
|
+
--max-depth <n> Max --help recursion depth (default: 2)
|
|
104
|
+
--format <fmt> Output format: ansi (default), json
|
|
105
|
+
-h, --help Show this help
|
|
106
|
+
`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function printFlowHelp() {
|
|
110
|
+
console.log(`
|
|
111
|
+
cli-tree flow — Render a CLI workflow YAML as a DAG
|
|
112
|
+
|
|
113
|
+
Usage:
|
|
114
|
+
cli-tree flow <file> [options]
|
|
115
|
+
|
|
116
|
+
Examples:
|
|
117
|
+
cli-tree flow workflows/git-pr-flow.yml
|
|
118
|
+
cli-tree flow my-workflow.yml --format html > flow.html
|
|
119
|
+
cli-tree flow workflows/docker-deploy.yml --compact --no-color
|
|
120
|
+
|
|
121
|
+
Options:
|
|
122
|
+
--compact Hide description and legend
|
|
123
|
+
--no-color Disable colors
|
|
124
|
+
--format <fmt> Output format: ansi (default), string, html
|
|
125
|
+
--no-validate Skip validation
|
|
126
|
+
-h, --help Show this help
|
|
127
|
+
`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function runTree(args: string[]) {
|
|
131
|
+
const opts: TreeOptions = {
|
|
132
|
+
color: true,
|
|
133
|
+
showFlags: true,
|
|
134
|
+
showArgs: true,
|
|
135
|
+
showDescriptions: true,
|
|
136
|
+
showTypes: true,
|
|
137
|
+
showDefaults: true,
|
|
138
|
+
compact: false,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
let depth = 2;
|
|
142
|
+
let format: "ansi" | "string" | "html" | "json" = "ansi";
|
|
143
|
+
let stdinName: string | null = null;
|
|
144
|
+
let binary: string | null = null;
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < args.length; i++) {
|
|
147
|
+
const arg = args[i]!;
|
|
148
|
+
if (arg === "--depth" && args[i + 1]) {
|
|
149
|
+
depth = Number.parseInt(args[++i]!, 10);
|
|
150
|
+
} else if (arg === "--compact") {
|
|
151
|
+
opts.compact = true;
|
|
152
|
+
opts.showDescriptions = false;
|
|
153
|
+
} else if (arg === "--no-flags") {
|
|
154
|
+
opts.showFlags = false;
|
|
155
|
+
} else if (arg === "--no-args") {
|
|
156
|
+
opts.showArgs = false;
|
|
157
|
+
} else if (arg === "--no-color") {
|
|
158
|
+
opts.color = false;
|
|
159
|
+
} else if (arg === "--format" && args[i + 1]) {
|
|
160
|
+
format = args[++i] as "ansi" | "string" | "html" | "json";
|
|
161
|
+
} else if (arg === "--stdin" && args[i + 1]) {
|
|
162
|
+
stdinName = args[++i]!;
|
|
163
|
+
} else if (!arg.startsWith("-")) {
|
|
164
|
+
binary = arg;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
let tree;
|
|
170
|
+
if (stdinName) {
|
|
171
|
+
const input = await readStdin();
|
|
172
|
+
tree = parseHelp(stdinName, input);
|
|
173
|
+
} else if (binary) {
|
|
174
|
+
tree = await parseHelpRecursive(binary, [], depth);
|
|
175
|
+
} else {
|
|
176
|
+
console.error("Error: provide a binary name or use --stdin <name>");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (format === "json") {
|
|
181
|
+
console.log(JSON.stringify(tree, null, 2));
|
|
182
|
+
} else if (format === "html") {
|
|
183
|
+
console.log(treeToHtml(tree, opts));
|
|
184
|
+
} else if (format === "string") {
|
|
185
|
+
console.log(treeToString(tree, opts));
|
|
186
|
+
} else {
|
|
187
|
+
printTree(tree, opts);
|
|
188
|
+
}
|
|
189
|
+
} catch (err: any) {
|
|
190
|
+
console.error(`Error: ${err.message}`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function runFlow(args: string[]) {
|
|
196
|
+
const opts: FlowRenderOptions = {
|
|
197
|
+
color: true,
|
|
198
|
+
showDescriptions: true,
|
|
199
|
+
showCommands: true,
|
|
200
|
+
compact: false,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
let format: "ansi" | "string" | "html" = "ansi";
|
|
204
|
+
let file: string | null = null;
|
|
205
|
+
let skipValidate = false;
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < args.length; i++) {
|
|
208
|
+
const arg = args[i]!;
|
|
209
|
+
if (arg === "--compact") {
|
|
210
|
+
opts.compact = true;
|
|
211
|
+
opts.showDescriptions = false;
|
|
212
|
+
} else if (arg === "--no-color") {
|
|
213
|
+
opts.color = false;
|
|
214
|
+
} else if (arg === "--no-validate") {
|
|
215
|
+
skipValidate = true;
|
|
216
|
+
} else if (arg === "--format" && args[i + 1]) {
|
|
217
|
+
format = args[++i] as "ansi" | "string" | "html";
|
|
218
|
+
} else if (!arg.startsWith("-")) {
|
|
219
|
+
file = arg;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!file) {
|
|
224
|
+
console.error("Error: provide a workflow YAML file");
|
|
225
|
+
console.error("Run 'cli-tree flow --help' for usage");
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const text = await Bun.file(file).text();
|
|
231
|
+
const workflow = parseWorkflow(text);
|
|
232
|
+
|
|
233
|
+
if (!skipValidate) {
|
|
234
|
+
const result = validateWorkflow(workflow);
|
|
235
|
+
for (const err of result.errors) {
|
|
236
|
+
const prefix = err.severity === "error" ? "\x1b[31m✗" : "\x1b[33m⚠";
|
|
237
|
+
const reset = "\x1b[0m";
|
|
238
|
+
const location = err.node ? ` [${err.node}]` : err.edge ? ` [${err.edge.from}→${err.edge.to}]` : "";
|
|
239
|
+
console.error(`${prefix}${location} ${err.message}${reset}`);
|
|
240
|
+
}
|
|
241
|
+
if (!result.valid) process.exit(1);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (format === "html") {
|
|
245
|
+
console.log(flowToHtml(workflow, opts));
|
|
246
|
+
} else if (format === "string") {
|
|
247
|
+
console.log(flowToString(workflow, opts));
|
|
248
|
+
} else {
|
|
249
|
+
console.log(flowToAnsi(workflow, opts));
|
|
250
|
+
}
|
|
251
|
+
} catch (err: any) {
|
|
252
|
+
console.error(`Error: ${err.message}`);
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function readStdin(): Promise<string> {
|
|
258
|
+
const chunks: Buffer[] = [];
|
|
259
|
+
for await (const chunk of process.stdin) {
|
|
260
|
+
chunks.push(chunk as Buffer);
|
|
261
|
+
}
|
|
262
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function runMine(args: string[]) {
|
|
266
|
+
let binary: string | null = null;
|
|
267
|
+
let minSupport = 3;
|
|
268
|
+
let historyPath: string | undefined;
|
|
269
|
+
let format: "ansi" | "json" = "ansi";
|
|
270
|
+
let maxPaths = 10;
|
|
271
|
+
|
|
272
|
+
for (let i = 0; i < args.length; i++) {
|
|
273
|
+
const arg = args[i]!;
|
|
274
|
+
if (arg === "--min-support" && args[i + 1]) {
|
|
275
|
+
minSupport = Number.parseInt(args[++i]!, 10);
|
|
276
|
+
} else if (arg === "--history-path" && args[i + 1]) {
|
|
277
|
+
historyPath = args[++i]!;
|
|
278
|
+
} else if (arg === "--format" && args[i + 1]) {
|
|
279
|
+
format = args[++i] as "ansi" | "json";
|
|
280
|
+
} else if (arg === "--max-paths" && args[i + 1]) {
|
|
281
|
+
maxPaths = Number.parseInt(args[++i]!, 10);
|
|
282
|
+
} else if (!arg.startsWith("-")) {
|
|
283
|
+
binary = arg;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!binary) {
|
|
288
|
+
console.error("Error: provide a binary name");
|
|
289
|
+
console.error("Run 'cli-tree mine --help' for usage");
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const result = await mineCli(binary, { minSupport, historyPath });
|
|
295
|
+
|
|
296
|
+
if (format === "json") {
|
|
297
|
+
console.log(JSON.stringify(result, null, 2));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const C = {
|
|
302
|
+
reset: "\x1b[0m",
|
|
303
|
+
bold: "\x1b[1m",
|
|
304
|
+
dim: "\x1b[2m",
|
|
305
|
+
cyan: "\x1b[36m",
|
|
306
|
+
green: "\x1b[32m",
|
|
307
|
+
yellow: "\x1b[33m",
|
|
308
|
+
magenta: "\x1b[35m",
|
|
309
|
+
gray: "\x1b[90m",
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
console.log(`\n${C.bold}${C.magenta}${binary}${C.reset} ${C.gray}— shell history analysis${C.reset}\n`);
|
|
313
|
+
console.log(`${C.bold}Usage:${C.reset}`);
|
|
314
|
+
console.log(` ${C.dim}Invocations:${C.reset} ${C.cyan}${result.stats.totalInvocations}${C.reset}`);
|
|
315
|
+
console.log(` ${C.dim}Subcommands:${C.reset} ${C.cyan}${result.stats.uniqueSubcommands}${C.reset}`);
|
|
316
|
+
console.log(` ${C.dim}Sessions:${C.reset} ${C.cyan}${result.sessionsAnalyzed}${C.reset}\n`);
|
|
317
|
+
|
|
318
|
+
if (result.stats.topSubcommands.length > 0) {
|
|
319
|
+
console.log(`${C.bold}Top subcommands:${C.reset}`);
|
|
320
|
+
const max = result.stats.topSubcommands[0]!.count;
|
|
321
|
+
for (const { subcommand, count } of result.stats.topSubcommands.slice(0, 5)) {
|
|
322
|
+
const bar = "█".repeat(Math.max(1, Math.round((count / max) * 30)));
|
|
323
|
+
console.log(` ${C.green}${subcommand.padEnd(20)}${C.reset} ${C.cyan}${bar}${C.reset} ${C.dim}${count}${C.reset}`);
|
|
324
|
+
}
|
|
325
|
+
console.log();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (result.workflows.length > 0) {
|
|
329
|
+
console.log(`${C.bold}Discovered workflows:${C.reset}`);
|
|
330
|
+
for (const wf of result.workflows.slice(0, maxPaths)) {
|
|
331
|
+
const chain = wf.path[0]!.map(p => `${C.green}${p}${C.reset}`).join(` ${C.gray}→${C.reset} `);
|
|
332
|
+
console.log(` ${chain} ${C.dim}(${wf.support}×)${C.reset}`);
|
|
333
|
+
}
|
|
334
|
+
console.log();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (result.suggestions.length > 0) {
|
|
338
|
+
console.log(`${C.bold}${C.yellow}💡 Skill suggestions:${C.reset}`);
|
|
339
|
+
for (const s of result.suggestions.slice(0, 3)) {
|
|
340
|
+
const badge =
|
|
341
|
+
s.priority === "high"
|
|
342
|
+
? `${C.green}[HIGH]${C.reset}`
|
|
343
|
+
: s.priority === "medium"
|
|
344
|
+
? `${C.yellow}[MED]${C.reset}`
|
|
345
|
+
: `${C.dim}[LOW]${C.reset}`;
|
|
346
|
+
console.log(`\n ${badge} ${C.bold}/${s.name}${C.reset} — ${s.description}`);
|
|
347
|
+
console.log(` ${C.dim}${s.reason}${C.reset}`);
|
|
348
|
+
console.log(` ${C.dim}${s.commands.join(" && ")}${C.reset}`);
|
|
349
|
+
}
|
|
350
|
+
console.log();
|
|
351
|
+
}
|
|
352
|
+
} catch (err: any) {
|
|
353
|
+
console.error(`Error: ${err.message}`);
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async function runArchaeologyCmd(args: string[]) {
|
|
359
|
+
let binary: string | null = null;
|
|
360
|
+
let skipCache = false;
|
|
361
|
+
let maxDepth = 2;
|
|
362
|
+
let format: "ansi" | "json" = "ansi";
|
|
363
|
+
|
|
364
|
+
for (let i = 0; i < args.length; i++) {
|
|
365
|
+
const arg = args[i]!;
|
|
366
|
+
if (arg === "--no-cache") {
|
|
367
|
+
skipCache = true;
|
|
368
|
+
} else if (arg === "--max-depth" && args[i + 1]) {
|
|
369
|
+
maxDepth = Number.parseInt(args[++i]!, 10);
|
|
370
|
+
} else if (arg === "--format" && args[i + 1]) {
|
|
371
|
+
format = args[++i] as "ansi" | "json";
|
|
372
|
+
} else if (!arg.startsWith("-")) {
|
|
373
|
+
binary = arg;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!binary) {
|
|
378
|
+
console.error("Error: provide a binary name");
|
|
379
|
+
console.error("Run 'cli-tree archaeology --help' for usage");
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const C = {
|
|
384
|
+
reset: "\x1b[0m",
|
|
385
|
+
bold: "\x1b[1m",
|
|
386
|
+
dim: "\x1b[2m",
|
|
387
|
+
cyan: "\x1b[36m",
|
|
388
|
+
green: "\x1b[32m",
|
|
389
|
+
yellow: "\x1b[33m",
|
|
390
|
+
red: "\x1b[31m",
|
|
391
|
+
magenta: "\x1b[35m",
|
|
392
|
+
gray: "\x1b[90m",
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
try {
|
|
396
|
+
console.error(`${C.dim}⚠ LLM archaeology requires running inside the clitree skill.${C.reset}`);
|
|
397
|
+
console.error(`${C.dim} Running deterministic phases only (help + history mining).${C.reset}`);
|
|
398
|
+
console.error("");
|
|
399
|
+
|
|
400
|
+
const [arch, mine] = await Promise.all([
|
|
401
|
+
runArchaeology(binary, new NullDelegate(), { skipCache, maxHelpDepth: maxDepth }),
|
|
402
|
+
mineCli(binary, { minSupport: 3 }).catch(() => null),
|
|
403
|
+
]);
|
|
404
|
+
|
|
405
|
+
if (format === "json") {
|
|
406
|
+
console.log(JSON.stringify({ archaeology: arch, mining: mine }, null, 2));
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
console.log(`${C.bold}${C.magenta}${binary}${C.reset}`);
|
|
411
|
+
if (arch.tree.description) {
|
|
412
|
+
console.log(`${C.gray}${arch.tree.description}${C.reset}`);
|
|
413
|
+
}
|
|
414
|
+
console.log();
|
|
415
|
+
|
|
416
|
+
console.log(`${C.bold}Tree:${C.reset}`);
|
|
417
|
+
console.log(` ${C.dim}Commands:${C.reset} ${C.cyan}${arch.stats.helpCommands}${C.reset}`);
|
|
418
|
+
console.log(` ${C.dim}Cached:${C.reset} ${arch.fromCache ? `${C.green}yes${C.reset}` : `${C.dim}no${C.reset}`}`);
|
|
419
|
+
console.log();
|
|
420
|
+
|
|
421
|
+
if (mine) {
|
|
422
|
+
console.log(`${C.bold}Usage from shell history:${C.reset}`);
|
|
423
|
+
console.log(` ${C.dim}Invocations:${C.reset} ${C.cyan}${mine.stats.totalInvocations}${C.reset}`);
|
|
424
|
+
console.log(` ${C.dim}Subcommands:${C.reset} ${C.cyan}${mine.stats.uniqueSubcommands}${C.reset}`);
|
|
425
|
+
|
|
426
|
+
if (mine.workflows.length > 0) {
|
|
427
|
+
console.log(`\n${C.bold}Workflows you repeat:${C.reset}`);
|
|
428
|
+
for (const wf of mine.workflows.slice(0, 5)) {
|
|
429
|
+
const chain = wf.path[0]!.map(p => `${C.green}${p}${C.reset}`).join(` ${C.gray}→${C.reset} `);
|
|
430
|
+
console.log(` ${chain} ${C.dim}(${wf.support}×)${C.reset}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (mine.suggestions.length > 0) {
|
|
435
|
+
console.log(`\n${C.bold}${C.yellow}💡 Skill suggestions:${C.reset}`);
|
|
436
|
+
for (const s of mine.suggestions.slice(0, 3)) {
|
|
437
|
+
console.log(` ${C.bold}/${s.name}${C.reset} — ${s.description}`);
|
|
438
|
+
console.log(` ${C.dim}${s.reason}${C.reset}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
console.log();
|
|
444
|
+
console.log(`${C.dim}Install the clitree skill for full LLM archaeology:${C.reset}`);
|
|
445
|
+
console.log(`${C.dim} bash ./skill/install.sh${C.reset}`);
|
|
446
|
+
console.log();
|
|
447
|
+
} catch (err: any) {
|
|
448
|
+
console.error(`${C.red}Error: ${err.message}${C.reset}`);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Grid } from "../grid";
|
|
2
|
+
import { ANSI_COLORS, ANSI_RESET, type AnsiColor } from "../types";
|
|
3
|
+
|
|
4
|
+
export function encodeAnsi(grid: Grid): string {
|
|
5
|
+
const lines: string[] = [];
|
|
6
|
+
|
|
7
|
+
for (let y = 0; y < grid.height; y++) {
|
|
8
|
+
let line = "";
|
|
9
|
+
let currentFg: string | null = null;
|
|
10
|
+
|
|
11
|
+
for (let x = 0; x < grid.width; x++) {
|
|
12
|
+
const cell = grid.get(x, y);
|
|
13
|
+
if (!cell) continue;
|
|
14
|
+
|
|
15
|
+
if (cell.fg !== currentFg) {
|
|
16
|
+
if (currentFg !== null) line += ANSI_RESET;
|
|
17
|
+
if (cell.fg !== null) line += ANSI_COLORS[cell.fg as AnsiColor] ?? "";
|
|
18
|
+
currentFg = cell.fg;
|
|
19
|
+
}
|
|
20
|
+
line += cell.char;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (currentFg !== null) line += ANSI_RESET;
|
|
24
|
+
lines.push(line.trimEnd());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
28
|
+
lines.pop();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Grid } from "../grid";
|
|
2
|
+
|
|
3
|
+
const COLOR_TO_CSS: Record<string, string> = {
|
|
4
|
+
red: "#ef4444",
|
|
5
|
+
green: "#22c55e",
|
|
6
|
+
yellow: "#eab308",
|
|
7
|
+
blue: "#3b82f6",
|
|
8
|
+
magenta: "#d946ef",
|
|
9
|
+
cyan: "#06b6d4",
|
|
10
|
+
white: "#e8e0d4",
|
|
11
|
+
gray: "#6b7c72",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface HtmlEncodeOptions {
|
|
15
|
+
wrap?: boolean;
|
|
16
|
+
background?: string;
|
|
17
|
+
foreground?: string;
|
|
18
|
+
fontFamily?: string;
|
|
19
|
+
fontSize?: string;
|
|
20
|
+
padding?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function encodeHtml(grid: Grid, opts: HtmlEncodeOptions = {}): string {
|
|
24
|
+
const wrap = opts.wrap ?? true;
|
|
25
|
+
const lines: string[] = [];
|
|
26
|
+
|
|
27
|
+
for (let y = 0; y < grid.height; y++) {
|
|
28
|
+
let line = "";
|
|
29
|
+
let currentFg: string | null = null;
|
|
30
|
+
|
|
31
|
+
for (let x = 0; x < grid.width; x++) {
|
|
32
|
+
const cell = grid.get(x, y);
|
|
33
|
+
if (!cell) continue;
|
|
34
|
+
|
|
35
|
+
if (cell.fg !== currentFg) {
|
|
36
|
+
if (currentFg !== null) line += "</span>";
|
|
37
|
+
if (cell.fg !== null) {
|
|
38
|
+
const css = COLOR_TO_CSS[cell.fg] ?? cell.fg;
|
|
39
|
+
line += `<span style="color:${css}">`;
|
|
40
|
+
}
|
|
41
|
+
currentFg = cell.fg;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const ch = cell.char;
|
|
45
|
+
if (ch === "<") line += "<";
|
|
46
|
+
else if (ch === ">") line += ">";
|
|
47
|
+
else if (ch === "&") line += "&";
|
|
48
|
+
else line += ch;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (currentFg !== null) line += "</span>";
|
|
52
|
+
lines.push(line);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const body = lines.join("\n");
|
|
56
|
+
if (!wrap) return body;
|
|
57
|
+
|
|
58
|
+
const bg = opts.background ?? "#0a0a0a";
|
|
59
|
+
const fg = opts.foreground ?? "#e8e0d4";
|
|
60
|
+
const font = opts.fontFamily ?? '"JetBrains Mono", "Fira Code", "SF Mono", Menlo, Monaco, Consolas, monospace';
|
|
61
|
+
const size = opts.fontSize ?? "14px";
|
|
62
|
+
const pad = opts.padding ?? "24px";
|
|
63
|
+
|
|
64
|
+
const style = [
|
|
65
|
+
`background: ${bg}`,
|
|
66
|
+
`color: ${fg}`,
|
|
67
|
+
`font-family: ${font}`,
|
|
68
|
+
`font-size: ${size}`,
|
|
69
|
+
`line-height: 1.3`,
|
|
70
|
+
`padding: ${pad}`,
|
|
71
|
+
`margin: 0`,
|
|
72
|
+
`white-space: pre`,
|
|
73
|
+
`overflow-x: auto`,
|
|
74
|
+
`border-radius: 8px`,
|
|
75
|
+
].join("; ");
|
|
76
|
+
|
|
77
|
+
return `<pre style="${style}">${body}</pre>`;
|
|
78
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Grid } from "../grid";
|
|
2
|
+
|
|
3
|
+
export function encodeString(grid: Grid): string {
|
|
4
|
+
const lines: string[] = [];
|
|
5
|
+
|
|
6
|
+
for (let y = 0; y < grid.height; y++) {
|
|
7
|
+
let line = "";
|
|
8
|
+
for (let x = 0; x < grid.width; x++) {
|
|
9
|
+
const cell = grid.get(x, y);
|
|
10
|
+
line += cell?.char ?? " ";
|
|
11
|
+
}
|
|
12
|
+
lines.push(line.trimEnd());
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
16
|
+
lines.pop();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return lines.join("\n");
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { encodeAnsi } from "../encoders/ansi";
|
|
2
|
+
import { encodeString } from "../encoders/string";
|
|
3
|
+
import { encodeHtml } from "../encoders/html";
|
|
4
|
+
import { renderFlow } from "./render";
|
|
5
|
+
import type { Workflow, FlowRenderOptions } from "./types";
|
|
6
|
+
|
|
7
|
+
export function flowToAnsi(workflow: Workflow, opts?: FlowRenderOptions): string {
|
|
8
|
+
return encodeAnsi(renderFlow(workflow, opts));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function flowToString(workflow: Workflow, opts?: FlowRenderOptions): string {
|
|
12
|
+
return encodeString(renderFlow(workflow, opts));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function flowToHtml(workflow: Workflow, opts?: FlowRenderOptions): string {
|
|
16
|
+
return encodeHtml(renderFlow(workflow, opts));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function printFlow(workflow: Workflow, opts?: FlowRenderOptions): void {
|
|
20
|
+
console.log(flowToAnsi(workflow, opts));
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { parseWorkflow } from "./parse";
|
|
2
|
+
export { parseYaml } from "./yaml";
|
|
3
|
+
export { validateWorkflow } from "./validate";
|
|
4
|
+
export { computeLayout } from "./layout";
|
|
5
|
+
export { renderFlow } from "./render";
|
|
6
|
+
export { flowToAnsi, flowToString, flowToHtml, printFlow } from "./encode";
|
|
7
|
+
export type {
|
|
8
|
+
Workflow,
|
|
9
|
+
WorkflowNode,
|
|
10
|
+
WorkflowEdge,
|
|
11
|
+
FlowRenderOptions,
|
|
12
|
+
ValidationError,
|
|
13
|
+
ValidationResult,
|
|
14
|
+
} from "./types";
|
|
15
|
+
export type { Layout, LayoutNode, LayoutEdge, LayoutOptions } from "./layout";
|