letmecode 0.1.6 → 0.1.8
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/README.md +2 -0
- package/ink-app/dist/cli-options.js +89 -0
- package/ink-app/dist/index.js +8 -6
- package/ink-app/dist/providers/claude.js +114 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function parseCliOptions(argv) {
|
|
4
|
+
let showHelp = false;
|
|
5
|
+
let verbose = false;
|
|
6
|
+
let logToPath;
|
|
7
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
8
|
+
const argument = argv[index] ?? "";
|
|
9
|
+
if (argument === "-h" || argument === "--help") {
|
|
10
|
+
showHelp = true;
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (argument === "-v" || argument === "--verbose") {
|
|
14
|
+
verbose = true;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (argument === "--log-to") {
|
|
18
|
+
const nextArgument = argv[index + 1];
|
|
19
|
+
if (!nextArgument) {
|
|
20
|
+
throw new Error("Expected a file path after --log-to.");
|
|
21
|
+
}
|
|
22
|
+
logToPath = nextArgument;
|
|
23
|
+
index += 1;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (argument.startsWith("--log-to=")) {
|
|
27
|
+
const value = argument.slice("--log-to=".length);
|
|
28
|
+
if (!value) {
|
|
29
|
+
throw new Error("Expected a file path after --log-to=.");
|
|
30
|
+
}
|
|
31
|
+
logToPath = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { showHelp, verbose, logToPath };
|
|
35
|
+
}
|
|
36
|
+
export function buildProviderStatsOptions(options) {
|
|
37
|
+
return {
|
|
38
|
+
verbose: options.verbose,
|
|
39
|
+
traceLogger: options.logToPath ? createFileTraceLogger(options.logToPath) : undefined
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function buildHelpText() {
|
|
43
|
+
return [
|
|
44
|
+
"letmecode - provider-based terminal usage dashboard",
|
|
45
|
+
"",
|
|
46
|
+
"Usage:",
|
|
47
|
+
" letmecode [options]",
|
|
48
|
+
"",
|
|
49
|
+
"Options:",
|
|
50
|
+
" -h, --help Show this help and exit",
|
|
51
|
+
" -v, --verbose Show extra provider warnings",
|
|
52
|
+
" --log-to PATH Write trace logs to PATH",
|
|
53
|
+
"",
|
|
54
|
+
"Controls:",
|
|
55
|
+
" [ ] / Tab Switch providers",
|
|
56
|
+
" Shift+Tab Switch providers backward",
|
|
57
|
+
" j / k Switch dashboard sections",
|
|
58
|
+
" Up / Down Switch dashboard sections",
|
|
59
|
+
" Left / Right Select the previous or next row",
|
|
60
|
+
" 1, h / l, Enter Run Copilot setup actions",
|
|
61
|
+
" q or Esc Quit",
|
|
62
|
+
"",
|
|
63
|
+
"Trace logging:",
|
|
64
|
+
" --log-to PATH writes Claude CLI SDK and Claude VSCode detection details,",
|
|
65
|
+
" every candidate binary path check, the final found/not-found result,",
|
|
66
|
+
" and the raw /usage command output."
|
|
67
|
+
].join("\n");
|
|
68
|
+
}
|
|
69
|
+
export function createFileTraceLogger(logPath) {
|
|
70
|
+
const resolvedPath = path.resolve(logPath);
|
|
71
|
+
fs.mkdirSync(path.dirname(resolvedPath), { recursive: true });
|
|
72
|
+
fs.writeFileSync(resolvedPath, [
|
|
73
|
+
"# letmecode trace",
|
|
74
|
+
`# started_at=${new Date().toISOString()}`,
|
|
75
|
+
`# cwd=${process.cwd()}`,
|
|
76
|
+
`# argv=${JSON.stringify(process.argv.slice(2))}`,
|
|
77
|
+
""
|
|
78
|
+
].join("\n"), "utf8");
|
|
79
|
+
return {
|
|
80
|
+
log(message) {
|
|
81
|
+
const timestamp = new Date().toISOString();
|
|
82
|
+
const formatted = message
|
|
83
|
+
.split(/\r?\n/)
|
|
84
|
+
.map((line, index) => (index === 0 ? `[${timestamp}] ${line}` : ` ${line}`))
|
|
85
|
+
.join("\n");
|
|
86
|
+
fs.appendFileSync(resolvedPath, `${formatted}\n`, "utf8");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
package/ink-app/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { Box, Text, measureElement, useApp, useInput, useStdin, useStdout, render } from "ink";
|
|
4
|
+
import { buildHelpText, buildProviderStatsOptions, parseCliOptions } from "./cli-options.js";
|
|
4
5
|
import { configureCopilotVsCodeLogging, createProviders } from "./providers/index.js";
|
|
5
6
|
import { reportAnonymousUsage } from "./reporting.js";
|
|
6
7
|
const ESC = String.fromCharCode(0x1b);
|
|
@@ -769,11 +770,6 @@ function getDayRows(providerState) {
|
|
|
769
770
|
function getLimitRowKey(row) {
|
|
770
771
|
return `${row.scope}-${row.planType}-${row.limitId}-${row.startTimeUtcIso}-${row.endTimeUtcIso}`;
|
|
771
772
|
}
|
|
772
|
-
function parseStatsOptions(argv) {
|
|
773
|
-
return {
|
|
774
|
-
verbose: argv.includes("-v") || argv.includes("--verbose")
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
773
|
function useViewportHeight() {
|
|
778
774
|
const { stdout } = useStdout();
|
|
779
775
|
const [viewportHeight, setViewportHeight] = useState(() => resolveViewportHeight(stdout.rows));
|
|
@@ -799,6 +795,12 @@ function resolveViewportHeight(rows) {
|
|
|
799
795
|
return Math.max(1, terminalRows - 1);
|
|
800
796
|
}
|
|
801
797
|
export function main(argv = process.argv.slice(2)) {
|
|
798
|
+
const cliOptions = parseCliOptions(argv);
|
|
799
|
+
if (cliOptions.showHelp) {
|
|
800
|
+
process.stdout.write(`${buildHelpText()}\n`);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const statsOptions = buildProviderStatsOptions(cliOptions);
|
|
802
804
|
const restoreFullscreen = enterFullscreenMode(process.stdout);
|
|
803
805
|
const disableMouse = enableMouseReporting(process.stdout);
|
|
804
806
|
const exitHandler = () => {
|
|
@@ -806,7 +808,7 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
806
808
|
restoreFullscreen();
|
|
807
809
|
};
|
|
808
810
|
process.once("exit", exitHandler);
|
|
809
|
-
const instance = render(_jsx(App, { statsOptions:
|
|
811
|
+
const instance = render(_jsx(App, { statsOptions: statsOptions }), {
|
|
810
812
|
stdout: process.stdout,
|
|
811
813
|
stdin: process.stdin,
|
|
812
814
|
stderr: process.stderr
|
|
@@ -61,7 +61,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
61
61
|
const resolvedSessionsRoot = await resolveClaudeSessionsRoot(this.root);
|
|
62
62
|
const sessionsRoot = resolvedSessionsRoot.rootPath;
|
|
63
63
|
const agentName = normalizeAnalyticsAgentName(this.label);
|
|
64
|
-
const userIdHash = await readClaudeUserIdHash(this.root, this.usageCommandKind, this.readAuthStatusOutput, agentName);
|
|
64
|
+
const userIdHash = await readClaudeUserIdHash(this.root, this.usageCommandKind, this.readAuthStatusOutput, agentName, options.traceLogger);
|
|
65
65
|
const byModel = new Map();
|
|
66
66
|
const byDay = createDailyUsageAggregates();
|
|
67
67
|
const windows = createLimitWindowAggregates();
|
|
@@ -131,6 +131,7 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
131
131
|
usageCommandKind: this.usageCommandKind,
|
|
132
132
|
readUsageCommandOutput: this.readUsageCommandOutput,
|
|
133
133
|
readAuthStatusOutput: this.readAuthStatusOutput,
|
|
134
|
+
traceLogger: options.traceLogger,
|
|
134
135
|
now: this.now(),
|
|
135
136
|
selectedEvents
|
|
136
137
|
});
|
|
@@ -470,12 +471,41 @@ function normalizeTimestamp(value) {
|
|
|
470
471
|
function extractRateLimits(payloadObject, message) {
|
|
471
472
|
return asRecord(payloadObject.rate_limits) ?? asRecord(message?.rate_limits);
|
|
472
473
|
}
|
|
474
|
+
function traceClaude(traceLogger, usageCommandKind, message) {
|
|
475
|
+
if (!traceLogger) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const targetLabel = usageCommandKind === "vscode" ? "Claude VSCode" : "Claude";
|
|
479
|
+
traceLogger.log(`[${targetLabel}] ${message}`);
|
|
480
|
+
}
|
|
481
|
+
function formatErrorMessage(error) {
|
|
482
|
+
if (!error) {
|
|
483
|
+
return "Unknown error";
|
|
484
|
+
}
|
|
485
|
+
if (error instanceof Error && error.message) {
|
|
486
|
+
return error.message;
|
|
487
|
+
}
|
|
488
|
+
return String(error);
|
|
489
|
+
}
|
|
490
|
+
function describeUsageOutput(output) {
|
|
491
|
+
if (output == null) {
|
|
492
|
+
return "<null>";
|
|
493
|
+
}
|
|
494
|
+
return output.trim() ? output : "<empty>";
|
|
495
|
+
}
|
|
496
|
+
function buildClaudeCommandEnvironment() {
|
|
497
|
+
return {
|
|
498
|
+
...process.env,
|
|
499
|
+
TZ: "UTC"
|
|
500
|
+
};
|
|
501
|
+
}
|
|
473
502
|
async function buildLiveLimitWindows(options) {
|
|
474
503
|
const [usageOutput, subscriptionType] = await Promise.all([
|
|
475
|
-
readClaudeUsageCommandOutput(options.root, options.usageCommandKind, options.readUsageCommandOutput),
|
|
476
|
-
readClaudeSubscriptionType(options.root, options.usageCommandKind, options.readAuthStatusOutput)
|
|
504
|
+
readClaudeUsageCommandOutput(options.root, options.usageCommandKind, options.readUsageCommandOutput, options.traceLogger),
|
|
505
|
+
readClaudeSubscriptionType(options.root, options.usageCommandKind, options.readAuthStatusOutput, options.traceLogger)
|
|
477
506
|
]);
|
|
478
507
|
const snapshots = parseLiveUsageWindowSnapshots(usageOutput, options.now);
|
|
508
|
+
traceClaude(options.traceLogger, options.usageCommandKind, `Parsed ${snapshots.length} live usage snapshot(s) from /usage output.`);
|
|
479
509
|
const resolvedPlanType = subscriptionType || "live";
|
|
480
510
|
return {
|
|
481
511
|
primaryLimitWindows: snapshots
|
|
@@ -486,41 +516,52 @@ async function buildLiveLimitWindows(options) {
|
|
|
486
516
|
.map((snapshot) => buildLiveLimitWindowRow(snapshot, resolvedPlanType, options.selectedEvents, options.now))
|
|
487
517
|
};
|
|
488
518
|
}
|
|
489
|
-
async function readClaudeSubscriptionType(root, usageCommandKind, override) {
|
|
490
|
-
const output = await readClaudeAuthStatusOutput(root, usageCommandKind, override);
|
|
491
|
-
|
|
519
|
+
async function readClaudeSubscriptionType(root, usageCommandKind, override, traceLogger) {
|
|
520
|
+
const output = await readClaudeAuthStatusOutput(root, usageCommandKind, override, traceLogger);
|
|
521
|
+
const subscriptionType = parseClaudeSubscriptionType(output);
|
|
522
|
+
traceClaude(traceLogger, usageCommandKind, `Subscription type result: ${subscriptionType ?? "<none>"}.`);
|
|
523
|
+
return subscriptionType;
|
|
492
524
|
}
|
|
493
|
-
async function readClaudeAuthStatusOutput(root, usageCommandKind, override) {
|
|
525
|
+
async function readClaudeAuthStatusOutput(root, usageCommandKind, override, traceLogger) {
|
|
494
526
|
if (override) {
|
|
495
527
|
try {
|
|
496
|
-
|
|
528
|
+
const output = await override();
|
|
529
|
+
traceClaude(traceLogger, usageCommandKind, "Using injected auth status output override.");
|
|
530
|
+
return output;
|
|
497
531
|
}
|
|
498
532
|
catch {
|
|
533
|
+
traceClaude(traceLogger, usageCommandKind, "Injected auth status output override failed.");
|
|
499
534
|
return null;
|
|
500
535
|
}
|
|
501
536
|
}
|
|
502
537
|
const cacheKey = `${usageCommandKind}:${path.resolve(root)}`;
|
|
503
538
|
const cached = claudeAuthStatusOutputCache.get(cacheKey);
|
|
504
539
|
if (cached) {
|
|
540
|
+
traceClaude(traceLogger, usageCommandKind, "Auth status output cache hit.");
|
|
505
541
|
return cached;
|
|
506
542
|
}
|
|
507
543
|
const pending = (async () => {
|
|
508
|
-
const binaryPath = await resolveClaudeBinaryPath(root, usageCommandKind);
|
|
544
|
+
const binaryPath = await resolveClaudeBinaryPath(root, usageCommandKind, traceLogger);
|
|
509
545
|
if (!binaryPath) {
|
|
546
|
+
traceClaude(traceLogger, usageCommandKind, "Skipping auth status command because no Claude binary was found.");
|
|
510
547
|
return null;
|
|
511
548
|
}
|
|
512
549
|
try {
|
|
550
|
+
traceClaude(traceLogger, usageCommandKind, `Running auth status command with ${binaryPath} (TZ=UTC).`);
|
|
513
551
|
const { stdout, stderr } = await execFileAsync(binaryPath, ["auth", "status"], {
|
|
514
552
|
encoding: "utf8",
|
|
553
|
+
env: buildClaudeCommandEnvironment(),
|
|
515
554
|
maxBuffer: 1024 * 1024,
|
|
516
555
|
timeout: 15000,
|
|
517
556
|
windowsHide: true
|
|
518
557
|
});
|
|
519
558
|
const combined = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
559
|
+
traceClaude(traceLogger, usageCommandKind, "Auth status command completed successfully.");
|
|
520
560
|
return combined || null;
|
|
521
561
|
}
|
|
522
562
|
catch (error) {
|
|
523
563
|
const combined = extractExecOutput(error);
|
|
564
|
+
traceClaude(traceLogger, usageCommandKind, `Auth status command failed: ${formatErrorMessage(error)}.`);
|
|
524
565
|
return combined || null;
|
|
525
566
|
}
|
|
526
567
|
})();
|
|
@@ -557,45 +598,58 @@ function parseClaudeAuthStatusSnapshot(output) {
|
|
|
557
598
|
return null;
|
|
558
599
|
}
|
|
559
600
|
}
|
|
560
|
-
async function readClaudeUserIdHash(root, usageCommandKind, override, agentName) {
|
|
561
|
-
const authStatusOutput = await readClaudeAuthStatusOutput(root, usageCommandKind, override);
|
|
601
|
+
async function readClaudeUserIdHash(root, usageCommandKind, override, agentName, traceLogger) {
|
|
602
|
+
const authStatusOutput = await readClaudeAuthStatusOutput(root, usageCommandKind, override, traceLogger);
|
|
562
603
|
const snapshot = parseClaudeAuthStatusSnapshot(authStatusOutput);
|
|
563
604
|
if (!snapshot) {
|
|
564
605
|
return null;
|
|
565
606
|
}
|
|
566
607
|
return buildUserIdHash([agentName, snapshot.email, snapshot.orgId, snapshot.orgName]);
|
|
567
608
|
}
|
|
568
|
-
async function readClaudeUsageCommandOutput(root, usageCommandKind, override) {
|
|
609
|
+
async function readClaudeUsageCommandOutput(root, usageCommandKind, override, traceLogger) {
|
|
569
610
|
if (override) {
|
|
570
611
|
try {
|
|
571
|
-
|
|
612
|
+
const output = await override();
|
|
613
|
+
traceClaude(traceLogger, usageCommandKind, "Using injected /usage output override.");
|
|
614
|
+
traceClaude(traceLogger, usageCommandKind, `Usage returned:\n${describeUsageOutput(output)}`);
|
|
615
|
+
return output;
|
|
572
616
|
}
|
|
573
617
|
catch {
|
|
618
|
+
traceClaude(traceLogger, usageCommandKind, "Injected /usage output override failed.");
|
|
574
619
|
return null;
|
|
575
620
|
}
|
|
576
621
|
}
|
|
577
622
|
const cacheKey = `${usageCommandKind}:${path.resolve(root)}`;
|
|
578
623
|
const cached = claudeUsageOutputCache.get(cacheKey);
|
|
579
624
|
if (cached) {
|
|
625
|
+
traceClaude(traceLogger, usageCommandKind, "Usage output cache hit.");
|
|
580
626
|
return cached;
|
|
581
627
|
}
|
|
582
628
|
const pending = (async () => {
|
|
583
|
-
const binaryPath = await resolveClaudeBinaryPath(root, usageCommandKind);
|
|
629
|
+
const binaryPath = await resolveClaudeBinaryPath(root, usageCommandKind, traceLogger);
|
|
584
630
|
if (!binaryPath) {
|
|
631
|
+
traceClaude(traceLogger, usageCommandKind, "Skipping /usage command because no Claude binary was found.");
|
|
632
|
+
traceClaude(traceLogger, usageCommandKind, "Usage returned:\n<not available>");
|
|
585
633
|
return null;
|
|
586
634
|
}
|
|
587
635
|
try {
|
|
636
|
+
traceClaude(traceLogger, usageCommandKind, `Running /usage command with ${binaryPath} (TZ=UTC).`);
|
|
588
637
|
const { stdout, stderr } = await execFileAsync(binaryPath, ["-p", "/usage"], {
|
|
589
638
|
encoding: "utf8",
|
|
639
|
+
env: buildClaudeCommandEnvironment(),
|
|
590
640
|
maxBuffer: 1024 * 1024,
|
|
591
641
|
timeout: 15000,
|
|
592
642
|
windowsHide: true
|
|
593
643
|
});
|
|
594
644
|
const combined = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
645
|
+
traceClaude(traceLogger, usageCommandKind, "Usage command completed successfully.");
|
|
646
|
+
traceClaude(traceLogger, usageCommandKind, `Usage returned:\n${describeUsageOutput(combined || null)}`);
|
|
595
647
|
return combined || null;
|
|
596
648
|
}
|
|
597
649
|
catch (error) {
|
|
598
650
|
const combined = extractExecOutput(error);
|
|
651
|
+
traceClaude(traceLogger, usageCommandKind, `Usage command failed: ${formatErrorMessage(error)}.`);
|
|
652
|
+
traceClaude(traceLogger, usageCommandKind, `Usage returned:\n${describeUsageOutput(combined || null)}`);
|
|
599
653
|
return combined || null;
|
|
600
654
|
}
|
|
601
655
|
})();
|
|
@@ -610,51 +664,68 @@ function extractExecOutput(error) {
|
|
|
610
664
|
const stderr = typeof error.stderr === "string" ? error.stderr : "";
|
|
611
665
|
return [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
612
666
|
}
|
|
613
|
-
async function resolveClaudeBinaryPath(root, usageCommandKind) {
|
|
667
|
+
async function resolveClaudeBinaryPath(root, usageCommandKind, traceLogger) {
|
|
614
668
|
const cacheKey = `${usageCommandKind}:${path.resolve(root)}`;
|
|
615
669
|
const cached = claudeBinaryPathCache.get(cacheKey);
|
|
616
670
|
if (cached) {
|
|
617
|
-
|
|
671
|
+
const binaryPath = await cached;
|
|
672
|
+
traceClaude(traceLogger, usageCommandKind, `Binary detection cache hit: ${binaryPath ? `found ${binaryPath}` : "not found"}.`);
|
|
673
|
+
return binaryPath;
|
|
618
674
|
}
|
|
619
|
-
const pending =
|
|
620
|
-
|
|
621
|
-
|
|
675
|
+
const pending = (async () => {
|
|
676
|
+
traceClaude(traceLogger, usageCommandKind, `Starting binary detection under ${root}.`);
|
|
677
|
+
const binaryPath = usageCommandKind === "vscode"
|
|
678
|
+
? await resolveVsCodeClaudeBinaryPath(root, traceLogger)
|
|
679
|
+
: await resolveCliClaudeBinaryPath(root, traceLogger);
|
|
680
|
+
traceClaude(traceLogger, usageCommandKind, `Binary detection result: ${binaryPath ? `found ${binaryPath}` : "not found"}.`);
|
|
681
|
+
return binaryPath;
|
|
682
|
+
})();
|
|
622
683
|
claudeBinaryPathCache.set(cacheKey, pending);
|
|
623
684
|
return pending;
|
|
624
685
|
}
|
|
625
|
-
async function resolveVsCodeClaudeBinaryPath(root) {
|
|
686
|
+
async function resolveVsCodeClaudeBinaryPath(root, traceLogger) {
|
|
626
687
|
const boosterDirectories = [
|
|
627
688
|
path.join(root, ".vscode", "extensions"),
|
|
628
689
|
path.join(root, ".vscode-server", "extensions"),
|
|
629
690
|
path.join(root, ".vscode-server-insiders", "extensions")
|
|
630
691
|
];
|
|
692
|
+
let firstFoundPath = null;
|
|
631
693
|
for (const directory of boosterDirectories) {
|
|
632
|
-
const binaryPath = await resolveClaudeBinaryFromExtensionDirectory(directory);
|
|
633
|
-
if (binaryPath) {
|
|
634
|
-
|
|
694
|
+
const binaryPath = await resolveClaudeBinaryFromExtensionDirectory(directory, traceLogger);
|
|
695
|
+
if (!firstFoundPath && binaryPath) {
|
|
696
|
+
firstFoundPath = binaryPath;
|
|
635
697
|
}
|
|
636
698
|
}
|
|
637
|
-
return
|
|
699
|
+
return firstFoundPath;
|
|
638
700
|
}
|
|
639
|
-
async function resolveClaudeBinaryFromExtensionDirectory(directory) {
|
|
701
|
+
async function resolveClaudeBinaryFromExtensionDirectory(directory, traceLogger) {
|
|
640
702
|
let entries;
|
|
703
|
+
traceClaude(traceLogger, "vscode", `Scanning extension directory ${directory}.`);
|
|
641
704
|
try {
|
|
642
705
|
entries = await fs.promises.readdir(directory, { withFileTypes: true });
|
|
643
706
|
}
|
|
644
|
-
catch {
|
|
707
|
+
catch (error) {
|
|
708
|
+
traceClaude(traceLogger, "vscode", `Could not read ${directory}: ${formatErrorMessage(error)}.`);
|
|
645
709
|
return null;
|
|
646
710
|
}
|
|
647
711
|
const candidates = entries
|
|
648
712
|
.filter((entry) => entry.isDirectory() && entry.name.startsWith(VSCODE_CLAUDE_EXTENSION_PREFIX))
|
|
649
713
|
.map((entry) => entry.name)
|
|
650
714
|
.sort(compareClaudeExtensionDirectoryNames);
|
|
715
|
+
if (candidates.length === 0) {
|
|
716
|
+
traceClaude(traceLogger, "vscode", `No Claude VSCode extension candidates found in ${directory}.`);
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
let firstFoundPath = null;
|
|
651
720
|
for (const candidate of candidates) {
|
|
652
721
|
const binaryPath = path.join(directory, candidate, "resources", "native-binary", "claude");
|
|
653
|
-
|
|
654
|
-
|
|
722
|
+
const accessCheck = await checkReadableExecutableFile(binaryPath);
|
|
723
|
+
traceClaude(traceLogger, "vscode", `Checked ${binaryPath} -> ${accessCheck.ok ? "success" : `failure (${accessCheck.errorMessage ?? "unknown"})`}.`);
|
|
724
|
+
if (!firstFoundPath && accessCheck.ok) {
|
|
725
|
+
firstFoundPath = binaryPath;
|
|
655
726
|
}
|
|
656
727
|
}
|
|
657
|
-
return
|
|
728
|
+
return firstFoundPath;
|
|
658
729
|
}
|
|
659
730
|
function compareClaudeExtensionDirectoryNames(left, right) {
|
|
660
731
|
const leftVersion = extractClaudeExtensionVersion(left);
|
|
@@ -678,25 +749,31 @@ function extractClaudeExtensionVersion(directoryName) {
|
|
|
678
749
|
.map((part) => Number(part))
|
|
679
750
|
.filter((part) => Number.isFinite(part));
|
|
680
751
|
}
|
|
681
|
-
async function resolveCliClaudeBinaryPath(root) {
|
|
752
|
+
async function resolveCliClaudeBinaryPath(root, traceLogger) {
|
|
682
753
|
const directCandidates = [
|
|
683
754
|
path.join(root, ".local", "bin", "claude"),
|
|
684
755
|
path.join(root, "bin", "claude")
|
|
685
756
|
];
|
|
757
|
+
let firstFoundPath = null;
|
|
686
758
|
for (const candidate of directCandidates) {
|
|
687
|
-
|
|
688
|
-
|
|
759
|
+
const accessCheck = await checkReadableExecutableFile(candidate);
|
|
760
|
+
traceClaude(traceLogger, "cli", `Checked ${candidate} -> ${accessCheck.ok ? "success" : `failure (${accessCheck.errorMessage ?? "unknown"})`}.`);
|
|
761
|
+
if (!firstFoundPath && accessCheck.ok) {
|
|
762
|
+
firstFoundPath = candidate;
|
|
689
763
|
}
|
|
690
764
|
}
|
|
691
|
-
return
|
|
765
|
+
return firstFoundPath;
|
|
692
766
|
}
|
|
693
|
-
async function
|
|
767
|
+
async function checkReadableExecutableFile(filePath) {
|
|
694
768
|
try {
|
|
695
769
|
await fs.promises.access(filePath, fs.constants.R_OK | fs.constants.X_OK);
|
|
696
|
-
return true;
|
|
770
|
+
return { ok: true };
|
|
697
771
|
}
|
|
698
|
-
catch {
|
|
699
|
-
return
|
|
772
|
+
catch (error) {
|
|
773
|
+
return {
|
|
774
|
+
ok: false,
|
|
775
|
+
errorMessage: formatErrorMessage(error)
|
|
776
|
+
};
|
|
700
777
|
}
|
|
701
778
|
}
|
|
702
779
|
function parseLiveUsageWindowSnapshots(usageOutput, now) {
|