tokensniff 0.1.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/LICENSE +21 -0
- package/README.md +809 -0
- package/bin/tokensniff-status.js +8 -0
- package/bin/tokensniff.js +8 -0
- package/dist/cli/run.d.ts +64 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli.js +2206 -0
- package/dist/cli.js.map +1 -0
- package/dist/collector/config.d.ts +43 -0
- package/dist/collector/config.d.ts.map +1 -0
- package/dist/collector/index.d.ts +14 -0
- package/dist/collector/index.d.ts.map +1 -0
- package/dist/collector/parse.d.ts +42 -0
- package/dist/collector/parse.d.ts.map +1 -0
- package/dist/collector/store.d.ts +76 -0
- package/dist/collector/store.d.ts.map +1 -0
- package/dist/dashboard/heatmap.d.ts +34 -0
- package/dist/dashboard/heatmap.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2474 -0
- package/dist/index.js.map +1 -0
- package/dist/pricing.js +145 -0
- package/dist/pricing.js.map +1 -0
- package/dist/proxy.js +1744 -0
- package/dist/proxy.js.map +1 -0
- package/dist/renderer/format.d.ts +33 -0
- package/dist/renderer/format.d.ts.map +1 -0
- package/dist/renderer/index.d.ts +42 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/schema.js +90 -0
- package/dist/schema.js.map +1 -0
- package/dist/shared/pricing.d.ts +60 -0
- package/dist/shared/pricing.d.ts.map +1 -0
- package/dist/shared/schema.d.ts +122 -0
- package/dist/shared/schema.d.ts.map +1 -0
- package/dist/status.js +521 -0
- package/dist/status.js.map +1 -0
- package/package.json +105 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/shared/pricing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAC/C;AA0BD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAgBvD;AAWD;;;GAGG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAuE9D;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,qBAA0B,GACpC,OAAO,CAAC,UAAU,CAAC,CAwDrB;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAmBlD;AAED;;;GAGG;AACH,wBAAgB,OAAO,CACrB,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE;IACL,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B,GACA,MAAM,CAoBR"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tokensniff — Canonical Domain Telemetry Schema (v1 Production)
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for telemetry interfaces, runtime validation guards,
|
|
5
|
+
* safe default factories, and agent state text parsing.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SCHEMA_VERSION: 1;
|
|
8
|
+
/**
|
|
9
|
+
* Summary representation of an aggregated tool call across a single turn.
|
|
10
|
+
*/
|
|
11
|
+
export interface ToolSummaryItem {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
readonly count: number;
|
|
14
|
+
readonly arg_tk: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Granular record of an individual tool call during an execution turn.
|
|
18
|
+
*/
|
|
19
|
+
export interface ToolCallItem {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly argTk: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Classification of the primary driver of a model turn.
|
|
25
|
+
*/
|
|
26
|
+
export type TurnKind = 'text' | 'tool' | 'thinking' | 'label' | 'error' | 'unknown';
|
|
27
|
+
/**
|
|
28
|
+
* Snapshot of provider upstream rate limits and token bucket quotas.
|
|
29
|
+
*/
|
|
30
|
+
export interface QuotaSnapshot {
|
|
31
|
+
readonly remainingPct: number | null;
|
|
32
|
+
readonly remainingFraction: number | null;
|
|
33
|
+
readonly resetTime: string | null;
|
|
34
|
+
readonly tier: string | null;
|
|
35
|
+
readonly isRateLimited: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Raw telemetry extracted directly from a single network interaction.
|
|
39
|
+
*/
|
|
40
|
+
export interface TurnMetrics {
|
|
41
|
+
readonly model: string;
|
|
42
|
+
readonly kind: TurnKind;
|
|
43
|
+
readonly input: number;
|
|
44
|
+
readonly output: number;
|
|
45
|
+
readonly cacheRead: number;
|
|
46
|
+
readonly cacheCreate: number;
|
|
47
|
+
readonly tools: readonly ToolCallItem[];
|
|
48
|
+
readonly toolsSummary: readonly ToolSummaryItem[];
|
|
49
|
+
readonly thinking: boolean;
|
|
50
|
+
readonly thinkingTokens: number;
|
|
51
|
+
readonly stopReason: string;
|
|
52
|
+
readonly label: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Fully hydrated telemetry state persisted to ~/.tokensniff/status/<session_id>.json.
|
|
56
|
+
* Consumed directly by terminal statusline renderers and CLI dashboards.
|
|
57
|
+
*/
|
|
58
|
+
export interface TokenSniffLatest {
|
|
59
|
+
readonly v: typeof SCHEMA_VERSION;
|
|
60
|
+
readonly session_id: string;
|
|
61
|
+
readonly turn_index: number;
|
|
62
|
+
readonly ts: number;
|
|
63
|
+
readonly model: string;
|
|
64
|
+
readonly is_bg: boolean;
|
|
65
|
+
readonly ctx: number;
|
|
66
|
+
readonly window: number;
|
|
67
|
+
readonly pct: number;
|
|
68
|
+
readonly cache_read: number;
|
|
69
|
+
readonly cache_create: number;
|
|
70
|
+
readonly delta_in: number;
|
|
71
|
+
readonly in_tokens: number;
|
|
72
|
+
readonly out_tokens: number;
|
|
73
|
+
readonly thinking: boolean;
|
|
74
|
+
readonly thinking_tokens: number;
|
|
75
|
+
readonly tool: string | null;
|
|
76
|
+
readonly tool_tk: number;
|
|
77
|
+
readonly tools_summary: readonly ToolSummaryItem[];
|
|
78
|
+
readonly stop_reason: string;
|
|
79
|
+
readonly is_label: boolean;
|
|
80
|
+
readonly label: string;
|
|
81
|
+
readonly ttft_ms: number;
|
|
82
|
+
readonly tps: number;
|
|
83
|
+
readonly dur_s: number;
|
|
84
|
+
readonly cost_turn: number;
|
|
85
|
+
readonly cost_session: number;
|
|
86
|
+
readonly cost_today: number;
|
|
87
|
+
readonly error: number | null;
|
|
88
|
+
readonly status: number;
|
|
89
|
+
readonly quota_pct: number | null;
|
|
90
|
+
readonly quota_reset: string | null;
|
|
91
|
+
readonly tier: string | null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Compact append-only historical turn record for analytics and sparklines.
|
|
95
|
+
*/
|
|
96
|
+
export interface HistoryEntry {
|
|
97
|
+
readonly d: string;
|
|
98
|
+
readonly ts: number;
|
|
99
|
+
readonly s: string;
|
|
100
|
+
readonly t: number;
|
|
101
|
+
readonly m: string;
|
|
102
|
+
readonly tk: number;
|
|
103
|
+
readonly c: number;
|
|
104
|
+
readonly cr: number;
|
|
105
|
+
readonly th: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Runtime Type Guard to safely validate deserialized disk telemetry without unsafe casts.
|
|
109
|
+
*/
|
|
110
|
+
export declare function isTokenSniffLatest(value: unknown): value is TokenSniffLatest;
|
|
111
|
+
/**
|
|
112
|
+
* Factory creating fully hydrated TokenSniffLatest records with strictly sanitized defaults.
|
|
113
|
+
*/
|
|
114
|
+
export declare function buildLatest(partial: Partial<TokenSniffLatest> & {
|
|
115
|
+
readonly session_id: string;
|
|
116
|
+
}): TokenSniffLatest;
|
|
117
|
+
/**
|
|
118
|
+
* Extracts a concise, human-readable 1-line label from AI agent task descriptions.
|
|
119
|
+
* Handles compound state/detail JSON structures, stringified JSON, and fallback text.
|
|
120
|
+
*/
|
|
121
|
+
export declare function extractCleanLabel(text: unknown): string;
|
|
122
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/shared/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/status.js
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
// src/renderer/index.ts
|
|
6
|
+
function toValidPort(value, fallback) {
|
|
7
|
+
const parsed = Number(value);
|
|
8
|
+
return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65535 ? parsed : fallback;
|
|
9
|
+
}
|
|
10
|
+
function toBoundedInt(value, fallback, min = 0, max = Number.MAX_SAFE_INTEGER) {
|
|
11
|
+
const parsed = Number(value);
|
|
12
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
13
|
+
const intVal = Math.floor(parsed);
|
|
14
|
+
return Math.max(min, Math.min(max, intVal));
|
|
15
|
+
}
|
|
16
|
+
function toNonEmptyString(value, fallback) {
|
|
17
|
+
if (typeof value !== "string") return fallback;
|
|
18
|
+
const trimmed = value.trim();
|
|
19
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
20
|
+
}
|
|
21
|
+
function toSafeBoolean(value, fallback) {
|
|
22
|
+
if (typeof value === "boolean") return value;
|
|
23
|
+
if (typeof value === "string") {
|
|
24
|
+
const lower = value.trim().toLowerCase();
|
|
25
|
+
if (lower === "true" || lower === "1") return true;
|
|
26
|
+
if (lower === "false" || lower === "0") return false;
|
|
27
|
+
}
|
|
28
|
+
return fallback;
|
|
29
|
+
}
|
|
30
|
+
function normalizeDirectory(targetPath) {
|
|
31
|
+
const resolved = path.resolve(targetPath.trim());
|
|
32
|
+
return path.normalize(resolved);
|
|
33
|
+
}
|
|
34
|
+
function getGlobalDir() {
|
|
35
|
+
const home = process.env.USERPROFILE || os.homedir();
|
|
36
|
+
return path.join(home, ".tokensniff");
|
|
37
|
+
}
|
|
38
|
+
function getGlobalConfigPath() {
|
|
39
|
+
return path.join(getGlobalDir(), "config.json");
|
|
40
|
+
}
|
|
41
|
+
function loadConfig(env = process.env) {
|
|
42
|
+
const targetConfigFile = env.TOKENSNIFF_CONFIG || getGlobalConfigPath();
|
|
43
|
+
let fileConfig = {};
|
|
44
|
+
if (fs.existsSync(targetConfigFile)) {
|
|
45
|
+
try {
|
|
46
|
+
const raw = fs.readFileSync(targetConfigFile, "utf8");
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
49
|
+
fileConfig = parsed;
|
|
50
|
+
}
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
53
|
+
console.error(`[tokensniff] ignoring corrupt config at ${targetConfigFile}: ${msg}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const resolve = (envKey, configKey, defaultValue) => {
|
|
57
|
+
return env[envKey] ?? fileConfig[configKey] ?? defaultValue;
|
|
58
|
+
};
|
|
59
|
+
const rootDir = getGlobalDir();
|
|
60
|
+
const defaultStatusDir = path.join(rootDir, "status");
|
|
61
|
+
const rawStatusDirs = resolve("TOKENSNIFF_STATUS_DIRS", "statusDirs", [defaultStatusDir]);
|
|
62
|
+
const parsedDirs = Array.isArray(rawStatusDirs) ? rawStatusDirs.map(String) : String(rawStatusDirs).split(",");
|
|
63
|
+
const normalizedStatusDirs = parsedDirs.map((d) => d.trim()).filter((d) => d.length > 0).map(normalizeDirectory);
|
|
64
|
+
const statusDirs = normalizedStatusDirs.length > 0 ? normalizedStatusDirs : [normalizeDirectory(defaultStatusDir)];
|
|
65
|
+
const isNoColorSet = typeof env.NO_COLOR === "string" && env.NO_COLOR.length > 0;
|
|
66
|
+
const colorDefault = !isNoColorSet;
|
|
67
|
+
return {
|
|
68
|
+
listenPort: toValidPort(resolve("TOKENSNIFF_PORT", "listenPort", 4e3), 4e3),
|
|
69
|
+
listenHost: toNonEmptyString(
|
|
70
|
+
resolve("TOKENSNIFF_HOST", "listenHost", "127.0.0.1"),
|
|
71
|
+
"127.0.0.1"
|
|
72
|
+
),
|
|
73
|
+
upstreamHost: toNonEmptyString(
|
|
74
|
+
resolve("TOKENSNIFF_UPSTREAM_HOST", "upstreamHost", "localhost"),
|
|
75
|
+
"localhost"
|
|
76
|
+
),
|
|
77
|
+
upstreamPort: toValidPort(resolve("TOKENSNIFF_UPSTREAM_PORT", "upstreamPort", 8085), 8085),
|
|
78
|
+
upstreamTimeoutMs: toBoundedInt(
|
|
79
|
+
resolve("TOKENSNIFF_UPSTREAM_TIMEOUT_MS", "upstreamTimeoutMs", 3e5),
|
|
80
|
+
3e5,
|
|
81
|
+
1e3,
|
|
82
|
+
36e5
|
|
83
|
+
),
|
|
84
|
+
maxBodyBytes: toBoundedInt(
|
|
85
|
+
resolve("TOKENSNIFF_MAX_BODY_BYTES", "maxBodyBytes", 10 * 1024 * 1024),
|
|
86
|
+
10485760,
|
|
87
|
+
1024,
|
|
88
|
+
100 * 1024 * 1024
|
|
89
|
+
),
|
|
90
|
+
statusDirs,
|
|
91
|
+
maxSessions: toBoundedInt(resolve("TOKENSNIFF_MAX_SESSIONS", "maxSessions", 50), 50, 1, 1e3),
|
|
92
|
+
deadSessionMs: toBoundedInt(
|
|
93
|
+
resolve("TOKENSNIFF_DEAD_SESSION_MS", "deadSessionMs", 72 * 3600 * 1e3),
|
|
94
|
+
2592e5,
|
|
95
|
+
3600 * 1e3
|
|
96
|
+
),
|
|
97
|
+
staleAfterS: toBoundedInt(
|
|
98
|
+
resolve("TOKENSNIFF_STALE_AFTER_S", "staleAfterS", 120),
|
|
99
|
+
120,
|
|
100
|
+
5,
|
|
101
|
+
86400
|
|
102
|
+
),
|
|
103
|
+
labelMaxIn: toBoundedInt(resolve("TOKENSNIFF_LABEL_MAX_IN", "labelMaxIn", 2e3), 2e3, 100),
|
|
104
|
+
labelMaxOut: toBoundedInt(resolve("TOKENSNIFF_LABEL_MAX_OUT", "labelMaxOut", 60), 60, 1),
|
|
105
|
+
warnPct: toBoundedInt(resolve("TOKENSNIFF_WARN_PCT", "warnPct", 60), 60, 1, 99),
|
|
106
|
+
critPct: toBoundedInt(resolve("TOKENSNIFF_CRIT_PCT", "critPct", 80), 80, 2, 100),
|
|
107
|
+
color: colorDefault && toSafeBoolean(resolve("TOKENSNIFF_COLOR", "color", true), true),
|
|
108
|
+
upstreamCommand: toNonEmptyString(
|
|
109
|
+
resolve(
|
|
110
|
+
"TOKENSNIFF_UPSTREAM_CMD",
|
|
111
|
+
"upstreamCommand",
|
|
112
|
+
"npx antigravity-claude-proxy@latest start"
|
|
113
|
+
),
|
|
114
|
+
"npx antigravity-claude-proxy@latest start"
|
|
115
|
+
),
|
|
116
|
+
upstreamStopCommand: String(
|
|
117
|
+
resolve("TOKENSNIFF_UPSTREAM_STOP_CMD", "upstreamStopCommand", "")
|
|
118
|
+
).trim(),
|
|
119
|
+
harnessCommand: toNonEmptyString(
|
|
120
|
+
resolve("TOKENSNIFF_HARNESS_CMD", "harnessCommand", "claude"),
|
|
121
|
+
"claude"
|
|
122
|
+
)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/shared/schema.ts
|
|
127
|
+
var SCHEMA_VERSION = 1;
|
|
128
|
+
function isTokenSniffLatest(value) {
|
|
129
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const candidate = value;
|
|
133
|
+
return candidate.v === SCHEMA_VERSION && typeof candidate.session_id === "string" && typeof candidate.turn_index === "number" && typeof candidate.ts === "number" && typeof candidate.model === "string" && typeof candidate.ctx === "number" && typeof candidate.window === "number" && typeof candidate.pct === "number";
|
|
134
|
+
}
|
|
135
|
+
function extractCleanLabel(text) {
|
|
136
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
137
|
+
return "";
|
|
138
|
+
}
|
|
139
|
+
const trimmed = text.trim();
|
|
140
|
+
if (trimmed.startsWith("{")) {
|
|
141
|
+
if (trimmed.endsWith("}")) {
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(trimmed);
|
|
144
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
145
|
+
const record = parsed;
|
|
146
|
+
if (record.state && record.detail) {
|
|
147
|
+
return `${String(record.state).trim()}: ${String(record.detail).trim()}`;
|
|
148
|
+
}
|
|
149
|
+
const candidate = record.title ?? record.summary ?? record.label ?? record.message ?? record.detail ?? record.state;
|
|
150
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
151
|
+
return candidate.trim();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const stateMatch = trimmed.match(/"state"\s*:\s*"([^"\\]*)(?:"|$)/);
|
|
158
|
+
const detailMatch = trimmed.match(/"detail"\s*:\s*"([^"\\]*)(?:"|$)/);
|
|
159
|
+
if (stateMatch?.[1] && detailMatch?.[1]) {
|
|
160
|
+
return `${stateMatch[1].trim()}: ${detailMatch[1].trim()}`;
|
|
161
|
+
}
|
|
162
|
+
const singleMatch = trimmed.match(
|
|
163
|
+
/"(?:title|summary|label|message|detail|state)"\s*:\s*"([^"\\]*)(?:"|$)/
|
|
164
|
+
);
|
|
165
|
+
if (singleMatch?.[1]) {
|
|
166
|
+
return singleMatch[1].trim();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return trimmed.replace(/^["']+|["']+$/g, "").trim();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/shared/pricing.ts
|
|
173
|
+
var rateCache = /* @__PURE__ */ new Map();
|
|
174
|
+
var catalogPromise = null;
|
|
175
|
+
function normalizeModelName(raw) {
|
|
176
|
+
if (typeof raw !== "string" || raw.trim().length === 0) {
|
|
177
|
+
return "unknown";
|
|
178
|
+
}
|
|
179
|
+
const trimmed = raw.trim().toLowerCase();
|
|
180
|
+
const withoutNamespace = trimmed.replace(/^~?[a-zA-Z0-9_-]+\//, "");
|
|
181
|
+
return withoutNamespace.replace(/:[a-zA-Z0-9_-]+$/, "").replace(/-(?:tiered|thinking|preview|low|high|latest)$/, "").replace(/-(?:\d{4}-\d{2}-\d{2}|\d{8}|\d{4})$/, "").replace(/\b(\d+)-(\d+)\b/g, "$1.$2");
|
|
182
|
+
}
|
|
183
|
+
function toSafeRate(value, fallback = 0) {
|
|
184
|
+
if (!value) return fallback;
|
|
185
|
+
const parsed = Number.parseFloat(value);
|
|
186
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
187
|
+
}
|
|
188
|
+
async function syncOpenRouterCatalog() {
|
|
189
|
+
if (catalogPromise) {
|
|
190
|
+
return catalogPromise;
|
|
191
|
+
}
|
|
192
|
+
catalogPromise = (async () => {
|
|
193
|
+
try {
|
|
194
|
+
const response = await fetch("https://openrouter.ai/api/v1/models", {
|
|
195
|
+
signal: AbortSignal.timeout(5e3),
|
|
196
|
+
headers: { Accept: "application/json" }
|
|
197
|
+
});
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
const json = await response.json();
|
|
202
|
+
if (!Array.isArray(json?.data)) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
for (const item of json.data) {
|
|
206
|
+
if (!item?.id || !item.pricing) continue;
|
|
207
|
+
const promptRate = toSafeRate(item.pricing.prompt);
|
|
208
|
+
const completionRate = toSafeRate(item.pricing.completion);
|
|
209
|
+
const cacheReadRate = toSafeRate(
|
|
210
|
+
item.pricing.input_cache_read,
|
|
211
|
+
promptRate > 0 ? promptRate * 0.1 : 0
|
|
212
|
+
);
|
|
213
|
+
const cacheWriteRate = toSafeRate(item.pricing.input_cache_write, promptRate);
|
|
214
|
+
const rawWindow = item.context_length;
|
|
215
|
+
const contextWindow = typeof rawWindow === "number" && Number.isFinite(rawWindow) && rawWindow > 0 ? rawWindow : 128e3;
|
|
216
|
+
const resolvedRates = {
|
|
217
|
+
in: promptRate,
|
|
218
|
+
out: completionRate,
|
|
219
|
+
cached: cacheReadRate,
|
|
220
|
+
cacheWrite: cacheWriteRate,
|
|
221
|
+
window: contextWindow
|
|
222
|
+
};
|
|
223
|
+
const fullId = item.id.toLowerCase();
|
|
224
|
+
const bareSlug = item.id.includes("/") ? item.id.split("/")[1]?.toLowerCase() ?? fullId : fullId;
|
|
225
|
+
const cleanSlug = normalizeModelName(item.id);
|
|
226
|
+
rateCache.set(fullId, resolvedRates);
|
|
227
|
+
rateCache.set(bareSlug, resolvedRates);
|
|
228
|
+
rateCache.set(cleanSlug, resolvedRates);
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
233
|
+
} finally {
|
|
234
|
+
catalogPromise = null;
|
|
235
|
+
}
|
|
236
|
+
})();
|
|
237
|
+
return catalogPromise;
|
|
238
|
+
}
|
|
239
|
+
function ratesFor(model) {
|
|
240
|
+
const clean = normalizeModelName(model);
|
|
241
|
+
const rawKey = model.trim().toLowerCase();
|
|
242
|
+
const hit = rateCache.get(clean) ?? rateCache.get(rawKey);
|
|
243
|
+
if (hit) {
|
|
244
|
+
return hit;
|
|
245
|
+
}
|
|
246
|
+
void syncOpenRouterCatalog();
|
|
247
|
+
return {
|
|
248
|
+
in: 0,
|
|
249
|
+
out: 0,
|
|
250
|
+
cached: 0,
|
|
251
|
+
cacheWrite: 0,
|
|
252
|
+
window: 128e3
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/renderer/format.ts
|
|
257
|
+
var ANSI_RED = "\x1B[31m";
|
|
258
|
+
var ANSI_YELLOW = "\x1B[33m";
|
|
259
|
+
var ANSI_GREEN = "\x1B[32m";
|
|
260
|
+
var ANSI_RESET = "\x1B[0m";
|
|
261
|
+
function stripAnsi(text) {
|
|
262
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
263
|
+
}
|
|
264
|
+
function formatTokenCount(value) {
|
|
265
|
+
if (value == null || !Number.isFinite(value)) return "0";
|
|
266
|
+
const absolute = Math.abs(value);
|
|
267
|
+
if (absolute >= 1e6) {
|
|
268
|
+
const formatted = (value / 1e6).toFixed(1);
|
|
269
|
+
return formatted.endsWith(".0") ? `${(value / 1e6).toFixed(0)}M` : `${formatted}M`;
|
|
270
|
+
}
|
|
271
|
+
if (absolute >= 1e3) {
|
|
272
|
+
const formatted = (value / 1e3).toFixed(1);
|
|
273
|
+
return formatted.endsWith(".0") ? `${(value / 1e3).toFixed(0)}k` : `${formatted}k`;
|
|
274
|
+
}
|
|
275
|
+
return String(value);
|
|
276
|
+
}
|
|
277
|
+
function sanitizeText(text, maxLength) {
|
|
278
|
+
return String(text || "").replace(/[\r\n\t]+/g, " ").trim().slice(0, maxLength);
|
|
279
|
+
}
|
|
280
|
+
function formatToolSegment(snapshot, isCompact = false) {
|
|
281
|
+
const summary = Array.isArray(snapshot.tools_summary) && snapshot.tools_summary.length > 0 ? snapshot.tools_summary : snapshot.tool ? [{ name: snapshot.tool, count: 1, arg_tk: snapshot.tool_tk || 0 }] : [];
|
|
282
|
+
if (summary.length === 0) {
|
|
283
|
+
return snapshot.tool ? `tool: ${sanitizeText(snapshot.tool, 20)}` : "";
|
|
284
|
+
}
|
|
285
|
+
let toolNamePart = "";
|
|
286
|
+
let totalArgTk = 0;
|
|
287
|
+
for (const item of summary) {
|
|
288
|
+
totalArgTk += item.arg_tk || 0;
|
|
289
|
+
}
|
|
290
|
+
if (summary.length === 1) {
|
|
291
|
+
const item = summary[0];
|
|
292
|
+
if (item) {
|
|
293
|
+
const countSuffix = item.count > 1 ? ` x${item.count}` : "";
|
|
294
|
+
toolNamePart = `${sanitizeText(item.name, 16)}${countSuffix}`;
|
|
295
|
+
}
|
|
296
|
+
} else if (summary.length === 2) {
|
|
297
|
+
const first = summary[0];
|
|
298
|
+
const second = summary[1];
|
|
299
|
+
toolNamePart = `${sanitizeText(first?.name, 10)}+${sanitizeText(second?.name, 10)}`;
|
|
300
|
+
} else {
|
|
301
|
+
const first = summary[0];
|
|
302
|
+
const second = summary[1];
|
|
303
|
+
toolNamePart = `${sanitizeText(first?.name, 8)}+${sanitizeText(second?.name, 8)} +${summary.length - 2}more`;
|
|
304
|
+
}
|
|
305
|
+
if (isCompact) {
|
|
306
|
+
return `tool: ${toolNamePart}`;
|
|
307
|
+
}
|
|
308
|
+
const argSnippet = totalArgTk > 0 ? ` (${totalArgTk} tok)` : "";
|
|
309
|
+
return `tool: ${toolNamePart}${argSnippet}`;
|
|
310
|
+
}
|
|
311
|
+
function formatStatus(snapshot, options = {}) {
|
|
312
|
+
const opts = {
|
|
313
|
+
warnPct: 60,
|
|
314
|
+
critPct: 80,
|
|
315
|
+
color: true,
|
|
316
|
+
staleAfterS: 120,
|
|
317
|
+
now: Date.now(),
|
|
318
|
+
modelFallback: "tokensniff",
|
|
319
|
+
columns: options.columns ?? (process.env.COLUMNS ? Number.parseInt(process.env.COLUMNS, 10) : void 0) ?? process.stdout?.columns ?? 120,
|
|
320
|
+
...options
|
|
321
|
+
};
|
|
322
|
+
if (!snapshot?.ts) {
|
|
323
|
+
return `[${opts.modelFallback}] waiting for first turn...`;
|
|
324
|
+
}
|
|
325
|
+
const maxCols = Math.max(50, opts.columns);
|
|
326
|
+
const turnIndex = snapshot.turn_index ?? 1;
|
|
327
|
+
const turnPrefix = `turn ${turnIndex}`;
|
|
328
|
+
const rawModel = snapshot.model || opts.modelFallback;
|
|
329
|
+
const normalized = normalizeModelName(rawModel);
|
|
330
|
+
const cleanModel = normalized === "unknown" ? opts.modelFallback : normalized.replace(/^(?:gemini|claude|gpt|deepseek)-/, "");
|
|
331
|
+
const bgBadge = snapshot.is_bg ? " [bg]" : "";
|
|
332
|
+
const identitySegment = `${turnPrefix} [${cleanModel}]${bgBadge}`;
|
|
333
|
+
const ctxTokens = snapshot.ctx || 0;
|
|
334
|
+
const fallbackWindow = ratesFor(snapshot.model ?? "").window;
|
|
335
|
+
const ctxWindow = snapshot.window && snapshot.window > 0 ? snapshot.window : fallbackWindow;
|
|
336
|
+
const windowStr = formatTokenCount(ctxWindow);
|
|
337
|
+
const percentage = typeof snapshot.pct === "number" ? snapshot.pct : ctxTokens / ctxWindow * 100;
|
|
338
|
+
const pctStr = `${percentage.toFixed(1)}%`;
|
|
339
|
+
const cacheTokens = snapshot.cache_read ?? 0;
|
|
340
|
+
const cacheHitPct = ctxTokens > 0 ? Math.min(100, cacheTokens / ctxTokens * 100).toFixed(1) : "0.0";
|
|
341
|
+
const formatContext = (isCompact = false) => {
|
|
342
|
+
let cacheText = "";
|
|
343
|
+
if (cacheTokens > 0) {
|
|
344
|
+
cacheText = isCompact ? ` [cache: ${cacheHitPct}%]` : ` [cache: ${formatTokenCount(cacheTokens)} (${cacheHitPct}%)]`;
|
|
345
|
+
}
|
|
346
|
+
const raw = `ctx: ${formatTokenCount(ctxTokens)}/${windowStr} (${pctStr})${cacheText}`;
|
|
347
|
+
if (!opts.color) return raw;
|
|
348
|
+
const colorCode = percentage >= opts.critPct ? ANSI_RED : percentage >= opts.warnPct ? ANSI_YELLOW : ANSI_GREEN;
|
|
349
|
+
return `${colorCode}${raw}${ANSI_RESET}`;
|
|
350
|
+
};
|
|
351
|
+
if (snapshot.error && (snapshot.error === 429 || snapshot.error >= 500)) {
|
|
352
|
+
const turnCostStr = `$${Number(snapshot.cost_turn || 0).toFixed(4)}`;
|
|
353
|
+
const sessCostStr = `$${Number(snapshot.cost_session || 0).toFixed(3)}`;
|
|
354
|
+
const costSegment = `cost: turn ${turnCostStr}, sess ${sessCostStr}`;
|
|
355
|
+
return `${identitySegment} | ${formatContext(true)} | error: rate limit ${snapshot.error} (retrying...) | ${costSegment}`;
|
|
356
|
+
}
|
|
357
|
+
const toolSegment = snapshot.tool || Array.isArray(snapshot.tools_summary) && snapshot.tools_summary.length > 0 ? formatToolSegment(snapshot, false) : "";
|
|
358
|
+
const labelSegment = snapshot.is_label && snapshot.label ? `label: "${sanitizeText(extractCleanLabel(snapshot.label), 35)}"` : "";
|
|
359
|
+
const formatTokens = () => {
|
|
360
|
+
const delta = snapshot.delta_in ?? 0;
|
|
361
|
+
let inText = "";
|
|
362
|
+
if (delta > 0) {
|
|
363
|
+
inText = `input: +${formatTokenCount(delta)} tok`;
|
|
364
|
+
} else if (delta < 0) {
|
|
365
|
+
inText = `input: freed ${formatTokenCount(Math.abs(delta))} tok`;
|
|
366
|
+
} else {
|
|
367
|
+
inText = `input: ${formatTokenCount(snapshot.in_tokens || 0)} tok`;
|
|
368
|
+
}
|
|
369
|
+
const outText = `output: ${formatTokenCount(snapshot.out_tokens || 0)} tok`;
|
|
370
|
+
let thinkText = "";
|
|
371
|
+
if (snapshot.thinking_tokens && snapshot.thinking_tokens > 0) {
|
|
372
|
+
thinkText = `, think: ${formatTokenCount(snapshot.thinking_tokens)} tok`;
|
|
373
|
+
}
|
|
374
|
+
return `${inText}, ${outText}${thinkText}`;
|
|
375
|
+
};
|
|
376
|
+
const formatPerformance = () => {
|
|
377
|
+
const ttft = snapshot.ttft_ms ?? 0;
|
|
378
|
+
const ttftStr = ttft >= 1e3 ? `${(ttft / 1e3).toFixed(1)}s` : `${ttft}ms`;
|
|
379
|
+
let tpsVal = snapshot.tps && snapshot.tps > 0 ? snapshot.tps : 0;
|
|
380
|
+
if (!tpsVal && snapshot.out_tokens && snapshot.out_tokens > 0 && snapshot.dur_s && snapshot.dur_s > 0) {
|
|
381
|
+
tpsVal = Math.max(1, Math.round(snapshot.out_tokens / snapshot.dur_s));
|
|
382
|
+
}
|
|
383
|
+
const tpsStr = tpsVal > 0 ? `${tpsVal} tok/s` : "0 tok/s";
|
|
384
|
+
return `ttft: ${ttftStr}, ${tpsStr}`;
|
|
385
|
+
};
|
|
386
|
+
const isStale = opts.now - (snapshot.ts || 0) > opts.staleAfterS * 1e3 ? " [idle]" : "";
|
|
387
|
+
const turnCost = Number(snapshot.cost_turn || 0).toFixed(4);
|
|
388
|
+
const sessCost = Number(snapshot.cost_session || 0).toFixed(3);
|
|
389
|
+
const todayCost = Number(snapshot.cost_today || 0).toFixed(3);
|
|
390
|
+
const fullCost = `cost: turn $${turnCost}, sess $${sessCost}, today $${todayCost}${isStale}`;
|
|
391
|
+
const singleParts = [identitySegment, formatContext(false)];
|
|
392
|
+
if (labelSegment) {
|
|
393
|
+
singleParts.push(labelSegment);
|
|
394
|
+
} else {
|
|
395
|
+
if (toolSegment) singleParts.push(toolSegment);
|
|
396
|
+
singleParts.push(formatTokens());
|
|
397
|
+
}
|
|
398
|
+
singleParts.push(formatPerformance());
|
|
399
|
+
singleParts.push(fullCost);
|
|
400
|
+
const singleLine = singleParts.join(" | ");
|
|
401
|
+
if (stripAnsi(singleLine).length <= maxCols) {
|
|
402
|
+
return singleLine.replace(/[\r\n\t]+/g, " ").trim();
|
|
403
|
+
}
|
|
404
|
+
const line1Parts = [identitySegment, formatContext(false)];
|
|
405
|
+
if (labelSegment) {
|
|
406
|
+
line1Parts.push(labelSegment);
|
|
407
|
+
} else if (toolSegment) {
|
|
408
|
+
line1Parts.push(toolSegment);
|
|
409
|
+
}
|
|
410
|
+
const line1 = line1Parts.join(" | ");
|
|
411
|
+
const line2Parts = [];
|
|
412
|
+
if (!labelSegment) {
|
|
413
|
+
line2Parts.push(formatTokens());
|
|
414
|
+
}
|
|
415
|
+
line2Parts.push(formatPerformance());
|
|
416
|
+
line2Parts.push(fullCost);
|
|
417
|
+
const line2 = line2Parts.join(" | ");
|
|
418
|
+
return `${line1.replace(/[\r\n\t]+/g, " ").trim()}
|
|
419
|
+
${line2.replace(/[\r\n\t]+/g, " ").trim()}`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/renderer/index.ts
|
|
423
|
+
function readStdin(timeoutMs = 300) {
|
|
424
|
+
return new Promise((resolve) => {
|
|
425
|
+
let raw = "";
|
|
426
|
+
let done = false;
|
|
427
|
+
const finish = (value) => {
|
|
428
|
+
if (done) return;
|
|
429
|
+
done = true;
|
|
430
|
+
clearTimeout(timer);
|
|
431
|
+
try {
|
|
432
|
+
process.stdin.destroy();
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
resolve(value);
|
|
436
|
+
};
|
|
437
|
+
const timer = setTimeout(() => finish({}), timeoutMs);
|
|
438
|
+
if (timer.unref) timer.unref();
|
|
439
|
+
if (process.stdin.isTTY) {
|
|
440
|
+
return finish({});
|
|
441
|
+
}
|
|
442
|
+
process.stdin.setEncoding("utf8");
|
|
443
|
+
process.stdin.on("data", (chunk) => {
|
|
444
|
+
raw += chunk;
|
|
445
|
+
});
|
|
446
|
+
process.stdin.on("end", () => {
|
|
447
|
+
try {
|
|
448
|
+
const parsed = JSON.parse(raw || "{}");
|
|
449
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
450
|
+
finish(parsed);
|
|
451
|
+
} else {
|
|
452
|
+
finish({});
|
|
453
|
+
}
|
|
454
|
+
} catch {
|
|
455
|
+
finish({});
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
process.stdin.resume();
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
function pickLatest(statusDirs, sessionId) {
|
|
462
|
+
const safeSessionId = sessionId?.trim().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
463
|
+
const fileCandidates = safeSessionId ? [`latest-${safeSessionId}.json`, "latest.json"] : ["latest.json"];
|
|
464
|
+
for (const dir of statusDirs) {
|
|
465
|
+
for (const name of fileCandidates) {
|
|
466
|
+
const fullPath = path.join(dir, name);
|
|
467
|
+
try {
|
|
468
|
+
if (fs.existsSync(fullPath)) {
|
|
469
|
+
const parsed = JSON.parse(fs.readFileSync(fullPath, "utf8"));
|
|
470
|
+
if (isTokenSniffLatest(parsed)) {
|
|
471
|
+
return parsed;
|
|
472
|
+
}
|
|
473
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
474
|
+
return parsed;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
} catch {
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
function extractModel(input) {
|
|
484
|
+
if (!input?.model) return void 0;
|
|
485
|
+
if (typeof input.model === "object") {
|
|
486
|
+
return input.model.id || input.model.display_name;
|
|
487
|
+
}
|
|
488
|
+
return String(input.model);
|
|
489
|
+
}
|
|
490
|
+
async function main(opts = {}) {
|
|
491
|
+
const cfg = loadConfig();
|
|
492
|
+
const statusDirs = opts.statusDirs ?? cfg.statusDirs;
|
|
493
|
+
try {
|
|
494
|
+
const input = await readStdin();
|
|
495
|
+
const sessionId = input.session_id || input.sessionId;
|
|
496
|
+
const latest = pickLatest(statusDirs, sessionId);
|
|
497
|
+
if (process.env.TOKENSNIFF_JSON === "1") {
|
|
498
|
+
console.log(JSON.stringify(latest || { waiting: true }));
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
console.log(
|
|
502
|
+
formatStatus(latest, {
|
|
503
|
+
staleAfterS: opts.staleAfterS ?? cfg.staleAfterS,
|
|
504
|
+
color: opts.color ?? cfg.color,
|
|
505
|
+
warnPct: cfg.warnPct,
|
|
506
|
+
critPct: cfg.critPct,
|
|
507
|
+
modelFallback: extractModel(input) || "tokensniff"
|
|
508
|
+
})
|
|
509
|
+
);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
512
|
+
try {
|
|
513
|
+
console.log(`[tokensniff] status unavailable (${msg})`);
|
|
514
|
+
} catch {
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export { extractModel, main, pickLatest, readStdin };
|
|
520
|
+
//# sourceMappingURL=status.js.map
|
|
521
|
+
//# sourceMappingURL=status.js.map
|