token-pilot 0.13.0 → 0.14.1
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/.claude-plugin/hooks/hooks.json +9 -0
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +29 -0
- package/README.md +28 -7
- package/dist/config/defaults.js +12 -0
- package/dist/core/architecture-fingerprint.d.ts +34 -0
- package/dist/core/architecture-fingerprint.js +127 -0
- package/dist/core/budget-planner.d.ts +21 -0
- package/dist/core/budget-planner.js +68 -0
- package/dist/core/confidence.d.ts +31 -0
- package/dist/core/confidence.js +99 -0
- package/dist/core/context-registry.d.ts +14 -0
- package/dist/core/context-registry.js +55 -0
- package/dist/core/decision-trace.d.ts +31 -0
- package/dist/core/decision-trace.js +45 -0
- package/dist/core/intent-classifier.d.ts +13 -0
- package/dist/core/intent-classifier.js +44 -0
- package/dist/core/policy-engine.d.ts +41 -0
- package/dist/core/policy-engine.js +76 -0
- package/dist/core/session-analytics.d.ts +8 -0
- package/dist/core/session-analytics.js +86 -7
- package/dist/core/session-cache.d.ts +74 -0
- package/dist/core/session-cache.js +162 -0
- package/dist/core/validation.d.ts +3 -0
- package/dist/core/validation.js +3 -0
- package/dist/git/file-watcher.d.ts +6 -0
- package/dist/git/file-watcher.js +18 -2
- package/dist/git/watcher.d.ts +3 -0
- package/dist/git/watcher.js +6 -0
- package/dist/handlers/code-audit.d.ts +7 -2
- package/dist/handlers/code-audit.js +19 -5
- package/dist/handlers/explore-area.d.ts +10 -0
- package/dist/handlers/explore-area.js +39 -13
- package/dist/handlers/find-unused.d.ts +3 -0
- package/dist/handlers/find-unused.js +3 -2
- package/dist/handlers/find-usages.d.ts +7 -0
- package/dist/handlers/find-usages.js +36 -5
- package/dist/handlers/module-info.d.ts +3 -0
- package/dist/handlers/module-info.js +22 -2
- package/dist/handlers/project-overview.d.ts +1 -1
- package/dist/handlers/project-overview.js +18 -2
- package/dist/handlers/read-for-edit.d.ts +3 -0
- package/dist/handlers/read-for-edit.js +185 -3
- package/dist/handlers/read-range.d.ts +1 -1
- package/dist/handlers/read-range.js +16 -1
- package/dist/handlers/read-symbol.d.ts +1 -1
- package/dist/handlers/read-symbol.js +26 -2
- package/dist/handlers/related-files.d.ts +11 -0
- package/dist/handlers/related-files.js +178 -42
- package/dist/handlers/smart-read-many.js +70 -16
- package/dist/handlers/smart-read.js +10 -1
- package/dist/handlers/test-summary.js +26 -3
- package/dist/hooks/installer.d.ts +12 -8
- package/dist/hooks/installer.js +24 -8
- package/dist/index.d.ts +16 -1
- package/dist/index.js +61 -55
- package/dist/server.js +395 -30
- package/dist/types.d.ts +12 -0
- package/package.json +5 -3
- package/start.sh +28 -27
- package/dist/handlers/class-hierarchy.d.ts +0 -11
- package/dist/handlers/class-hierarchy.js +0 -28
- package/dist/handlers/export-ast-index.d.ts +0 -22
- package/dist/handlers/export-ast-index.js +0 -175
- package/dist/handlers/find-implementations.d.ts +0 -11
- package/dist/handlers/find-implementations.js +0 -27
- package/dist/handlers/search-code.d.ts +0 -14
- package/dist/handlers/search-code.js +0 -32
|
@@ -1,32 +1,86 @@
|
|
|
1
1
|
import { handleSmartRead } from './smart-read.js';
|
|
2
|
-
import { estimateTokens } from '../core/token-estimator.js';
|
|
2
|
+
import { estimateTokens, formatSavings } from '../core/token-estimator.js';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { resolveSafePath } from '../core/validation.js';
|
|
5
|
+
const MAX_BATCH_FILES = 20;
|
|
6
|
+
const MAX_BATCH_TOKENS = 1400;
|
|
7
|
+
const MAX_FILE_TOKENS = 220;
|
|
8
|
+
const MAX_FILE_LINES = 24;
|
|
9
|
+
const BATCH_CONCURRENCY = 4;
|
|
3
10
|
export async function handleSmartReadMany(args, projectRoot, astIndex, fileCache, contextRegistry, config) {
|
|
4
11
|
if (!args.paths || args.paths.length === 0) {
|
|
5
12
|
return {
|
|
6
13
|
content: [{ type: 'text', text: 'No paths provided.' }],
|
|
7
14
|
};
|
|
8
15
|
}
|
|
9
|
-
if (args.paths.length >
|
|
16
|
+
if (args.paths.length > MAX_BATCH_FILES) {
|
|
10
17
|
return {
|
|
11
|
-
content: [{ type: 'text', text: `Too many files (${args.paths.length}). Maximum is
|
|
18
|
+
content: [{ type: 'text', text: `Too many files (${args.paths.length}). Maximum is ${MAX_BATCH_FILES} per batch.` }],
|
|
12
19
|
};
|
|
13
20
|
}
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
for (
|
|
17
|
-
|
|
21
|
+
const uniquePaths = Array.from(new Set(args.paths));
|
|
22
|
+
const entries = [];
|
|
23
|
+
for (let i = 0; i < uniquePaths.length; i += BATCH_CONCURRENCY) {
|
|
24
|
+
const batch = uniquePaths.slice(i, i + BATCH_CONCURRENCY);
|
|
25
|
+
const settled = await Promise.allSettled(batch.map(async (path) => {
|
|
18
26
|
const result = await handleSmartRead({ path }, projectRoot, astIndex, fileCache, contextRegistry, config);
|
|
19
27
|
const text = result.content[0]?.text ?? '';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
28
|
+
const fullTokens = await estimateFullFileTokens(projectRoot, path);
|
|
29
|
+
return { path, text, fullTokens };
|
|
30
|
+
}));
|
|
31
|
+
for (let index = 0; index < settled.length; index++) {
|
|
32
|
+
const outcome = settled[index];
|
|
33
|
+
const path = batch[index];
|
|
34
|
+
if (outcome.status === 'fulfilled') {
|
|
35
|
+
entries.push(outcome.value);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const msg = outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
|
|
39
|
+
entries.push({ path, text: `FILE: ${path}\nERROR: ${msg}`, fullTokens: 0 });
|
|
40
|
+
}
|
|
26
41
|
}
|
|
27
42
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
let remainingBudget = MAX_BATCH_TOKENS;
|
|
44
|
+
const renderedEntries = [];
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const compacted = compactBatchEntry(entry, remainingBudget);
|
|
47
|
+
renderedEntries.push(compacted);
|
|
48
|
+
remainingBudget = Math.max(0, remainingBudget - estimateTokens(compacted));
|
|
49
|
+
}
|
|
50
|
+
const body = renderedEntries.join('\n\n---\n\n');
|
|
51
|
+
const actualTokens = estimateTokens(body);
|
|
52
|
+
const fullTokens = entries.reduce((sum, entry) => sum + entry.fullTokens, 0);
|
|
53
|
+
const duplicatesRemoved = args.paths.length - uniquePaths.length;
|
|
54
|
+
const footer = [''];
|
|
55
|
+
footer.push(`BATCH: ${uniquePaths.length} unique files loaded${duplicatesRemoved > 0 ? ` (${duplicatesRemoved} duplicates skipped)` : ''}`);
|
|
56
|
+
footer.push(`OUTPUT: ~${actualTokens} tokens`);
|
|
57
|
+
if (fullTokens > 0) {
|
|
58
|
+
footer.push(formatSavings(actualTokens, fullTokens));
|
|
59
|
+
}
|
|
60
|
+
footer.push('HINT: Re-run smart_read(path) on any compacted file for full detail.');
|
|
61
|
+
return { content: [{ type: 'text', text: body + '\n' + footer.join('\n') }] };
|
|
62
|
+
}
|
|
63
|
+
function compactBatchEntry(entry, remainingBudget) {
|
|
64
|
+
const rawTokens = estimateTokens(entry.text);
|
|
65
|
+
if (remainingBudget <= 60) {
|
|
66
|
+
return `FILE: ${entry.path}\n(compacted in batch mode — use smart_read("${entry.path}") for full detail)`;
|
|
67
|
+
}
|
|
68
|
+
if (rawTokens <= Math.min(MAX_FILE_TOKENS, remainingBudget)) {
|
|
69
|
+
return entry.text;
|
|
70
|
+
}
|
|
71
|
+
const lines = entry.text.split('\n');
|
|
72
|
+
const head = lines.slice(0, MAX_FILE_LINES).join('\n');
|
|
73
|
+
const suffix = `\n\n... compacted for batch mode. Use smart_read("${entry.path}") for full detail.`;
|
|
74
|
+
return head + suffix;
|
|
75
|
+
}
|
|
76
|
+
async function estimateFullFileTokens(projectRoot, relativePath) {
|
|
77
|
+
try {
|
|
78
|
+
const absPath = resolveSafePath(projectRoot, relativePath);
|
|
79
|
+
const content = await readFile(absPath, 'utf-8');
|
|
80
|
+
return estimateTokens(content);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
31
85
|
}
|
|
32
86
|
//# sourceMappingURL=smart-read-many.js.map
|
|
@@ -4,6 +4,7 @@ import { formatOutline } from '../formatters/structure.js';
|
|
|
4
4
|
import { estimateTokens, formatSavings } from '../core/token-estimator.js';
|
|
5
5
|
import { resolveSafePath } from '../core/validation.js';
|
|
6
6
|
import { isNonCodeStructured, handleNonCodeRead } from './non-code.js';
|
|
7
|
+
import { assessConfidence, formatConfidence } from '../core/confidence.js';
|
|
7
8
|
export async function handleSmartRead(args, projectRoot, astIndex, fileCache, contextRegistry, config) {
|
|
8
9
|
const absPath = resolveSafePath(projectRoot, args.path);
|
|
9
10
|
// 0. Guard: directory passed instead of file
|
|
@@ -134,6 +135,14 @@ export async function handleSmartRead(args, projectRoot, astIndex, fileCache, co
|
|
|
134
135
|
tokens: structureTokens,
|
|
135
136
|
});
|
|
136
137
|
contextRegistry.setContentHash(absPath, cached.hash);
|
|
137
|
-
|
|
138
|
+
// 9. Confidence metadata
|
|
139
|
+
const confidenceMeta = assessConfidence({
|
|
140
|
+
symbolResolved: (cached.structure.symbols?.length ?? 0) > 0,
|
|
141
|
+
fullFile: false,
|
|
142
|
+
truncated: false,
|
|
143
|
+
astAvailable: true,
|
|
144
|
+
crossFileDeps: cached.structure.imports?.length ?? 0,
|
|
145
|
+
});
|
|
146
|
+
return { content: [{ type: 'text', text: output + savings + formatConfidence(confidenceMeta) }] };
|
|
138
147
|
}
|
|
139
148
|
//# sourceMappingURL=smart-read.js.map
|
|
@@ -37,7 +37,16 @@ export async function handleTestSummary(args, projectRoot) {
|
|
|
37
37
|
const rawTokens = estimateTokens(rawOutput);
|
|
38
38
|
const runner = args.runner ?? detectRunner(command, rawOutput);
|
|
39
39
|
const result = parseTestOutput(rawOutput, runner);
|
|
40
|
-
const
|
|
40
|
+
const commandFailed = exitCode !== 0;
|
|
41
|
+
if (commandFailed && result.failed === 0) {
|
|
42
|
+
result.failed = 1;
|
|
43
|
+
result.total = Math.max(result.total, result.passed + result.failed + result.skipped);
|
|
44
|
+
result.failures.unshift({
|
|
45
|
+
name: `Command exited with code ${exitCode}`,
|
|
46
|
+
error: summarizeCommandError(rawOutput),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const formatted = formatTestSummary(result, command, runner, rawTokens, exitCode, commandFailed);
|
|
41
50
|
return {
|
|
42
51
|
content: [{ type: 'text', text: formatted }],
|
|
43
52
|
rawTokens,
|
|
@@ -277,12 +286,23 @@ function parseGeneric(output) {
|
|
|
277
286
|
: result.passed + result.failed + result.skipped;
|
|
278
287
|
return result;
|
|
279
288
|
}
|
|
289
|
+
function summarizeCommandError(output) {
|
|
290
|
+
const lines = output
|
|
291
|
+
.split('\n')
|
|
292
|
+
.map(line => line.trim())
|
|
293
|
+
.filter(line => line.length > 0)
|
|
294
|
+
.filter(line => !line.startsWith('at ') && !line.startsWith('>'));
|
|
295
|
+
if (lines.length === 0) {
|
|
296
|
+
return 'Command failed without producing output.';
|
|
297
|
+
}
|
|
298
|
+
return lines.slice(0, 3).join('\n').substring(0, 300);
|
|
299
|
+
}
|
|
280
300
|
// ──────────────────────────────────────────────
|
|
281
301
|
// Formatter
|
|
282
302
|
// ──────────────────────────────────────────────
|
|
283
|
-
function formatTestSummary(result, command, runner, rawTokens) {
|
|
303
|
+
function formatTestSummary(result, command, runner, rawTokens, exitCode, commandFailed) {
|
|
284
304
|
const lines = [];
|
|
285
|
-
const status = result.failed > 0 ? '❌ FAIL' : '✅ PASS';
|
|
305
|
+
const status = result.failed > 0 || commandFailed ? '❌ FAIL' : '✅ PASS';
|
|
286
306
|
lines.push(`TEST RESULT: ${status} (${runner})`);
|
|
287
307
|
lines.push('');
|
|
288
308
|
// Stats line
|
|
@@ -298,6 +318,9 @@ function formatTestSummary(result, command, runner, rawTokens) {
|
|
|
298
318
|
if (result.suites)
|
|
299
319
|
parts.push(`${result.suites} suites`);
|
|
300
320
|
lines.push(parts.join(' | '));
|
|
321
|
+
if (commandFailed && exitCode != null) {
|
|
322
|
+
lines.push(`Exit code: ${exitCode}`);
|
|
323
|
+
}
|
|
301
324
|
// Failed tests detail
|
|
302
325
|
if (result.failures.length > 0) {
|
|
303
326
|
lines.push('');
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
export interface HookInstallResult {
|
|
2
|
+
installed: boolean;
|
|
3
|
+
fatal: boolean;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface HookUninstallResult {
|
|
7
|
+
removed: boolean;
|
|
8
|
+
fatal: boolean;
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
1
11
|
/**
|
|
2
12
|
* Install Token Pilot hook into Claude Code settings.
|
|
3
13
|
* Creates or updates .claude/settings.json with PreToolUse hook.
|
|
4
14
|
*/
|
|
5
|
-
export declare function installHook(projectRoot: string): Promise<
|
|
6
|
-
installed: boolean;
|
|
7
|
-
message: string;
|
|
8
|
-
}>;
|
|
15
|
+
export declare function installHook(projectRoot: string): Promise<HookInstallResult>;
|
|
9
16
|
/**
|
|
10
17
|
* Remove Token Pilot hook from Claude Code settings.
|
|
11
18
|
*/
|
|
12
|
-
export declare function uninstallHook(projectRoot: string): Promise<
|
|
13
|
-
removed: boolean;
|
|
14
|
-
message: string;
|
|
15
|
-
}>;
|
|
19
|
+
export declare function uninstallHook(projectRoot: string): Promise<HookUninstallResult>;
|
|
16
20
|
//# sourceMappingURL=installer.d.ts.map
|
package/dist/hooks/installer.js
CHANGED
|
@@ -42,12 +42,20 @@ export async function installHook(projectRoot) {
|
|
|
42
42
|
}
|
|
43
43
|
catch {
|
|
44
44
|
// File exists but has invalid JSON — don't destroy it
|
|
45
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
installed: false,
|
|
47
|
+
fatal: true,
|
|
48
|
+
message: `Settings file exists but contains invalid JSON: ${settingsPath}. Fix it manually before installing hooks.`,
|
|
49
|
+
};
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
52
|
catch (err) {
|
|
49
53
|
if (err?.code !== 'ENOENT') {
|
|
50
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
installed: false,
|
|
56
|
+
fatal: true,
|
|
57
|
+
message: `Cannot read settings: ${err?.message ?? err}`,
|
|
58
|
+
};
|
|
51
59
|
}
|
|
52
60
|
// ENOENT — file doesn't exist, start fresh
|
|
53
61
|
}
|
|
@@ -58,7 +66,7 @@ export async function installHook(projectRoot) {
|
|
|
58
66
|
const hasRead = existingHooks.some((h) => h.matcher === 'Read' && isTokenPilotHook(h));
|
|
59
67
|
const hasEdit = existingHooks.some((h) => h.matcher === 'Edit' && isTokenPilotHook(h));
|
|
60
68
|
if (hasRead && hasEdit) {
|
|
61
|
-
return { installed: false, message: 'Token Pilot hooks already installed.' };
|
|
69
|
+
return { installed: false, fatal: false, message: 'Token Pilot hooks already installed.' };
|
|
62
70
|
}
|
|
63
71
|
// Add missing hooks
|
|
64
72
|
for (const hookDef of HOOK_CONFIG.hooks.PreToolUse) {
|
|
@@ -77,12 +85,13 @@ export async function installHook(projectRoot) {
|
|
|
77
85
|
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
78
86
|
return {
|
|
79
87
|
installed: true,
|
|
88
|
+
fatal: false,
|
|
80
89
|
message: `Hooks installed at ${settingsPath}. Token Pilot will block unbounded Read on large code files and suggest read_for_edit before Edit.`,
|
|
81
90
|
};
|
|
82
91
|
}
|
|
83
92
|
catch (err) {
|
|
84
93
|
const msg = err instanceof Error ? err.message : String(err);
|
|
85
|
-
return { installed: false, message: `Failed to install hook: ${msg}` };
|
|
94
|
+
return { installed: false, fatal: true, message: `Failed to install hook: ${msg}` };
|
|
86
95
|
}
|
|
87
96
|
}
|
|
88
97
|
/**
|
|
@@ -94,7 +103,7 @@ export async function uninstallHook(projectRoot) {
|
|
|
94
103
|
const raw = await readFile(settingsPath, 'utf-8');
|
|
95
104
|
const settings = JSON.parse(raw);
|
|
96
105
|
if (!settings.hooks?.PreToolUse) {
|
|
97
|
-
return { removed: false, message: 'No hooks to remove.' };
|
|
106
|
+
return { removed: false, fatal: false, message: 'No hooks to remove.' };
|
|
98
107
|
}
|
|
99
108
|
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((h) => !h.hooks?.some((hook) => hook.command?.includes('token-pilot')));
|
|
100
109
|
if (settings.hooks.PreToolUse.length === 0) {
|
|
@@ -104,13 +113,20 @@ export async function uninstallHook(projectRoot) {
|
|
|
104
113
|
delete settings.hooks;
|
|
105
114
|
}
|
|
106
115
|
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
107
|
-
return { removed: true, message: 'Token Pilot hook removed.' };
|
|
116
|
+
return { removed: true, fatal: false, message: 'Token Pilot hook removed.' };
|
|
108
117
|
}
|
|
109
118
|
catch (err) {
|
|
110
119
|
if (err?.code === 'ENOENT') {
|
|
111
|
-
return { removed: false, message: 'Settings file not found.' };
|
|
120
|
+
return { removed: false, fatal: false, message: 'Settings file not found.' };
|
|
112
121
|
}
|
|
113
|
-
|
|
122
|
+
if (err instanceof SyntaxError) {
|
|
123
|
+
return {
|
|
124
|
+
removed: false,
|
|
125
|
+
fatal: true,
|
|
126
|
+
message: `Settings file contains invalid JSON: ${settingsPath}. Fix it manually before uninstalling hooks.`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return { removed: false, fatal: true, message: `Failed to process settings: ${err?.message ?? err}` };
|
|
114
130
|
}
|
|
115
131
|
}
|
|
116
132
|
//# sourceMappingURL=installer.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export
|
|
2
|
+
export declare const CODE_EXTENSIONS: Set<string>;
|
|
3
|
+
export declare function getVersion(): string;
|
|
4
|
+
export declare function main(cliArgs?: string[]): Promise<void>;
|
|
5
|
+
export declare function startServer(cliArgs?: string[]): Promise<void>;
|
|
6
|
+
export declare function handleHookRead(filePathArg?: string): void;
|
|
7
|
+
export declare function handleHookEdit(): void;
|
|
8
|
+
export declare function handleInstallHook(projectRoot: string): Promise<void>;
|
|
9
|
+
export declare function handleUninstallHook(projectRoot: string): Promise<void>;
|
|
10
|
+
export declare function handleInstallAstIndex(): Promise<void>;
|
|
11
|
+
export declare function handleDoctor(): Promise<void>;
|
|
12
|
+
export declare function handleInit(targetDir: string): Promise<void>;
|
|
13
|
+
export declare function checkNpmLatest(packageName: string): Promise<string | null>;
|
|
14
|
+
import type { TokenPilotConfig } from './types.js';
|
|
15
|
+
import type { BinaryStatus } from './ast-index/binary-manager.js';
|
|
16
|
+
export declare function checkAllUpdates(config: TokenPilotConfig, binaryStatus: BinaryStatus): Promise<void>;
|
|
17
|
+
export declare function printHelp(): void;
|
|
3
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
4
|
import { execFile } from 'node:child_process';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
6
7
|
import { createServer } from './server.js';
|
|
7
8
|
import { installHook, uninstallHook } from './hooks/installer.js';
|
|
8
9
|
import { findBinary, installBinary, checkBinaryUpdate, isNewerVersion } from './ast-index/binary-manager.js';
|
|
@@ -10,14 +11,14 @@ import { loadConfig } from './config/loader.js';
|
|
|
10
11
|
import { isDangerousRoot } from './core/validation.js';
|
|
11
12
|
const execFileAsync = promisify(execFile);
|
|
12
13
|
const HOOK_DENY_THRESHOLD = 500;
|
|
13
|
-
const CODE_EXTENSIONS = new Set([
|
|
14
|
+
export const CODE_EXTENSIONS = new Set([
|
|
14
15
|
'ts', 'tsx', 'js', 'jsx', 'mjs', 'py', 'go', 'rs', 'java', 'kt', 'kts',
|
|
15
16
|
'swift', 'cs', 'cpp', 'cc', 'cxx', 'hpp', 'c', 'h', 'php', 'rb', 'scala',
|
|
16
17
|
'dart', 'lua', 'sh', 'bash', 'sql', 'r', 'vue', 'svelte', 'pl', 'pm',
|
|
17
18
|
'ex', 'exs', 'groovy', 'm', 'proto', 'bsl',
|
|
18
19
|
'lisp', 'lsp', 'cl', 'asd',
|
|
19
20
|
]);
|
|
20
|
-
function getVersion() {
|
|
21
|
+
export function getVersion() {
|
|
21
22
|
try {
|
|
22
23
|
const pkgPath = new URL('../package.json', import.meta.url).pathname;
|
|
23
24
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
@@ -27,50 +28,48 @@ function getVersion() {
|
|
|
27
28
|
return '0.0.0';
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
switch (
|
|
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
|
-
break;
|
|
31
|
+
export async function main(cliArgs = process.argv.slice(2)) {
|
|
32
|
+
switch (cliArgs[0]) {
|
|
33
|
+
case 'hook-read':
|
|
34
|
+
handleHookRead(cliArgs[1]);
|
|
35
|
+
return;
|
|
36
|
+
case 'hook-edit':
|
|
37
|
+
handleHookEdit();
|
|
38
|
+
return;
|
|
39
|
+
case 'install-hook':
|
|
40
|
+
await handleInstallHook(cliArgs[1] || process.cwd());
|
|
41
|
+
return;
|
|
42
|
+
case 'uninstall-hook':
|
|
43
|
+
await handleUninstallHook(cliArgs[1] || process.cwd());
|
|
44
|
+
return;
|
|
45
|
+
case 'install-ast-index':
|
|
46
|
+
await handleInstallAstIndex();
|
|
47
|
+
return;
|
|
48
|
+
case 'doctor':
|
|
49
|
+
await handleDoctor();
|
|
50
|
+
return;
|
|
51
|
+
case 'init':
|
|
52
|
+
await handleInit(cliArgs[1] || process.cwd());
|
|
53
|
+
return;
|
|
54
|
+
case '--version':
|
|
55
|
+
case '-v':
|
|
56
|
+
console.log(getVersion());
|
|
57
|
+
process.exit(0);
|
|
58
|
+
return;
|
|
59
|
+
case '--help':
|
|
60
|
+
case '-h':
|
|
61
|
+
printHelp();
|
|
62
|
+
return;
|
|
63
|
+
default:
|
|
64
|
+
await startServer(cliArgs);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
68
67
|
}
|
|
69
|
-
async function startServer() {
|
|
70
|
-
let projectRoot =
|
|
68
|
+
export async function startServer(cliArgs = process.argv.slice(2)) {
|
|
69
|
+
let projectRoot = cliArgs[0] || process.cwd();
|
|
71
70
|
// Detect git root for reliable project root
|
|
72
71
|
// Try multiple sources: args[0] → INIT_CWD (npm/npx invoking dir) → PWD → cwd
|
|
73
|
-
if (!
|
|
72
|
+
if (!cliArgs[0]) {
|
|
74
73
|
const candidates = [
|
|
75
74
|
process.env.INIT_CWD, // npm/npx sets this to invoking directory
|
|
76
75
|
process.env.PWD, // shell working directory (may differ from cwd)
|
|
@@ -140,7 +139,7 @@ async function startServer() {
|
|
|
140
139
|
process.exit(0);
|
|
141
140
|
});
|
|
142
141
|
}
|
|
143
|
-
function handleHookRead(filePathArg) {
|
|
142
|
+
export function handleHookRead(filePathArg) {
|
|
144
143
|
// Parse stdin (Claude Code hook format) to get tool_input
|
|
145
144
|
let filePath = filePathArg;
|
|
146
145
|
let hasOffset = false;
|
|
@@ -193,7 +192,7 @@ function handleHookRead(filePathArg) {
|
|
|
193
192
|
process.stdout.write(deny);
|
|
194
193
|
process.exit(0);
|
|
195
194
|
}
|
|
196
|
-
function handleHookEdit() {
|
|
195
|
+
export function handleHookEdit() {
|
|
197
196
|
// Parse stdin for Edit tool_input
|
|
198
197
|
let filePath;
|
|
199
198
|
try {
|
|
@@ -223,17 +222,17 @@ function handleHookEdit() {
|
|
|
223
222
|
process.stdout.write(context);
|
|
224
223
|
process.exit(0);
|
|
225
224
|
}
|
|
226
|
-
async function handleInstallHook(projectRoot) {
|
|
225
|
+
export async function handleInstallHook(projectRoot) {
|
|
227
226
|
const result = await installHook(projectRoot);
|
|
228
227
|
console.log(result.message);
|
|
229
|
-
process.exit(result.
|
|
228
|
+
process.exit(result.fatal ? 1 : 0);
|
|
230
229
|
}
|
|
231
|
-
async function handleUninstallHook(projectRoot) {
|
|
230
|
+
export async function handleUninstallHook(projectRoot) {
|
|
232
231
|
const result = await uninstallHook(projectRoot);
|
|
233
232
|
console.log(result.message);
|
|
234
|
-
process.exit(result.
|
|
233
|
+
process.exit(result.fatal ? 1 : 0);
|
|
235
234
|
}
|
|
236
|
-
async function handleInstallAstIndex() {
|
|
235
|
+
export async function handleInstallAstIndex() {
|
|
237
236
|
const status = await findBinary();
|
|
238
237
|
if (status.available) {
|
|
239
238
|
// Check if update is available
|
|
@@ -256,7 +255,7 @@ async function handleInstallAstIndex() {
|
|
|
256
255
|
process.exit(1);
|
|
257
256
|
}
|
|
258
257
|
}
|
|
259
|
-
async function handleDoctor() {
|
|
258
|
+
export async function handleDoctor() {
|
|
260
259
|
const version = getVersion();
|
|
261
260
|
const { existsSync } = await import('node:fs');
|
|
262
261
|
const { join } = await import('node:path');
|
|
@@ -324,7 +323,7 @@ async function handleDoctor() {
|
|
|
324
323
|
console.log('');
|
|
325
324
|
process.exit(0);
|
|
326
325
|
}
|
|
327
|
-
async function handleInit(targetDir) {
|
|
326
|
+
export async function handleInit(targetDir) {
|
|
328
327
|
const { existsSync, readFileSync: readFs, writeFileSync } = await import('node:fs');
|
|
329
328
|
const { join } = await import('node:path');
|
|
330
329
|
const mcpPath = join(targetDir, '.mcp.json');
|
|
@@ -379,7 +378,7 @@ async function handleInit(targetDir) {
|
|
|
379
378
|
// ──────────────────────────────────────────────
|
|
380
379
|
// Update checking
|
|
381
380
|
// ──────────────────────────────────────────────
|
|
382
|
-
async function checkNpmLatest(packageName) {
|
|
381
|
+
export async function checkNpmLatest(packageName) {
|
|
383
382
|
try {
|
|
384
383
|
const controller = new AbortController();
|
|
385
384
|
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
@@ -396,7 +395,7 @@ async function checkNpmLatest(packageName) {
|
|
|
396
395
|
return null;
|
|
397
396
|
}
|
|
398
397
|
}
|
|
399
|
-
async function checkAllUpdates(config, binaryStatus) {
|
|
398
|
+
export async function checkAllUpdates(config, binaryStatus) {
|
|
400
399
|
if (!config.updates.checkOnStartup)
|
|
401
400
|
return;
|
|
402
401
|
const [tpLatest, astUpdate, cmLatest] = await Promise.allSettled([
|
|
@@ -427,7 +426,7 @@ async function checkAllUpdates(config, binaryStatus) {
|
|
|
427
426
|
// On startup, we only notify if explicitly useful.
|
|
428
427
|
}
|
|
429
428
|
}
|
|
430
|
-
function printHelp() {
|
|
429
|
+
export function printHelp() {
|
|
431
430
|
console.log(`token-pilot v${getVersion()} — MCP server for token-efficient code reading
|
|
432
431
|
|
|
433
432
|
Usage:
|
|
@@ -450,4 +449,11 @@ MCP Tools (18):
|
|
|
450
449
|
`);
|
|
451
450
|
process.exit(0);
|
|
452
451
|
}
|
|
452
|
+
const isDirectRun = process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1];
|
|
453
|
+
if (isDirectRun) {
|
|
454
|
+
main().catch(err => {
|
|
455
|
+
console.error(`[token-pilot] Fatal: ${err instanceof Error ? err.message : err}`);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
});
|
|
458
|
+
}
|
|
453
459
|
//# sourceMappingURL=index.js.map
|