@crafter/cli-tree 0.1.2 → 0.2.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/dist/{chunk-dnq2rnr7.js → chunk-9pnqbn7b.js} +224 -3
- package/dist/{chunk-dnq2rnr7.js.map → chunk-9pnqbn7b.js.map} +7 -4
- package/dist/cli.js +351 -20
- package/dist/cli.js.map +3 -3
- package/dist/miner/activity.d.ts +21 -0
- package/dist/miner/cross-cli.d.ts +55 -0
- package/dist/miner/index.d.ts +6 -2
- package/dist/miner/index.js +14 -2
- package/dist/miner/index.js.map +1 -1
- package/dist/miner/sparkline.d.ts +14 -0
- package/package.json +1 -1
- package/skill/SKILL.md +69 -8
- package/src/cli.ts +401 -21
- package/src/miner/activity.ts +71 -0
- package/src/miner/cross-cli.ts +216 -0
- package/src/miner/index.ts +28 -1
- package/src/miner/sparkline.ts +31 -0
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,13 @@ import {
|
|
|
5
5
|
treeToString
|
|
6
6
|
} from "./chunk-v5w3w6bd.js";
|
|
7
7
|
import {
|
|
8
|
+
crossCliToFlowWorkflow,
|
|
9
|
+
formatTimeRange,
|
|
8
10
|
mineCli,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
mineCrossCli,
|
|
12
|
+
minedToFlowWorkflow,
|
|
13
|
+
sparkline
|
|
14
|
+
} from "./chunk-9pnqbn7b.js";
|
|
11
15
|
import {
|
|
12
16
|
NullDelegate,
|
|
13
17
|
runArchaeology
|
|
@@ -28,7 +32,7 @@ import"./chunk-q4se2rwe.js";
|
|
|
28
32
|
// src/cli.ts
|
|
29
33
|
var args = process.argv.slice(2);
|
|
30
34
|
var subcommand = args[0];
|
|
31
|
-
var HELP_SUBCOMMANDS = new Set(["flow", "mine", "archaeology"]);
|
|
35
|
+
var HELP_SUBCOMMANDS = new Set(["flow", "mine", "archaeology", "safe-help", "cross"]);
|
|
32
36
|
var isHelpFlag = args.includes("--help") || args.includes("-h");
|
|
33
37
|
if (!subcommand || isHelpFlag && !HELP_SUBCOMMANDS.has(subcommand ?? "")) {
|
|
34
38
|
printMainHelp();
|
|
@@ -41,6 +45,10 @@ if (isHelpFlag) {
|
|
|
41
45
|
printMineHelp();
|
|
42
46
|
else if (subcommand === "archaeology")
|
|
43
47
|
printArchaeologyHelp();
|
|
48
|
+
else if (subcommand === "safe-help")
|
|
49
|
+
printSafeHelpHelp();
|
|
50
|
+
else if (subcommand === "cross")
|
|
51
|
+
printCrossHelp();
|
|
44
52
|
else
|
|
45
53
|
printMainHelp();
|
|
46
54
|
process.exit(0);
|
|
@@ -51,6 +59,10 @@ if (subcommand === "flow") {
|
|
|
51
59
|
await runMine(args.slice(1));
|
|
52
60
|
} else if (subcommand === "archaeology") {
|
|
53
61
|
await runArchaeologyCmd(args.slice(1));
|
|
62
|
+
} else if (subcommand === "safe-help") {
|
|
63
|
+
await runSafeHelp(args.slice(1));
|
|
64
|
+
} else if (subcommand === "cross") {
|
|
65
|
+
await runCross(args.slice(1));
|
|
54
66
|
} else {
|
|
55
67
|
await runTree(args);
|
|
56
68
|
}
|
|
@@ -62,22 +74,98 @@ function printMainHelp() {
|
|
|
62
74
|
clitree <binary> [options] Render command tree from --help
|
|
63
75
|
clitree flow <file> [options] Render a workflow YAML as a DAG
|
|
64
76
|
clitree mine <binary> [options] Mine shell history for workflows
|
|
77
|
+
clitree cross [options] Detect cross-CLI workflows (git + gh, docker + kubectl)
|
|
65
78
|
clitree archaeology <binary> [options] Full analysis: tree + mining + (LLM)
|
|
79
|
+
clitree safe-help <binary> [sub...] Fetch clean help for any CLI (no pager, no overstriking)
|
|
66
80
|
|
|
67
81
|
Examples:
|
|
68
82
|
clitree docker # basic tree
|
|
69
83
|
clitree mine git # "what workflows do I repeat with git?"
|
|
84
|
+
clitree cross # cross-CLI flows across your history
|
|
70
85
|
clitree archaeology bun # tree + mining + LLM archaeology
|
|
86
|
+
clitree safe-help git commit # avoid the git man-page pager trap
|
|
71
87
|
|
|
72
88
|
Subcommands:
|
|
73
89
|
tree Parse --help output (default; passing a binary name invokes this)
|
|
74
90
|
flow Render a workflow YAML file as an ASCII DAG
|
|
75
|
-
mine Discover workflows from your shell history
|
|
91
|
+
mine Discover workflows from your shell history (single CLI)
|
|
92
|
+
cross Discover workflows that span multiple CLIs (e.g. git → gh)
|
|
76
93
|
archaeology Run full analysis (tree + mine + LLM proposals when available)
|
|
94
|
+
safe-help Return inline help for any CLI, handling pager/overstrike edge cases
|
|
77
95
|
|
|
78
96
|
Help for a subcommand: clitree <subcommand> --help
|
|
79
97
|
`);
|
|
80
98
|
}
|
|
99
|
+
function printCrossHelp() {
|
|
100
|
+
console.log(`
|
|
101
|
+
clitree cross — Mine cross-CLI workflows from your shell history
|
|
102
|
+
|
|
103
|
+
Usage:
|
|
104
|
+
clitree cross [options]
|
|
105
|
+
|
|
106
|
+
This is mineCli's bigger sibling. Instead of filtering history to a single
|
|
107
|
+
binary, it looks at every command in a session and finds sequences that weave
|
|
108
|
+
between tools — e.g.:
|
|
109
|
+
|
|
110
|
+
git push → gh pr create
|
|
111
|
+
docker build → docker push → kubectl apply
|
|
112
|
+
bun test → git commit → git push
|
|
113
|
+
|
|
114
|
+
These patterns are invisible to 'clitree mine <cli>' because they cross boundaries.
|
|
115
|
+
|
|
116
|
+
Examples:
|
|
117
|
+
clitree cross # top 10 cross-CLI workflows
|
|
118
|
+
clitree cross --top-k 5 # just the top 5
|
|
119
|
+
clitree cross --only git,gh,docker # restrict to a set of CLIs
|
|
120
|
+
clitree cross --format json # raw data
|
|
121
|
+
|
|
122
|
+
Options:
|
|
123
|
+
--top-k <n> Max workflows to return (default: 10)
|
|
124
|
+
--min-support <n> Minimum occurrences (default: 3)
|
|
125
|
+
--min-path-length <n> Minimum chain length (default: 2)
|
|
126
|
+
--max-path-length <n> Maximum chain length (default: 5)
|
|
127
|
+
--min-distinct-clis <n> Require at least this many distinct CLIs (default: 2)
|
|
128
|
+
--only <csv> Whitelist: only include these CLIs (comma-separated)
|
|
129
|
+
--format <fmt> ansi (default) or json
|
|
130
|
+
--no-color Disable ANSI colors
|
|
131
|
+
--no-flow Skip DAG rendering of the top workflow
|
|
132
|
+
-h, --help Show this help
|
|
133
|
+
`);
|
|
134
|
+
}
|
|
135
|
+
function printSafeHelpHelp() {
|
|
136
|
+
console.log(`
|
|
137
|
+
clitree safe-help — Fetch clean help output for any CLI
|
|
138
|
+
|
|
139
|
+
Usage:
|
|
140
|
+
clitree safe-help <binary> [subcommand...] [options]
|
|
141
|
+
|
|
142
|
+
Why this exists:
|
|
143
|
+
Running '<cli> --help' directly is a minefield on agents and scripts:
|
|
144
|
+
- 'git commit --help' opens a pager in a TTY and hangs
|
|
145
|
+
- Piping 'git <sub> --help' returns nroff with overstrike (ffiixxuupp)
|
|
146
|
+
- Some CLIs use 'help <sub>' instead of '<sub> --help'
|
|
147
|
+
- macOS man pages emit \\b sequences that need col -bx
|
|
148
|
+
|
|
149
|
+
safe-help tries the right variants in order, cleans overstrikes, and
|
|
150
|
+
returns plain text every time.
|
|
151
|
+
|
|
152
|
+
Strategy (in order):
|
|
153
|
+
1. <cli> <sub> -h — short inline help
|
|
154
|
+
2. <cli> <sub> --help — long help (captured, overstrike-stripped)
|
|
155
|
+
3. <cli> help <sub> — alternate syntax
|
|
156
|
+
4. MANPAGER=cat man <cli>-<sub> | col -bx — final man fallback
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
clitree safe-help git commit
|
|
160
|
+
clitree safe-help docker run
|
|
161
|
+
clitree safe-help kubectl get pods
|
|
162
|
+
clitree safe-help bun install
|
|
163
|
+
|
|
164
|
+
Options:
|
|
165
|
+
--no-color Disable ANSI codes (auto-detects non-TTY)
|
|
166
|
+
-h, --help Show this help
|
|
167
|
+
`);
|
|
168
|
+
}
|
|
81
169
|
function printMineHelp() {
|
|
82
170
|
console.log(`
|
|
83
171
|
clitree mine — Mine your shell history for CLI workflows
|
|
@@ -87,22 +175,26 @@ function printMineHelp() {
|
|
|
87
175
|
|
|
88
176
|
Examples:
|
|
89
177
|
clitree mine git
|
|
178
|
+
clitree mine git --top-k 3 # render top 3 workflows as DAGs
|
|
90
179
|
clitree mine docker --min-support 5 --with-flow
|
|
91
180
|
clitree mine bun --format json > bun-flows.json
|
|
92
181
|
clitree mine git --no-color | tee report.txt
|
|
182
|
+
clitree mine git --no-activity # skip temporal sparklines
|
|
93
183
|
|
|
94
184
|
Options:
|
|
95
185
|
--min-support <n> Minimum occurrences to count as a workflow (default: 3)
|
|
96
186
|
--history-path <p> Custom shell history path (default: ~/.zsh_history)
|
|
97
187
|
--format <fmt> Output format: ansi (default), json
|
|
98
|
-
--max-paths <n> Max workflows to show (default: 10)
|
|
99
|
-
--
|
|
100
|
-
--
|
|
188
|
+
--max-paths <n> Max workflows to show in text list (default: 10)
|
|
189
|
+
--top-k <n> Render top N workflows as DAGs (default: 1)
|
|
190
|
+
--with-flow Include 2-step workflows in DAG rendering
|
|
191
|
+
--no-flow Skip DAG rendering entirely
|
|
192
|
+
--no-activity Skip hour/day/30-day activity sparklines
|
|
101
193
|
--no-color Disable ANSI colors (also auto-disabled when stdout is not a TTY)
|
|
102
194
|
-h, --help Show this help
|
|
103
195
|
|
|
104
|
-
By default, the top workflow is rendered as a DAG
|
|
105
|
-
|
|
196
|
+
By default, the top 3+ step workflow is rendered as a DAG.
|
|
197
|
+
--top-k bumps that to N distinct workflows stacked on top of each other.
|
|
106
198
|
`);
|
|
107
199
|
}
|
|
108
200
|
function printArchaeologyHelp() {
|
|
@@ -273,6 +365,117 @@ async function readStdin() {
|
|
|
273
365
|
}
|
|
274
366
|
return Buffer.concat(chunks).toString("utf-8");
|
|
275
367
|
}
|
|
368
|
+
function stripOverstrike(text) {
|
|
369
|
+
return text.replace(/(.)\b\1/g, "$1").replace(/_\b(.)/g, "$1").replace(/\b/g, "").replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
|
|
370
|
+
}
|
|
371
|
+
function looksLikeHelp(text) {
|
|
372
|
+
if (!text.trim())
|
|
373
|
+
return false;
|
|
374
|
+
if (text.length < 40)
|
|
375
|
+
return false;
|
|
376
|
+
const firstChunk = text.slice(0, 200).toLowerCase();
|
|
377
|
+
if (/(unknown|invalid|no such|not a \w+ command|flag needs an argument|unrecognized)/.test(firstChunk)) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
if (/run '.+' for more information/.test(firstChunk))
|
|
381
|
+
return false;
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
async function tryHelpVariant(binary, argv, timeoutMs = 5000) {
|
|
385
|
+
try {
|
|
386
|
+
const proc = Bun.spawn([binary, ...argv], {
|
|
387
|
+
stdout: "pipe",
|
|
388
|
+
stderr: "pipe",
|
|
389
|
+
env: { ...process.env, PAGER: "cat", MANPAGER: "cat", GIT_PAGER: "cat" }
|
|
390
|
+
});
|
|
391
|
+
const timer = setTimeout(() => proc.kill(), timeoutMs);
|
|
392
|
+
const [stdout, stderr] = await Promise.all([
|
|
393
|
+
new Response(proc.stdout).text(),
|
|
394
|
+
new Response(proc.stderr).text()
|
|
395
|
+
]);
|
|
396
|
+
clearTimeout(timer);
|
|
397
|
+
await proc.exited;
|
|
398
|
+
const combined = stdout || stderr;
|
|
399
|
+
if (!looksLikeHelp(combined))
|
|
400
|
+
return null;
|
|
401
|
+
return stripOverstrike(combined);
|
|
402
|
+
} catch {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async function tryManPage(binary, subPath, timeoutMs = 5000) {
|
|
407
|
+
const candidates = [
|
|
408
|
+
subPath.length > 0 ? [`${binary}-${subPath.join("-")}`] : [binary],
|
|
409
|
+
[binary, ...subPath]
|
|
410
|
+
];
|
|
411
|
+
for (const args2 of candidates) {
|
|
412
|
+
try {
|
|
413
|
+
const proc = Bun.spawn(["man", ...args2], {
|
|
414
|
+
stdout: "pipe",
|
|
415
|
+
stderr: "pipe",
|
|
416
|
+
env: { ...process.env, MANPAGER: "cat", PAGER: "cat" }
|
|
417
|
+
});
|
|
418
|
+
const timer = setTimeout(() => proc.kill(), timeoutMs);
|
|
419
|
+
const [stdout] = await Promise.all([
|
|
420
|
+
new Response(proc.stdout).text(),
|
|
421
|
+
new Response(proc.stderr).text()
|
|
422
|
+
]);
|
|
423
|
+
clearTimeout(timer);
|
|
424
|
+
await proc.exited;
|
|
425
|
+
if (looksLikeHelp(stdout))
|
|
426
|
+
return stripOverstrike(stdout);
|
|
427
|
+
} catch {}
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
async function runSafeHelp(args2) {
|
|
432
|
+
let binary = null;
|
|
433
|
+
const subPath = [];
|
|
434
|
+
for (const arg of args2) {
|
|
435
|
+
if (arg === "--no-color" || arg === "--help" || arg === "-h")
|
|
436
|
+
continue;
|
|
437
|
+
if (!binary) {
|
|
438
|
+
binary = arg;
|
|
439
|
+
} else {
|
|
440
|
+
subPath.push(arg);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (!binary) {
|
|
444
|
+
console.error("Error: provide a binary name");
|
|
445
|
+
console.error("Run 'clitree safe-help --help' for usage");
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
const variants = [
|
|
449
|
+
{ label: "short help (-h)", args: [...subPath, "-h"] },
|
|
450
|
+
{ label: "long help (--help)", args: [...subPath, "--help"] }
|
|
451
|
+
];
|
|
452
|
+
if (subPath.length > 0) {
|
|
453
|
+
variants.push({ label: "help subcommand", args: ["help", ...subPath] });
|
|
454
|
+
}
|
|
455
|
+
for (const variant of variants) {
|
|
456
|
+
const output = await tryHelpVariant(binary, variant.args);
|
|
457
|
+
if (output) {
|
|
458
|
+
process.stdout.write(output);
|
|
459
|
+
if (!output.endsWith(`
|
|
460
|
+
`))
|
|
461
|
+
process.stdout.write(`
|
|
462
|
+
`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const manOutput = await tryManPage(binary, subPath);
|
|
467
|
+
if (manOutput) {
|
|
468
|
+
process.stdout.write(manOutput);
|
|
469
|
+
if (!manOutput.endsWith(`
|
|
470
|
+
`))
|
|
471
|
+
process.stdout.write(`
|
|
472
|
+
`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
console.error(`Error: could not fetch help for ${binary} ${subPath.join(" ")}`);
|
|
476
|
+
console.error("Tried: -h, --help, help subcommand, and man page.");
|
|
477
|
+
process.exit(1);
|
|
478
|
+
}
|
|
276
479
|
function shouldUseColor(args2) {
|
|
277
480
|
if (args2.includes("--no-color"))
|
|
278
481
|
return false;
|
|
@@ -305,6 +508,8 @@ async function runMine(args2) {
|
|
|
305
508
|
let format = "ansi";
|
|
306
509
|
let maxPaths = 10;
|
|
307
510
|
let withFlow = "auto";
|
|
511
|
+
let topK = 1;
|
|
512
|
+
let showActivity = true;
|
|
308
513
|
for (let i = 0;i < args2.length; i++) {
|
|
309
514
|
const arg = args2[i];
|
|
310
515
|
if (arg === "--min-support" && args2[i + 1]) {
|
|
@@ -315,10 +520,16 @@ async function runMine(args2) {
|
|
|
315
520
|
format = args2[++i];
|
|
316
521
|
} else if (arg === "--max-paths" && args2[i + 1]) {
|
|
317
522
|
maxPaths = Number.parseInt(args2[++i], 10);
|
|
523
|
+
} else if (arg === "--top-k" && args2[i + 1]) {
|
|
524
|
+
topK = Number.parseInt(args2[++i], 10);
|
|
525
|
+
if (withFlow === "auto")
|
|
526
|
+
withFlow = "on";
|
|
318
527
|
} else if (arg === "--with-flow" || arg === "--flow") {
|
|
319
528
|
withFlow = "on";
|
|
320
529
|
} else if (arg === "--no-flow") {
|
|
321
530
|
withFlow = "off";
|
|
531
|
+
} else if (arg === "--no-activity") {
|
|
532
|
+
showActivity = false;
|
|
322
533
|
} else if (arg !== "--no-color" && !arg.startsWith("-")) {
|
|
323
534
|
binary = arg;
|
|
324
535
|
}
|
|
@@ -352,6 +563,9 @@ ${C.bold}${C.magenta}${binary}${C.reset} ${C.gray}— shell history analysis${C.
|
|
|
352
563
|
}
|
|
353
564
|
console.log();
|
|
354
565
|
}
|
|
566
|
+
if (showActivity && result.activity.total > 0) {
|
|
567
|
+
renderActivitySection(result.activity, C);
|
|
568
|
+
}
|
|
355
569
|
if (result.workflows.length > 0) {
|
|
356
570
|
console.log(`${C.bold}Discovered workflows:${C.reset}`);
|
|
357
571
|
for (const wf of result.workflows.slice(0, maxPaths)) {
|
|
@@ -373,12 +587,18 @@ ${C.bold}${C.magenta}${binary}${C.reset} ${C.gray}— shell history analysis${C.
|
|
|
373
587
|
}
|
|
374
588
|
if (withFlow !== "off") {
|
|
375
589
|
const minSteps = withFlow === "on" ? 2 : 3;
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
console.log(`${C.bold}
|
|
381
|
-
|
|
590
|
+
const topK_effective = Math.max(1, topK);
|
|
591
|
+
const candidates = pickWorkflowsForFlow(result.workflows, minSteps, topK_effective);
|
|
592
|
+
if (candidates.length > 0) {
|
|
593
|
+
const header = candidates.length === 1 ? "Top workflow (visualized):" : `Top ${candidates.length} workflows (visualized):`;
|
|
594
|
+
console.log(`${C.bold}${header}${C.reset}`);
|
|
595
|
+
for (let i = 0;i < candidates.length; i++) {
|
|
596
|
+
if (i > 0)
|
|
597
|
+
console.log(`${C.gray}${"─".repeat(60)}${C.reset}`);
|
|
598
|
+
const flowWorkflow = minedToFlowWorkflow(candidates[i]);
|
|
599
|
+
const rendered = shouldUseColor(args2) ? flowToAnsi(flowWorkflow) : flowToString(flowWorkflow);
|
|
600
|
+
console.log(rendered);
|
|
601
|
+
}
|
|
382
602
|
console.log();
|
|
383
603
|
}
|
|
384
604
|
}
|
|
@@ -387,13 +607,124 @@ ${C.bold}${C.magenta}${binary}${C.reset} ${C.gray}— shell history analysis${C.
|
|
|
387
607
|
process.exit(1);
|
|
388
608
|
}
|
|
389
609
|
}
|
|
390
|
-
function
|
|
610
|
+
function renderActivitySection(activity, C) {
|
|
611
|
+
if (activity.firstSeen === 0)
|
|
612
|
+
return;
|
|
613
|
+
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
614
|
+
console.log(`${C.bold}Activity:${C.reset}`);
|
|
615
|
+
const range = formatTimeRange(activity.firstSeen, activity.lastSeen);
|
|
616
|
+
console.log(` ${C.dim}Tracked over:${C.reset} ${C.cyan}${range}${C.reset}`);
|
|
617
|
+
const hourSpark = sparkline(activity.hourOfDay);
|
|
618
|
+
console.log(` ${C.dim}Hour of day: ${C.reset}${C.cyan}${hourSpark}${C.reset} ${C.dim}(0h → 23h)${C.reset}`);
|
|
619
|
+
const dowMax = Math.max(...activity.dayOfWeek);
|
|
620
|
+
if (dowMax > 0) {
|
|
621
|
+
console.log(` ${C.dim}Day of week:${C.reset}`);
|
|
622
|
+
for (let i = 0;i < 7; i++) {
|
|
623
|
+
const count = activity.dayOfWeek[i];
|
|
624
|
+
const barLen = dowMax > 0 ? Math.round(count / dowMax * 20) : 0;
|
|
625
|
+
const bar = "█".repeat(Math.max(count > 0 ? 1 : 0, barLen));
|
|
626
|
+
console.log(` ${C.gray}${dayNames[i]}${C.reset} ${C.cyan}${bar}${C.reset} ${C.dim}${count}${C.reset}`);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (activity.last30Days.some((v) => v > 0)) {
|
|
630
|
+
const monthSpark = sparkline(activity.last30Days);
|
|
631
|
+
console.log(` ${C.dim}Last 30 days:${C.reset} ${C.cyan}${monthSpark}${C.reset} ${C.dim}(30d ago → today)${C.reset}`);
|
|
632
|
+
}
|
|
633
|
+
console.log();
|
|
634
|
+
}
|
|
635
|
+
function pickWorkflowsForFlow(workflows, minSteps, topK) {
|
|
636
|
+
const result = [];
|
|
637
|
+
const signatures = new Set;
|
|
391
638
|
for (const wf of workflows) {
|
|
392
639
|
const len = wf.path[0]?.length ?? 0;
|
|
393
|
-
if (len
|
|
394
|
-
|
|
640
|
+
if (len < minSteps)
|
|
641
|
+
continue;
|
|
642
|
+
const sig = wf.path[0].join(" → ");
|
|
643
|
+
if (signatures.has(sig))
|
|
644
|
+
continue;
|
|
645
|
+
signatures.add(sig);
|
|
646
|
+
result.push(wf);
|
|
647
|
+
if (result.length >= topK)
|
|
648
|
+
break;
|
|
649
|
+
}
|
|
650
|
+
return result;
|
|
651
|
+
}
|
|
652
|
+
async function runCross(args2) {
|
|
653
|
+
let format = "ansi";
|
|
654
|
+
let topK = 10;
|
|
655
|
+
let minSupport = 3;
|
|
656
|
+
let minPathLength = 2;
|
|
657
|
+
let maxPathLength = 5;
|
|
658
|
+
let minDistinctCLIs = 2;
|
|
659
|
+
let onlyList = [];
|
|
660
|
+
let withFlow = true;
|
|
661
|
+
for (let i = 0;i < args2.length; i++) {
|
|
662
|
+
const arg = args2[i];
|
|
663
|
+
if (arg === "--top-k" && args2[i + 1]) {
|
|
664
|
+
topK = Number.parseInt(args2[++i], 10);
|
|
665
|
+
} else if (arg === "--min-support" && args2[i + 1]) {
|
|
666
|
+
minSupport = Number.parseInt(args2[++i], 10);
|
|
667
|
+
} else if (arg === "--min-path-length" && args2[i + 1]) {
|
|
668
|
+
minPathLength = Number.parseInt(args2[++i], 10);
|
|
669
|
+
} else if (arg === "--max-path-length" && args2[i + 1]) {
|
|
670
|
+
maxPathLength = Number.parseInt(args2[++i], 10);
|
|
671
|
+
} else if (arg === "--min-distinct-clis" && args2[i + 1]) {
|
|
672
|
+
minDistinctCLIs = Number.parseInt(args2[++i], 10);
|
|
673
|
+
} else if (arg === "--only" && args2[i + 1]) {
|
|
674
|
+
onlyList = args2[++i].split(",").map((s) => s.trim()).filter(Boolean);
|
|
675
|
+
} else if (arg === "--format" && args2[i + 1]) {
|
|
676
|
+
format = args2[++i];
|
|
677
|
+
} else if (arg === "--no-flow") {
|
|
678
|
+
withFlow = false;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
const result = await mineCrossCli({
|
|
683
|
+
topK,
|
|
684
|
+
minSupport,
|
|
685
|
+
minPathLength,
|
|
686
|
+
maxPathLength,
|
|
687
|
+
minDistinctCLIs,
|
|
688
|
+
allowedCLIs: onlyList.length > 0 ? onlyList : undefined
|
|
689
|
+
});
|
|
690
|
+
if (format === "json") {
|
|
691
|
+
console.log(JSON.stringify(result, null, 2));
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const C = makeColors(shouldUseColor(args2));
|
|
695
|
+
console.log(`
|
|
696
|
+
${C.bold}${C.magenta}cross-CLI workflow analysis${C.reset}
|
|
697
|
+
`);
|
|
698
|
+
console.log(`${C.bold}Scope:${C.reset}`);
|
|
699
|
+
console.log(` ${C.dim}Sessions analyzed:${C.reset} ${C.cyan}${result.sessionsAnalyzed}${C.reset}`);
|
|
700
|
+
console.log(` ${C.dim}Distinct CLIs:${C.reset} ${C.cyan}${result.distinctCLIs.length}${C.reset}`);
|
|
701
|
+
console.log(` ${C.dim}Transitions:${C.reset} ${C.cyan}${result.totalTransitions}${C.reset}
|
|
702
|
+
`);
|
|
703
|
+
if (result.workflows.length === 0) {
|
|
704
|
+
console.log(`${C.dim}No cross-CLI workflows found. Try lowering --min-support or --min-distinct-clis.${C.reset}
|
|
705
|
+
`);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
console.log(`${C.bold}Cross-CLI workflows (top ${result.workflows.length}):${C.reset}`);
|
|
709
|
+
for (let i = 0;i < result.workflows.length; i++) {
|
|
710
|
+
const wf = result.workflows[i];
|
|
711
|
+
const chain = wf.path.map((s) => `${C.green}${s.cli}${C.reset} ${C.cyan}${s.subcommand}${C.reset}`).join(` ${C.gray}→${C.reset} `);
|
|
712
|
+
console.log(` ${C.dim}${(i + 1).toString().padStart(2)}.${C.reset} ${chain}`);
|
|
713
|
+
console.log(` ${C.dim}seen ${wf.support}×, ${wf.uniqueCLIs} distinct CLIs${C.reset}`);
|
|
714
|
+
}
|
|
715
|
+
console.log();
|
|
716
|
+
if (withFlow && result.workflows.length > 0) {
|
|
717
|
+
const top = result.workflows[0];
|
|
718
|
+
const flowWorkflow = crossCliToFlowWorkflow(top);
|
|
719
|
+
const rendered = shouldUseColor(args2) ? flowToAnsi(flowWorkflow) : flowToString(flowWorkflow);
|
|
720
|
+
console.log(`${C.bold}Top cross-CLI workflow (visualized):${C.reset}`);
|
|
721
|
+
console.log(rendered);
|
|
722
|
+
console.log();
|
|
723
|
+
}
|
|
724
|
+
} catch (err) {
|
|
725
|
+
console.error(`Error: ${err.message}`);
|
|
726
|
+
process.exit(1);
|
|
395
727
|
}
|
|
396
|
-
return null;
|
|
397
728
|
}
|
|
398
729
|
async function runArchaeologyCmd(args2) {
|
|
399
730
|
let binary = null;
|
|
@@ -470,4 +801,4 @@ ${C.bold}${C.yellow}\uD83D\uDCA1 Skill suggestions:${C.reset}`);
|
|
|
470
801
|
}
|
|
471
802
|
}
|
|
472
803
|
|
|
473
|
-
//# debugId=
|
|
804
|
+
//# debugId=36C7EC67BF0C8B9764756E2164756E21
|