@openagentsinc/pylon 0.1.16 → 0.2.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/README.md CHANGED
@@ -16,6 +16,7 @@ bun install -g @openagentsinc/pylon && pylon
16
16
  npx @openagentsinc/pylon --version 0.1.16
17
17
  npx @openagentsinc/pylon --no-launch
18
18
  npx @openagentsinc/pylon --no-updates
19
+ npx @openagentsinc/pylon status --json
19
20
  npx @openagentsinc/pylon --download-curated-cache --model gemma-4-e2b --run-diagnostics
20
21
  npx @openagentsinc/pylon --verbose
21
22
  ```
@@ -27,8 +28,9 @@ The launcher:
27
28
  - checks GitHub for the latest tagged `pylon-v...` release on each default run,
28
29
  or resolves a specific tagged `Pylon` version when `--version` is provided
29
30
  - only installs releases initiated by `AtlantisPleb` in GitHub Releases
30
- - resolves the correct `pylon-v<version>-<os>-<arch>.tar.gz` asset for the
31
- current machine
31
+ - resolves the correct release asset for the current machine:
32
+ `pylon-v<version>-<os>-<arch>.tar.gz` on darwin/linux and
33
+ `pylon-v<version>-windows-x86_64.zip` on native Windows x86_64
32
34
  - falls back to the exact tagged source checkout and builds `pylon` plus
33
35
  `pylon-tui` locally when no matching release asset exists for the machine
34
36
  - prompts before installing the Rust toolchain via `rustup` if a source build
@@ -60,23 +62,31 @@ The launcher:
60
62
  - starts the installed `pylon-tui` by default after the smoke path; that TUI
61
63
  starts and supervises the earning worker
62
64
  unless `--no-launch` is set
65
+ - forwards CLI subcommands such as `pylon status --json` to the installed
66
+ `pylon` binary after bootstrap instead of opening `pylon-tui`
67
+ - on native Windows x86_64, installs `pylon.exe` and `pylon-tui.exe` from the
68
+ Windows `.zip` release asset and forwards CLI subcommands to `pylon.exe`
63
69
  - while the TUI is running on the default release track, checks GitHub Releases
64
- every 30 seconds and restarts the TUI from a newer trusted cached release
70
+ on a six-hour cadence and restarts the TUI from a newer trusted cached release
65
71
  without replacing the global npm/bun command
66
72
  - use `--no-updates` to keep the current installed release running without
67
- background GitHub release checks; `--version` remains a pinned release run
73
+ background GitHub release checks; `--version` remains a pinned release run.
74
+ Set `GITHUB_TOKEN` or `GH_TOKEN` when you want authenticated GitHub release
75
+ lookups.
68
76
  - owns the current auto-update contract. Directly extracted GitHub release
69
77
  assets do not contain a native updater today; if an operator runs
70
78
  `./pylon` from an unpacked archive, that process stays on its compiled
71
79
  version until the operator manually replaces the archive or switches back to
72
80
  the npm/bun launcher.
73
- - for hosted homework/training work, use launcher `0.1.16` or newer so the
81
+ - for hosted homework/training work, use launcher `0.1.17` or newer so the
74
82
  cached standalone binary auto-updates while the dashboard is open. The
75
83
  `pylon-v0.1.16` standalone binary keeps the long hosted homework ID hashing
76
84
  from `0.1.14`, refuses to seal terminal training windows until the worker
77
85
  contribution artifact bundle has uploaded and verified for validator replay,
78
86
  and packages the minimal Psionic training runtime so standalone installs can
79
- advertise homework-worker capability.
87
+ advertise homework-worker capability. Launcher `0.1.17` adds CLI subcommand
88
+ forwarding and bounds background GitHub release checks to avoid
89
+ unauthenticated rate-limit churn.
80
90
  - does not try to install or register a local runtime automatically; the
81
91
  bootstrap stays honest about the separate local Gemma runtime
82
92
  prerequisite instead of mutating the host behind the user's back
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@openagentsinc/pylon",
3
- "version": "0.1.16",
3
+ "version": "0.2.2",
4
4
  "description": "Bootstrap the standalone OpenAgents Pylon release asset and run first-run smoke checks.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "pylon": "./bin/pylon"
7
+ "pylon": "bin/pylon"
8
8
  },
9
9
  "files": [
10
10
  "bin",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "https://github.com/OpenAgentsInc/openagents.git",
19
+ "url": "git+https://github.com/OpenAgentsInc/openagents.git",
20
20
  "directory": "packages/pylon-bootstrap"
21
21
  },
22
22
  "scripts": {
package/src/cli.js CHANGED
@@ -6,11 +6,11 @@ import {
6
6
  DEFAULT_RELEASE_REPO,
7
7
  bootstrapInstalledPylon,
8
8
  ensureReleaseInstall,
9
- launchInstalledPylon,
10
9
  launchInstalledPylonWithUpdates,
11
10
  resolveBootstrapOutcome,
12
11
  resolvePlatformTarget,
13
12
  renderBootstrapSummary,
13
+ runInstalledPylonCli,
14
14
  } from "./index.js";
15
15
  import {
16
16
  createTelemetryClient,
@@ -78,6 +78,8 @@ export function usage() {
78
78
  npx @openagentsinc/pylon [options]
79
79
  bunx @openagentsinc/pylon [options]
80
80
  pylon [options]
81
+ pylon [options] <pylon-command> [pylon-options]
82
+ pylon [options] -- <pylon-command> [pylon-options]
81
83
 
82
84
  Description:
83
85
  Download the latest tagged standalone Pylon release asset for this machine,
@@ -86,11 +88,15 @@ Description:
86
88
  and build it locally instead. Cache the binaries, run the first-run smoke
87
89
  path, and then start the Pylon terminal UI by default. The terminal UI manages
88
90
  the earning worker and keeps live status visible. The launcher checks GitHub
89
- for newer tagged pylon-v... releases on each default run and every 30 seconds
91
+ for newer tagged pylon-v... releases on each default run and periodically
90
92
  while the dashboard is open. Only releases initiated by AtlantisPleb are
91
93
  accepted. New standalone binaries are cached under the local bootstrap root;
92
94
  the global npm or bun pylon command is not replaced.
93
95
 
96
+ When a Pylon command is provided, the launcher bootstraps the managed release
97
+ and forwards that command to the installed pylon binary instead of opening
98
+ pylon-tui. For example: pylon status --json.
99
+
94
100
  Options:
95
101
  --version <x.y.z> Resolve a specific Pylon release.
96
102
  --install-root <path> Override the launcher cache/install root.
@@ -109,7 +115,7 @@ Options:
109
115
  --skip-model-download Keep the curated GGUF cache skipped.
110
116
  --skip-diagnostics Keep optional pylon gemma diagnose skipped.
111
117
  --no-launch Do not start pylon-tui after bootstrap.
112
- --no-updates Disable 30-second GitHub release polling
118
+ --no-updates Disable background GitHub release polling
113
119
  and dashboard restart while pylon runs.
114
120
  --verbose Print extra network and recovery detail.
115
121
  --debug-network Alias for --verbose.
@@ -140,10 +146,19 @@ export function parseArgs(argv) {
140
146
  verbose: false,
141
147
  json: false,
142
148
  help: false,
149
+ pylonArgs: [],
143
150
  };
144
151
 
145
152
  for (let index = 0; index < argv.length; index += 1) {
146
153
  const arg = argv[index];
154
+ if (arg === "--") {
155
+ options.pylonArgs = argv.slice(index + 1);
156
+ break;
157
+ }
158
+ if (!arg.startsWith("-")) {
159
+ options.pylonArgs = argv.slice(index);
160
+ break;
161
+ }
147
162
  switch (arg) {
148
163
  case "--version":
149
164
  options.version = argv[++index];
@@ -241,6 +256,7 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
241
256
  ensureReleaseInstallImpl = ensureReleaseInstall,
242
257
  bootstrapInstalledPylonImpl = bootstrapInstalledPylon,
243
258
  launchInstalledPylonImpl = launchInstalledPylonWithUpdates,
259
+ runInstalledPylonCliImpl = runInstalledPylonCli,
244
260
  createTelemetryClientImpl = createTelemetryClient,
245
261
  } = dependencies;
246
262
  const options = parseArgs(argv);
@@ -320,7 +336,21 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
320
336
  reporter?.warning(`Pylon ${outcome.verdict}`, outcome.detail);
321
337
  }
322
338
  console.log(renderBootstrapSummary(summary));
323
- if (!options.noLaunch) {
339
+ if (options.pylonArgs.length > 0) {
340
+ await runInstalledPylonCliImpl(
341
+ {
342
+ ...options,
343
+ ...install,
344
+ version: install.version,
345
+ },
346
+ options.pylonArgs,
347
+ {
348
+ ...dependencies,
349
+ onStatus: reporter?.status,
350
+ telemetryClient,
351
+ },
352
+ );
353
+ } else if (!options.noLaunch) {
324
354
  await launchInstalledPylonImpl(
325
355
  {
326
356
  ...options,
package/src/index.js CHANGED
@@ -20,7 +20,7 @@ export const DEFAULT_MODEL_ID = "gemma-4-e4b";
20
20
  export const DEFAULT_DIAGNOSTIC_REPEATS = 3;
21
21
  export const DEFAULT_DIAGNOSTIC_MAX_OUTPUT_TOKENS = 96;
22
22
  export const DEFAULT_FETCH_TIMEOUT_MS = 15_000;
23
- export const DEFAULT_UPDATE_CHECK_INTERVAL_MS = 30_000;
23
+ export const DEFAULT_UPDATE_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
24
24
  export const DEFAULT_TRUSTED_RELEASE_AUTHOR = "AtlantisPleb";
25
25
  const PYLON_RELEASE_TAG_PREFIX = "pylon-v";
26
26
  const RELEASE_ASSET_INSTALL_METHOD = "release_asset";
@@ -30,6 +30,18 @@ const LEGACY_SOURCE_BUILD_SIBLING_REPOSITORIES = {
30
30
  "spark-sdk": "https://github.com/AtlantisPleb/spark-sdk.git",
31
31
  };
32
32
 
33
+ function archiveExtensionForTarget(target) {
34
+ return target?.os === "windows" ? ".zip" : ".tar.gz";
35
+ }
36
+
37
+ function binaryExtensionForTarget(target) {
38
+ return target?.os === "windows" ? ".exe" : "";
39
+ }
40
+
41
+ function binaryNameForTarget(binaryName, target) {
42
+ return `${binaryName}${binaryExtensionForTarget(target)}`;
43
+ }
44
+
33
45
  function emitStatus(onStatus, message, detail = null) {
34
46
  if (typeof onStatus === "function") {
35
47
  onStatus({ message, detail });
@@ -215,8 +227,9 @@ function requestHeaders() {
215
227
  accept: "application/vnd.github+json",
216
228
  "user-agent": "@openagentsinc/pylon bootstrap",
217
229
  };
218
- if (process.env.GITHUB_TOKEN) {
219
- headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
230
+ const githubToken = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN;
231
+ if (githubToken) {
232
+ headers.authorization = `Bearer ${githubToken}`;
220
233
  }
221
234
  return headers;
222
235
  }
@@ -389,10 +402,11 @@ export function resolvePlatformTarget(
389
402
  {
390
403
  darwin: "darwin",
391
404
  linux: "linux",
405
+ win32: "windows",
392
406
  }[platform] ?? null;
393
407
  if (!osLabel) {
394
408
  throw new Error(
395
- `Unsupported platform \`${platform}\`. The npm launcher only supports darwin and linux in v1.`,
409
+ `Unsupported platform \`${platform}\`. The npm launcher only supports darwin, linux, and native Windows in v1.`,
396
410
  );
397
411
  }
398
412
 
@@ -419,10 +433,12 @@ export function buildArchiveBasename(version, target) {
419
433
 
420
434
  export function buildAssetNames(version, target) {
421
435
  const archiveBasename = buildArchiveBasename(version, target);
436
+ const archiveExtension = archiveExtensionForTarget(target);
437
+ const archiveName = `${archiveBasename}${archiveExtension}`;
422
438
  return {
423
439
  archiveBasename,
424
- archiveName: `${archiveBasename}.tar.gz`,
425
- checksumName: `${archiveBasename}.tar.gz.sha256`,
440
+ archiveName,
441
+ checksumName: `${archiveName}.sha256`,
426
442
  };
427
443
  }
428
444
 
@@ -761,8 +777,8 @@ export function buildInstallPaths(installRoot, version, target) {
761
777
  archivePath: path.join(downloadsDir, archiveName),
762
778
  checksumPath: path.join(downloadsDir, checksumName),
763
779
  manifestPath: path.join(installDir, "install.json"),
764
- pylonPath: path.join(installDir, "pylon"),
765
- pylonTuiPath: path.join(installDir, "pylon-tui"),
780
+ pylonPath: path.join(installDir, binaryNameForTarget("pylon", target)),
781
+ pylonTuiPath: path.join(installDir, binaryNameForTarget("pylon-tui", target)),
766
782
  };
767
783
  }
768
784
 
@@ -1218,8 +1234,18 @@ async function installSourceBuild(
1218
1234
  stdio: "inherit",
1219
1235
  });
1220
1236
 
1221
- const builtPylonPath = path.join(repoDir, "target", "release", "pylon");
1222
- const builtPylonTuiPath = path.join(repoDir, "target", "release", "pylon-tui");
1237
+ const builtPylonPath = path.join(
1238
+ repoDir,
1239
+ "target",
1240
+ "release",
1241
+ binaryNameForTarget("pylon", target),
1242
+ );
1243
+ const builtPylonTuiPath = path.join(
1244
+ repoDir,
1245
+ "target",
1246
+ "release",
1247
+ binaryNameForTarget("pylon-tui", target),
1248
+ );
1223
1249
  if (!(await pathExists(builtPylonPath)) || !(await pathExists(builtPylonTuiPath))) {
1224
1250
  throw new Error(
1225
1251
  `Source build completed without the expected binaries at ${path.join(repoDir, "target", "release")}.`,
@@ -1314,8 +1340,11 @@ async function findLatestCachedInstall(installRoot, target) {
1314
1340
 
1315
1341
  const installDir = path.join(versionsDir, entry.name);
1316
1342
  const manifestPath = path.join(installDir, "install.json");
1317
- const pylonPath = path.join(installDir, "pylon");
1318
- const pylonTuiPath = path.join(installDir, "pylon-tui");
1343
+ const pylonPath = path.join(installDir, binaryNameForTarget("pylon", target));
1344
+ const pylonTuiPath = path.join(
1345
+ installDir,
1346
+ binaryNameForTarget("pylon-tui", target),
1347
+ );
1319
1348
  if (
1320
1349
  !(await pathExists(manifestPath)) ||
1321
1350
  !(await pathExists(pylonPath)) ||
@@ -1446,8 +1475,34 @@ function spawnPylonTui(pylonTuiPath, options, spawnProcessImpl) {
1446
1475
  });
1447
1476
  }
1448
1477
 
1449
- async function extractArchive(archivePath, destinationDir, runProcessImpl) {
1478
+ async function extractArchive(archivePath, destinationDir, target, runProcessImpl) {
1450
1479
  await fs.mkdir(destinationDir, { recursive: true });
1480
+ if (target?.os === "windows") {
1481
+ const args = [
1482
+ "-NoProfile",
1483
+ "-NonInteractive",
1484
+ "-ExecutionPolicy",
1485
+ "Bypass",
1486
+ "-Command",
1487
+ "& { param([string]$ArchivePath, [string]$DestinationPath) Expand-Archive -LiteralPath $ArchivePath -DestinationPath $DestinationPath -Force }",
1488
+ archivePath,
1489
+ destinationDir,
1490
+ ];
1491
+ for (const command of ["powershell.exe", "pwsh", "powershell"]) {
1492
+ try {
1493
+ await runProcessImpl(command, args);
1494
+ return;
1495
+ } catch (error) {
1496
+ const message = error instanceof Error ? error.message : String(error);
1497
+ if (!message.startsWith(`Failed to start ${command}`)) {
1498
+ throw error;
1499
+ }
1500
+ }
1501
+ }
1502
+ throw new Error(
1503
+ "Windows archive extraction requires PowerShell (tried powershell.exe, pwsh, and powershell).",
1504
+ );
1505
+ }
1451
1506
  await runProcessImpl("tar", ["-xzf", archivePath, "-C", destinationDir]);
1452
1507
  }
1453
1508
 
@@ -1781,7 +1836,7 @@ export async function ensureReleaseInstall(
1781
1836
  paths.installDir,
1782
1837
  );
1783
1838
  await fs.rm(paths.installDir, { recursive: true, force: true });
1784
- await extractArchive(paths.archivePath, paths.versionsDir, runProcessImpl);
1839
+ await extractArchive(paths.archivePath, paths.versionsDir, target, runProcessImpl);
1785
1840
 
1786
1841
  if (!(await pathExists(paths.pylonPath)) || !(await pathExists(paths.pylonTuiPath))) {
1787
1842
  throw new Error(
@@ -2016,6 +2071,26 @@ export async function launchInstalledPylon(
2016
2071
 
2017
2072
  export const launchInstalledPylonTui = launchInstalledPylon;
2018
2073
 
2074
+ export async function runInstalledPylonCli(
2075
+ options,
2076
+ args = [],
2077
+ {
2078
+ runProcessImpl = runProcess,
2079
+ onStatus = null,
2080
+ } = {},
2081
+ ) {
2082
+ const pylonPath = path.resolve(options.pylonPath);
2083
+ emitStatus(
2084
+ onStatus,
2085
+ "Running Pylon CLI command",
2086
+ [path.basename(pylonPath), ...args].join(" "),
2087
+ );
2088
+ return runProcessImpl(pylonPath, args, {
2089
+ env: buildPylonEnv(options),
2090
+ stdio: "inherit",
2091
+ });
2092
+ }
2093
+
2019
2094
  export async function launchInstalledPylonWithUpdates(
2020
2095
  options,
2021
2096
  {