@openagentsinc/pylon 0.1.4 → 0.1.6
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 +3 -2
- package/package.json +1 -1
- package/src/cli.js +8 -8
- package/src/index.js +139 -5
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
|
|
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
|
-
-
|
|
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
package/src/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
DEFAULT_RELEASE_REPO,
|
|
7
7
|
bootstrapInstalledPylon,
|
|
8
8
|
ensureReleaseInstall,
|
|
9
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
325
|
-
"pass no flag to
|
|
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
|
|
1913
|
+
export async function launchInstalledPylon(
|
|
1782
1914
|
options,
|
|
1783
1915
|
{
|
|
1784
1916
|
runProcessImpl = runProcess,
|
|
1785
1917
|
onStatus = null,
|
|
1786
1918
|
} = {},
|
|
1787
1919
|
) {
|
|
1788
|
-
const
|
|
1789
|
-
emitStatus(onStatus, "
|
|
1790
|
-
return runProcessImpl(
|
|
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:
|
|
1992
|
+
lines.push("Next step: run `pylon`; it starts the default online earning loop.");
|
|
1859
1993
|
return lines;
|
|
1860
1994
|
}
|
|
1861
1995
|
|