agentworth 0.1.16 → 0.1.18

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.
Files changed (3) hide show
  1. package/README.md +13 -13
  2. package/lib/resolver.js +225 -26
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,10 +3,10 @@
3
3
  Official npm launcher for **AgentWorth** — discover, normalize, and understand AI-agent histories locally.
4
4
 
5
5
  ```bash
6
- npx agentworth
6
+ npx -y agentworth@latest
7
7
  ```
8
8
 
9
- When run with no arguments, `npx agentworth` defaults to launching the local web UI (`serve --open`). All native subcommands and flags are forwarded transparently to the native binary.
9
+ When run with no arguments, `npx -y agentworth@latest` defaults to launching the local web UI (`serve --open`). All native subcommands and flags are forwarded transparently to the native binary.
10
10
 
11
11
  ---
12
12
 
@@ -17,7 +17,7 @@ When run with no arguments, `npx agentworth` defaults to launching the local web
17
17
  Launch the local interactive UI:
18
18
 
19
19
  ```bash
20
- npx agentworth
20
+ npx -y agentworth@latest
21
21
  ```
22
22
 
23
23
  ### CLI Subcommands
@@ -25,45 +25,45 @@ npx agentworth
25
25
  Scan and index local agent histories across 11 agent adapters:
26
26
 
27
27
  ```bash
28
- npx agentworth scan
28
+ npx -y agentworth@latest scan
29
29
  ```
30
30
 
31
31
  View summary statistics across all indexed traces:
32
32
 
33
33
  ```bash
34
- npx agentworth stats
34
+ npx -y agentworth@latest stats
35
35
  ```
36
36
 
37
37
  Inspect token rollups, costs, and rolling pacing:
38
38
 
39
39
  ```bash
40
- npx agentworth usage --period day
41
- npx agentworth usage --pacing
40
+ npx -y agentworth@latest usage --period day
41
+ npx -y agentworth@latest usage --pacing
42
42
  ```
43
43
 
44
44
  Trace file modifications back to the AI agent session and prompt:
45
45
 
46
46
  ```bash
47
- npx agentworth blame src/main.rs
47
+ npx -y agentworth@latest blame src/main.rs
48
48
  ```
49
49
 
50
50
  List indexed sessions with filtering by adapter or model:
51
51
 
52
52
  ```bash
53
- npx agentworth traces --limit 20
54
- npx agentworth traces --adapter claude_code --json
53
+ npx -y agentworth@latest traces --limit 20
54
+ npx -y agentworth@latest traces --adapter claude_code --json
55
55
  ```
56
56
 
57
57
  Inspect a specific session with timeline and outcome analysis:
58
58
 
59
59
  ```bash
60
- npx agentworth inspect <session-id>
60
+ npx -y agentworth@latest inspect <session-id>
61
61
  ```
62
62
 
63
63
  Export traces safely with automatic secret and path redaction:
64
64
 
65
65
  ```bash
66
- npx agentworth export <session-id> --redact --format atif --output session.atif.json
66
+ npx -y agentworth@latest export <session-id> --redact --format atif --output session.atif.json
67
67
  ```
68
68
 
69
69
  ---
@@ -94,7 +94,7 @@ You can also install the native AgentWorth binary directly:
94
94
  | **Standalone Script** | `curl -fsSL https://agentworth.dev/install.sh | sh` | Installs the pre-built native binary directly to `~/.local/bin`. |
95
95
  | **Homebrew** | `brew install unfoundbox-crew/tap/agentworth` | Installs via official Homebrew tap. |
96
96
  | **Cargo (Native)** | `cargo install agentworth-cli` | Compiles and installs `agentworth` and its short alias `archie` to `~/.cargo/bin`. |
97
- | **NPX (Instant)** | `npx agentworth` | Zero-install runner that detects or downloads the native binary. |
97
+ | **NPX (Instant)** | `npx -y agentworth@latest` | Zero-install runner that detects or downloads the native binary. |
98
98
 
99
99
  ---
100
100
 
package/lib/resolver.js CHANGED
@@ -9,6 +9,80 @@ import { fileURLToPath } from 'node:url';
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = path.dirname(__filename);
11
11
 
12
+ // -----------------------------------------------------------------------------
13
+ // The brand line
14
+ // -----------------------------------------------------------------------------
15
+ // The launcher's first run is an onboarding screen, so it prints the same one-line Archie
16
+ // form the CLI and the install script do -- packages/ui/brand/archie/archie-tui.txt:
17
+ //
18
+ // (o) archie downloading ----------....... 68% 15.9 / 22.2 MB
19
+ //
20
+ // The lamp is the state, the label says what is happening, the rest is evidence. Glyph set
21
+ // is docs/DESIGN.md's: ASCII, U+2500-259F, and the five extras. No emoji, no colour: this
22
+ // goes to stderr, which lands in CI logs and pipes with the colour stripped. Everything
23
+ // here writes to stderr so `npx agentworth stats --json | jq` still works.
24
+
25
+ const BRAND_LABEL_WIDTH = 11;
26
+
27
+ /** Fixed overhead of the wide layout: the lamp, the name, the label, the percent, the sizes. */
28
+ const BRAND_WIDE_OVERHEAD = 50;
29
+ /** The same for the narrow layout, which drops the label. */
30
+ const BRAND_NARROW_OVERHEAD = 28;
31
+ /** Below this the label gives way -- the CLI's ARCHIE_BLOCK_MIN_COLUMNS rule, same shape. */
32
+ const BRAND_WIDE_MIN = 56;
33
+
34
+ function brandColumns() {
35
+ const raw = process.stderr.columns || Number(process.env.COLUMNS) || 80;
36
+ return Math.min(100, Math.max(46, raw));
37
+ }
38
+
39
+ /** ` (*) archie installed archie in ~/.agentworth/bin/v0.1.16` */
40
+ export function brandLine(lamp, label, rest) {
41
+ return ` (${lamp}) archie ${label.padEnd(BRAND_LABEL_WIDTH)} ${rest}`;
42
+ }
43
+
44
+ function mib(bytes) {
45
+ return (bytes / 1048576).toFixed(1);
46
+ }
47
+
48
+ /**
49
+ * One redraw of the download line. Pure string building so the layout is testable without
50
+ * a socket: `cols` and `unicode` are injected rather than read off the terminal.
51
+ */
52
+ export function downloadLine(done, total, { cols = 80, unicode = true, lamp = 'o' } = {}) {
53
+ const fill = unicode ? '─' : '-';
54
+ const track = unicode ? '·' : '.';
55
+
56
+ // A server that sent no Content-Length gets bytes and no bar: a bar with a made-up
57
+ // denominator is a progress indicator that lies.
58
+ if (!total || total < 100) {
59
+ return brandLine(lamp, 'downloading', `${mib(done)} MB`);
60
+ }
61
+
62
+ const pct = Math.min(100, Math.floor((done / total) * 100));
63
+ const pctCell = `${String(pct).padStart(3)}%`;
64
+
65
+ if (cols >= BRAND_WIDE_MIN) {
66
+ const bw = Math.min(28, Math.max(6, cols - BRAND_WIDE_OVERHEAD));
67
+ const on = Math.round((pct * bw) / 100);
68
+ return brandLine(
69
+ lamp,
70
+ 'downloading',
71
+ `${fill.repeat(on)}${track.repeat(bw - on)} ${pctCell} ${mib(done)} / ${mib(total)} MB`,
72
+ );
73
+ }
74
+
75
+ const bw = Math.min(20, Math.max(6, cols - BRAND_NARROW_OVERHEAD));
76
+ const on = Math.round((pct * bw) / 100);
77
+ return ` (${lamp}) archie ${fill.repeat(on)}${track.repeat(bw - on)} ${pctCell} ${mib(done)} MB`;
78
+ }
79
+
80
+ /** `~/.agentworth/bin/v0.1.16` rather than the expanded home, so the line fits 80 columns. */
81
+ function tilde(p) {
82
+ const home = os.homedir();
83
+ return home && p.startsWith(home) ? `~${p.slice(home.length)}` : p;
84
+ }
85
+
12
86
  /**
13
87
  * Returns the normalized platform key (e.g. darwin-arm64, linux-x64, win32-x64).
14
88
  *
@@ -167,7 +241,7 @@ export function findCargoTargetBinary(startDir, binName = getBinaryName()) {
167
241
  * @param {number} [redirects=5]
168
242
  * @returns {Promise<void>}
169
243
  */
170
- export function downloadFile(url, destPath, redirects = 5) {
244
+ export function downloadFile(url, destPath, redirects = 5, onProgress = null) {
171
245
  return new Promise((resolve, reject) => {
172
246
  if (redirects < 0) {
173
247
  return reject(new Error('Too many redirects while downloading binary.'));
@@ -175,17 +249,30 @@ export function downloadFile(url, destPath, redirects = 5) {
175
249
 
176
250
  const request = https.get(url, { headers: { 'User-Agent': 'agentworth-npm-resolver' } }, (res) => {
177
251
  if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
178
- return downloadFile(res.headers.location, destPath, redirects - 1).then(resolve, reject);
252
+ return downloadFile(res.headers.location, destPath, redirects - 1, onProgress).then(resolve, reject);
179
253
  }
180
254
 
181
255
  if (res.statusCode !== 200) {
182
256
  return reject(new Error(`Failed to download binary: HTTP ${res.statusCode} from ${url}`));
183
257
  }
184
258
 
259
+ // The asset is ~23 MB and the release CDN is often slow, so silence here reads as a
260
+ // hang. Content-Length comes off the final 200, never the 302 hops above.
261
+ const total = Number(res.headers['content-length']) || 0;
262
+ let received = 0;
263
+ if (onProgress) {
264
+ onProgress(0, total);
265
+ res.on('data', (chunk) => {
266
+ received += chunk.length;
267
+ onProgress(received, total);
268
+ });
269
+ }
270
+
185
271
  const fileStream = fs.createWriteStream(destPath);
186
272
  res.pipe(fileStream);
187
273
 
188
274
  fileStream.on('finish', () => {
275
+ if (onProgress) onProgress(received, total || received);
189
276
  fileStream.close(resolve);
190
277
  });
191
278
 
@@ -221,6 +308,110 @@ function pathBinaryMatchesVersion(binPath, expected) {
221
308
  }
222
309
  }
223
310
 
311
+ /**
312
+ * The version a binary reports, or null if it cannot be asked. Any failure — missing
313
+ * binary, wrong arch, timeout, unparseable output — is a null, never a guess.
314
+ *
315
+ * @param {string} binPath
316
+ * @returns {string|null}
317
+ */
318
+ export function readBinaryVersion(binPath) {
319
+ try {
320
+ const out = execFileSync(binPath, ['--version'], {
321
+ encoding: 'utf8',
322
+ timeout: 5000,
323
+ stdio: ['ignore', 'pipe', 'ignore'],
324
+ });
325
+ const match = out.match(/\d+\.\d+\.\d+/);
326
+ return match ? match[0] : null;
327
+ } catch (_) {
328
+ return null;
329
+ }
330
+ }
331
+
332
+ /** `0.1.17` -> `[0, 1, 17]`, or null when the string is not version-shaped. */
333
+ function versionTriplet(v) {
334
+ const match = String(v || '').match(/(\d+)\.(\d+)\.(\d+)/);
335
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
336
+ }
337
+
338
+ /**
339
+ * The one line printed when the binary about to run is older than the package that is
340
+ * running it. Null on anything else — an equal or newer binary is fine, and a version
341
+ * either side could not be parsed is not evidence of staleness.
342
+ *
343
+ * This cannot see the case that hurts most, a bare `npx agentworth` reusing a months-old
344
+ * *package* from npx's own cache: the package version is then stale too, and both agree.
345
+ * `archie update` says that part; this covers a stale binary under a current package.
346
+ *
347
+ * @param {string|null} resolved - the version the resolved binary reports
348
+ * @param {string} expected - this package's own version
349
+ * @returns {string|null}
350
+ */
351
+ export function staleBinaryNotice(resolved, expected) {
352
+ const a = versionTriplet(resolved);
353
+ const b = versionTriplet(expected);
354
+ if (!a || !b) return null;
355
+ for (let i = 0; i < 3; i += 1) {
356
+ if (a[i] !== b[i]) {
357
+ return a[i] < b[i]
358
+ ? brandLine('-', 'stale', `binary v${resolved} is older than agentworth v${expected}`)
359
+ : null;
360
+ }
361
+ }
362
+ return null;
363
+ }
364
+
365
+ /**
366
+ * The two resolution sources that hand back whatever binary is there without asking its
367
+ * version. Sources 3-7 already refuse a mismatch, and the download cache (8) is versioned
368
+ * by its own path, so only these two can serve something older than this package.
369
+ */
370
+ const UNVERSIONED_SOURCES = new Set(['env:AGENTWORTH_BIN', 'vendor']);
371
+
372
+ /**
373
+ * Drives the download line: redraws in place on a TTY, prints one line to anything else.
374
+ *
375
+ * A pipe or a CI log cannot move the cursor, so it gets the size once and silence after --
376
+ * a loop that scrolls is a loop that lies. The redraw is throttled to 200ms and the lamp
377
+ * alternates on each frame, which is the dig loop's rhythm and the only thing that moves
378
+ * while the first byte is still in flight.
379
+ */
380
+ function startDownloadProgress() {
381
+ const tty = Boolean(process.stderr.isTTY);
382
+ const cols = brandColumns();
383
+ let frame = 0;
384
+ let last = 0;
385
+ let announced = false;
386
+ let drawn = false;
387
+
388
+ const tick = (done, total) => {
389
+ if (!tty) {
390
+ if (announced) return;
391
+ announced = true;
392
+ console.error(
393
+ total > 0
394
+ ? brandLine('*', 'downloading', `${(total / 1048576).toFixed(1)} MB`)
395
+ : brandLine('*', 'downloading', 'the native binary'),
396
+ );
397
+ return;
398
+ }
399
+ const now = Date.now();
400
+ const complete = total > 0 && done >= total;
401
+ if (!complete && drawn && now - last < 200) return;
402
+ last = now;
403
+ const lamp = complete ? '*' : frame++ % 2 === 0 ? '*' : 'o';
404
+ process.stderr.write(`\r${downloadLine(done, total, { cols, unicode: true, lamp })}\x1b[K`);
405
+ drawn = true;
406
+ };
407
+
408
+ const done = () => {
409
+ if (tty && drawn) process.stderr.write('\n');
410
+ };
411
+
412
+ return { tick, done };
413
+ }
414
+
224
415
  /**
225
416
  * Downloads and extracts the precompiled native binary from GitHub Releases into the user's local cache.
226
417
  *
@@ -256,13 +447,18 @@ export async function downloadAndExtractBinary(options = {}) {
256
447
  const archiveName = `agentworth-v${version}-${targetTriple}.tar.gz`;
257
448
  const url = `https://github.com/unfoundbox-crew/agentworth/releases/download/v${version}/${archiveName}`;
258
449
 
259
- if (!options.silent) {
260
- console.error(`\x1b[36m⚡ AgentWorth native binary not found locally. Downloading v${version} for ${platform}-${arch}...\x1b[0m`);
261
- }
262
-
263
450
  const archivePath = path.join(cacheDir, archiveName);
451
+ const progress = options.silent ? null : startDownloadProgress();
452
+
453
+ if (progress) {
454
+ console.error(brandLine('*', 'resolving', `v${version} ${targetTriple}`));
455
+ }
264
456
 
265
- await downloadFile(url, archivePath);
457
+ try {
458
+ await downloadFile(url, archivePath, 5, progress ? progress.tick : null);
459
+ } finally {
460
+ if (progress) progress.done();
461
+ }
266
462
 
267
463
  // No --force-local here: it was a Windows-only GNU tar workaround, and Windows
268
464
  // support was dropped in 8b837c3. BSD tar (macOS's default) doesn't recognize
@@ -289,7 +485,7 @@ export async function downloadAndExtractBinary(options = {}) {
289
485
  }
290
486
 
291
487
  if (!options.silent) {
292
- console.error(`\x1b[32m✔ Successfully installed AgentWorth native binary to ${cachedBinary}\x1b[0m\n`);
488
+ console.error(brandLine('*', 'installed', `${binName} in ${tilde(cacheDir)}`));
293
489
  }
294
490
 
295
491
  return cachedBinary;
@@ -515,25 +711,19 @@ export function resolveBinary(options = {}) {
515
711
  * @returns {string}
516
712
  */
517
713
  export function formatMissingBinaryMessage(platformKey = getPlatformKey()) {
714
+ // The error beat: the torch goes out, the failure line prints under it, nothing moves
715
+ // afterwards. Same voice as the CLI's own screens -- a label, then the command.
518
716
  return [
519
- `\x1b[31m✖ Error: AgentWorth native binary not found for ${platformKey}.\x1b[0m`,
520
- '',
521
- 'AgentWorth requires the native binary to run. You can install or build it via:',
522
- '',
523
- ' \x1b[36m• Standalone install script:\x1b[0m',
524
- ' curl -fsSL https://agentworth.dev/install.sh | sh',
525
- '',
526
- ' \x1b[36m• Cargo:\x1b[0m',
527
- ' cargo install agentworth-cli',
717
+ brandLine(' ', 'error', `no native binary for ${platformKey}`),
528
718
  '',
529
- ' \x1b[36m• Zero-install via npx:\x1b[0m',
530
- ' npx -y agentworth',
719
+ ' archie is a Rust binary with an npm launcher in front of it. Install the binary',
720
+ ' with whichever of these matches how you got here:',
531
721
  '',
532
- ' \x1b[36m• Download the release for your platform:\x1b[0m',
533
- ` https://github.com/unfoundbox-crew/agentworth/releases/latest`,
534
- '',
535
- ' \x1b[36m• Set custom binary path:\x1b[0m',
536
- ' export AGENTWORTH_BIN=/path/to/agentworth',
722
+ ' install script curl -fsSL https://agentworth.dev/install.sh | sh',
723
+ ' cargo cargo install agentworth-cli',
724
+ ' npx npx -y agentworth@latest',
725
+ ' release https://github.com/unfoundbox-crew/agentworth/releases/latest',
726
+ ' a build you own export AGENTWORTH_BIN=/path/to/archie',
537
727
  '',
538
728
  ].join('\n');
539
729
  }
@@ -603,14 +793,23 @@ export function run(argv = process.argv.slice(2), options = {}) {
603
793
  // recursion guard in findPathBinary checks AGENTWORTH_LAUNCHER_ACTIVE). The npm version
604
794
  // is threaded through too, purely so `agentworth version`/`agentworth update` can report
605
795
  // it -- this launcher never reads it back itself.
606
- const childEnv = buildChildEnv(options.env || process.env, getPackageVersion(options.baseDir || __dirname));
796
+ const packageVersion = getPackageVersion(options.baseDir || __dirname);
797
+
798
+ // Say so rather than run something old in silence. Only the sources that were never
799
+ // version-checked are asked, so the usual path costs no extra process.
800
+ if (UNVERSIONED_SOURCES.has(binaryResult.source)) {
801
+ const notice = staleBinaryNotice(readBinaryVersion(binaryResult.path), packageVersion);
802
+ if (notice) console.error(notice);
803
+ }
804
+
805
+ const childEnv = buildChildEnv(options.env || process.env, packageVersion);
607
806
  const result = spawnSync(binaryResult.path, resolvedArgs, {
608
807
  stdio: 'inherit',
609
808
  env: childEnv,
610
809
  });
611
810
 
612
811
  if (result.error) {
613
- console.error(`\x1b[31m✖ Failed to execute ${binaryResult.path}:\x1b[0m`, result.error.message);
812
+ console.error(brandLine(' ', 'error', `could not run ${binaryResult.path}: ${result.error.message}`));
614
813
  return 1;
615
814
  }
616
815
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworth",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Discover, normalize, and understand AI-agent histories locally.",
5
5
  "type": "module",
6
6
  "main": "./lib/resolver.js",