agentworth 0.1.1 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +6 -10
  2. package/lib/resolver.js +234 -33
  3. package/package.json +10 -2
package/README.md CHANGED
@@ -89,16 +89,12 @@ The launcher searches for the native binary in the following priority order:
89
89
 
90
90
  You can also install the native AgentWorth binary directly:
91
91
 
92
- ```bash
93
- # Via Standalone Installer
94
- curl -fsSL https://agentworth.dev/install.sh | sh
95
-
96
- # Via Homebrew
97
- brew install unfoundbox/tap/agentworth
98
-
99
- # Via Cargo
100
- cargo install agentworth-cli
101
- ```
92
+ | Method | Command | Description |
93
+ | :--- | :--- | :--- |
94
+ | **Standalone Script** | `curl -fsSL https://agentworth.dev/install.sh | sh` | Installs the pre-built native binary directly to `~/.local/bin`. |
95
+ | **Homebrew** | `brew install unfoundbox-crew/tap/agentworth` | Installs via official Homebrew tap. |
96
+ | **Cargo (Native)** | `cargo install agentworth-cli` | Compiles and installs `agentworth` & `agwt` to `~/.cargo/bin`. |
97
+ | **NPX (Instant)** | `npx agentworth` | Zero-install runner that detects or downloads the native binary. |
102
98
 
103
99
  ---
104
100
 
package/lib/resolver.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
- import { spawnSync } from 'node:child_process';
4
+ import https from 'node:https';
5
+ import zlib from 'node:zlib';
6
+ import { spawnSync, execFileSync } from 'node:child_process';
5
7
  import { fileURLToPath } from 'node:url';
6
8
 
7
9
  const __filename = fileURLToPath(import.meta.url);
@@ -18,6 +20,54 @@ export function getPlatformKey(platform = process.platform, arch = process.arch)
18
20
  return `${platform}-${arch}`;
19
21
  }
20
22
 
23
+ /**
24
+ * Maps Node platform and arch to Rust release target triple.
25
+ *
26
+ * @param {string} [platform=process.platform]
27
+ * @param {string} [arch=process.arch]
28
+ * @returns {string | null}
29
+ */
30
+ export function getTargetTriple(platform = process.platform, arch = process.arch) {
31
+ if (platform === 'darwin' && arch === 'arm64') return 'aarch64-apple-darwin';
32
+ if (platform === 'darwin' && arch === 'x64') return 'x86_64-apple-darwin';
33
+ if (platform === 'linux' && arch === 'x64') return 'x86_64-unknown-linux-gnu';
34
+ if (platform === 'linux' && arch === 'arm64') return 'aarch64-unknown-linux-gnu';
35
+ if (platform === 'win32' && arch === 'x64') return 'x86_64-pc-windows-msvc';
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * Reads the package version from package.json.
41
+ *
42
+ * @param {string} [baseDir=__dirname]
43
+ * @returns {string}
44
+ */
45
+ export function getPackageVersion(baseDir = __dirname) {
46
+ try {
47
+ const pkgPath = path.resolve(baseDir, '..', 'package.json');
48
+ if (fs.existsSync(pkgPath)) {
49
+ const data = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
50
+ return data.version || '0.1.3';
51
+ }
52
+ } catch {
53
+ // fallback
54
+ }
55
+ return '0.1.3';
56
+ }
57
+
58
+ /**
59
+ * Returns the local cache directory for AgentWorth binaries (~/.agentworth/bin/v{version}/).
60
+ *
61
+ * @param {string} [version]
62
+ * @param {string} [homeDir]
63
+ * @returns {string}
64
+ */
65
+ export function getCacheDir(version, homeDir) {
66
+ const v = version || getPackageVersion();
67
+ const home = homeDir || os.homedir();
68
+ return path.join(home, '.agentworth', 'bin', `v${v}`);
69
+ }
70
+
21
71
  /**
22
72
  * Returns the expected native binary name for the target platform.
23
73
  *
@@ -95,6 +145,117 @@ export function findCargoTargetBinary(startDir, binName = getBinaryName()) {
95
145
  return null;
96
146
  }
97
147
 
148
+ /**
149
+ * Downloads a file following HTTP redirects.
150
+ *
151
+ * @param {string} url
152
+ * @param {string} destPath
153
+ * @param {number} [redirects=5]
154
+ * @returns {Promise<void>}
155
+ */
156
+ export function downloadFile(url, destPath, redirects = 5) {
157
+ return new Promise((resolve, reject) => {
158
+ if (redirects < 0) {
159
+ return reject(new Error('Too many redirects while downloading binary.'));
160
+ }
161
+
162
+ const request = https.get(url, { headers: { 'User-Agent': 'agentworth-npm-resolver' } }, (res) => {
163
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
164
+ return downloadFile(res.headers.location, destPath, redirects - 1).then(resolve, reject);
165
+ }
166
+
167
+ if (res.statusCode !== 200) {
168
+ return reject(new Error(`Failed to download binary: HTTP ${res.statusCode} from ${url}`));
169
+ }
170
+
171
+ const fileStream = fs.createWriteStream(destPath);
172
+ res.pipe(fileStream);
173
+
174
+ fileStream.on('finish', () => {
175
+ fileStream.close(resolve);
176
+ });
177
+
178
+ fileStream.on('error', (err) => {
179
+ fs.unlink(destPath, () => reject(err));
180
+ });
181
+ });
182
+
183
+ request.on('error', (err) => {
184
+ reject(err);
185
+ });
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Downloads and extracts the precompiled native binary from GitHub Releases into the user's local cache.
191
+ *
192
+ * @param {Object} [options={}]
193
+ * @param {string} [options.platform=process.platform]
194
+ * @param {string} [options.arch=process.arch]
195
+ * @param {string} [options.version]
196
+ * @param {string} [options.homeDir]
197
+ * @param {boolean} [options.silent=false]
198
+ * @returns {Promise<string>} Path to extracted binary
199
+ */
200
+ export async function downloadAndExtractBinary(options = {}) {
201
+ const platform = options.platform || process.platform;
202
+ const arch = options.arch || process.arch;
203
+ const version = options.version || getPackageVersion();
204
+ const targetTriple = getTargetTriple(platform, arch);
205
+ const binName = getBinaryName(platform);
206
+
207
+ if (!targetTriple) {
208
+ throw new Error(`Unsupported platform/architecture: ${platform}-${arch}`);
209
+ }
210
+
211
+ const cacheDir = getCacheDir(version, options.homeDir);
212
+ const cachedBinary = path.join(cacheDir, binName);
213
+
214
+ if (isExecutable(cachedBinary)) {
215
+ return cachedBinary;
216
+ }
217
+
218
+ fs.mkdirSync(cacheDir, { recursive: true });
219
+
220
+ const archiveName = `agentworth-v${version}-${targetTriple}.tar.gz`;
221
+ const url = `https://github.com/unfoundbox-crew/agentworth/releases/download/v${version}/${archiveName}`;
222
+
223
+ if (!options.silent) {
224
+ console.error(`\x1b[36m⚡ AgentWorth native binary not found locally. Downloading v${version} for ${platform}-${arch}...\x1b[0m`);
225
+ }
226
+
227
+ const archivePath = path.join(cacheDir, archiveName);
228
+
229
+ await downloadFile(url, archivePath);
230
+
231
+ // Extract archive
232
+ try {
233
+ execFileSync('tar', ['-xzf', archivePath, '-C', cacheDir]);
234
+ } catch (err) {
235
+ throw new Error(`Failed to extract ${archiveName}: ${err.message}`);
236
+ } finally {
237
+ try {
238
+ if (fs.existsSync(archivePath)) {
239
+ fs.unlinkSync(archivePath);
240
+ }
241
+ } catch {}
242
+ }
243
+
244
+ if (process.platform !== 'win32') {
245
+ fs.chmodSync(cachedBinary, 0o755);
246
+ }
247
+
248
+ if (!isExecutable(cachedBinary)) {
249
+ throw new Error(`Downloaded binary is not executable: ${cachedBinary}`);
250
+ }
251
+
252
+ if (!options.silent) {
253
+ console.error(`\x1b[32m✔ Successfully installed AgentWorth native binary to ${cachedBinary}\x1b[0m\n`);
254
+ }
255
+
256
+ return cachedBinary;
257
+ }
258
+
98
259
  /**
99
260
  * Searches for the agentworth binary in the system PATH.
100
261
  *
@@ -130,11 +291,12 @@ export function findPathBinary(binName = getBinaryName(), pathEnv = process.env.
130
291
  * Search priority:
131
292
  * 1. AGENTWORTH_BIN environment variable
132
293
  * 2. Platform-specific optional package / vendor directory
133
- * 3. Cargo target in cwd directory hierarchy (release or debug)
134
- * 4. Cargo target in package directory hierarchy
135
- * 5. CARGO_TARGET_DIR if set
136
- * 6. User ~/.cargo/bin/
137
- * 7. System PATH
294
+ * 3. System PATH
295
+ * 4. Cargo target in cwd directory hierarchy (release or debug)
296
+ * 5. Cargo target in package directory hierarchy
297
+ * 6. CARGO_TARGET_DIR if set
298
+ * 7. User ~/.cargo/bin/
299
+ * 8. User local cache ~/.agentworth/bin/v{version}/
138
300
  *
139
301
  * @param {Object} [options={}]
140
302
  * @param {string} [options.cwd=process.cwd()]
@@ -142,6 +304,7 @@ export function findPathBinary(binName = getBinaryName(), pathEnv = process.env.
142
304
  * @param {string} [options.arch=process.arch]
143
305
  * @param {NodeJS.ProcessEnv} [options.env=process.env]
144
306
  * @param {string} [options.baseDir=__dirname]
307
+ * @param {string} [options.homeDir]
145
308
  * @returns {{ found: boolean, path?: string, source?: string, error?: string }}
146
309
  */
147
310
  export function resolveBinary(options = {}) {
@@ -150,6 +313,7 @@ export function resolveBinary(options = {}) {
150
313
  const arch = options.arch || process.arch;
151
314
  const env = options.env || process.env;
152
315
  const baseDir = options.baseDir || __dirname;
316
+ const homeDir = options.homeDir || (env.HOME ? env.HOME : os.homedir());
153
317
  const binName = getBinaryName(platform);
154
318
  const platformKey = getPlatformKey(platform, arch);
155
319
 
@@ -172,7 +336,20 @@ export function resolveBinary(options = {}) {
172
336
  };
173
337
  }
174
338
 
175
- // 3. Local Cargo target from working directory
339
+ // 3. System PATH (highest user priority for installed binaries)
340
+ const currentBin = path.resolve(baseDir, '..', 'bin', 'agentworth.js');
341
+ if (env.PATH !== undefined) {
342
+ const pathBin = findPathBinary(binName, env.PATH, currentBin);
343
+ if (pathBin) {
344
+ return {
345
+ found: true,
346
+ path: pathBin,
347
+ source: 'path',
348
+ };
349
+ }
350
+ }
351
+
352
+ // 4. Local Cargo target from working directory
176
353
  const cwdCargoBin = findCargoTargetBinary(cwd, binName);
177
354
  if (cwdCargoBin) {
178
355
  return {
@@ -182,7 +359,7 @@ export function resolveBinary(options = {}) {
182
359
  };
183
360
  }
184
361
 
185
- // 4. Local Cargo target from package directory hierarchy
362
+ // 5. Local Cargo target from package directory hierarchy
186
363
  const pkgCargoBin = findCargoTargetBinary(baseDir, binName);
187
364
  if (pkgCargoBin) {
188
365
  return {
@@ -192,7 +369,7 @@ export function resolveBinary(options = {}) {
192
369
  };
193
370
  }
194
371
 
195
- // 5. CARGO_TARGET_DIR environment variable if specified
372
+ // 6. CARGO_TARGET_DIR environment variable if specified
196
373
  if (env.CARGO_TARGET_DIR) {
197
374
  const targetDirRelease = path.join(env.CARGO_TARGET_DIR, 'release', binName);
198
375
  if (isExecutable(targetDirRelease)) {
@@ -212,25 +389,27 @@ export function resolveBinary(options = {}) {
212
389
  }
213
390
  }
214
391
 
215
- // 6. User ~/.cargo/bin directory
216
- const cargoHomeBin = path.join(os.homedir(), '.cargo', 'bin', binName);
217
- if (isExecutable(cargoHomeBin)) {
218
- return {
219
- found: true,
220
- path: cargoHomeBin,
221
- source: 'cargo-home-bin',
222
- };
223
- }
392
+ // 7. User ~/.cargo/bin directory
393
+ if (homeDir) {
394
+ const cargoHomeBin = path.join(homeDir, '.cargo', 'bin', binName);
395
+ if (isExecutable(cargoHomeBin)) {
396
+ return {
397
+ found: true,
398
+ path: cargoHomeBin,
399
+ source: 'cargo-home-bin',
400
+ };
401
+ }
224
402
 
225
- // 7. System PATH
226
- const currentBin = path.resolve(baseDir, '..', 'bin', 'agentworth.js');
227
- const pathBin = findPathBinary(binName, env.PATH, currentBin);
228
- if (pathBin) {
229
- return {
230
- found: true,
231
- path: pathBin,
232
- source: 'path',
233
- };
403
+ // 8. User local cache (~/.agentworth/bin/v{version}/)
404
+ const cacheDir = getCacheDir(getPackageVersion(baseDir), homeDir);
405
+ const cachedBin = path.join(cacheDir, binName);
406
+ if (isExecutable(cachedBin)) {
407
+ return {
408
+ found: true,
409
+ path: cachedBin,
410
+ source: 'cache',
411
+ };
412
+ }
234
413
  }
235
414
 
236
415
  return {
@@ -257,11 +436,8 @@ export function formatMissingBinaryMessage(platformKey = getPlatformKey()) {
257
436
  ' \x1b[36m• Install via Cargo:\x1b[0m',
258
437
  ' cargo install --path apps/cli',
259
438
  '',
260
- ' \x1b[36m• Install via Homebrew:\x1b[0m',
261
- ' brew install agentworth',
262
- '',
263
- ' \x1b[36m• Install via Shell script:\x1b[0m',
264
- ' curl -fsSL https://agentworth.dev/install.sh | sh',
439
+ ' \x1b[36m• Download the release for your platform:\x1b[0m',
440
+ ` https://github.com/unfoundbox-crew/agentworth/releases/latest`,
265
441
  '',
266
442
  ' \x1b[36m• Set custom binary path:\x1b[0m',
267
443
  ' export AGENTWORTH_BIN=/path/to/agentworth',
@@ -278,7 +454,32 @@ export function formatMissingBinaryMessage(platformKey = getPlatformKey()) {
278
454
  */
279
455
  export function run(argv = process.argv.slice(2), options = {}) {
280
456
  const resolvedArgs = resolveArguments(argv);
281
- const binaryResult = resolveBinary(options);
457
+ let binaryResult = resolveBinary(options);
458
+
459
+ // If binary not found on clean machine, attempt on-demand download from GitHub Release
460
+ if ((!binaryResult.found || !binaryResult.path) && options.autoDownload !== false) {
461
+ try {
462
+ const resolverModuleUrl = new URL('./resolver.js', import.meta.url).href;
463
+ const syncDownloadScript = `
464
+ import { downloadAndExtractBinary } from '${resolverModuleUrl}';
465
+ await downloadAndExtractBinary({
466
+ platform: ${JSON.stringify(options.platform || process.platform)},
467
+ arch: ${JSON.stringify(options.arch || process.arch)},
468
+ homeDir: ${JSON.stringify(options.homeDir || (options.env && options.env.HOME) || '')}
469
+ });
470
+ `;
471
+ const dlResult = spawnSync(process.execPath, ['--input-type=module', '-e', syncDownloadScript], {
472
+ stdio: 'inherit',
473
+ env: options.env || process.env,
474
+ });
475
+
476
+ if (dlResult.status === 0) {
477
+ binaryResult = resolveBinary(options);
478
+ }
479
+ } catch {
480
+ // Fall through to error message below
481
+ }
482
+ }
282
483
 
283
484
  if (!binaryResult.found || !binaryResult.path) {
284
485
  const message = formatMissingBinaryMessage(getPlatformKey(options.platform, options.arch));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentworth",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "Discover, normalize, and understand AI-agent histories locally.",
5
5
  "type": "module",
6
6
  "main": "./lib/resolver.js",
@@ -23,8 +23,16 @@
23
23
  "analytics",
24
24
  "developer-tools"
25
25
  ],
26
- "author": "Unfoundbox",
26
+ "author": "Unfoundbox Crew",
27
27
  "license": "Apache-2.0",
28
+ "homepage": "https://agentworth.dev",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/unfoundbox-crew/agentworth.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/unfoundbox-crew/agentworth/issues"
35
+ },
28
36
  "engines": {
29
37
  "node": ">=18.0.0"
30
38
  },