glassbox-trace 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/glassbox.html +1 -1
- package/package.json +1 -1
- package/src/cli.mjs +6 -5
- package/src/collect.mjs +2 -1
- package/src/textfile.mjs +17 -0
- package/src/trace-core.js +1 -1
package/dist/glassbox.html
CHANGED
|
@@ -408,7 +408,7 @@ kbd{font:11px var(--mono);border:1px solid var(--line);border-bottom-width:2px;b
|
|
|
408
408
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
409
409
|
'use strict';
|
|
410
410
|
|
|
411
|
-
const VERSION = '0.7.
|
|
411
|
+
const VERSION = '0.7.1';
|
|
412
412
|
|
|
413
413
|
// ---------------------------------------------------------------------------
|
|
414
414
|
// Rate card (USD per million tokens). Prefix-matched against model ids so dated
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glassbox-trace",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Other tools show what happened in a Claude Code session; Glassbox tells you what went wrong. Timeline, token and cost burn, auto-diagnostics with evidence and advice, session compare, live tail, a CLI and a Claude Code Stop hook that feeds the findings back to the agent.",
|
|
5
5
|
"main": "src/trace-core.js",
|
|
6
6
|
"scripts": {
|
package/src/cli.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { spawn } from 'node:child_process';
|
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
7
|
import { createRequire } from 'node:module';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { readTextFile } from './textfile.mjs';
|
|
9
10
|
const require = createRequire(import.meta.url);
|
|
10
11
|
const core = require('./trace-core.js');
|
|
11
12
|
// fileURLToPath, not URL.pathname: on Windows the latter gives "/C:/…" which path.resolve turns into "\C:\…".
|
|
@@ -89,7 +90,7 @@ export function analyse(files, rates) { const trace = core.parseTrace(files); co
|
|
|
89
90
|
// Entries override or extend the built-in card; cache-write rates default to 1.25× / 2× input.
|
|
90
91
|
export function loadRates(file) {
|
|
91
92
|
if (!file) return null;
|
|
92
|
-
let json; try { json = JSON.parse(
|
|
93
|
+
let json; try { json = JSON.parse(readTextFile(String(file))); } catch (e) { throw new Error(`--rates ${file}: ${e.code === 'ENOENT' ? 'no such file' : 'not valid JSON'}`); }
|
|
93
94
|
if (!json || typeof json !== 'object' || Array.isArray(json)) throw new Error(`--rates ${file}: expected an object keyed by model id`);
|
|
94
95
|
const out = Object.assign({}, core.RATES);
|
|
95
96
|
for (const [model, r] of Object.entries(json)) {
|
|
@@ -136,7 +137,7 @@ export class Legend {
|
|
|
136
137
|
constructor(data) { this.salt = data && data.salt || crypto.randomBytes(32).toString('hex'); this.files = data && data.files || {}; this.created = data && data.created || new Date().toISOString(); this.byPath = new Map(Object.entries(this.files).map(([k, v]) => [core.normalisePath(v), k])); this.dirty = !data; }
|
|
137
138
|
static load(file) {
|
|
138
139
|
if (!file || !fs.existsSync(file)) return new Legend(null);
|
|
139
|
-
let json; try { json = JSON.parse(
|
|
140
|
+
let json; try { json = JSON.parse(readTextFile(file)); } catch (e) { throw new Error(`--legend ${file}: not valid JSON (delete it to start a new legend)`); }
|
|
140
141
|
if (!json || typeof json.salt !== 'string' || typeof json.files !== 'object') throw new Error(`--legend ${file}: not a Glassbox legend`);
|
|
141
142
|
return new Legend(json);
|
|
142
143
|
}
|
|
@@ -293,7 +294,7 @@ export function settingsPath(home) { return path.join(home || claudeHome(), 'set
|
|
|
293
294
|
export function installHook(opts = {}) {
|
|
294
295
|
const file = settingsPath(opts.home);
|
|
295
296
|
let settings = {};
|
|
296
|
-
if (fs.existsSync(file)) { try { settings = JSON.parse(
|
|
297
|
+
if (fs.existsSync(file)) { try { settings = JSON.parse(readTextFile(file)); } catch (e) { throw new Error(`${file} is not valid JSON — fix it first (nothing was changed)`); } fs.copyFileSync(file, file + '.glassbox-backup'); }
|
|
297
298
|
settings.hooks = settings.hooks || {};
|
|
298
299
|
const cmd = `${opts.command || 'npx -y glassbox-trace'} hook${opts.feedback ? ' --feedback' : ''}${opts.context ? ' --context' : ''}${opts.failOn ? ' --fail-on ' + opts.failOn : ''}`;
|
|
299
300
|
for (const ev of opts.events || (opts.context ? ['Stop', 'SessionStart'] : ['Stop'])) {
|
|
@@ -309,7 +310,7 @@ export function installHook(opts = {}) {
|
|
|
309
310
|
export function uninstallHook(opts = {}) {
|
|
310
311
|
const file = settingsPath(opts.home);
|
|
311
312
|
if (!fs.existsSync(file)) return { file, removed: 0 };
|
|
312
|
-
const settings = JSON.parse(
|
|
313
|
+
const settings = JSON.parse(readTextFile(file));
|
|
313
314
|
let removed = 0;
|
|
314
315
|
for (const ev of Object.keys(settings.hooks || {})) {
|
|
315
316
|
const list = settings.hooks[ev]; if (!Array.isArray(list)) continue;
|
|
@@ -417,7 +418,7 @@ export async function main(argv, io = {}) {
|
|
|
417
418
|
if (cmd === 'reveal') {
|
|
418
419
|
if (!args._[1]) throw new Error('reveal needs a file: glassbox reveal report.md --legend audit.legend.json');
|
|
419
420
|
if (!legendFile || !fs.existsSync(legendFile)) throw new Error('reveal needs --legend FILE (the legend written by check --redact --legend)');
|
|
420
|
-
const r = legend.reveal(
|
|
421
|
+
const r = legend.reveal(readTextFile(args._[1]));
|
|
421
422
|
out(r.text.replace(/\n$/, ''));
|
|
422
423
|
if (r.unknown) err(`reveal: ${r.unknown} key${r.unknown === 1 ? '' : 's'} not in this legend (left as they are)`);
|
|
423
424
|
return 0;
|
package/src/collect.mjs
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
|
+
import { readTextFile } from './textfile.mjs';
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
14
|
const core = require('./trace-core.js');
|
|
14
15
|
|
|
@@ -28,7 +29,7 @@ export function readSources(dir, opts = {}) {
|
|
|
28
29
|
const entries = fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && /\.json$/i.test(e.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
29
30
|
for (const e of entries) {
|
|
30
31
|
const file = path.join(dir, e.name);
|
|
31
|
-
let doc; try { doc = JSON.parse(
|
|
32
|
+
let doc; try { doc = JSON.parse(readTextFile(file)); } catch (err) { skipped.push({ file: e.name, reason: 'not JSON' }); continue; }
|
|
32
33
|
const src = sourceFromJson(e.name.replace(/\.json$/i, ''), doc);
|
|
33
34
|
if (!src) { skipped.push({ file: e.name, reason: 'not a glassbox check report' }); continue; }
|
|
34
35
|
if (!src.redacted && !opts.allowUnredacted) { skipped.push({ file: e.name, reason: 'not redacted (run check with --redact, or pass --allow-unredacted)' }); continue; }
|
package/src/textfile.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Text files a person may have saved on Windows. PowerShell 5.1's `>` writes UTF-16LE with a byte-order
|
|
2
|
+
// mark; `Set-Content -Encoding UTF8` and older Notepad write a UTF-8 BOM. Read all of them as the same text,
|
|
3
|
+
// so a report, rate card or legend works however it was saved. Transcripts go through parseLines, which
|
|
4
|
+
// already drops a UTF-8 BOM; everything a user hands to the CLI goes through here.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
|
|
7
|
+
export function decodeText(input) {
|
|
8
|
+
const buf = Buffer.isBuffer(input) ? input : Buffer.from(input);
|
|
9
|
+
if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) return buf.subarray(2).toString('utf16le');
|
|
10
|
+
if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) { const le = Buffer.from(buf.subarray(2)); le.swap16(); return le.toString('utf16le'); }
|
|
11
|
+
if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) return buf.subarray(3).toString('utf8');
|
|
12
|
+
return buf.toString('utf8');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function readTextFile(file) {
|
|
16
|
+
return decodeText(fs.readFileSync(file));
|
|
17
|
+
}
|
package/src/trace-core.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
10
10
|
'use strict';
|
|
11
11
|
|
|
12
|
-
const VERSION = '0.7.
|
|
12
|
+
const VERSION = '0.7.1';
|
|
13
13
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Rate card (USD per million tokens). Prefix-matched against model ids so dated
|