tokenoscopy 0.3.1 → 0.3.2
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/dist/chunk-AZYPMFOF.js +10 -45
- package/dist/cli.js +30 -68
- package/dist/index.js +1 -22
- package/package.json +1 -1
package/dist/chunk-AZYPMFOF.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
|
|
1
6
|
// src/auth-mode.ts
|
|
2
|
-
import { readFileSync } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import { join } from "path";
|
|
5
7
|
function keyWasRejected(key, cfg) {
|
|
6
8
|
const rejected = cfg?.customApiKeyResponses?.rejected ?? [];
|
|
7
9
|
return rejected.includes(key.slice(-20));
|
|
@@ -157,9 +159,6 @@ function redactValue(value) {
|
|
|
157
159
|
}
|
|
158
160
|
return value;
|
|
159
161
|
}
|
|
160
|
-
|
|
161
|
-
// ../shared/src/events.ts
|
|
162
|
-
import { z } from "zod";
|
|
163
162
|
var agentProductSchema = z.enum([
|
|
164
163
|
"claude_code",
|
|
165
164
|
"codex",
|
|
@@ -266,12 +265,12 @@ var sessionSummarySchema = z.object({
|
|
|
266
265
|
/** Count of transcript records the parser did not recognize (drift signal). */
|
|
267
266
|
unknown_record_count: z.number().int().nonnegative().default(0)
|
|
268
267
|
});
|
|
269
|
-
|
|
268
|
+
z.object({
|
|
270
269
|
schema_version: z.literal(1),
|
|
271
270
|
summary: sessionSummarySchema,
|
|
272
271
|
events: z.array(sessionEventSchema)
|
|
273
272
|
});
|
|
274
|
-
|
|
273
|
+
z.object({
|
|
275
274
|
hook_event_name: z.string(),
|
|
276
275
|
session_id: z.string().min(8),
|
|
277
276
|
transcript_path: z.string().optional(),
|
|
@@ -288,14 +287,14 @@ var hookEventSchema = z.object({
|
|
|
288
287
|
agent_id: z.string().optional(),
|
|
289
288
|
agent_type: z.string().optional()
|
|
290
289
|
}).passthrough();
|
|
291
|
-
|
|
290
|
+
z.object({
|
|
292
291
|
session_id: z.string().min(8),
|
|
293
292
|
tool_name: z.string(),
|
|
294
293
|
cwd: z.string().optional(),
|
|
295
294
|
git_repo: z.string().optional()
|
|
296
295
|
});
|
|
297
296
|
var guardDecisionSchema = z.enum(["allow", "ask", "deny"]);
|
|
298
|
-
|
|
297
|
+
z.object({
|
|
299
298
|
decision: guardDecisionSchema,
|
|
300
299
|
reason: z.string().optional(),
|
|
301
300
|
budget: z.object({
|
|
@@ -357,22 +356,6 @@ function estimateCostUsd(model, usage) {
|
|
|
357
356
|
return Math.round(cost * 1e6) / 1e6;
|
|
358
357
|
}
|
|
359
358
|
|
|
360
|
-
// ../shared/src/diagnose.ts
|
|
361
|
-
var DIAG = {
|
|
362
|
-
/** A session counts as live if it produced an event this recently. */
|
|
363
|
-
LIVE_WINDOW_MS: 15 * 6e4,
|
|
364
|
-
RUNAWAY_MIN_COST: 10,
|
|
365
|
-
RUNAWAY_CTX_TOKENS: 4e5,
|
|
366
|
-
RUNAWAY_CYCLE: 40,
|
|
367
|
-
RUNAWAY_REPEAT: 10,
|
|
368
|
-
BLOAT_CTX_TOKENS: 2e5,
|
|
369
|
-
BLOAT_MIN_COST: 5,
|
|
370
|
-
CYCLE_MIN: 25,
|
|
371
|
-
REPEAT_MIN: 5,
|
|
372
|
-
/** Requests that rebuilt the cache, summed, before it's worth a habit change. */
|
|
373
|
-
REBUILD_MIN_COST: 3
|
|
374
|
-
};
|
|
375
|
-
|
|
376
359
|
// ../shared/src/guard-contract.ts
|
|
377
360
|
var GUARD_TIMEOUT_MS = 1500;
|
|
378
361
|
var ALLOW_CACHE_MS = 15e3;
|
|
@@ -466,9 +449,6 @@ function withRelPath(event, root) {
|
|
|
466
449
|
return { ...event, payload: { ...event.payload, rel_path: rel } };
|
|
467
450
|
}
|
|
468
451
|
|
|
469
|
-
// ../shared/src/anatomy.ts
|
|
470
|
-
var CACHE_TTL_MS = 5 * 6e4;
|
|
471
|
-
|
|
472
452
|
// src/transcript.ts
|
|
473
453
|
var SKIPPED_TYPES = /* @__PURE__ */ new Set([
|
|
474
454
|
"summary",
|
|
@@ -739,19 +719,4 @@ function parseTranscript(jsonl) {
|
|
|
739
719
|
return { summary, events, warnings };
|
|
740
720
|
}
|
|
741
721
|
|
|
742
|
-
export {
|
|
743
|
-
classifyAuth,
|
|
744
|
-
readClaudeJson,
|
|
745
|
-
detectAuth,
|
|
746
|
-
detectAuthMode,
|
|
747
|
-
costIsRealMoney,
|
|
748
|
-
redactText,
|
|
749
|
-
redactValue,
|
|
750
|
-
GUARD_TIMEOUT_MS,
|
|
751
|
-
ALLOW_CACHE_MS,
|
|
752
|
-
naturalDedupKey,
|
|
753
|
-
truncatePayload,
|
|
754
|
-
withRelPath,
|
|
755
|
-
repoLabelFromCwd,
|
|
756
|
-
parseTranscript
|
|
757
|
-
};
|
|
722
|
+
export { ALLOW_CACHE_MS, GUARD_TIMEOUT_MS, classifyAuth, costIsRealMoney, detectAuth, detectAuthMode, naturalDedupKey, parseTranscript, readClaudeJson, redactText, redactValue, repoLabelFromCwd, truncatePayload, withRelPath };
|
package/dist/cli.js
CHANGED
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
parseTranscript,
|
|
9
|
-
redactValue,
|
|
10
|
-
truncatePayload,
|
|
11
|
-
withRelPath
|
|
12
|
-
} from "./chunk-AZYPMFOF.js";
|
|
2
|
+
import { detectAuth, GUARD_TIMEOUT_MS, parseTranscript, detectAuthMode, withRelPath, redactValue, ALLOW_CACHE_MS, truncatePayload, naturalDedupKey } from './chunk-AZYPMFOF.js';
|
|
3
|
+
import { existsSync, readFileSync, readdirSync, statSync, watch, copyFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
+
import { homedir, userInfo, hostname, tmpdir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { execFileSync } from 'child_process';
|
|
7
|
+
import { createInterface } from 'readline/promises';
|
|
13
8
|
|
|
14
|
-
// src/commands/doctor.ts
|
|
15
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
16
|
-
import { homedir as homedir2 } from "os";
|
|
17
|
-
import { join as join2 } from "path";
|
|
18
|
-
|
|
19
|
-
// src/config.ts
|
|
20
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
21
|
-
import { homedir } from "os";
|
|
22
|
-
import { join } from "path";
|
|
23
9
|
var DEFAULT_API_URL = "https://tokenoscopy.com";
|
|
24
10
|
function configDir() {
|
|
25
11
|
return process.env.TOKENOSCOPY_CONFIG_DIR ?? join(homedir(), ".config", "tokenoscopy");
|
|
@@ -59,10 +45,10 @@ async function runDoctor() {
|
|
|
59
45
|
out("\u2717 no config \u2014 run `npx tokenoscopy init`");
|
|
60
46
|
failures++;
|
|
61
47
|
}
|
|
62
|
-
const settingsPath =
|
|
63
|
-
if (
|
|
48
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
49
|
+
if (existsSync(settingsPath)) {
|
|
64
50
|
try {
|
|
65
|
-
const settings = JSON.parse(
|
|
51
|
+
const settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
66
52
|
const raw = JSON.stringify(settings.hooks ?? {});
|
|
67
53
|
const hasHttp = raw.includes("/api/ingest/hook");
|
|
68
54
|
const hasFlush = raw.includes("tokenoscopy flush");
|
|
@@ -87,8 +73,8 @@ async function runDoctor() {
|
|
|
87
73
|
}[auth.mode];
|
|
88
74
|
out(`${auth.mode === "unknown" ? "\u26A0" : "\u2713"} billing lane: ${laneLabel}`);
|
|
89
75
|
out(` evidence: ${auth.evidence}`);
|
|
90
|
-
const projectsDir =
|
|
91
|
-
if (
|
|
76
|
+
const projectsDir = join(homedir(), ".claude", "projects");
|
|
77
|
+
if (existsSync(projectsDir)) {
|
|
92
78
|
out(`\u2713 transcript directory readable (${projectsDir})`);
|
|
93
79
|
} else {
|
|
94
80
|
out(`\u26A0 no transcript directory yet (${projectsDir}) \u2014 appears after the first session`);
|
|
@@ -131,13 +117,6 @@ async function runDoctor() {
|
|
|
131
117
|
${failures} problem(s) found.`);
|
|
132
118
|
return failures === 0 ? 0 : 1;
|
|
133
119
|
}
|
|
134
|
-
|
|
135
|
-
// src/commands/flush.ts
|
|
136
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
137
|
-
|
|
138
|
-
// src/identity.ts
|
|
139
|
-
import { execFileSync } from "child_process";
|
|
140
|
-
import { hostname, userInfo } from "os";
|
|
141
120
|
function git(args, cwd) {
|
|
142
121
|
try {
|
|
143
122
|
const out = execFileSync("git", args, {
|
|
@@ -274,14 +253,14 @@ async function runFlush(args) {
|
|
|
274
253
|
}
|
|
275
254
|
}
|
|
276
255
|
}
|
|
277
|
-
if (!transcriptPath || !
|
|
256
|
+
if (!transcriptPath || !existsSync(transcriptPath)) {
|
|
278
257
|
process.stderr.write("tokenoscopy flush: no transcript to flush\n");
|
|
279
258
|
return 0;
|
|
280
259
|
}
|
|
281
260
|
let upload;
|
|
282
261
|
try {
|
|
283
262
|
const { summary, events, warnings } = parseTranscript(
|
|
284
|
-
|
|
263
|
+
readFileSync(transcriptPath, "utf8")
|
|
285
264
|
);
|
|
286
265
|
const identity = detectIdentity(summary.cwd);
|
|
287
266
|
summary.actor.machine_id = identity.machine;
|
|
@@ -351,18 +330,13 @@ async function runFlush(args) {
|
|
|
351
330
|
}
|
|
352
331
|
return 0;
|
|
353
332
|
}
|
|
354
|
-
|
|
355
|
-
// src/commands/guard.ts
|
|
356
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
357
|
-
import { tmpdir } from "os";
|
|
358
|
-
import { join as join3 } from "path";
|
|
359
333
|
function cachePath(sessionId) {
|
|
360
334
|
const safe = sessionId.replace(/[^a-zA-Z0-9-]/g, "").slice(0, 64);
|
|
361
|
-
return
|
|
335
|
+
return join(tmpdir(), `tokenoscopy-guard-${safe}.json`);
|
|
362
336
|
}
|
|
363
337
|
function readAllowCache(sessionId) {
|
|
364
338
|
try {
|
|
365
|
-
const raw = JSON.parse(
|
|
339
|
+
const raw = JSON.parse(readFileSync(cachePath(sessionId), "utf8"));
|
|
366
340
|
return typeof raw.until === "number" && raw.until > Date.now();
|
|
367
341
|
} catch {
|
|
368
342
|
return false;
|
|
@@ -370,7 +344,7 @@ function readAllowCache(sessionId) {
|
|
|
370
344
|
}
|
|
371
345
|
function writeAllowCache(sessionId) {
|
|
372
346
|
try {
|
|
373
|
-
|
|
347
|
+
writeFileSync(
|
|
374
348
|
cachePath(sessionId),
|
|
375
349
|
JSON.stringify({ until: Date.now() + ALLOW_CACHE_MS })
|
|
376
350
|
);
|
|
@@ -447,13 +421,6 @@ async function runGuard() {
|
|
|
447
421
|
return 0;
|
|
448
422
|
}
|
|
449
423
|
}
|
|
450
|
-
|
|
451
|
-
// src/commands/init.ts
|
|
452
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
453
|
-
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, copyFileSync } from "fs";
|
|
454
|
-
import { homedir as homedir3 } from "os";
|
|
455
|
-
import { join as join4 } from "path";
|
|
456
|
-
import { createInterface } from "readline/promises";
|
|
457
424
|
var MARKER = "tokenoscopy";
|
|
458
425
|
function isOurs(group) {
|
|
459
426
|
return JSON.stringify(group).toLowerCase().includes(MARKER);
|
|
@@ -462,7 +429,7 @@ function resolveInvocation() {
|
|
|
462
429
|
const override = process.env.TOKENOSCOPY_BIN?.trim();
|
|
463
430
|
if (override) return override;
|
|
464
431
|
try {
|
|
465
|
-
const found =
|
|
432
|
+
const found = execFileSync("command", ["-v", "tokenoscopy"], {
|
|
466
433
|
encoding: "utf8",
|
|
467
434
|
shell: "/bin/sh",
|
|
468
435
|
timeout: 2e3,
|
|
@@ -553,11 +520,11 @@ async function runInit(args) {
|
|
|
553
520
|
}
|
|
554
521
|
const config = { apiUrl, key };
|
|
555
522
|
saveConfig(config);
|
|
556
|
-
const settingsPath = flags.get("--project") ?
|
|
523
|
+
const settingsPath = flags.get("--project") ? join(process.cwd(), ".claude", "settings.json") : join(homedir(), ".claude", "settings.json");
|
|
557
524
|
let settings = {};
|
|
558
|
-
if (
|
|
525
|
+
if (existsSync(settingsPath)) {
|
|
559
526
|
try {
|
|
560
|
-
settings = JSON.parse(
|
|
527
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
561
528
|
copyFileSync(settingsPath, `${settingsPath}.tokenoscopy-backup`);
|
|
562
529
|
} catch {
|
|
563
530
|
process.stderr.write(`\u2717 ${settingsPath} exists but isn't valid JSON \u2014 fix it first.
|
|
@@ -574,12 +541,12 @@ async function runInit(args) {
|
|
|
574
541
|
`);
|
|
575
542
|
return 0;
|
|
576
543
|
}
|
|
577
|
-
const dir =
|
|
578
|
-
if (!
|
|
579
|
-
const { mkdirSync: mkdirSync2 } = await import(
|
|
544
|
+
const dir = join(settingsPath, "..");
|
|
545
|
+
if (!existsSync(dir)) {
|
|
546
|
+
const { mkdirSync: mkdirSync2 } = await import('fs');
|
|
580
547
|
mkdirSync2(dir, { recursive: true });
|
|
581
548
|
}
|
|
582
|
-
|
|
549
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
583
550
|
`);
|
|
584
551
|
process.stderr.write(
|
|
585
552
|
[
|
|
@@ -592,15 +559,10 @@ async function runInit(args) {
|
|
|
592
559
|
);
|
|
593
560
|
return 0;
|
|
594
561
|
}
|
|
595
|
-
|
|
596
|
-
// src/commands/tail.ts
|
|
597
|
-
import { watch, existsSync as existsSync5, readdirSync, statSync } from "fs";
|
|
598
|
-
import { homedir as homedir4 } from "os";
|
|
599
|
-
import { join as join5 } from "path";
|
|
600
562
|
var DEBOUNCE_MS = 3e3;
|
|
601
563
|
async function runTail(args) {
|
|
602
|
-
const projectsDir = process.env.TOKENOSCOPY_PROJECTS_DIR ??
|
|
603
|
-
if (!
|
|
564
|
+
const projectsDir = process.env.TOKENOSCOPY_PROJECTS_DIR ?? join(homedir(), ".claude", "projects");
|
|
565
|
+
if (!existsSync(projectsDir)) {
|
|
604
566
|
process.stderr.write(`tokenoscopy tail: ${projectsDir} does not exist
|
|
605
567
|
`);
|
|
606
568
|
return 1;
|
|
@@ -612,7 +574,7 @@ async function runTail(args) {
|
|
|
612
574
|
const cutoff = Date.now() - days * 24 * 3600 * 1e3;
|
|
613
575
|
let count = 0;
|
|
614
576
|
for (const dir of readdirSync(projectsDir)) {
|
|
615
|
-
const full =
|
|
577
|
+
const full = join(projectsDir, dir);
|
|
616
578
|
let entries;
|
|
617
579
|
try {
|
|
618
580
|
entries = readdirSync(full);
|
|
@@ -621,7 +583,7 @@ async function runTail(args) {
|
|
|
621
583
|
}
|
|
622
584
|
for (const f of entries) {
|
|
623
585
|
if (!f.endsWith(".jsonl")) continue;
|
|
624
|
-
const p =
|
|
586
|
+
const p = join(full, f);
|
|
625
587
|
try {
|
|
626
588
|
if (statSync(p).mtimeMs >= cutoff) {
|
|
627
589
|
await runFlush(["--transcript", p]);
|
|
@@ -640,14 +602,14 @@ async function runTail(args) {
|
|
|
640
602
|
const pending = /* @__PURE__ */ new Map();
|
|
641
603
|
watch(projectsDir, { recursive: true }, (_event, filename) => {
|
|
642
604
|
if (!filename || !filename.endsWith(".jsonl")) return;
|
|
643
|
-
const full =
|
|
605
|
+
const full = join(projectsDir, filename);
|
|
644
606
|
const existing = pending.get(full);
|
|
645
607
|
if (existing) clearTimeout(existing);
|
|
646
608
|
pending.set(
|
|
647
609
|
full,
|
|
648
610
|
setTimeout(() => {
|
|
649
611
|
pending.delete(full);
|
|
650
|
-
if (
|
|
612
|
+
if (existsSync(full)) {
|
|
651
613
|
void runFlush(["--transcript", full]);
|
|
652
614
|
}
|
|
653
615
|
}, DEBOUNCE_MS)
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1 @@
|
|
|
1
|
-
|
|
2
|
-
classifyAuth,
|
|
3
|
-
costIsRealMoney,
|
|
4
|
-
detectAuth,
|
|
5
|
-
detectAuthMode,
|
|
6
|
-
parseTranscript,
|
|
7
|
-
readClaudeJson,
|
|
8
|
-
redactText,
|
|
9
|
-
redactValue,
|
|
10
|
-
repoLabelFromCwd
|
|
11
|
-
} from "./chunk-AZYPMFOF.js";
|
|
12
|
-
export {
|
|
13
|
-
classifyAuth,
|
|
14
|
-
costIsRealMoney,
|
|
15
|
-
detectAuth,
|
|
16
|
-
detectAuthMode,
|
|
17
|
-
parseTranscript,
|
|
18
|
-
readClaudeJson,
|
|
19
|
-
redactText,
|
|
20
|
-
redactValue,
|
|
21
|
-
repoLabelFromCwd
|
|
22
|
-
};
|
|
1
|
+
export { classifyAuth, costIsRealMoney, detectAuth, detectAuthMode, parseTranscript, readClaudeJson, redactText, redactValue, repoLabelFromCwd } from './chunk-AZYPMFOF.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokenoscopy",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Flight recorder & spend control for coding-agent fleets. Records Claude Code sessions (hooks + transcripts), replays them, and enforces budgets.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|