-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathoptions.ts
More file actions
151 lines (143 loc) · 5.44 KB
/
Copy pathoptions.ts
File metadata and controls
151 lines (143 loc) · 5.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/**
* Commander wiring + parsed CLI shape — extracted from `cli.ts` so the main
* orchestrator stays small and so `cli/pipeline.ts` / `cli/modes.ts` can share
* the typed `CliOptions` without circular imports.
*/
import { Command } from 'commander';
// Injected by esbuild from package.json at build time. `tsx` source-mode
// runs (npm run cli) read package.json directly; everything else gets the
// build-time-baked value.
declare const __PKG_VERSION__: string | undefined;
const PKG_VERSION = (() => {
try {
return __PKG_VERSION__;
} catch {
return undefined;
}
})() ?? '0.0.0-source';
export interface CliOptions {
latest?: boolean;
pick?: boolean;
llm?: boolean; // commander converts --no-llm to { llm: false }
dumpJson?: string;
verbose?: boolean;
trace?: boolean;
dryRun?: boolean;
model?: string;
maxLlmTokens?: string;
redactStrict?: boolean;
redactDryrun?: boolean;
includeSidechains?: boolean;
flattenSidechains?: boolean;
dropSidechains?: boolean;
// Output modes (terminal-only)
list?: boolean;
snapshot?: string;
mode?: 'continue' | 'fork';
tui?: boolean;
filter?: string;
group?: boolean; // --no-group disables consecutive-file collapsing
color?: boolean; // --no-color disables ANSI even on TTY
phasesOnly?: boolean; // --phases-only collapses sub-actions
picks?: boolean; // --picks lists every pick across every session
unstar?: string; // --unstar <node-id-or-number> removes the ⭐
diff?: string[]; // --diff <a> <b> compares two nodes
}
export type ParsedArgs =
| { ok: true; opts: CliOptions; sessionArg: string | undefined }
| { ok: false; exitCode: number };
export function parseCliArgs(argv: string[]): ParsedArgs {
const program = new Command();
program
.name('agent-tree')
.description(
'Navigate a Claude Code session as a numbered file-tree in your terminal and resume from any node.',
)
.version(PKG_VERSION, '-V, --version', 'print agent-tree version')
.argument('[session-id]', 'session UUID or short prefix (e.g. 69c2f35e)')
.option('--latest', 'use the most recently modified session')
.option('--pick', 'interactive picker over recent sessions')
.option('--no-llm', 'skip LLM labeling and run heuristic-only')
.option('--dump-json <dir>', 'dump intermediate artifacts (raw events / graph / segments / tree) as JSON')
.option('-v, --verbose', 'debug logging')
.option('--trace', 'trace logging (implies --verbose)')
.option('--dry-run', 'run the analysis pipeline but do not emit any output')
.option(
'--model <name>',
'Anthropic model for LLM labeling',
'claude-sonnet-4-6',
)
.option(
'--max-llm-tokens <n>',
'input token budget ceiling across segments',
'50000',
)
.option('--redact-strict', 'add PII patterns (email/phone/SSN/RRN); card check is always on')
.option('--redact-dryrun', 'print redaction hit counts to stderr')
.option('--include-sidechains', 'keep sidechain segments as a branch (default)')
.option('--flatten-sidechains', 'merge sidechains into main tree')
.option('--drop-sidechains', 'omit sidechain events entirely')
.option('--list', 'print numbered ASCII tree to stdout (skill-friendly)')
.option('--snapshot <id>', 'print single node\'s snapshot markdown to stdout')
.option(
'--mode <continue|fork>',
'snapshot mode (used with --snapshot)',
'continue',
)
.option('--tui', 'interactive readline prompt with numbered selection')
.option('--filter <kw>', 'show only rows whose label/time/range matches keyword (case-insensitive)')
.option('--no-group', 'do not collapse consecutive same-file rows')
.option('--no-color', 'force-disable ANSI color even on TTY')
.option('--phases-only', 'show only phase headers (user prompts), hide sub-actions')
.option('--picks', 'list every pick across every session (no session arg needed)')
.option('--unstar <id>', 'remove the ⭐ from a previously-picked node')
.option(
'--diff <ids...>',
'summarise what happened between two nodes (numbers or n_NNN ids)',
)
.exitOverride();
try {
program.parse(argv, { from: 'node' });
return {
ok: true,
opts: program.opts<CliOptions>(),
sessionArg: program.args[0],
};
} catch (err) {
const commanderErr = err as { exitCode?: number; code?: string };
if (
commanderErr.code === 'commander.helpDisplayed' ||
commanderErr.code === 'commander.version'
) {
return { ok: false, exitCode: 0 };
}
return { ok: false, exitCode: commanderErr.exitCode ?? 1 };
}
}
/**
* Resolve which output mode is active. Default rules:
* - --picks / --unstar / --diff are all session-utility modes
* - --list / --snapshot / --tui explicit → that mode
* - none + TTY stdout → tui (interactive)
* - none + non-TTY → list (machine-readable for skill use)
*/
export interface EffectiveMode {
list: boolean;
snapshot: boolean;
tui: boolean;
picks: boolean;
unstar: boolean;
diff: boolean;
}
export function resolveMode(opts: CliOptions, isTty: boolean): EffectiveMode {
const utilityFlag = !!opts.picks || !!opts.unstar || !!opts.diff?.length;
const flagSet = !!opts.list || !!opts.snapshot || !!opts.tui || utilityFlag;
return {
list: !!opts.list || (!flagSet && !isTty),
snapshot: !!opts.snapshot,
tui: !!opts.tui || (!flagSet && isTty),
picks: !!opts.picks,
unstar: !!opts.unstar,
diff: !!opts.diff?.length,
};
}