@tetsuo-ai/agenc 0.2.0 → 0.2.1
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/bin/agenc +4 -0
- package/generated/.gitkeep +0 -1
- package/generated/agenc-runtime-manifest.json +50 -12
- package/lib/runtime-manager.mjs +169 -0
- package/package.json +13 -21
- package/scripts/build-runtime-tarball.mjs +201 -0
- package/scripts/gen-manifest.mjs +91 -0
- package/scripts/postinstall.mjs +19 -0
- package/src/launcher.mjs +246 -0
- package/README.md +0 -144
- package/bin/agenc-runtime.js +0 -5
- package/bin/agenc.js +0 -5
- package/generated/agenc-runtime-manifest.json.sig +0 -1
- package/generated/agenc-runtime-public-key.pem +0 -3
- package/generated/agenc-runtime-trust-policy.json +0 -9
- package/lib/cli.js +0 -261
- package/lib/runtime-manager.js +0 -1111
- package/scripts/postinstall.js +0 -6
package/bin/agenc
ADDED
package/generated/.gitkeep
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1,24 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifestVersion": 1,
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"generatedAt": "2026-04-12T08:17:12.539Z",
|
|
6
|
-
"releaseChannel": "github-releases",
|
|
7
|
-
"releaseRepository": "tetsuo-ai/agenc-core",
|
|
3
|
+
"runtimeVersion": "0.2.0",
|
|
4
|
+
"releaseRepository": "tetsuo-ai/agenc-releases",
|
|
8
5
|
"releaseTag": "agenc-v0.2.0",
|
|
9
6
|
"artifacts": [
|
|
7
|
+
{
|
|
8
|
+
"platform": "darwin",
|
|
9
|
+
"arch": "arm64",
|
|
10
|
+
"runtimeVersion": "0.2.0",
|
|
11
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.2.0/agenc-runtime-0.2.0-darwin-arm64.tar.gz",
|
|
12
|
+
"sha256": "ad777d177b249cc1a0655c9c0a88cd41b749859103d08185f2d303364bfcbb5e",
|
|
13
|
+
"bytes": 81090457,
|
|
14
|
+
"bins": {
|
|
15
|
+
"agenc": "node_modules/@tetsuo-ai/runtime/bin/agenc"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"platform": "darwin",
|
|
20
|
+
"arch": "x64",
|
|
21
|
+
"runtimeVersion": "0.2.0",
|
|
22
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.2.0/agenc-runtime-0.2.0-darwin-x64.tar.gz",
|
|
23
|
+
"sha256": "1f7fc36fd46152265eae8e4d678092fef826fbd784fa2bb2fa89ee0535e09235",
|
|
24
|
+
"bytes": 81890941,
|
|
25
|
+
"bins": {
|
|
26
|
+
"agenc": "node_modules/@tetsuo-ai/runtime/bin/agenc"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
10
29
|
{
|
|
11
30
|
"platform": "linux",
|
|
31
|
+
"arch": "arm64",
|
|
32
|
+
"runtimeVersion": "0.2.0",
|
|
33
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.2.0/agenc-runtime-0.2.0-linux-arm64.tar.gz",
|
|
34
|
+
"sha256": "2fc204dc5c8ed0484c5be3cb96344069d8db497226e13a164d557882b2f457af",
|
|
35
|
+
"bytes": 81489030,
|
|
36
|
+
"bins": {
|
|
37
|
+
"agenc": "node_modules/@tetsuo-ai/runtime/bin/agenc"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"platform": "linux",
|
|
42
|
+
"arch": "x64",
|
|
43
|
+
"runtimeVersion": "0.2.0",
|
|
44
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.2.0/agenc-runtime-0.2.0-linux-x64.tar.gz",
|
|
45
|
+
"sha256": "84f9f908f6a51d32b28eec9ac479677b7c73c6812ab79ee4ac80b65d0ca82855",
|
|
46
|
+
"bytes": 81500626,
|
|
47
|
+
"bins": {
|
|
48
|
+
"agenc": "node_modules/@tetsuo-ai/runtime/bin/agenc"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"platform": "win",
|
|
12
53
|
"arch": "x64",
|
|
13
|
-
"nodeRange": ">=18.0.0",
|
|
14
54
|
"runtimeVersion": "0.2.0",
|
|
15
|
-
"url": "https://github.com/tetsuo-ai/agenc-
|
|
16
|
-
"sha256": "
|
|
55
|
+
"url": "https://github.com/tetsuo-ai/agenc-releases/releases/download/agenc-v0.2.0/agenc-runtime-0.2.0-win-x64.tar.gz",
|
|
56
|
+
"sha256": "5b93930e876a73534d9e0b718953bdcc1ae2854e3c8ad53f20b9cbced7bdc539",
|
|
57
|
+
"bytes": 80859755,
|
|
17
58
|
"bins": {
|
|
18
|
-
"agenc": "node_modules/@tetsuo-ai/runtime/
|
|
19
|
-
"agenc-runtime": "node_modules/@tetsuo-ai/runtime/dist/bin/agenc-runtime.js",
|
|
20
|
-
"daemon": "node_modules/@tetsuo-ai/runtime/dist/bin/daemon.js",
|
|
21
|
-
"agenc-watch": "node_modules/@tetsuo-ai/runtime/dist/bin/agenc-watch.js"
|
|
59
|
+
"agenc": "node_modules/@tetsuo-ai/runtime/bin/agenc"
|
|
22
60
|
}
|
|
23
61
|
}
|
|
24
62
|
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Install-side runtime resolver for the published launcher.
|
|
2
|
+
//
|
|
3
|
+
// The npm launcher is tiny; the real runtime is a per-platform tarball on
|
|
4
|
+
// GitHub Releases (see scripts/build-runtime-tarball.mjs). This module:
|
|
5
|
+
// 1. resolves the already-extracted runtime under <agenc-home>/runtime/<ver>/,
|
|
6
|
+
// 2. or, if absent, reads the bundled manifest, downloads the artifact for the
|
|
7
|
+
// current platform, verifies its sha256, and extracts it there.
|
|
8
|
+
//
|
|
9
|
+
// It is intentionally dependency-free (Node built-ins only) so it can run from
|
|
10
|
+
// `postinstall` before anything else is available.
|
|
11
|
+
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import {
|
|
15
|
+
createReadStream,
|
|
16
|
+
createWriteStream,
|
|
17
|
+
existsSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
readFileSync,
|
|
20
|
+
rmSync,
|
|
21
|
+
writeFileSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { homedir, tmpdir } from "node:os";
|
|
24
|
+
import { dirname, join, resolve } from "node:path";
|
|
25
|
+
import { Readable } from "node:stream";
|
|
26
|
+
import { pipeline } from "node:stream/promises";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
|
|
29
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const MANIFEST_PATH = resolve(
|
|
31
|
+
__dirname,
|
|
32
|
+
"..",
|
|
33
|
+
"generated",
|
|
34
|
+
"agenc-runtime-manifest.json",
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export function resolveAgenCHome(env = process.env, userHome = homedir()) {
|
|
38
|
+
const configured = env.AGENC_HOME?.trim();
|
|
39
|
+
return configured && configured.length > 0
|
|
40
|
+
? configured
|
|
41
|
+
: join(userHome, ".agenc");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function platformSlug(platform = process.platform, arch = process.arch) {
|
|
45
|
+
const os = platform === "win32" ? "win" : platform;
|
|
46
|
+
return { os, arch };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function readManifest(manifestPath = MANIFEST_PATH) {
|
|
50
|
+
if (!existsSync(manifestPath)) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`agenc: runtime manifest missing (${manifestPath}); this launcher build is incomplete`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function selectArtifact(manifest, slug = platformSlug()) {
|
|
59
|
+
const match = manifest.artifacts?.find(
|
|
60
|
+
(a) => a.platform === slug.os && a.arch === slug.arch,
|
|
61
|
+
);
|
|
62
|
+
if (!match) {
|
|
63
|
+
const have = (manifest.artifacts ?? [])
|
|
64
|
+
.map((a) => `${a.platform}-${a.arch}`)
|
|
65
|
+
.join(", ");
|
|
66
|
+
throw new Error(
|
|
67
|
+
`agenc: no runtime build for ${slug.os}-${slug.arch} (available: ${have || "none"})`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return match;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Directory the artifact extracts to, and the runtime bin inside it.
|
|
74
|
+
export function runtimeInstallDir(home, version) {
|
|
75
|
+
return join(home, "runtime", version);
|
|
76
|
+
}
|
|
77
|
+
export function runtimeBinPath(home, version, artifact) {
|
|
78
|
+
const rel = artifact?.bins?.agenc ?? "node_modules/@tetsuo-ai/runtime/bin/agenc";
|
|
79
|
+
return join(runtimeInstallDir(home, version), rel);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function markerPath(installDir) {
|
|
83
|
+
return join(installDir, ".agenc-runtime-ok");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// A runtime install is "good" only if the success marker exists AND records the
|
|
87
|
+
// expected sha256 — guards against a half-extracted or stale tree.
|
|
88
|
+
export function isInstalled(installDir, expectedSha) {
|
|
89
|
+
const marker = markerPath(installDir);
|
|
90
|
+
if (!existsSync(marker)) return false;
|
|
91
|
+
try {
|
|
92
|
+
return readFileSync(marker, "utf8").trim() === expectedSha;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function sha256File(file) {
|
|
99
|
+
const hash = createHash("sha256");
|
|
100
|
+
await pipeline(createReadStream(file), hash);
|
|
101
|
+
return hash.digest("hex");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function download(url, dest, fetchImpl = globalThis.fetch) {
|
|
105
|
+
// Local file:// support keeps the whole flow testable without a real release.
|
|
106
|
+
if (url.startsWith("file://")) {
|
|
107
|
+
await pipeline(createReadStream(fileURLToPath(url)), createWriteStream(dest));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const res = await fetchImpl(url, { redirect: "follow" });
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
throw new Error(`agenc: download failed ${res.status} ${res.statusText}: ${url}`);
|
|
113
|
+
}
|
|
114
|
+
await pipeline(Readable.fromWeb(res.body), createWriteStream(dest));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function extractTarGz(archive, destDir) {
|
|
118
|
+
mkdirSync(destDir, { recursive: true });
|
|
119
|
+
const res = spawnSync("tar", ["-xzf", archive, "-C", destDir], {
|
|
120
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
121
|
+
});
|
|
122
|
+
if (res.status !== 0) {
|
|
123
|
+
throw new Error(`agenc: tar extraction failed (status ${res.status ?? res.signal})`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Ensure the runtime for `manifest.runtimeVersion` is present; returns the bin
|
|
128
|
+
// path. Idempotent: a verified existing install short-circuits the download.
|
|
129
|
+
export async function ensureRuntime({
|
|
130
|
+
env = process.env,
|
|
131
|
+
userHome = homedir(),
|
|
132
|
+
manifest = readManifest(),
|
|
133
|
+
slug = platformSlug(),
|
|
134
|
+
fetchImpl = globalThis.fetch,
|
|
135
|
+
log = (m) => process.stderr.write(m + "\n"),
|
|
136
|
+
} = {}) {
|
|
137
|
+
const home = resolveAgenCHome(env, userHome);
|
|
138
|
+
const version = manifest.runtimeVersion;
|
|
139
|
+
const artifact = selectArtifact(manifest, slug);
|
|
140
|
+
const installDir = runtimeInstallDir(home, version);
|
|
141
|
+
|
|
142
|
+
if (isInstalled(installDir, artifact.sha256)) {
|
|
143
|
+
return runtimeBinPath(home, version, artifact);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
log(`agenc: fetching runtime ${version} (${slug.os}-${slug.arch})...`);
|
|
147
|
+
const tmp = join(tmpdir(), `agenc-runtime-${version}-${process.pid}.tar.gz`);
|
|
148
|
+
try {
|
|
149
|
+
await download(artifact.url, tmp, fetchImpl);
|
|
150
|
+
const actual = await sha256File(tmp);
|
|
151
|
+
if (actual !== artifact.sha256) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
`agenc: runtime checksum mismatch (expected ${artifact.sha256}, got ${actual})`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
// Replace any partial/old tree atomically-ish: clean then extract.
|
|
157
|
+
rmSync(installDir, { recursive: true, force: true });
|
|
158
|
+
extractTarGz(tmp, installDir);
|
|
159
|
+
const bin = runtimeBinPath(home, version, artifact);
|
|
160
|
+
if (!existsSync(bin)) {
|
|
161
|
+
throw new Error(`agenc: runtime extracted but entry missing: ${bin}`);
|
|
162
|
+
}
|
|
163
|
+
writeFileSync(markerPath(installDir), artifact.sha256);
|
|
164
|
+
log(`agenc: runtime ${version} ready`);
|
|
165
|
+
return bin;
|
|
166
|
+
} finally {
|
|
167
|
+
rmSync(tmp, { force: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
package/package.json
CHANGED
|
@@ -1,40 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tetsuo-ai/agenc",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Public CLI
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Public AgenC CLI launcher package",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"bin": {
|
|
7
|
-
"agenc": "
|
|
8
|
-
"agenc-runtime": "./bin/agenc-runtime.js"
|
|
8
|
+
"agenc": "bin/agenc"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"bin",
|
|
12
|
-
"
|
|
12
|
+
"src",
|
|
13
13
|
"lib",
|
|
14
14
|
"scripts",
|
|
15
|
-
"
|
|
15
|
+
"generated"
|
|
16
16
|
],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"postinstall": "node ./scripts/postinstall.
|
|
19
|
-
"
|
|
18
|
+
"postinstall": "node ./scripts/postinstall.mjs",
|
|
19
|
+
"build:runtime-tarball": "node ./scripts/build-runtime-tarball.mjs",
|
|
20
|
+
"gen:manifest": "node ./scripts/gen-manifest.mjs",
|
|
21
|
+
"test": "node --test test/*.test.mjs"
|
|
20
22
|
},
|
|
21
|
-
"keywords": [
|
|
22
|
-
"agents",
|
|
23
|
-
"cli",
|
|
24
|
-
"runtime",
|
|
25
|
-
"marketplace"
|
|
26
|
-
],
|
|
27
|
-
"author": "Tetsuo AI",
|
|
28
|
-
"license": "MIT",
|
|
29
23
|
"publishConfig": {
|
|
30
24
|
"access": "public"
|
|
31
25
|
},
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"url": "https://github.com/tetsuo-ai/agenc-core",
|
|
35
|
-
"directory": "packages/agenc"
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@tetsuo-ai/runtime": "file:../../runtime"
|
|
36
28
|
},
|
|
37
29
|
"engines": {
|
|
38
|
-
"node": ">=
|
|
30
|
+
"node": ">=25.0.0"
|
|
39
31
|
}
|
|
40
32
|
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Build a self-contained, per-platform runtime artifact for GitHub Releases.
|
|
3
|
+
//
|
|
4
|
+
// The runtime (@tetsuo-ai/runtime) is NOT published to npm — it ships as a
|
|
5
|
+
// platform-specific tarball because it pulls native deps (better-sqlite3,
|
|
6
|
+
// node-pty) that must be compiled for the host OS/arch. This script produces
|
|
7
|
+
// one such artifact for the CURRENT platform; the release CI matrix runs it on
|
|
8
|
+
// each target runner.
|
|
9
|
+
//
|
|
10
|
+
// Output layout (what the install-side runtime-manager extracts verbatim into
|
|
11
|
+
// ~/.agenc/runtime/<version>/):
|
|
12
|
+
//
|
|
13
|
+
// node_modules/@tetsuo-ai/runtime/{bin,dist,package.json,README.md}
|
|
14
|
+
// node_modules/<every production dep, natively built for this platform>/...
|
|
15
|
+
//
|
|
16
|
+
// So the runtime entry after extraction is:
|
|
17
|
+
// <root>/node_modules/@tetsuo-ai/runtime/bin/agenc
|
|
18
|
+
//
|
|
19
|
+
// Steps: build runtime → `npm pack` it (respects its `files`) → `npm install`
|
|
20
|
+
// that tarball with --omit=dev into a staging dir (resolves the prod dep tree
|
|
21
|
+
// and compiles native modules for THIS platform) → tar the resulting
|
|
22
|
+
// node_modules → emit sha256. Pure npm; no hand-rolled dependency resolution.
|
|
23
|
+
|
|
24
|
+
import { spawnSync } from "node:child_process";
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
26
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { createReadStream } from "node:fs";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import { dirname, join, resolve } from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
|
|
32
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const launcherDir = resolve(__dirname, "..");
|
|
34
|
+
const repoRoot = resolve(launcherDir, "..", "..");
|
|
35
|
+
const runtimeDir = join(repoRoot, "runtime");
|
|
36
|
+
|
|
37
|
+
// On Windows, npm/tar-style launchers are .cmd shims that spawnSync cannot
|
|
38
|
+
// exec directly (ENOENT surfaces as a null status — exactly the CI matrix
|
|
39
|
+
// failure mode). `shell: true` resolves them through cmd.exe; argv here is
|
|
40
|
+
// always static, never user input.
|
|
41
|
+
const IS_WINDOWS = process.platform === "win32";
|
|
42
|
+
|
|
43
|
+
function run(cmd, args, opts = {}) {
|
|
44
|
+
const res = spawnSync(cmd, args, {
|
|
45
|
+
stdio: "inherit",
|
|
46
|
+
shell: IS_WINDOWS,
|
|
47
|
+
...opts,
|
|
48
|
+
});
|
|
49
|
+
if (res.status !== 0) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`command failed (${res.status ?? res.signal}): ${cmd} ${args.join(" ")}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return res;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function capture(cmd, args, opts = {}) {
|
|
58
|
+
const res = spawnSync(cmd, args, {
|
|
59
|
+
encoding: "utf8",
|
|
60
|
+
shell: IS_WINDOWS,
|
|
61
|
+
...opts,
|
|
62
|
+
});
|
|
63
|
+
if (res.status !== 0) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`command failed (${res.status ?? res.signal}): ${cmd} ${args.join(" ")}\n${res.stderr ?? ""}`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return res.stdout.trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// node platform/arch → the artifact slug used in filenames + the manifest.
|
|
72
|
+
function platformSlug() {
|
|
73
|
+
const os = process.platform === "win32" ? "win" : process.platform; // linux | darwin | win
|
|
74
|
+
const arch = process.arch; // x64 | arm64
|
|
75
|
+
return { os, arch, slug: `${os}-${arch}` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function sha256(file) {
|
|
79
|
+
return new Promise((resolveHash, reject) => {
|
|
80
|
+
const hash = createHash("sha256");
|
|
81
|
+
const stream = createReadStream(file);
|
|
82
|
+
stream.on("error", reject);
|
|
83
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
84
|
+
stream.on("end", () => resolveHash(hash.digest("hex")));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function main() {
|
|
89
|
+
const runtimePkg = JSON.parse(
|
|
90
|
+
readFileSync(join(runtimeDir, "package.json"), "utf8"),
|
|
91
|
+
);
|
|
92
|
+
const version = runtimePkg.version;
|
|
93
|
+
const { os, arch, slug } = platformSlug();
|
|
94
|
+
|
|
95
|
+
const outDir = resolve(
|
|
96
|
+
process.env.AGENC_RELEASE_OUT_DIR ?? join(launcherDir, "release-artifacts"),
|
|
97
|
+
);
|
|
98
|
+
mkdirSync(outDir, { recursive: true });
|
|
99
|
+
|
|
100
|
+
const stage = mkdtempSync(join(tmpdir(), "agenc-runtime-build-"));
|
|
101
|
+
try {
|
|
102
|
+
// 1. Build the runtime (produces dist/).
|
|
103
|
+
console.error(`[build] runtime ${version} (${slug})`);
|
|
104
|
+
run("npm", ["run", "build"], { cwd: runtimeDir });
|
|
105
|
+
|
|
106
|
+
// 2. Pack the runtime package into a tgz (honors runtime's `files`).
|
|
107
|
+
const packed = capture(
|
|
108
|
+
"npm",
|
|
109
|
+
["pack", "--silent", "--pack-destination", stage],
|
|
110
|
+
{ cwd: runtimeDir },
|
|
111
|
+
)
|
|
112
|
+
.split("\n")
|
|
113
|
+
.map((l) => l.trim())
|
|
114
|
+
.filter(Boolean)
|
|
115
|
+
.pop();
|
|
116
|
+
const runtimeTgz = join(stage, packed);
|
|
117
|
+
|
|
118
|
+
// 3. Install the packed runtime + its PROD deps into the staging dir,
|
|
119
|
+
// compiling native modules for THIS platform.
|
|
120
|
+
const installRoot = join(stage, "install");
|
|
121
|
+
mkdirSync(installRoot, { recursive: true });
|
|
122
|
+
// A bare package.json so npm installs into install/node_modules.
|
|
123
|
+
// (Written in-process: a `node -e` child with embedded quotes breaks
|
|
124
|
+
// under the Windows shell resolution above.)
|
|
125
|
+
writeFileSync(
|
|
126
|
+
join(installRoot, "package.json"),
|
|
127
|
+
JSON.stringify({
|
|
128
|
+
name: "agenc-runtime-bundle",
|
|
129
|
+
private: true,
|
|
130
|
+
version: "0.0.0",
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
run(
|
|
134
|
+
"npm",
|
|
135
|
+
[
|
|
136
|
+
"install",
|
|
137
|
+
runtimeTgz,
|
|
138
|
+
"--omit=dev",
|
|
139
|
+
"--no-audit",
|
|
140
|
+
"--no-fund",
|
|
141
|
+
"--install-strategy=hoisted",
|
|
142
|
+
],
|
|
143
|
+
{ cwd: installRoot },
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const nodeModules = join(installRoot, "node_modules");
|
|
147
|
+
const runtimeEntry = join(
|
|
148
|
+
nodeModules,
|
|
149
|
+
"@tetsuo-ai",
|
|
150
|
+
"runtime",
|
|
151
|
+
"bin",
|
|
152
|
+
"agenc",
|
|
153
|
+
);
|
|
154
|
+
statSync(runtimeEntry); // hard-fail if the layout isn't what we promise
|
|
155
|
+
|
|
156
|
+
// 4. Tar node_modules into the release artifact.
|
|
157
|
+
const artifactName = `agenc-runtime-${version}-${slug}.tar.gz`;
|
|
158
|
+
const artifactPath = join(outDir, artifactName);
|
|
159
|
+
// Windows: bash-on-PATH resolves `tar` to GNU tar, which parses drive
|
|
160
|
+
// letters (D:\...) as remote hosts ("Cannot connect to D:"). The system
|
|
161
|
+
// bsdtar handles Windows paths natively — use it explicitly.
|
|
162
|
+
const tarBin =
|
|
163
|
+
IS_WINDOWS && existsSync("C:\\Windows\\System32\\tar.exe")
|
|
164
|
+
? "C:\\Windows\\System32\\tar.exe"
|
|
165
|
+
: "tar";
|
|
166
|
+
run(tarBin, ["-czf", artifactPath, "-C", installRoot, "node_modules"]);
|
|
167
|
+
|
|
168
|
+
// 5. Hash + report.
|
|
169
|
+
const digest = await sha256(artifactPath);
|
|
170
|
+
const size = statSync(artifactPath).size;
|
|
171
|
+
const meta = {
|
|
172
|
+
platform: os,
|
|
173
|
+
arch,
|
|
174
|
+
runtimeVersion: version,
|
|
175
|
+
artifact: artifactName,
|
|
176
|
+
sha256: digest,
|
|
177
|
+
bytes: size,
|
|
178
|
+
bins: {
|
|
179
|
+
agenc: "node_modules/@tetsuo-ai/runtime/bin/agenc",
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
// Sidecar meta so gen-manifest.mjs can assemble the manifest by globbing
|
|
183
|
+
// release-artifacts/ across the CI matrix's downloaded artifacts.
|
|
184
|
+
writeFileSync(
|
|
185
|
+
join(outDir, `${artifactName}.meta.json`),
|
|
186
|
+
JSON.stringify(meta, null, 2),
|
|
187
|
+
);
|
|
188
|
+
// Machine-readable line for CI; human summary on stderr.
|
|
189
|
+
process.stdout.write(JSON.stringify(meta) + "\n");
|
|
190
|
+
console.error(
|
|
191
|
+
`[build] wrote ${artifactPath} (${(size / 1e6).toFixed(1)} MB)\n[build] sha256 ${digest}`,
|
|
192
|
+
);
|
|
193
|
+
} finally {
|
|
194
|
+
rmSync(stage, { recursive: true, force: true });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
main().catch((err) => {
|
|
199
|
+
console.error(`[build] FAILED: ${err?.stack ?? err}`);
|
|
200
|
+
process.exitCode = 1;
|
|
201
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Assemble generated/agenc-runtime-manifest.json from the per-platform sidecar
|
|
3
|
+
// meta files (release-artifacts/*.meta.json) produced by build-runtime-tarball.
|
|
4
|
+
//
|
|
5
|
+
// In CI the matrix builds each platform on its own runner, uploads the tarball +
|
|
6
|
+
// .meta.json as workflow artifacts; this step runs after they're all collected,
|
|
7
|
+
// then the manifest is committed into the npm launcher before publish.
|
|
8
|
+
//
|
|
9
|
+
// Usage:
|
|
10
|
+
// node scripts/gen-manifest.mjs --repo tetsuo-ai/agenc-releases --tag agenc-v0.2.0
|
|
11
|
+
// [--artifacts <dir>] (default: packages/agenc/release-artifacts)
|
|
12
|
+
// [--base-url <url>] (override; default: GitHub Releases download URL)
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
readdirSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
} from "node:fs";
|
|
21
|
+
import { dirname, join, resolve } from "node:path";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
|
|
24
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const launcherDir = resolve(__dirname, "..");
|
|
26
|
+
|
|
27
|
+
function arg(name, fallback) {
|
|
28
|
+
const i = process.argv.indexOf(`--${name}`);
|
|
29
|
+
return i !== -1 && i + 1 < process.argv.length ? process.argv[i + 1] : fallback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const repo = arg("repo", "tetsuo-ai/agenc-releases");
|
|
33
|
+
const tag = arg("tag");
|
|
34
|
+
const artifactsDir = resolve(
|
|
35
|
+
arg("artifacts", join(launcherDir, "release-artifacts")),
|
|
36
|
+
);
|
|
37
|
+
const baseUrl =
|
|
38
|
+
arg("base-url") ??
|
|
39
|
+
(tag ? `https://github.com/${repo}/releases/download/${tag}` : undefined);
|
|
40
|
+
|
|
41
|
+
if (!tag) {
|
|
42
|
+
console.error("gen-manifest: --tag is required (e.g. agenc-v0.2.0)");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
if (!existsSync(artifactsDir)) {
|
|
46
|
+
console.error(`gen-manifest: artifacts dir not found: ${artifactsDir}`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const metas = readdirSync(artifactsDir)
|
|
51
|
+
.filter((f) => f.endsWith(".meta.json"))
|
|
52
|
+
.map((f) => JSON.parse(readFileSync(join(artifactsDir, f), "utf8")));
|
|
53
|
+
|
|
54
|
+
if (metas.length === 0) {
|
|
55
|
+
console.error(`gen-manifest: no *.meta.json in ${artifactsDir}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const versions = [...new Set(metas.map((m) => m.runtimeVersion))];
|
|
60
|
+
if (versions.length !== 1) {
|
|
61
|
+
console.error(`gen-manifest: mixed runtime versions: ${versions.join(", ")}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const manifest = {
|
|
66
|
+
manifestVersion: 1,
|
|
67
|
+
runtimeVersion: versions[0],
|
|
68
|
+
releaseRepository: repo,
|
|
69
|
+
releaseTag: tag,
|
|
70
|
+
artifacts: metas
|
|
71
|
+
.map((m) => ({
|
|
72
|
+
platform: m.platform,
|
|
73
|
+
arch: m.arch,
|
|
74
|
+
runtimeVersion: m.runtimeVersion,
|
|
75
|
+
url: `${baseUrl}/${m.artifact}`,
|
|
76
|
+
sha256: m.sha256,
|
|
77
|
+
bytes: m.bytes,
|
|
78
|
+
bins: m.bins,
|
|
79
|
+
}))
|
|
80
|
+
.sort((a, b) => `${a.platform}-${a.arch}`.localeCompare(`${b.platform}-${b.arch}`)),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const outDir = join(launcherDir, "generated");
|
|
84
|
+
mkdirSync(outDir, { recursive: true });
|
|
85
|
+
const outPath = join(outDir, "agenc-runtime-manifest.json");
|
|
86
|
+
writeFileSync(outPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
87
|
+
console.error(
|
|
88
|
+
`gen-manifest: wrote ${outPath} (${manifest.artifacts.length} platform(s): ${manifest.artifacts
|
|
89
|
+
.map((a) => `${a.platform}-${a.arch}`)
|
|
90
|
+
.join(", ")})`,
|
|
91
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runs on `npm install @tetsuo-ai/agenc`. Pre-fetches the platform runtime so
|
|
3
|
+
// the first `agenc` invocation is fast. Best-effort: a failure here (offline
|
|
4
|
+
// install, CI, etc.) is NOT fatal — the launcher fetches lazily on first run.
|
|
5
|
+
|
|
6
|
+
// Skip in obviously non-interactive / packaging contexts.
|
|
7
|
+
if (process.env.AGENC_SKIP_POSTINSTALL === "1" || process.env.CI === "true") {
|
|
8
|
+
process.exit(0);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const { ensureRuntime } = await import("../lib/runtime-manager.mjs");
|
|
13
|
+
await ensureRuntime();
|
|
14
|
+
} catch (err) {
|
|
15
|
+
process.stderr.write(
|
|
16
|
+
`agenc: runtime pre-fetch skipped (${err?.message ?? err}); it will be fetched on first run.\n`,
|
|
17
|
+
);
|
|
18
|
+
// Non-fatal by design.
|
|
19
|
+
}
|