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 CHANGED
@@ -1,7 +1,9 @@
1
- # OMP namespace registration
1
+ # only-my-pi 0.4.0-preview.1
2
2
 
3
- This registration-only version contains no executable or runtime. It reserves the package name so the maintainer can configure GitHub Actions trusted publishing. It is not an OMP Preview release.
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
- 此版本仅用于注册包名及配置可信发布,不包含可执行程序,不是 OMP 预览版。
5
+ macOS 14+ Apple Silicon 预览版。npm/npx 安装前需准备 Node >=22.19.0 和可用的 Git;运行包已包含 fd 和 ripgrep。
6
6
 
7
- [Project / 项目](https://github.com/Ricardo121380/only-my-pi)
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.0.0-bootstrap.0",
3
+ "version": "0.4.0-preview.1",
4
4
  "private": false,
5
- "description": "Namespace registration for future verified OMP releases; no executable included",
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
+ }