@openagentsinc/pylon 0.1.4 → 0.1.5

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
@@ -3,7 +3,7 @@
3
3
  Bootstrap the latest tagged standalone `Pylon` release asset from GitHub
4
4
  Releases, fall back to a deterministic source build when no matching asset
5
5
  exists for the local platform, stream first-run status updates in the terminal,
6
- and open the Pylon terminal UI without Cargo when prebuilt binaries are
6
+ and start the Pylon default earning loop without Cargo when prebuilt binaries are
7
7
  available.
8
8
 
9
9
  ## Usage
@@ -51,7 +51,8 @@ The launcher:
51
51
  itself
52
52
  - falls back to `curl` for release metadata and asset downloads when the Node
53
53
  fetch path fails in constrained network contexts
54
- - opens `pylon-tui` by default after the smoke path unless `--no-launch` is set
54
+ - starts the installed `pylon` earning loop by default after the smoke path
55
+ unless `--no-launch` is set
55
56
  - does not try to install or register a local runtime automatically; the
56
57
  bootstrap stays honest about the separate Ollama-compatible runtime
57
58
  prerequisite instead of mutating the host behind the user's back
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openagentsinc/pylon",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Bootstrap the standalone OpenAgents Pylon release asset and run first-run smoke checks.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  DEFAULT_RELEASE_REPO,
7
7
  bootstrapInstalledPylon,
8
8
  ensureReleaseInstall,
9
- launchInstalledPylonTui,
9
+ launchInstalledPylon,
10
10
  resolveBootstrapOutcome,
11
11
  resolvePlatformTarget,
12
12
  renderBootstrapSummary,
@@ -83,7 +83,7 @@ Description:
83
83
  or a specific tagged Pylon version when --version is set. If no matching
84
84
  asset exists for the local platform, fetch the exact tagged source checkout
85
85
  and build it locally instead. Cache the binaries, run the first-run smoke
86
- path, and then open the Pylon terminal UI by default with live status
86
+ path, and then start the Pylon default earning loop by default with live status
87
87
  updates. The launcher checks GitHub for newer tagged pylon-v... releases on
88
88
  each default run, but only caches the standalone binaries under the local
89
89
  bootstrap root; it does not replace your global npm or bun pylon command.
@@ -97,14 +97,14 @@ Options:
97
97
  prefetch into the local GGUF cache.
98
98
  Default: ${DEFAULT_MODEL_ID}
99
99
  --download-curated-cache Prefetch the optional Hugging Face GGUF
100
- cache before opening the TUI.
100
+ cache before launching pylon.
101
101
  --diagnostic-repeats <n> Repeat count for pylon gemma diagnose.
102
102
  Default: ${DEFAULT_DIAGNOSTIC_REPEATS}
103
103
  --diagnostic-max-output-tokens <n> Max output tokens for diagnostics.
104
104
  Default: ${DEFAULT_DIAGNOSTIC_MAX_OUTPUT_TOKENS}
105
105
  --skip-model-download Keep the curated GGUF cache skipped.
106
106
  --skip-diagnostics Skip pylon gemma diagnose.
107
- --no-launch Do not open pylon-tui after bootstrap.
107
+ --no-launch Do not start pylon after bootstrap.
108
108
  --verbose Print extra network and recovery detail.
109
109
  --debug-network Alias for --verbose.
110
110
  --json Emit a machine-readable JSON summary.
@@ -227,7 +227,7 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
227
227
  const {
228
228
  ensureReleaseInstallImpl = ensureReleaseInstall,
229
229
  bootstrapInstalledPylonImpl = bootstrapInstalledPylon,
230
- launchInstalledPylonTuiImpl = launchInstalledPylonTui,
230
+ launchInstalledPylonImpl = launchInstalledPylon,
231
231
  createTelemetryClientImpl = createTelemetryClient,
232
232
  } = dependencies;
233
233
  const options = parseArgs(argv);
@@ -308,7 +308,7 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
308
308
  }
309
309
  console.log(renderBootstrapSummary(summary));
310
310
  if (!options.noLaunch) {
311
- await launchInstalledPylonTuiImpl(
311
+ await launchInstalledPylonImpl(
312
312
  {
313
313
  ...options,
314
314
  ...install,
@@ -321,8 +321,8 @@ export async function main(argv = process.argv.slice(2), dependencies = {}) {
321
321
  );
322
322
  } else {
323
323
  reporter?.warning(
324
- "Skipped Pylon terminal UI launch",
325
- "pass no flag to open pylon-tui by default",
324
+ "Skipped Pylon launch",
325
+ "pass no flag to start the default earning loop",
326
326
  );
327
327
  }
328
328
  }
package/src/index.js CHANGED
@@ -24,6 +24,9 @@ const PYLON_RELEASE_TAG_PREFIX = "pylon-v";
24
24
  const RELEASE_ASSET_INSTALL_METHOD = "release_asset";
25
25
  const SOURCE_BUILD_INSTALL_METHOD = "source_build";
26
26
  const PREFERRED_RUNTIME_MODEL_NAME = "gemma4:e4b";
27
+ const LEGACY_SOURCE_BUILD_SIBLING_REPOSITORIES = {
28
+ "spark-sdk": "https://github.com/AtlantisPleb/spark-sdk.git",
29
+ };
27
30
 
28
31
  function emitStatus(onStatus, message, detail = null) {
29
32
  if (typeof onStatus === "function") {
@@ -128,6 +131,27 @@ async function pathExists(value) {
128
131
  }
129
132
  }
130
133
 
134
+ async function findFilesNamed(rootDir, targetName, results = []) {
135
+ const entries = await fs.readdir(rootDir, { withFileTypes: true });
136
+ for (const entry of entries) {
137
+ if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "target") {
138
+ continue;
139
+ }
140
+
141
+ const fullPath = path.join(rootDir, entry.name);
142
+ if (entry.isDirectory()) {
143
+ await findFilesNamed(fullPath, targetName, results);
144
+ continue;
145
+ }
146
+
147
+ if (entry.isFile() && entry.name === targetName) {
148
+ results.push(fullPath);
149
+ }
150
+ }
151
+
152
+ return results;
153
+ }
154
+
131
155
  function defaultInstallRoot() {
132
156
  return path.join(os.homedir(), ".openagents", "pylon", "bootstrap");
133
157
  }
@@ -800,6 +824,105 @@ function manualSourceBuildCommands(tagName, cloneUrl) {
800
824
  ].join("\n");
801
825
  }
802
826
 
827
+ function inferLegacySiblingRepositoryName(relativeDependencyPath) {
828
+ if (typeof relativeDependencyPath !== "string" || !relativeDependencyPath.startsWith("..")) {
829
+ return null;
830
+ }
831
+
832
+ const segments = relativeDependencyPath
833
+ .split(/[\\/]+/)
834
+ .filter(Boolean);
835
+ const firstRepoSegment = segments.find((segment) => segment !== "..");
836
+ if (!firstRepoSegment) {
837
+ return null;
838
+ }
839
+
840
+ return Object.hasOwn(LEGACY_SOURCE_BUILD_SIBLING_REPOSITORIES, firstRepoSegment)
841
+ ? firstRepoSegment
842
+ : null;
843
+ }
844
+
845
+ async function discoverLegacySiblingRepositories(repoDir) {
846
+ const cargoTomlFiles = await findFilesNamed(repoDir, "Cargo.toml");
847
+ const discovered = new Set();
848
+
849
+ for (const cargoTomlPath of cargoTomlFiles) {
850
+ const payload = await fs.readFile(cargoTomlPath, "utf8");
851
+ const matches = payload.matchAll(/path\s*=\s*"([^"]+)"/g);
852
+ for (const match of matches) {
853
+ const dependencyPath = match[1]?.trim();
854
+ const repoName = inferLegacySiblingRepositoryName(dependencyPath);
855
+ if (!repoName) {
856
+ continue;
857
+ }
858
+
859
+ const resolvedPath = path.resolve(path.dirname(cargoTomlPath), dependencyPath);
860
+ if (resolvedPath.startsWith(`${repoDir}${path.sep}`) || resolvedPath === repoDir) {
861
+ continue;
862
+ }
863
+
864
+ discovered.add(repoName);
865
+ }
866
+ }
867
+
868
+ return [...discovered];
869
+ }
870
+
871
+ async function hydrateLegacySiblingRepositories({
872
+ repoDir,
873
+ buildEnv,
874
+ runProcessImpl,
875
+ onStatus,
876
+ telemetryClient,
877
+ releaseTag,
878
+ target,
879
+ }) {
880
+ const repoNames = await discoverLegacySiblingRepositories(repoDir);
881
+ const workspaceRoot = path.dirname(repoDir);
882
+
883
+ for (const repoName of repoNames) {
884
+ const cloneUrl = LEGACY_SOURCE_BUILD_SIBLING_REPOSITORIES[repoName];
885
+ const checkoutDir = path.join(workspaceRoot, repoName);
886
+ if (!cloneUrl || (await pathExists(checkoutDir))) {
887
+ continue;
888
+ }
889
+
890
+ emitStatus(onStatus, "Hydrating legacy sibling checkout", repoName);
891
+ emitTelemetry(telemetryClient, "installer_legacy_sibling_checkout_started", {
892
+ release_tag: releaseTag,
893
+ os: target.os,
894
+ arch: target.arch,
895
+ repository: repoName,
896
+ });
897
+
898
+ try {
899
+ await runProcessImpl(
900
+ "git",
901
+ ["clone", "--depth", "1", cloneUrl, checkoutDir],
902
+ {
903
+ cwd: workspaceRoot,
904
+ env: buildEnv,
905
+ },
906
+ );
907
+ emitTelemetry(telemetryClient, "installer_legacy_sibling_checkout_completed", {
908
+ release_tag: releaseTag,
909
+ os: target.os,
910
+ arch: target.arch,
911
+ repository: repoName,
912
+ });
913
+ } catch (error) {
914
+ emitTelemetry(telemetryClient, "installer_legacy_sibling_checkout_failed", {
915
+ release_tag: releaseTag,
916
+ os: target.os,
917
+ arch: target.arch,
918
+ repository: repoName,
919
+ ...telemetryFailureContext(error, "legacy_sibling_checkout"),
920
+ });
921
+ throw error;
922
+ }
923
+ }
924
+ }
925
+
803
926
  function rustInstallCommand() {
804
927
  return "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y";
805
928
  }
@@ -1008,6 +1131,15 @@ async function installSourceBuild(
1008
1131
  env: buildEnv,
1009
1132
  },
1010
1133
  );
1134
+ await hydrateLegacySiblingRepositories({
1135
+ repoDir,
1136
+ buildEnv,
1137
+ runProcessImpl,
1138
+ onStatus,
1139
+ telemetryClient,
1140
+ releaseTag: selected.tagName,
1141
+ target,
1142
+ });
1011
1143
 
1012
1144
  const { stdout: commitStdout } = await runProcessImpl(
1013
1145
  "git",
@@ -1778,21 +1910,23 @@ export async function bootstrapInstalledPylon(
1778
1910
  }
1779
1911
  }
1780
1912
 
1781
- export async function launchInstalledPylonTui(
1913
+ export async function launchInstalledPylon(
1782
1914
  options,
1783
1915
  {
1784
1916
  runProcessImpl = runProcess,
1785
1917
  onStatus = null,
1786
1918
  } = {},
1787
1919
  ) {
1788
- const pylonTuiPath = path.resolve(options.pylonTuiPath);
1789
- emitStatus(onStatus, "Opening Pylon terminal UI", path.basename(pylonTuiPath));
1790
- return runProcessImpl(pylonTuiPath, [], {
1920
+ const pylonPath = path.resolve(options.pylonPath);
1921
+ emitStatus(onStatus, "Starting Pylon default earning loop", path.basename(pylonPath));
1922
+ return runProcessImpl(pylonPath, [], {
1791
1923
  env: buildPylonEnv(options),
1792
1924
  stdio: "inherit",
1793
1925
  });
1794
1926
  }
1795
1927
 
1928
+ export const launchInstalledPylonTui = launchInstalledPylon;
1929
+
1796
1930
  export function resolveBootstrapOutcome(summary) {
1797
1931
  const runtimeState =
1798
1932
  summary.status?.snapshot?.runtime?.authoritative_status ?? "unknown";
@@ -1855,7 +1989,7 @@ function renderBootstrapNextSteps(summary, outcome) {
1855
1989
  ];
1856
1990
 
1857
1991
  if (outcome.verdict === "fully online" || outcome.verdict === "runtime ready") {
1858
- lines.push("Next step: open the TUI with `pylon`, or keep using the package-managed launcher.");
1992
+ lines.push("Next step: run `pylon`; it starts the default online earning loop.");
1859
1993
  return lines;
1860
1994
  }
1861
1995