openlimiter 2.0.2 → 2.0.4
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 +3 -1
- package/dist/api-spend.d.ts +27 -0
- package/dist/api-spend.d.ts.map +1 -0
- package/dist/api-spend.js +109 -0
- package/dist/api-spend.js.map +1 -0
- package/dist/bin.js +1 -0
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +13 -20
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +153 -56
- package/dist/cli.js.map +1 -1
- package/dist/codex-device-login.d.ts.map +1 -1
- package/dist/codex-device-login.js +1 -0
- package/dist/codex-device-login.js.map +1 -1
- package/dist/config.d.ts +4 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +23 -5
- package/dist/config.js.map +1 -1
- package/dist/hub-auth.d.ts +2 -0
- package/dist/hub-auth.d.ts.map +1 -1
- package/dist/hub-auth.js +18 -1
- package/dist/hub-auth.js.map +1 -1
- package/dist/hub-sync.d.ts +4 -2
- package/dist/hub-sync.d.ts.map +1 -1
- package/dist/hub-sync.js +40 -9
- package/dist/hub-sync.js.map +1 -1
- package/dist/ingest.d.ts +26 -0
- package/dist/ingest.d.ts.map +1 -1
- package/dist/ingest.js +46 -62
- package/dist/ingest.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +42 -11
- package/dist/render.js.map +1 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +1 -4
- package/dist/session.js.map +1 -1
- package/dist/statusline-wrapper.d.ts.map +1 -1
- package/dist/statusline-wrapper.js +2 -1
- package/dist/statusline-wrapper.js.map +1 -1
- package/dist/statusline.d.ts +5 -1
- package/dist/statusline.d.ts.map +1 -1
- package/dist/statusline.js +200 -34
- package/dist/statusline.js.map +1 -1
- package/dist/terminal-backup.d.ts +85 -1
- package/dist/terminal-backup.d.ts.map +1 -1
- package/dist/terminal-backup.js +148 -22
- package/dist/terminal-backup.js.map +1 -1
- package/dist/terminal-fallback.d.ts.map +1 -1
- package/dist/terminal-fallback.js +13 -3
- package/dist/terminal-fallback.js.map +1 -1
- package/dist/terminal-toml.d.ts +8 -0
- package/dist/terminal-toml.d.ts.map +1 -1
- package/dist/terminal-toml.js +59 -2
- package/dist/terminal-toml.js.map +1 -1
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +149 -40
- package/dist/terminal.js.map +1 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { ACQUISITION_OUTCOME_SENTENCE, captureStatuslineAccount, pruneSnapshotCache, ACQUISITION_PROVIDERS, registerManagedCodexAccount, CREDENTIAL_FAILURE_SENTENCE, PROVIDER_CODES, acquireRefreshLock, clearRefreshSpawnFailure, mergeAcquiredSnapshots, createFetchTransport, readRefreshSpawnFailure,
|
|
2
|
-
import { antigravityFixture, claudeFixture, codexFixture, grokFixture, kimiFixture, connectors, manualFixture, opencodeFixture, openrouterFixture,
|
|
3
|
-
import { AGENT_COMPATIBILITY, agentContextFromCache, agentContextSpillFromCache, agentVersionCompatibility, changeAgentHook, detectAgentInstallation, readAgentHookStatus, runAgentHook, validateAgentExecutableStamp, writeAgentContextSnapshot, loadHostedContextTrust,
|
|
1
|
+
import { ACQUISITION_OUTCOME_SENTENCE, captureStatuslineAccount, pruneSnapshotCache, ACQUISITION_PROVIDERS, registerManagedCodexAccount, CREDENTIAL_FAILURE_SENTENCE, PROVIDER_CODES, acquireRefreshLock, clearRefreshSpawnFailure, mergeAcquiredSnapshots, createFetchTransport, readRefreshSpawnFailure, buildAdvice, canonicalJson, claudeSpec, codexSpec, cursorSpec, dedupeFailures, desktopHoldsCache, failureFromConnectorReason, freshness, geminiCliSpec, grokSpec, isProviderDue, kimiSpec, mergeSnapshotCache, mergeSnapshots, normalizeMeters, normalizeMetersReport, openrouterSpec, readAcquisitionCredential, readAcquisitionSchedule, readSnapshotCache, readWindowsCredentialWith, resolveStateDirectory, runAcquisition, spawnDetachedRefresh, trustedHelperWorkingDirectory, writeAcquisitionSchedule, windowsSystemTool } from "@openlimiter/core";
|
|
2
|
+
import { antigravityFixture, claudeFixture, codexFixture, grokFixture, kimiFixture, connectors, manualFixture, opencodeFixture, openrouterFixture, parseAntigravityPayload, parseClaudePayload, parseCodexPayload, parseCursorPayload, parseGeminiCliPayload, parseGrokPayload, parseKimiPayload, parseManualPayload, parseOpencodePayload, parseOpenrouterPayload } from "@openlimiter/connectors";
|
|
3
|
+
import { AGENT_COMPATIBILITY, agentContextFromCache, agentContextSpillFromCache, agentVersionCompatibility, changeAgentHook, detectAgentInstallation, readAgentHookStatus, runAgentHook, validateAgentExecutableStamp, writeAgentContextSnapshot, loadHostedContextTrust, } from "@openlimiter/adapters";
|
|
4
4
|
import { execFile, spawn } from "node:child_process";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import packageJson from "../package.json" with { type: "json" };
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { PROVIDER_KEYS, STATUSLINE_KEYS, defaultConfig, initialize, isProviderKey, isStatuslineKey, providerValueText, readConfig, readProvidersConfig, readStatuslineConfig, setProviderValue, setStatuslineValue, statuslineValueText, writeConfig } from "./config.js";
|
|
10
|
-
import { ACQUISITION_PROVENANCE, ANTIGRAVITY_STATUSLINE_PROVENANCE, GROK_STATUSLINE_PROVENANCE, INGEST_PROVENANCE, MANUAL_PROVENANCE, STATUSLINE_PROVENANCE, environmentWithLocalMarkers, STDIN_BYTE_LIMIT, parseAntigravityStatuslinePayload, parseGrokStatuslinePayload, parseJsonText, persistSnapshots, readManualDocument, withProvenance } from "./ingest.js";
|
|
10
|
+
import { ACQUISITION_PROVENANCE, ANTIGRAVITY_STATUSLINE_PROVENANCE, GROK_STATUSLINE_PROVENANCE, INGEST_PROVENANCE, MANUAL_PROVENANCE, STATUSLINE_PROVENANCE, environmentWithLocalMarkers, STDIN_BYTE_LIMIT, parseAntigravityStatuslinePayload, antigravityStatuslineAccount, parseGrokStatuslinePayload, parseJsonText, persistSnapshots, clearStatuslineFailure, readStatuslineFailure, recordStatuslineFailure, readManualDocument, withProvenance } from "./ingest.js";
|
|
11
11
|
import { UnavailableCredentialStore } from "./credentials.js";
|
|
12
12
|
import { failureLine, failureLines, renderTable, supportsColor } from "./render.js";
|
|
13
|
-
import { isStatuslineHost, renderStatuslineLayout, statuslineColor, statuslineUnicode } from "./statusline.js";
|
|
13
|
+
import { isStatuslineHost, renderPlainStatusline, renderStatuslineLayout, resolveProviderOrder, statuslineColor, statuslineUnicode } from "./statusline.js";
|
|
14
14
|
import { parseStatuslineSession } from "./statusline-ingest.js";
|
|
15
|
+
import { readApiSpend } from "./api-spend.js";
|
|
15
16
|
import { TERMINAL_HOST_NAMES, installHost, terminalHide, terminalShow, terminalStatusTable, uninstallHost } from "./terminal.js";
|
|
16
17
|
import { createFetchHubTransport, hubConfigured } from "./hub.js";
|
|
17
18
|
import { DELIVERY_UNCONFIRMED_SENTENCE, REVOKED_SENTENCE, ensureFreshSession, isAborted, runDeviceLogin } from "./hub-auth.js";
|
|
@@ -48,8 +49,8 @@ function defaults() {
|
|
|
48
49
|
throw new Error("No acquisition transport was injected");
|
|
49
50
|
},
|
|
50
51
|
spawnDetached: () => undefined,
|
|
51
|
-
probeAntigravity: async () => ({ ok: false, reason: "not_running" }),
|
|
52
52
|
promptChoice: async () => "",
|
|
53
|
+
interactive: false,
|
|
53
54
|
hubTransport: async () => {
|
|
54
55
|
throw new Error("No hub transport was injected");
|
|
55
56
|
},
|
|
@@ -88,7 +89,12 @@ const NEWLINE = "\n";
|
|
|
88
89
|
const execFileRunner = async (executable, helperArguments, timeoutMilliseconds) => await new Promise((resolve) => {
|
|
89
90
|
execFile(executable, [...helperArguments], (() => {
|
|
90
91
|
if (!executable.toLowerCase().endsWith("powershell.exe")) {
|
|
91
|
-
return {
|
|
92
|
+
return {
|
|
93
|
+
cwd: trustedHelperWorkingDirectory(process.platform, process.env),
|
|
94
|
+
timeout: timeoutMilliseconds,
|
|
95
|
+
maxBuffer: 262_144,
|
|
96
|
+
windowsHide: true
|
|
97
|
+
};
|
|
92
98
|
}
|
|
93
99
|
const environment = { ...process.env };
|
|
94
100
|
/* Windows environment names ignore case, but a copied process.env keeps
|
|
@@ -100,6 +106,7 @@ const execFileRunner = async (executable, helperArguments, timeoutMilliseconds)
|
|
|
100
106
|
}
|
|
101
107
|
environment["PSModulePath"] = path.win32.join(process.env["SystemRoot"] ?? "C:\\Windows", "System32", "WindowsPowerShell", "v1.0", "Modules");
|
|
102
108
|
return {
|
|
109
|
+
cwd: trustedHelperWorkingDirectory(process.platform, environment),
|
|
103
110
|
timeout: timeoutMilliseconds,
|
|
104
111
|
maxBuffer: 262_144,
|
|
105
112
|
windowsHide: true,
|
|
@@ -109,17 +116,48 @@ const execFileRunner = async (executable, helperArguments, timeoutMilliseconds)
|
|
|
109
116
|
resolve(error === null ? { ok: true, stdout } : { ok: false });
|
|
110
117
|
});
|
|
111
118
|
});
|
|
119
|
+
/** Build a direct browser opener invocation without a command shell. */
|
|
120
|
+
export function browserOpenInvocation(url, platform, environment = process.env) {
|
|
121
|
+
if (url.length > 2_048)
|
|
122
|
+
return null;
|
|
123
|
+
try {
|
|
124
|
+
if (new URL(url).protocol !== "https:")
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
if (platform === "win32") {
|
|
131
|
+
return {
|
|
132
|
+
executable: windowsSystemTool("rundll32.exe", environment),
|
|
133
|
+
arguments: ["url.dll,FileProtocolHandler", url],
|
|
134
|
+
cwd: trustedHelperWorkingDirectory(platform, environment)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return platform === "darwin"
|
|
138
|
+
? {
|
|
139
|
+
executable: "open",
|
|
140
|
+
arguments: [url],
|
|
141
|
+
cwd: trustedHelperWorkingDirectory(platform, environment)
|
|
142
|
+
}
|
|
143
|
+
: {
|
|
144
|
+
executable: "xdg-open",
|
|
145
|
+
arguments: [url],
|
|
146
|
+
cwd: trustedHelperWorkingDirectory(platform, environment)
|
|
147
|
+
};
|
|
148
|
+
}
|
|
112
149
|
/** Open a URL in the person's browser, best effort and never awaited. */
|
|
113
150
|
function openBrowserPlatform(url, platform) {
|
|
114
|
-
|
|
151
|
+
const invocation = browserOpenInvocation(url, platform);
|
|
152
|
+
if (invocation === null)
|
|
115
153
|
return;
|
|
116
|
-
const [command, commandArguments] = platform === "win32"
|
|
117
|
-
? ["cmd", ["/c", "start", "", url]]
|
|
118
|
-
: platform === "darwin"
|
|
119
|
-
? ["open", [url]]
|
|
120
|
-
: ["xdg-open", [url]];
|
|
121
154
|
try {
|
|
122
|
-
const child = spawn(
|
|
155
|
+
const child = spawn(invocation.executable, [...invocation.arguments], {
|
|
156
|
+
cwd: invocation.cwd,
|
|
157
|
+
stdio: "ignore",
|
|
158
|
+
detached: true,
|
|
159
|
+
shell: false
|
|
160
|
+
});
|
|
123
161
|
child.once("error", () => undefined);
|
|
124
162
|
child.unref();
|
|
125
163
|
}
|
|
@@ -131,13 +169,9 @@ function openBrowserPlatform(url, platform) {
|
|
|
131
169
|
export function runtimeDependencies() {
|
|
132
170
|
return {
|
|
133
171
|
acquisitionTransport: createFetchTransport(),
|
|
134
|
-
probeAntigravity: (options) => probeAntigravity({
|
|
135
|
-
...options,
|
|
136
|
-
runCommand: options?.runCommand ?? execFileRunner
|
|
137
|
-
}),
|
|
138
|
-
resolveExecutablePath: resolveAgyExecutablePath,
|
|
139
172
|
spawnDetached: (executable, argumentsList, options) => {
|
|
140
173
|
const child = spawn(executable, [...argumentsList], {
|
|
174
|
+
cwd: options.cwd,
|
|
141
175
|
detached: true,
|
|
142
176
|
stdio: "ignore",
|
|
143
177
|
windowsHide: true
|
|
@@ -281,11 +315,13 @@ async function refresh(dependencies, now) {
|
|
|
281
315
|
*/
|
|
282
316
|
export function acquisitionSpecs(providers) {
|
|
283
317
|
return [
|
|
284
|
-
claudeSpec({
|
|
318
|
+
claudeSpec({
|
|
319
|
+
parse: parseClaudePayload,
|
|
320
|
+
enabled: providers.claude.recorded && providers.claude.poll
|
|
321
|
+
}),
|
|
285
322
|
codexSpec(parseCodexPayload),
|
|
286
323
|
cursorSpec(parseCursorPayload),
|
|
287
324
|
geminiCliSpec(parseGeminiCliPayload),
|
|
288
|
-
antigravitySpec(parseAntigravityCodeAssistPayload),
|
|
289
325
|
grokSpec(parseGrokPayload),
|
|
290
326
|
kimiSpec(parseKimiPayload),
|
|
291
327
|
openrouterSpec(parseOpenrouterPayload)
|
|
@@ -330,7 +366,7 @@ function credentialReader(dependencies) {
|
|
|
330
366
|
}
|
|
331
367
|
};
|
|
332
368
|
}
|
|
333
|
-
|
|
369
|
+
const result = await readAcquisitionCredential(provider, {
|
|
334
370
|
...(dependencies.stateDirectory === undefined ? {} : { stateDirectory: dependencies.stateDirectory }),
|
|
335
371
|
platform: dependencies.platform,
|
|
336
372
|
environment: dependencies.environment,
|
|
@@ -342,6 +378,26 @@ function credentialReader(dependencies) {
|
|
|
342
378
|
readWindowsCredential: async (target) => await readWindowsCredentialWith(target, { runCommand: runner })
|
|
343
379
|
})
|
|
344
380
|
});
|
|
381
|
+
if (provider !== "CODEX" || !result.ok)
|
|
382
|
+
return result;
|
|
383
|
+
const installation = Object.prototype.hasOwnProperty.call(dependencies.detectedAgentInstallations, "codex")
|
|
384
|
+
? dependencies.detectedAgentInstallations.codex ?? null
|
|
385
|
+
: await detectAgentInstallation("codex", {
|
|
386
|
+
environment: dependencies.environment,
|
|
387
|
+
platform: dependencies.platform,
|
|
388
|
+
runCommand: async (executable, argumentsList, timeoutMilliseconds) => {
|
|
389
|
+
const command = await execFileRunner(executable, argumentsList, timeoutMilliseconds);
|
|
390
|
+
return command.ok
|
|
391
|
+
? { ok: true, stdout: command.stdout, stderr: "" }
|
|
392
|
+
: command;
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
if (installation === null)
|
|
396
|
+
return { ok: false, reason: "absent" };
|
|
397
|
+
return {
|
|
398
|
+
ok: true,
|
|
399
|
+
credential: { ...result.credential, executable: installation.executable }
|
|
400
|
+
};
|
|
345
401
|
};
|
|
346
402
|
}
|
|
347
403
|
/**
|
|
@@ -423,6 +479,12 @@ async function acquisitionStatusRow(spec, schedule, readCredential, snapshots, n
|
|
|
423
479
|
nextAttemptAt
|
|
424
480
|
};
|
|
425
481
|
}
|
|
482
|
+
/** What each status line write failure means, in words with no dashes. */
|
|
483
|
+
const STATUSLINE_FAILURE_SENTENCE = {
|
|
484
|
+
ingest: "the last status line payload could not be read",
|
|
485
|
+
cache_write: "the last status line reading could not be saved to the cache",
|
|
486
|
+
agent_context: "the last status line reading was saved, the agent context file could not be written"
|
|
487
|
+
};
|
|
426
488
|
/** The acquisition block doctor prints under the connector block. */
|
|
427
489
|
async function acquisitionDoctorRows(dependencies, snapshots, now) {
|
|
428
490
|
const providers = await readProvidersConfig(dependencies.stateDirectory);
|
|
@@ -437,29 +499,16 @@ async function acquisitionDoctorRows(dependencies, snapshots, now) {
|
|
|
437
499
|
rows.push("REFRESH SPAWN FAILED " + failedAt +
|
|
438
500
|
" the background refresh could not be started on this machine");
|
|
439
501
|
}
|
|
502
|
+
const statusline = await readStatuslineFailure(dependencies.stateDirectory);
|
|
503
|
+
if (statusline !== null) {
|
|
504
|
+
rows.push(["STATUSLINE WRITE FAILED", statusline.at, statusline.stage, statusline.errorClass,
|
|
505
|
+
STATUSLINE_FAILURE_SENTENCE[statusline.stage]].join(" "));
|
|
506
|
+
}
|
|
440
507
|
return [ACQUISITION_HEADER, ...rows].join(NEWLINE);
|
|
441
508
|
}
|
|
442
|
-
/**
|
|
443
|
-
* Where a person can still see a reading this row could not take.
|
|
444
|
-
*
|
|
445
|
-
* Antigravity and Gemini CLI meter the same Google Code Assist pool. When
|
|
446
|
-
* Google withholds the reading from this client but another tool on the machine
|
|
447
|
-
* has already written one, the honest thing is to point at it rather than leave
|
|
448
|
-
* a bare refusal, so the person knows the number exists and where.
|
|
449
|
-
*/
|
|
450
|
-
function sharedQuotaNote(row, snapshots) {
|
|
451
|
-
if (row.provider !== "ANTIGRAVITY")
|
|
452
|
-
return null;
|
|
453
|
-
return snapshots.some((snapshot) => snapshot.provider === "GEMINI_CLI")
|
|
454
|
-
? "the shared Code Assist quota is shown under gemini_cli"
|
|
455
|
-
: null;
|
|
456
|
-
}
|
|
457
509
|
/** One row of the refresh report, in the space separated grammar doctor uses. */
|
|
458
|
-
function acquisitionLine(row,
|
|
459
|
-
const
|
|
460
|
-
const note = [row.reason ?? row.disclosure ?? "", shared ?? ""]
|
|
461
|
-
.filter((part) => part !== "")
|
|
462
|
-
.join(", ");
|
|
510
|
+
function acquisitionLine(row, _snapshots = []) {
|
|
511
|
+
const note = row.reason ?? row.disclosure ?? "";
|
|
463
512
|
return [
|
|
464
513
|
row.provider.toLowerCase(),
|
|
465
514
|
row.detected ? "yes" : "no",
|
|
@@ -510,13 +559,7 @@ async function refreshCommand(dependencies, now) {
|
|
|
510
559
|
now,
|
|
511
560
|
schedule,
|
|
512
561
|
readCredential: credentialReader(dependencies),
|
|
513
|
-
stamp: acquisitionStamp
|
|
514
|
-
...(dependencies.probeAntigravity === undefined
|
|
515
|
-
? {}
|
|
516
|
-
: { probeAntigravity: dependencies.probeAntigravity }),
|
|
517
|
-
...(dependencies.resolveExecutablePath === undefined
|
|
518
|
-
? {}
|
|
519
|
-
: { resolveExecutablePath: dependencies.resolveExecutablePath })
|
|
562
|
+
stamp: acquisitionStamp
|
|
520
563
|
});
|
|
521
564
|
/*
|
|
522
565
|
* Ownership is checked before every write, not once at the start. A round
|
|
@@ -702,12 +745,15 @@ async function ingestStandardInput(dependencies, now, host = "claude") {
|
|
|
702
745
|
if (host === "codex" || host === "shell")
|
|
703
746
|
return null;
|
|
704
747
|
try {
|
|
705
|
-
const
|
|
748
|
+
const accountIdBeforePayload = host === "antigravity" ? null : await captureStatuslineAccount(host === "grok" ? "GROK" : "CLAUDE", {
|
|
706
749
|
environment: dependencies.environment, homeDirectory: dependencies.homeDirectory, platform: dependencies.platform, ...(dependencies.stateDirectory ? { stateDirectory: dependencies.stateDirectory } : {}), now
|
|
707
750
|
});
|
|
708
751
|
const document = parseJsonText(await dependencies.readStandardInput());
|
|
709
752
|
if (!document.ok)
|
|
710
753
|
return null;
|
|
754
|
+
const accountId = host === "antigravity"
|
|
755
|
+
? antigravityStatuslineAccount(document.value)
|
|
756
|
+
: accountIdBeforePayload;
|
|
711
757
|
const meters = host === "antigravity"
|
|
712
758
|
? parseAntigravityStatuslinePayload(document.value, now)
|
|
713
759
|
: host === "grok"
|
|
@@ -725,22 +771,37 @@ async function ingestStandardInput(dependencies, now, host = "claude") {
|
|
|
725
771
|
const incoming = normalizeMeters(withProvenance(meters, provenance).map(meter => ({ ...meter, ...(accountId ? { accountId } : {}) })));
|
|
726
772
|
if (incoming.length === 0)
|
|
727
773
|
return { snapshots: null, payload: document.value };
|
|
774
|
+
/* The render still draws when a write fails, so each failure is kept for
|
|
775
|
+
doctor by its stage, and the next full success clears it. */
|
|
776
|
+
let merged;
|
|
728
777
|
try {
|
|
729
|
-
|
|
730
|
-
return { snapshots: merged, payload: document.value };
|
|
778
|
+
merged = (await mergeSnapshotCache(incoming, dependencies.stateDirectory ?? resolveStateDirectory(), Date.parse(now))).merged;
|
|
731
779
|
}
|
|
732
|
-
catch {
|
|
780
|
+
catch (error) {
|
|
781
|
+
await recordStatuslineFailure(dependencies.stateDirectory, now, "cache_write", error);
|
|
733
782
|
const existing = await cachedSnapshots(dependencies.stateDirectory);
|
|
734
783
|
return { snapshots: mergeSnapshots(existing, incoming), payload: document.value };
|
|
735
784
|
}
|
|
785
|
+
try {
|
|
786
|
+
await writeAgentContextSnapshot(merged, dependencies.stateDirectory, now);
|
|
787
|
+
await clearStatuslineFailure(dependencies.stateDirectory);
|
|
788
|
+
}
|
|
789
|
+
catch (error) {
|
|
790
|
+
await recordStatuslineFailure(dependencies.stateDirectory, now, "agent_context", error);
|
|
791
|
+
}
|
|
792
|
+
return { snapshots: merged, payload: document.value };
|
|
736
793
|
}
|
|
737
|
-
catch {
|
|
794
|
+
catch (error) {
|
|
795
|
+
await recordStatuslineFailure(dependencies.stateDirectory, now, "ingest", error);
|
|
738
796
|
return null;
|
|
739
797
|
}
|
|
740
798
|
}
|
|
741
799
|
async function initCommand(dependencies) {
|
|
742
800
|
const environment = await environmentWithLocalMarkers(dependencies.environment, dependencies.stateDirectory);
|
|
743
801
|
try {
|
|
802
|
+
if (!(await ensureClaudePollChoice(dependencies))) {
|
|
803
|
+
return fail(EXIT_FAILURE, "openlimiter init: configuration could not be written, so Claude usage stays off.");
|
|
804
|
+
}
|
|
744
805
|
const result = await initialize(environment, dependencies.credentialStore, dependencies.promptForSecret, dependencies.stateDirectory);
|
|
745
806
|
const detected = result.config.connectors
|
|
746
807
|
.filter((connector) => connector.detected)
|
|
@@ -951,14 +1012,16 @@ async function statuslineCommand(dependencies, argumentsList, now) {
|
|
|
951
1012
|
}
|
|
952
1013
|
const advice = buildAdvice(snapshots, now, PROVIDER_CODES);
|
|
953
1014
|
const config = await readStatuslineConfig(dependencies.stateDirectory);
|
|
954
|
-
if (!config.bars)
|
|
955
|
-
return succeed(
|
|
1015
|
+
if (!config.bars) {
|
|
1016
|
+
return succeed(renderPlainStatusline(advice, snapshots, now, resolveProviderOrder(config.order), config.meters));
|
|
1017
|
+
}
|
|
956
1018
|
return succeed(renderStatuslineLayout({
|
|
957
1019
|
advice,
|
|
958
1020
|
snapshots,
|
|
959
1021
|
now,
|
|
960
1022
|
config,
|
|
961
1023
|
session: parseStatuslineSession(ingested?.payload),
|
|
1024
|
+
apiSpend: await readApiSpend(dependencies.stateDirectory ?? resolveStateDirectory()),
|
|
962
1025
|
color: statuslineColor(config.color, dependencies.environment, dependencies.colorOutput, host),
|
|
963
1026
|
unicode: statuslineUnicode(dependencies.environment),
|
|
964
1027
|
host
|
|
@@ -1532,10 +1595,39 @@ async function triggerSyncAfterRefresh(dependencies, now) {
|
|
|
1532
1595
|
}
|
|
1533
1596
|
/* -------------------------------------------------------------- setup */
|
|
1534
1597
|
const SETUP_SIGN_IN_PROMPT = "Sign in to sync your bars to the hub and your phone (free)";
|
|
1598
|
+
const CLAUDE_POLL_DISCLOSURE = "OpenLimiter can read the same usage Claude shows by sending a direct usage request with your Claude sign in on this computer. Anthropic's policy for tools like this is unresolved.";
|
|
1599
|
+
const CLAUDE_POLL_QUESTION = "Show Fable limit? Enter to turn it on, N to keep it off: ";
|
|
1535
1600
|
async function promptOrSkip(dependencies, question) {
|
|
1601
|
+
if (!dependencies.interactive)
|
|
1602
|
+
return false;
|
|
1536
1603
|
const answer = await dependencies.promptChoice?.(question) ?? "";
|
|
1537
1604
|
return !answer.trim().toLowerCase().startsWith("s");
|
|
1538
1605
|
}
|
|
1606
|
+
/** Record one real Claude polling choice before any terminal acquisition. */
|
|
1607
|
+
async function ensureClaudePollChoice(dependencies) {
|
|
1608
|
+
const stored = await readConfig(dependencies.stateDirectory);
|
|
1609
|
+
if (!stored.ok && stored.reason !== "missing")
|
|
1610
|
+
return false;
|
|
1611
|
+
const config = stored.ok ? stored.config : defaultConfig(dependencies.environment);
|
|
1612
|
+
if (config.providers.claude.recorded)
|
|
1613
|
+
return true;
|
|
1614
|
+
let enabled = false;
|
|
1615
|
+
if (dependencies.interactive) {
|
|
1616
|
+
dependencies.emit(CLAUDE_POLL_DISCLOSURE);
|
|
1617
|
+
const answer = await dependencies.promptChoice?.(CLAUDE_POLL_QUESTION) ?? "n";
|
|
1618
|
+
enabled = !answer.trim().toLowerCase().startsWith("n");
|
|
1619
|
+
}
|
|
1620
|
+
try {
|
|
1621
|
+
await writeConfig({
|
|
1622
|
+
...config,
|
|
1623
|
+
providers: { claude: { poll: enabled, recorded: true } }
|
|
1624
|
+
}, dependencies.stateDirectory);
|
|
1625
|
+
return true;
|
|
1626
|
+
}
|
|
1627
|
+
catch {
|
|
1628
|
+
return false;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1539
1631
|
/** Step one: sign in, or say plainly why this machine did not. */
|
|
1540
1632
|
async function setupSignInStep(dependencies) {
|
|
1541
1633
|
const lines = ["1. Sign in", SETUP_SIGN_IN_PROMPT];
|
|
@@ -1750,6 +1842,9 @@ async function setupShowBarsStep(dependencies) {
|
|
|
1750
1842
|
async function setupCommand(dependencies, now) {
|
|
1751
1843
|
await setupSignInStep(dependencies);
|
|
1752
1844
|
await setupConnectStep(dependencies);
|
|
1845
|
+
if (!(await ensureClaudePollChoice(dependencies))) {
|
|
1846
|
+
dependencies.emit("Claude usage stays off because the choice could not be saved.");
|
|
1847
|
+
}
|
|
1753
1848
|
const collected = await refreshCommand(dependencies, now);
|
|
1754
1849
|
if (collected.exitCode !== 0 && collected.stderr !== "")
|
|
1755
1850
|
dependencies.emit(collected.stderr);
|
|
@@ -1761,9 +1856,11 @@ async function setupCommand(dependencies, now) {
|
|
|
1761
1856
|
export async function runCli(argumentsList, overrides = {}) {
|
|
1762
1857
|
const setupOutput = [];
|
|
1763
1858
|
const hasOutputSink = overrides.emit !== undefined;
|
|
1859
|
+
const interactive = overrides.interactive ?? overrides.promptChoice !== undefined;
|
|
1764
1860
|
const dependencies = {
|
|
1765
1861
|
...defaults(),
|
|
1766
1862
|
...overrides,
|
|
1863
|
+
interactive,
|
|
1767
1864
|
...(hasOutputSink ? {} : { emit: (line) => setupOutput.push(line) })
|
|
1768
1865
|
};
|
|
1769
1866
|
const command = argumentsList[0] ?? "setup";
|