only-my-pi 0.0.0-bootstrap.0 → 0.4.0-preview.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/README.md +6 -4
- package/loader.mjs +67 -0
- package/package.json +26 -8
- package/resource-hash.mjs +105 -0
- package/runtime-packages.json +11 -0
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# only-my-pi 0.4.0-preview.1
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Public Preview for macOS 14+ Apple Silicon. Requires Node >=22.19.0 and Git on PATH before npm/npx installation. The runtime includes fd and ripgrep.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
macOS 14+ Apple Silicon 预览版。npm/npx 安装前需准备 Node >=22.19.0 和可用的 Git;运行包已包含 fd 和 ripgrep。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Run `omp` after installation. Configure model authentication with `omp admin pi`.
|
|
8
|
+
|
|
9
|
+
[English documentation](https://github.com/Ricardo121380/only-my-pi) · [中文说明](https://github.com/Ricardo121380/only-my-pi/blob/main/README.zh-CN.md)
|
package/loader.mjs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copied into the public CLI package together with the dependency-free tree
|
|
3
|
+
// verifier. Do not import code from a platform payload before checking it.
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import crypto from "node:crypto";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
import { hashResourcePath } from "./resource-hash.mjs";
|
|
11
|
+
|
|
12
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
function fail(message) { throw new Error(`only-my-pi: ${message}`); }
|
|
14
|
+
function canonical(value) {
|
|
15
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
16
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonical(value[key])]));
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
async function json(filename) {
|
|
20
|
+
const stat = await fs.lstat(filename);
|
|
21
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 8 * 1024 * 1024) fail("unsafe package metadata");
|
|
22
|
+
return JSON.parse(await fs.readFile(filename, "utf8"));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function locateRuntime() {
|
|
26
|
+
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
27
|
+
if (major < 22 || (major === 22 && minor < 19)) fail("Node >=22.19.0 is required");
|
|
28
|
+
const descriptor = await json(path.join(here, "runtime-packages.json"));
|
|
29
|
+
const target = `${process.platform}-${process.arch}`;
|
|
30
|
+
const expected = descriptor.platforms[target];
|
|
31
|
+
if (!expected || (process.platform === "darwin" && Number.parseInt(os.release(), 10) < 23)
|
|
32
|
+
|| (process.platform === "linux" && !process.report.getReport().header.glibcVersionRuntime)) fail(`unsupported platform: ${target}`);
|
|
33
|
+
let metadata;
|
|
34
|
+
try { metadata = createRequire(import.meta.url).resolve(`${expected.name}/package.json`); }
|
|
35
|
+
catch { fail("the platform runtime package is missing; reinstall with optional dependencies enabled"); }
|
|
36
|
+
const packageManifest = await json(metadata);
|
|
37
|
+
if (packageManifest.name !== expected.name || packageManifest.version !== descriptor.version) fail("platform package version mismatch");
|
|
38
|
+
const root = await fs.realpath(path.join(path.dirname(metadata), "runtime"));
|
|
39
|
+
const manifest = await json(path.join(root, "distribution-manifest.json"));
|
|
40
|
+
const { distributionId, ...identity } = manifest;
|
|
41
|
+
const calculated = `sha256:${crypto.createHash("sha256").update(JSON.stringify(canonical(identity))).digest("hex")}`;
|
|
42
|
+
if (distributionId !== expected.distributionId || calculated !== distributionId || manifest.version !== descriptor.version
|
|
43
|
+
|| manifest.platform.os !== process.platform || manifest.platform.arch !== process.arch) fail("runtime identity differs from the CLI release");
|
|
44
|
+
const paths = { app: "only-my-pi/package", pi: "pi", external: "external-npm" };
|
|
45
|
+
for (const [id, relative] of Object.entries(paths)) {
|
|
46
|
+
if (manifest.components[id]?.path !== relative) fail("invalid runtime component path");
|
|
47
|
+
const hash = `sha256:${await hashResourcePath({ artifactRoot: root, relativePath: relative, allowContainedSymlinks: true })}`;
|
|
48
|
+
if (hash !== manifest.components[id].treeDigest) fail(`${id} runtime content is damaged`);
|
|
49
|
+
}
|
|
50
|
+
return { root, packageRoot: path.join(root, paths.app) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
54
|
+
const runtime = await locateRuntime();
|
|
55
|
+
if (argv.length === 1 && argv[0] === "--verify-install") {
|
|
56
|
+
const { requireSystemDependencies } = await import(pathToFileURL(path.join(runtime.packageRoot, "packages/distribution/system-dependencies.mjs")).href);
|
|
57
|
+
await requireSystemDependencies();
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
const { runOmpEntrypoint } = await import(pathToFileURL(path.join(runtime.packageRoot, "bin/omp.mjs")).href);
|
|
61
|
+
return runOmpEntrypoint({ argv, rootDir: runtime.packageRoot });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (process.argv[1] && await fs.realpath(process.argv[1]) === await fs.realpath(fileURLToPath(import.meta.url))) {
|
|
65
|
+
try { process.exitCode = await main(); }
|
|
66
|
+
catch (error) { process.stderr.write(`${error.message}\n`); process.exitCode = 1; }
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "only-my-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-preview.1",
|
|
4
4
|
"private": false,
|
|
5
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"description": "Guarded terminal coding agent built on Pi (Public Preview)",
|
|
8
|
+
"bin": {
|
|
9
|
+
"omp": "loader.mjs"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=22.19.0"
|
|
13
|
+
},
|
|
14
|
+
"os": [
|
|
15
|
+
"darwin"
|
|
16
|
+
],
|
|
17
|
+
"cpu": [
|
|
18
|
+
"arm64"
|
|
19
|
+
],
|
|
20
|
+
"optionalDependencies": {
|
|
21
|
+
"only-my-pi-runtime-darwin-arm64": "0.4.0-preview.1"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"postinstall": "node loader.mjs --verify-install"
|
|
25
|
+
},
|
|
7
26
|
"repository": {
|
|
8
27
|
"type": "git",
|
|
9
28
|
"url": "git+https://github.com/Ricardo121380/only-my-pi.git"
|
|
10
29
|
},
|
|
30
|
+
"homepage": "https://github.com/Ricardo121380/only-my-pi",
|
|
11
31
|
"files": [
|
|
32
|
+
"loader.mjs",
|
|
33
|
+
"resource-hash.mjs",
|
|
34
|
+
"runtime-packages.json",
|
|
12
35
|
"README.md",
|
|
13
36
|
"LICENSE"
|
|
14
|
-
]
|
|
15
|
-
"publishConfig": {
|
|
16
|
-
"access": "public",
|
|
17
|
-
"tag": "bootstrap",
|
|
18
|
-
"registry": "https://registry.npmjs.org/"
|
|
19
|
-
}
|
|
37
|
+
]
|
|
20
38
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
function fail(code, message) {
|
|
6
|
+
throw Object.assign(new Error(message), { code });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function assertSafeRelativePath(value, label = "path") {
|
|
10
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\0") || value.includes("\\")) {
|
|
11
|
+
fail("UNSAFE_RELATIVE_PATH", `${label} must be a non-empty portable relative path`);
|
|
12
|
+
}
|
|
13
|
+
if (path.posix.isAbsolute(value) || path.posix.normalize(value) !== value || value === ".") {
|
|
14
|
+
fail("UNSAFE_RELATIVE_PATH", `${label} must be a normalized relative path: ${value}`);
|
|
15
|
+
}
|
|
16
|
+
if (value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
17
|
+
fail("UNSAFE_RELATIVE_PATH", `${label} contains an unsafe path segment: ${value}`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function hashTreeEntry(absolutePath, relativePath, hash, options) {
|
|
23
|
+
const stat = await fs.lstat(absolutePath);
|
|
24
|
+
if (stat.isSymbolicLink()) {
|
|
25
|
+
if (!options.allowContainedSymlinks) {
|
|
26
|
+
fail("RESOURCE_SYMLINK", `resource graph does not admit symlinks: ${relativePath}`);
|
|
27
|
+
}
|
|
28
|
+
let realTarget;
|
|
29
|
+
try {
|
|
30
|
+
realTarget = await fs.realpath(absolutePath);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
fail("UNSAFE_TREE_SYMLINK", `managed tree contains a dangling symlink: ${relativePath}`, error);
|
|
33
|
+
}
|
|
34
|
+
if (realTarget !== options.realRoot && !realTarget.startsWith(`${options.realRoot}${path.sep}`)) {
|
|
35
|
+
fail("UNSAFE_TREE_SYMLINK", `managed tree symlink escapes its root: ${relativePath}`);
|
|
36
|
+
}
|
|
37
|
+
const link = await fs.readlink(absolutePath);
|
|
38
|
+
hash.update("L\0");
|
|
39
|
+
hash.update(relativePath);
|
|
40
|
+
hash.update("\0");
|
|
41
|
+
hash.update(link);
|
|
42
|
+
hash.update("\0");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (stat.isDirectory()) {
|
|
46
|
+
hash.update("D\0");
|
|
47
|
+
hash.update(relativePath);
|
|
48
|
+
hash.update("\0");
|
|
49
|
+
const names = await fs.readdir(absolutePath);
|
|
50
|
+
names.sort();
|
|
51
|
+
for (const name of names) {
|
|
52
|
+
await hashTreeEntry(
|
|
53
|
+
path.join(absolutePath, name),
|
|
54
|
+
relativePath === "." ? name : `${relativePath}/${name}`,
|
|
55
|
+
hash,
|
|
56
|
+
options,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!stat.isFile()) {
|
|
62
|
+
fail("UNSUPPORTED_RESOURCE_TYPE", `resource graph only admits regular files and directories: ${relativePath}`);
|
|
63
|
+
}
|
|
64
|
+
const bytes = await fs.readFile(absolutePath);
|
|
65
|
+
hash.update("F\0");
|
|
66
|
+
hash.update(relativePath);
|
|
67
|
+
hash.update("\0");
|
|
68
|
+
hash.update(String(bytes.length));
|
|
69
|
+
hash.update("\0");
|
|
70
|
+
hash.update(bytes);
|
|
71
|
+
hash.update("\0");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function hashResourcePath({ artifactRoot, relativePath, allowContainedSymlinks = false }) {
|
|
75
|
+
assertSafeRelativePath(relativePath, "resource path");
|
|
76
|
+
const root = path.resolve(artifactRoot);
|
|
77
|
+
const absolutePath = path.resolve(root, ...relativePath.split("/"));
|
|
78
|
+
if (absolutePath === root || !absolutePath.startsWith(`${root}${path.sep}`)) {
|
|
79
|
+
fail("RESOURCE_PATH_ESCAPE", `resource path escapes the artifact root: ${relativePath}`);
|
|
80
|
+
}
|
|
81
|
+
const hash = crypto.createHash("sha256");
|
|
82
|
+
try {
|
|
83
|
+
const rootStat = await fs.lstat(root);
|
|
84
|
+
if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
|
|
85
|
+
fail("UNSAFE_RESOURCE_ROOT", "resource artifact root must be a real directory");
|
|
86
|
+
}
|
|
87
|
+
let current = root;
|
|
88
|
+
for (const segment of relativePath.split("/")) {
|
|
89
|
+
current = path.join(current, segment);
|
|
90
|
+
const stat = await fs.lstat(current);
|
|
91
|
+
if (stat.isSymbolicLink() && current !== absolutePath) {
|
|
92
|
+
fail("RESOURCE_SYMLINK", `resource path traverses a symlink: ${relativePath}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const realRoot = await fs.realpath(root);
|
|
96
|
+
await hashTreeEntry(absolutePath, ".", hash, { root, realRoot, allowContainedSymlinks });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error?.code === "ENOENT") {
|
|
99
|
+
fail("RESOURCE_MISSING", `resource path does not exist: ${relativePath}`);
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
return hash.digest("hex");
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"formatVersion": 1,
|
|
3
|
+
"version": "0.4.0-preview.1",
|
|
4
|
+
"sourceCommit": "38e71adc8c2c52cc332db288f5dcdd14f73bd8cb",
|
|
5
|
+
"platforms": {
|
|
6
|
+
"darwin-arm64": {
|
|
7
|
+
"name": "only-my-pi-runtime-darwin-arm64",
|
|
8
|
+
"distributionId": "sha256:a7af13f85bafadd22f591f88071078ffba8268c2ddee786f35bc000b10a4b75a"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|