@langgexyz/anycode 0.0.0-stage → 0.4.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 +129 -2
- package/npm/README.md +41 -0
- package/npm/dist/cli.mjs +914 -0
- package/package.json +46 -4
package/README.md
CHANGED
|
@@ -1,3 +1,130 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AnyCode
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AnyCode 是原子 capability 执行层。它负责能力注册、项目 binding、Provider
|
|
4
|
+
调用、幂等记录和 evidence;宿主负责组合、调度和最终交付判断。
|
|
5
|
+
|
|
6
|
+
## 唯一运行路径
|
|
7
|
+
|
|
8
|
+
```text
|
|
9
|
+
Native host session (OpenCode / Claude Code / other host)
|
|
10
|
+
-> host adapter
|
|
11
|
+
-> anycode capability invoke
|
|
12
|
+
-> atomic catalog
|
|
13
|
+
-> project binding
|
|
14
|
+
-> provider
|
|
15
|
+
-> result + evidence
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
一次调用只包含请求、项目配置和原子输入。OpenCode、Claude Code 和其他宿主使用各自的
|
|
19
|
+
Host Adapter 和同一 capability protocol;AnyCode 不提供宿主编排入口。
|
|
20
|
+
|
|
21
|
+
## 原子调用
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
anycode capability invoke \
|
|
25
|
+
--protocol-version anycode.capability.invoke.v1 \
|
|
26
|
+
--request-id <request-id> \
|
|
27
|
+
--contract-version <contract-version> \
|
|
28
|
+
--session-id <native-session-id> \
|
|
29
|
+
--execution-root <workspace-directory> \
|
|
30
|
+
--capability <domain.capability> \
|
|
31
|
+
--config-root <project>/.anycode \
|
|
32
|
+
--target <target> \
|
|
33
|
+
--component <component> \
|
|
34
|
+
--idempotency-key <key> \
|
|
35
|
+
--timeout-ms 30000 < input.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`execution-root` 是 provider 执行时使用的 workspace/worktree 目录,不是权限边界。
|
|
39
|
+
AnyCode 校验项目配置、target binding 和 capability 输入;workspace 内文件操作不做
|
|
40
|
+
应用层权限判断。结果使用稳定协议:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"status": "pass | na | blocked | failed",
|
|
45
|
+
"certainty": "confirmed | unknown",
|
|
46
|
+
"side_effect": "none | possible | confirmed",
|
|
47
|
+
"output": {},
|
|
48
|
+
"evidence": []
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`certainty=unknown` 表示 Provider 可能已经产生副作用但结果未确认,AnyCode 不自动
|
|
53
|
+
重放;由宿主决定查询、重试或暂停。
|
|
54
|
+
|
|
55
|
+
## 配置与安装
|
|
56
|
+
|
|
57
|
+
项目配置位于 `.anycode/`,由宿主或 Connector 作为 capability request 的输入提供;runtime
|
|
58
|
+
不再维护第二套高层 CLI。安装和更新通过 anycode.me 的原生发布包完成,能力执行统一使用
|
|
59
|
+
`capability invoke`。
|
|
60
|
+
|
|
61
|
+
组合、并发、取消、重试和结果聚合由宿主原生机制负责。OpenCode runtime 对 Provider 错误
|
|
62
|
+
只做统一分类并提示安全的下一步,不在失败后盲目重放或偷偷切换模型;其他宿主只需适配同一
|
|
63
|
+
原子协议。
|
|
64
|
+
|
|
65
|
+
产品安装与宿主接入:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
curl -fsSL https://anycode.me/install.sh | sh
|
|
69
|
+
curl -fsSL https://anycode.me/install.sh | ANYCODE_VERSION=<version> sh
|
|
70
|
+
anycode --help
|
|
71
|
+
anycode doctor
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
发布渠道提供 stable 和 rc。稳定版发布完成后,站点更新
|
|
75
|
+
`https://anycode.me/releases/latest.json`;不指定版本默认安装 stable。发布包支持 macOS 和 Linux 的 `arm64`、`x64` 以及 Windows `x64`;安装器支持
|
|
76
|
+
macOS 和 Linux,Windows 用户从 `https://anycode.me/releases/v<版本>/` 手动下载。安装目录为
|
|
77
|
+
`~/.local/share/anycode`,启动器为 `~/.local/bin/anycode`。本地工程操作不要求登录;需要访问
|
|
78
|
+
AnyCode 账户的操作使用 `anycode login`,升级使用 `anycode update`;Windows 暂不支持自动更新,需手动下载指定版本。
|
|
79
|
+
|
|
80
|
+
仓库内没有 workflow 推送 tag;维护者从具备仓库写权限的 Git 环境推送 tag,测试机只用于构建与测试。
|
|
81
|
+
|
|
82
|
+
发版前使用 `release-prepare` 取得版本号,脚本会刷新远端 tags,并读取标题包含“发布 AnyCode
|
|
83
|
+
<版本号>”的 open PR。正式模式在写入 `package.json` 前再次读取这两类占用状态,发现竞争时自动重新取号;
|
|
84
|
+
`--dry-run` 只输出计划。输出为单行 JSON,便于脚本消费:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
bun scripts/release-prepare.ts --channel rc --dry-run
|
|
88
|
+
bun scripts/release-prepare.ts --channel rc
|
|
89
|
+
bun scripts/release-prepare.ts --channel stable
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
正式模式写入版本后,按 JSON 中的 `commands` 创建分支、提交并发起版本 PR;创建 PR 前应再次确认远端没有
|
|
93
|
+
新增同版本 tag 或 open 版本 PR。输出 JSON 的 `status` 为 `ready` 表示已确认并写入版本;若连续 10 次复核
|
|
94
|
+
都检测到版本号竞争,脚本以 `status: "blocked"`、`version: null`、空 `commands` 和非零退出码结束,且不改动
|
|
95
|
+
`package.json`,此时应等待竞争版本落地后重试。
|
|
96
|
+
|
|
97
|
+
新机器的依赖、Harness 接入、`doctor`、升级和回滚流程见
|
|
98
|
+
[安装与首次启动](docs/installation.md)。
|
|
99
|
+
|
|
100
|
+
Agent 安装后的 Harness 接入、渐进式 Connector/账号引导和任务启动/完成门禁见
|
|
101
|
+
[Agent onboarding and Harness usage](docs/agent-onboarding.md)。
|
|
102
|
+
|
|
103
|
+
安装只替换单一 immutable 安装目录;Host Adapter 由对应 Harness 的原生安装机制管理,并通过
|
|
104
|
+
已安装版本提供的稳定 entrypoint/manifest 接入,不依赖源码路径,不创建 workspace 权限或工作流状态。
|
|
105
|
+
|
|
106
|
+
## 验证
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm run catalog:check
|
|
110
|
+
npm run typecheck
|
|
111
|
+
npm run architecture:doctor
|
|
112
|
+
bun scripts/run-bounded-test.ts --max-concurrency=4
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
当前完整测试覆盖原子 catalog、workspace 执行上下文、幂等/不确定结果、Provider、配置和
|
|
116
|
+
delivery capability。发布时必须同时验证 capability protocol、实际 CLI 和 immutable package。
|
|
117
|
+
|
|
118
|
+
## 维护 capability catalog 文档
|
|
119
|
+
|
|
120
|
+
`docs/capability-catalog.md` 由 `runtime/providers/src/catalog.ts` 生成,禁止手工编辑生成段落。
|
|
121
|
+
任何改动 `catalog.ts`(新增/删除/重命名能力,或修改 domain、inputs、outputs、effect、
|
|
122
|
+
providerKind、evidence、compensation)的 PR,都必须在更新后的 main 上重新生成并提交文档:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm run catalog:generate
|
|
126
|
+
npm run catalog:check
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`npm run catalog:check` 已接入 `.github/workflows/ci.yml`,未重新生成文档的 PR 会因
|
|
130
|
+
catalog 漂移而 CI 失败。合并前请先 rebase/merge 最新 main 再重跑上述两条命令,避免与并行改动冲突。
|
package/npm/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# `@langgexyz/anycode`
|
|
2
|
+
|
|
3
|
+
The primary install channel for AnyCode is `anycode.me`. The npm package is an
|
|
4
|
+
additional channel: it bootstraps the same verified native runtime, and any
|
|
5
|
+
non-`install` command is forwarded to the activated native runtime.
|
|
6
|
+
|
|
7
|
+
## npm / npx
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Download, verify, and activate the native runtime for this platform
|
|
11
|
+
npx @langgexyz/anycode install
|
|
12
|
+
npx @langgexyz/anycode install --version <version> --harness opencode-v1
|
|
13
|
+
npx @langgexyz/anycode install --version <version> --harness opencode-v2
|
|
14
|
+
npx @langgexyz/anycode install --version <version> --harness claude
|
|
15
|
+
npx @langgexyz/anycode install --version <version> --harness pi
|
|
16
|
+
|
|
17
|
+
# Global install
|
|
18
|
+
npm install -g @langgexyz/anycode
|
|
19
|
+
|
|
20
|
+
# Forward any command to the installed native runtime
|
|
21
|
+
npx @langgexyz/anycode <command>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`--harness` accepts `opencode-v1`, `opencode-v2`, `claude`, or `pi`.
|
|
25
|
+
|
|
26
|
+
## anycode.me
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
curl -fsSL https://anycode.me/install.sh | sh
|
|
30
|
+
curl -fsSL https://anycode.me/install.sh | ANYCODE_VERSION=<version> sh
|
|
31
|
+
anycode update
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The release packages support macOS and Linux on `arm64` and `x64`, plus Windows
|
|
35
|
+
`x64`. The installer supports macOS and Linux; Windows users can manually
|
|
36
|
+
download the `windows-x64` package from
|
|
37
|
+
`https://anycode.me/releases/v<version>/`. During preview, the default install
|
|
38
|
+
command resolves the stable manifest published by the release workflow. The
|
|
39
|
+
installer places the launcher at
|
|
40
|
+
`~/.local/bin/anycode` and the runtime at `~/.local/share/anycode`. Windows does
|
|
41
|
+
not support automatic `anycode update`; manually download a versioned package.
|
package/npm/dist/cli.mjs
ADDED
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// npm/src/cli.ts
|
|
4
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
5
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
|
|
11
|
+
// runtime/archive/src/index.ts
|
|
12
|
+
import { gunzipSync, gzipSync } from "node:zlib";
|
|
13
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { dirname, join, posix, relative, resolve } from "node:path";
|
|
15
|
+
function readTarGzip(archive) {
|
|
16
|
+
const bytes = gunzipSync(readFileSync(archive));
|
|
17
|
+
const entries = [];
|
|
18
|
+
let offset = 0;
|
|
19
|
+
let pendingPath = "";
|
|
20
|
+
let pendingPax = {};
|
|
21
|
+
while (offset + 512 <= bytes.length) {
|
|
22
|
+
const header = bytes.subarray(offset, offset + 512);
|
|
23
|
+
offset += 512;
|
|
24
|
+
if (header.every((value) => value === 0))
|
|
25
|
+
break;
|
|
26
|
+
verifyChecksum(header);
|
|
27
|
+
const typeFlag = text(header, 156, 1) || "0";
|
|
28
|
+
const size = octal(header, 124, 12);
|
|
29
|
+
const bodySize = Math.ceil(size / 512) * 512;
|
|
30
|
+
if (offset + bodySize > bytes.length)
|
|
31
|
+
throw new Error("archive entry exceeds archive size");
|
|
32
|
+
const body = bytes.subarray(offset, offset + size);
|
|
33
|
+
offset += bodySize;
|
|
34
|
+
if (typeFlag === "x" || typeFlag === "g") {
|
|
35
|
+
pendingPax = typeFlag === "x" ? parsePax(body) : pendingPax;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (typeFlag === "L") {
|
|
39
|
+
pendingPath = body.toString("utf8").replace(/\0+$/u, "").replace(/\n+$/u, "");
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const path = pendingPax["path"] || pendingPath || join(text(header, 345, 155), text(header, 0, 100));
|
|
43
|
+
pendingPath = "";
|
|
44
|
+
pendingPax = {};
|
|
45
|
+
if (!path)
|
|
46
|
+
throw new Error("archive entry path is empty");
|
|
47
|
+
const mode = pendingNumber(header, 100, 8, "mode");
|
|
48
|
+
const type = typeFlag === "0" || typeFlag === "" ? "file" : typeFlag === "5" ? "directory" : "unsupported";
|
|
49
|
+
entries.push({ path, type, mode, data: Buffer.from(body) });
|
|
50
|
+
}
|
|
51
|
+
return entries;
|
|
52
|
+
}
|
|
53
|
+
function extractTarGzip(archive, destination) {
|
|
54
|
+
const entries = readTarGzip(archive);
|
|
55
|
+
const seen = new Set;
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
const path = safeArchivePath(destination, entry.path);
|
|
58
|
+
const normalized = normalizeArchivePath(entry.path);
|
|
59
|
+
if (seen.has(normalized))
|
|
60
|
+
throw new Error(`archive contains duplicate member: ${entry.path}`);
|
|
61
|
+
seen.add(normalized);
|
|
62
|
+
if (entry.type === "unsupported")
|
|
63
|
+
throw new Error("package contains unsupported archive member type");
|
|
64
|
+
if (entry.type === "directory") {
|
|
65
|
+
mkdirSync(path, { recursive: true });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
69
|
+
writeFileSync(path, entry.data, { mode: entry.mode || 420 });
|
|
70
|
+
chmodSync(path, entry.mode & 4095 || 420);
|
|
71
|
+
}
|
|
72
|
+
return entries;
|
|
73
|
+
}
|
|
74
|
+
function parsePax(body) {
|
|
75
|
+
const result = {};
|
|
76
|
+
let offset = 0;
|
|
77
|
+
while (offset < body.length) {
|
|
78
|
+
const space = body.indexOf(32, offset);
|
|
79
|
+
if (space < 0)
|
|
80
|
+
throw new Error("invalid archive PAX header");
|
|
81
|
+
const length = Number(body.subarray(offset, space).toString("ascii"));
|
|
82
|
+
if (!Number.isInteger(length) || length <= 0 || offset + length > body.length)
|
|
83
|
+
throw new Error("invalid archive PAX length");
|
|
84
|
+
const record = body.subarray(space + 1, offset + length - 1).toString("utf8");
|
|
85
|
+
const equal = record.indexOf("=");
|
|
86
|
+
if (equal <= 0)
|
|
87
|
+
throw new Error("invalid archive PAX record");
|
|
88
|
+
result[record.slice(0, equal)] = record.slice(equal + 1);
|
|
89
|
+
offset += length;
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
function safeArchivePath(root, value) {
|
|
94
|
+
const normalized = normalizeArchivePath(value);
|
|
95
|
+
const path = resolve(root, normalized);
|
|
96
|
+
const relativePath = relative(resolve(root), path);
|
|
97
|
+
if (!relativePath || relativePath === ".." || relativePath.startsWith(`..${"/"}`) || relativePath.startsWith("/"))
|
|
98
|
+
throw new Error(`package contains unsafe path: ${value}`);
|
|
99
|
+
return path;
|
|
100
|
+
}
|
|
101
|
+
function normalizeArchivePath(value) {
|
|
102
|
+
const normalized = posix.normalize(value.replaceAll("\\", "/")).replace(/^\.\//u, "");
|
|
103
|
+
if (!normalized || normalized === "." || normalized.startsWith("..") || normalized.includes("/../") || normalized.startsWith("/"))
|
|
104
|
+
throw new Error(`package contains unsafe path: ${value}`);
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
function verifyChecksum(header) {
|
|
108
|
+
const expected = octal(header, 148, 8);
|
|
109
|
+
const copy = Buffer.from(header);
|
|
110
|
+
copy.fill(32, 148, 156);
|
|
111
|
+
const actual = [...copy].reduce((sum, value) => sum + value, 0);
|
|
112
|
+
if (expected !== actual)
|
|
113
|
+
throw new Error("archive header checksum mismatch");
|
|
114
|
+
}
|
|
115
|
+
function text(buffer, offset, length) {
|
|
116
|
+
return buffer.subarray(offset, offset + length).toString("utf8").replace(/\0+$/u, "").trim();
|
|
117
|
+
}
|
|
118
|
+
function octal(buffer, offset, length) {
|
|
119
|
+
const value = text(buffer, offset, length).replace(/\0/g, "").trim();
|
|
120
|
+
return value ? Number.parseInt(value, 8) : 0;
|
|
121
|
+
}
|
|
122
|
+
function pendingNumber(buffer, offset, length, field) {
|
|
123
|
+
const value = octal(buffer, offset, length);
|
|
124
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
125
|
+
throw new Error(`archive ${field} is invalid`);
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// runtime/core/src/connector.ts
|
|
130
|
+
class ConnectorRegistry {
|
|
131
|
+
connectors = new Map;
|
|
132
|
+
capabilityOwners = new Map;
|
|
133
|
+
capabilitySetups = new Map;
|
|
134
|
+
constructor(connectors = [], setups = []) {
|
|
135
|
+
for (const setup of setups)
|
|
136
|
+
this.registerSetup(setup);
|
|
137
|
+
for (const connector of connectors)
|
|
138
|
+
this.register(connector);
|
|
139
|
+
}
|
|
140
|
+
register(connector) {
|
|
141
|
+
validateConnector(connector);
|
|
142
|
+
if (this.connectors.has(connector.id))
|
|
143
|
+
throw new Error(`duplicate connector: ${connector.id}`);
|
|
144
|
+
for (const capability of connector.capabilities) {
|
|
145
|
+
const setup = connector.setup?.[capability];
|
|
146
|
+
if (setup !== undefined) {
|
|
147
|
+
validateSetup(setup);
|
|
148
|
+
if (setup.connectorId !== connector.id || setup.capability !== capability) {
|
|
149
|
+
throw new Error(`Connector setup identity does not match its owner: ${connector.id}/${capability}`);
|
|
150
|
+
}
|
|
151
|
+
if (this.capabilitySetups.has(setupKey(connector.id, capability)))
|
|
152
|
+
throw new Error(`capability setup already registered: ${capability}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
this.connectors.set(connector.id, connector);
|
|
156
|
+
for (const capability of connector.capabilities) {
|
|
157
|
+
const owners = this.capabilityOwners.get(capability) ?? [];
|
|
158
|
+
owners.push(connector);
|
|
159
|
+
this.capabilityOwners.set(capability, owners);
|
|
160
|
+
const setup = connector.setup?.[capability];
|
|
161
|
+
if (setup !== undefined)
|
|
162
|
+
this.capabilitySetups.set(setupKey(connector.id, capability), setup);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
registerSetup(setup) {
|
|
166
|
+
validateSetup(setup);
|
|
167
|
+
const key = setupKey(setup.connectorId, setup.capability);
|
|
168
|
+
if (this.capabilitySetups.has(key))
|
|
169
|
+
throw new Error(`capability setup already registered: ${setup.capability}`);
|
|
170
|
+
this.capabilitySetups.set(key, setup);
|
|
171
|
+
}
|
|
172
|
+
resolve(capabilityId, connectorId) {
|
|
173
|
+
const owners = this.capabilityOwners.get(capabilityId) ?? [];
|
|
174
|
+
if (connectorId !== undefined)
|
|
175
|
+
return owners.find((connector) => connector.id === connectorId);
|
|
176
|
+
return owners.length === 1 ? owners[0] : undefined;
|
|
177
|
+
}
|
|
178
|
+
all() {
|
|
179
|
+
return [...this.connectors.values()];
|
|
180
|
+
}
|
|
181
|
+
declaredCapabilities() {
|
|
182
|
+
return [...new Set([...this.capabilityOwners.keys(), ...[...this.capabilitySetups.values()].map((setup) => setup.capability)])];
|
|
183
|
+
}
|
|
184
|
+
async execute(request, connectorId) {
|
|
185
|
+
validateCapabilityRequest(request);
|
|
186
|
+
const owners = this.capabilityOwners.get(request.capabilityId) ?? [];
|
|
187
|
+
if (connectorId !== undefined && !owners.some((connector2) => connector2.id === connectorId)) {
|
|
188
|
+
return unavailable(request, this.capabilitySetups.get(setupKey(connectorId, request.capabilityId)));
|
|
189
|
+
}
|
|
190
|
+
if (connectorId === undefined && owners.length > 1) {
|
|
191
|
+
return failed(request, `multiple Connectors implement ${request.capabilityId}; select one in the project binding`, "connector_binding_required");
|
|
192
|
+
}
|
|
193
|
+
const connector = connectorId === undefined ? owners[0] : this.resolve(request.capabilityId, connectorId);
|
|
194
|
+
if (!connector) {
|
|
195
|
+
const setups = [...this.capabilitySetups.values()].filter((setup) => setup.capability === request.capabilityId);
|
|
196
|
+
return unavailable(request, connectorId === undefined && setups.length === 1 ? setups[0] : undefined);
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const result = await connector.execute(request);
|
|
200
|
+
validateCapabilityResult(result);
|
|
201
|
+
if (result.capability !== request.capabilityId || result.runId !== request.runId)
|
|
202
|
+
return failed(request, "connector result identity does not match capability request", "connector_identity_mismatch");
|
|
203
|
+
return result;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return failed(request, error instanceof Error ? error.message : String(error), "connector_execution_failed");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function setupKey(connectorId, capability) {
|
|
210
|
+
return `${connectorId}\x00${capability}`;
|
|
211
|
+
}
|
|
212
|
+
function validateSetup(setup) {
|
|
213
|
+
if (!setup.connectorId.trim() || !setup.capability.trim())
|
|
214
|
+
throw new Error("Connector setup requires connectorId and capability");
|
|
215
|
+
}
|
|
216
|
+
function validateConnector(connector) {
|
|
217
|
+
if (!connector.id.trim())
|
|
218
|
+
throw new Error("connector id must not be empty");
|
|
219
|
+
if (connector.capabilities.length === 0)
|
|
220
|
+
throw new Error(`connector requires capabilities: ${connector.id}`);
|
|
221
|
+
const unique = new Set;
|
|
222
|
+
for (const capability of connector.capabilities) {
|
|
223
|
+
if (!/^[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*$/u.test(capability))
|
|
224
|
+
throw new Error(`connector capability must use domain.name form: ${capability}`);
|
|
225
|
+
if (unique.has(capability))
|
|
226
|
+
throw new Error(`connector declares duplicate capability: ${capability}`);
|
|
227
|
+
unique.add(capability);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function unavailable(request, setup) {
|
|
231
|
+
return {
|
|
232
|
+
capability: request.capabilityId,
|
|
233
|
+
runId: request.runId,
|
|
234
|
+
status: "blocked",
|
|
235
|
+
inputs: request.inputs,
|
|
236
|
+
outputs: {},
|
|
237
|
+
evidence: [],
|
|
238
|
+
reason: `connector unavailable for capability: ${request.capabilityId}`,
|
|
239
|
+
certainty: "confirmed",
|
|
240
|
+
sideEffect: "none",
|
|
241
|
+
errorCode: "connector_unavailable",
|
|
242
|
+
declaration: "real",
|
|
243
|
+
recovery: {
|
|
244
|
+
action: "configure_connector",
|
|
245
|
+
capability: request.capabilityId,
|
|
246
|
+
target: request.trace.target,
|
|
247
|
+
idempotencyKey: request.idempotencyKey,
|
|
248
|
+
...setup === undefined ? {} : { setup }
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function failed(request, reason, errorCode) {
|
|
253
|
+
return {
|
|
254
|
+
capability: request.capabilityId,
|
|
255
|
+
runId: request.runId,
|
|
256
|
+
status: "failed",
|
|
257
|
+
inputs: request.inputs,
|
|
258
|
+
outputs: {},
|
|
259
|
+
evidence: [],
|
|
260
|
+
reason,
|
|
261
|
+
certainty: "unknown",
|
|
262
|
+
sideEffect: "possible",
|
|
263
|
+
errorCode,
|
|
264
|
+
declaration: "real",
|
|
265
|
+
recovery: {
|
|
266
|
+
action: "probe_connector",
|
|
267
|
+
capability: request.capabilityId,
|
|
268
|
+
target: request.trace.target,
|
|
269
|
+
idempotencyKey: request.idempotencyKey
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
// runtime/core/src/protection.ts
|
|
274
|
+
var LOCAL_CAPABILITY_DOMAINS = new Set(["change", "project", "verification", "git"]);
|
|
275
|
+
var TARGETS = new Set(["workstation", "test", "beta", "prod", "external"]);
|
|
276
|
+
// runtime/core/src/index.ts
|
|
277
|
+
class CapabilityRegistry {
|
|
278
|
+
specs = new Map;
|
|
279
|
+
constructor(specs = []) {
|
|
280
|
+
for (const spec of specs)
|
|
281
|
+
this.register(spec);
|
|
282
|
+
}
|
|
283
|
+
register(spec) {
|
|
284
|
+
if (this.specs.has(spec.capabilityId))
|
|
285
|
+
throw new Error(`duplicate capability: ${spec.capabilityId}`);
|
|
286
|
+
this.specs.set(spec.capabilityId, spec);
|
|
287
|
+
}
|
|
288
|
+
resolve(capabilityId) {
|
|
289
|
+
const spec = this.specs.get(capabilityId);
|
|
290
|
+
if (!spec)
|
|
291
|
+
throw new Error(`capability is not registered: ${capabilityId}`);
|
|
292
|
+
return spec;
|
|
293
|
+
}
|
|
294
|
+
all() {
|
|
295
|
+
return [...this.specs.values()];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function validateCapabilityResult(result) {
|
|
299
|
+
if (!result.capability.trim() || !result.runId.trim())
|
|
300
|
+
throw new Error("capability and runId must not be empty");
|
|
301
|
+
if (result.declaration !== "real" && result.declaration !== "probe")
|
|
302
|
+
throw new Error("declaration must be real or probe");
|
|
303
|
+
if (result.status === "pass" && result.evidence.length === 0)
|
|
304
|
+
throw new Error("pass result must include evidence");
|
|
305
|
+
if (result.status !== "pass" && !result.reason.trim())
|
|
306
|
+
throw new Error(`${result.status} result must include reason`);
|
|
307
|
+
if (result.recovery?.retryAfterMs !== undefined && (!Number.isFinite(result.recovery.retryAfterMs) || result.recovery.retryAfterMs < 0))
|
|
308
|
+
throw new Error("recovery retryAfterMs must be a non-negative finite number");
|
|
309
|
+
}
|
|
310
|
+
function validateCapabilityRequest(request) {
|
|
311
|
+
if (!request.capabilityId.trim() || !request.runId.trim())
|
|
312
|
+
throw new Error("capabilityId and runId must not be empty");
|
|
313
|
+
if (!request.trace.traceId.trim() || !request.trace.requestId.trim() || !request.trace.sessionId.trim()) {
|
|
314
|
+
throw new Error("trace requires traceId, requestId and sessionId");
|
|
315
|
+
}
|
|
316
|
+
if (request.timeoutMs !== undefined && (!Number.isInteger(request.timeoutMs) || request.timeoutMs <= 0)) {
|
|
317
|
+
throw new Error("timeoutMs must be a positive integer");
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// runtime/providers/src/installation/package-build.ts
|
|
322
|
+
var LAUNCHER = `#!/bin/sh
|
|
323
|
+
` + `script="$0"
|
|
324
|
+
` + `while [ -L "$script" ]; do
|
|
325
|
+
` + ` target="$(readlink "$script")"
|
|
326
|
+
` + ` case "$target" in
|
|
327
|
+
` + ` /*) script="$target" ;;
|
|
328
|
+
` + ` *) script="$(dirname "$script")/$target" ;;
|
|
329
|
+
` + ` esac
|
|
330
|
+
` + `done
|
|
331
|
+
` + `root="$(CDPATH= cd -- "$(dirname -- "$script")/.." && pwd)"
|
|
332
|
+
` + `if [ "$1" = "claude-hook" ]; then
|
|
333
|
+
` + ' exec "${ANYCODE_NODE:-node}" "$root/runtime/claude/dist/cli.mjs" "$@"\n' + `fi
|
|
334
|
+
` + 'exec "${ANYCODE_NODE:-node}" "$root/runtime/providers/dist/cli.mjs" "$@"\n';
|
|
335
|
+
var WINDOWS_LAUNCHER = `@echo off\r
|
|
336
|
+
` + `set "script=%~dp0"\r
|
|
337
|
+
` + `set "root=%script%.."\r
|
|
338
|
+
` + `if "%ANYCODE_NODE%"=="" set "ANYCODE_NODE=node"\r
|
|
339
|
+
` + `if "%~1"=="claude-hook" (\r
|
|
340
|
+
` + ` "%ANYCODE_NODE%" "%root%\\runtime\\claude\\dist\\cli.mjs" %*\r
|
|
341
|
+
` + ` exit /b %ERRORLEVEL%\r
|
|
342
|
+
` + `)\r
|
|
343
|
+
` + `"%ANYCODE_NODE%" "%root%\\runtime\\providers\\dist\\cli.mjs" %*\r
|
|
344
|
+
`;
|
|
345
|
+
// runtime/providers/src/installation/package-installed.ts
|
|
346
|
+
import { createHash } from "node:crypto";
|
|
347
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync2, readdirSync as readdirSync2, readlinkSync } from "node:fs";
|
|
348
|
+
import { join as join2, posix as posix2, relative as relative2, sep } from "node:path";
|
|
349
|
+
var PACKAGE_SCHEMA = "anycode.package.v1";
|
|
350
|
+
var FULL_REVISION = /^[0-9a-f]{40}$/u;
|
|
351
|
+
function validateExistingInstall(root) {
|
|
352
|
+
let stat;
|
|
353
|
+
try {
|
|
354
|
+
stat = lstatSync2(root);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
if (error.code === "ENOENT")
|
|
357
|
+
return;
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
360
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
361
|
+
throw new Error(`AnyCode install path must be a directory: ${root}`);
|
|
362
|
+
const names = readdirSync2(root);
|
|
363
|
+
if (!names.length)
|
|
364
|
+
return;
|
|
365
|
+
if (isLegacyVersionedInstall(root, names))
|
|
366
|
+
return;
|
|
367
|
+
const manifestPath = join2(root, "package-manifest.json");
|
|
368
|
+
if (!names.includes("package-manifest.json"))
|
|
369
|
+
throw new Error(`AnyCode install directory contains unrecognized files: ${root}`);
|
|
370
|
+
try {
|
|
371
|
+
JSON.parse(readFileSync2(manifestPath, "utf8"));
|
|
372
|
+
} catch {
|
|
373
|
+
throw new Error(`旧版扁平安装清单解析失败:${manifestPath}`);
|
|
374
|
+
}
|
|
375
|
+
verifyInstalledPackage(root);
|
|
376
|
+
}
|
|
377
|
+
function isLegacyVersionedInstall(root, names) {
|
|
378
|
+
if (!names.includes("current") || names.some((name) => name !== "current" && !/^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(name)))
|
|
379
|
+
return false;
|
|
380
|
+
try {
|
|
381
|
+
const currentPath = join2(root, "current");
|
|
382
|
+
if (!lstatSync2(currentPath).isSymbolicLink())
|
|
383
|
+
return false;
|
|
384
|
+
const target = readlinkSync(currentPath);
|
|
385
|
+
if (target.includes("\\") || target.startsWith("/") || target.includes("..") || !names.includes(target))
|
|
386
|
+
return false;
|
|
387
|
+
for (const name of names) {
|
|
388
|
+
if (name === "current")
|
|
389
|
+
continue;
|
|
390
|
+
const versionRoot = join2(root, name);
|
|
391
|
+
if (!lstatSync2(versionRoot).isDirectory())
|
|
392
|
+
return false;
|
|
393
|
+
const manifest = verifyInstalledPackage(versionRoot);
|
|
394
|
+
if (name !== `v${manifest.version}`)
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
return true;
|
|
398
|
+
} catch {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function verifyInstalledPackage(root) {
|
|
403
|
+
if (!existsSync2(root) || !lstatSync2(root).isDirectory())
|
|
404
|
+
throw new Error(`installed package does not exist: ${root}`);
|
|
405
|
+
const manifestPath = join2(root, "package-manifest.json");
|
|
406
|
+
if (!existsSync2(manifestPath) || !lstatSync2(manifestPath).isFile())
|
|
407
|
+
throw new Error(`installed package is missing manifest: ${manifestPath}`);
|
|
408
|
+
const value = JSON.parse(readFileSync2(manifestPath, "utf8"));
|
|
409
|
+
if (!isRecord(value))
|
|
410
|
+
throw new Error("installed package manifest is invalid");
|
|
411
|
+
if (value["schema"] !== PACKAGE_SCHEMA || value["name"] !== "anycode")
|
|
412
|
+
throw new Error("installed package manifest identity is invalid");
|
|
413
|
+
if (typeof value["version"] !== "string" || !value["version"])
|
|
414
|
+
throw new Error("installed package version is invalid");
|
|
415
|
+
if (typeof value["source_revision"] !== "string" || !FULL_REVISION.test(value["source_revision"]))
|
|
416
|
+
throw new Error("installed package source_revision is invalid");
|
|
417
|
+
if (!isRecord(value["files"]) || Object.keys(value["files"]).length === 0)
|
|
418
|
+
throw new Error("installed package files are invalid");
|
|
419
|
+
if (!Object.hasOwn(value["files"], "bin/anycode") && !Object.hasOwn(value["files"], "bin/anycode.exe"))
|
|
420
|
+
throw new Error("installed package is missing launcher: bin/anycode");
|
|
421
|
+
const files = {};
|
|
422
|
+
for (const [name, digest] of Object.entries(value["files"])) {
|
|
423
|
+
const relative3 = normalizeMember(name);
|
|
424
|
+
if (typeof digest !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(digest))
|
|
425
|
+
throw new Error(`installed package file digest is invalid: ${name}`);
|
|
426
|
+
files[relative3] = digest;
|
|
427
|
+
const path = join2(root, ...relative3.split("/"));
|
|
428
|
+
if (!existsSync2(path) || !lstatSync2(path).isFile() || lstatSync2(path).isSymbolicLink())
|
|
429
|
+
throw new Error(`installed package verification failed: ${relative3}`);
|
|
430
|
+
if (fileDigest(path) !== digest)
|
|
431
|
+
throw new Error(`installed package verification failed: ${relative3}`);
|
|
432
|
+
}
|
|
433
|
+
if (typeof value["payload_digest"] !== "string" || value["payload_digest"] !== payloadDigest(files))
|
|
434
|
+
throw new Error("installed package payload digest is invalid");
|
|
435
|
+
const extras = installedFiles(root).filter((name) => !(name in files) && name !== "package-manifest.json");
|
|
436
|
+
if (extras.length > 0)
|
|
437
|
+
throw new Error(`installed package contains untracked files: ${extras.slice(0, 10).join(", ")}`);
|
|
438
|
+
return { schema: value["schema"], name: value["name"], version: value["version"], source_revision: value["source_revision"], files, payload_digest: value["payload_digest"] };
|
|
439
|
+
}
|
|
440
|
+
function installedFiles(root) {
|
|
441
|
+
const result = [];
|
|
442
|
+
const walk = (path) => {
|
|
443
|
+
for (const name of readdirSync2(path).sort()) {
|
|
444
|
+
const child = join2(path, name);
|
|
445
|
+
if (lstatSync2(child).isSymbolicLink())
|
|
446
|
+
throw new Error(`installed package contains unsupported symlink: ${child}`);
|
|
447
|
+
if (lstatSync2(child).isFile())
|
|
448
|
+
result.push(relative2(root, child).split(sep).join("/"));
|
|
449
|
+
else if (lstatSync2(child).isDirectory())
|
|
450
|
+
walk(child);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
walk(root);
|
|
454
|
+
return result;
|
|
455
|
+
}
|
|
456
|
+
function normalizeMember(value) {
|
|
457
|
+
const normalized = posix2.normalize(value.replace(/^\.\//u, ""));
|
|
458
|
+
if (!value || value.includes("\\") || normalized === "." || normalized === ".." || normalized.startsWith("../") || value.startsWith("/"))
|
|
459
|
+
throw new Error(`installed package contains unsafe path: ${value}`);
|
|
460
|
+
return normalized;
|
|
461
|
+
}
|
|
462
|
+
function payloadDigest(files) {
|
|
463
|
+
const ordered = Object.fromEntries(Object.entries(files).sort(([left], [right]) => left.localeCompare(right)));
|
|
464
|
+
return digestText(JSON.stringify(ordered));
|
|
465
|
+
}
|
|
466
|
+
function fileDigest(path) {
|
|
467
|
+
return digestBytes(readFileSync2(path));
|
|
468
|
+
}
|
|
469
|
+
function digestText(value) {
|
|
470
|
+
return digestBytes(new TextEncoder().encode(value));
|
|
471
|
+
}
|
|
472
|
+
function digestBytes(value) {
|
|
473
|
+
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
474
|
+
}
|
|
475
|
+
function isRecord(value) {
|
|
476
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// runtime/providers/src/installation/activation.ts
|
|
480
|
+
import { copyFileSync, existsSync as existsSync3, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readlinkSync as readlinkSync2, realpathSync, renameSync, rmSync, statSync as statSync2, symlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
481
|
+
import { dirname as dirname2, join as join3, relative as relative3, resolve as resolve2 } from "node:path";
|
|
482
|
+
function activatePackage(stagedPackage, installRoot, launcherPath, options = {}) {
|
|
483
|
+
const platform = options.platform ?? process.platform;
|
|
484
|
+
const executable = options.executable ?? (platform === "win32" ? "anycode.exe" : "anycode");
|
|
485
|
+
mkdirSync2(dirname2(installRoot), { recursive: true });
|
|
486
|
+
const releaseLock = acquireActivationLock(installRoot);
|
|
487
|
+
const previous = `${installRoot}.previous`;
|
|
488
|
+
let movedCurrent = false;
|
|
489
|
+
let installedNew = false;
|
|
490
|
+
try {
|
|
491
|
+
recoverInterruptedActivation(installRoot, previous);
|
|
492
|
+
const manifest = verifyInstalledPackage(stagedPackage);
|
|
493
|
+
validateExistingInstall(installRoot);
|
|
494
|
+
validateLauncherOwnership(launcherPath, installRoot, stagedPackage, executable, platform);
|
|
495
|
+
if (existsSync3(installRoot)) {
|
|
496
|
+
rmSync(previous, { recursive: true, force: true });
|
|
497
|
+
renameSync(installRoot, previous);
|
|
498
|
+
movedCurrent = true;
|
|
499
|
+
}
|
|
500
|
+
renameSync(stagedPackage, installRoot);
|
|
501
|
+
installedNew = true;
|
|
502
|
+
const installed = verifyInstalledPackage(installRoot);
|
|
503
|
+
if (installed.version !== manifest.version || installed.payload_digest !== manifest.payload_digest)
|
|
504
|
+
throw new Error("安装后的 AnyCode 包校验失败");
|
|
505
|
+
linkLauncher(join3(installRoot, "bin", executable), launcherPath, platform);
|
|
506
|
+
return installRoot;
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if (installedNew && existsSync3(installRoot)) {
|
|
509
|
+
rmSync(installRoot, { recursive: true, force: true });
|
|
510
|
+
}
|
|
511
|
+
if (movedCurrent && existsSync3(previous) && !existsSync3(installRoot))
|
|
512
|
+
renameSync(previous, installRoot);
|
|
513
|
+
throw error;
|
|
514
|
+
} finally {
|
|
515
|
+
releaseLock();
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function acquireActivationLock(installRoot) {
|
|
519
|
+
const lock = `${installRoot}.activation-lock`;
|
|
520
|
+
const deadline = Date.now() + 30000;
|
|
521
|
+
const token = `${process.pid}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
|
|
522
|
+
while (true) {
|
|
523
|
+
try {
|
|
524
|
+
mkdirSync2(lock);
|
|
525
|
+
writeFileSync2(join3(lock, "owner"), token, { flag: "wx", mode: 384 });
|
|
526
|
+
return () => {
|
|
527
|
+
try {
|
|
528
|
+
if (readFileSync3(join3(lock, "owner"), "utf8") === token)
|
|
529
|
+
rmSync(lock, { recursive: true, force: true });
|
|
530
|
+
} catch {}
|
|
531
|
+
};
|
|
532
|
+
} catch (error) {
|
|
533
|
+
if (error.code !== "EEXIST")
|
|
534
|
+
throw error;
|
|
535
|
+
const ownerPath = join3(lock, "owner");
|
|
536
|
+
let stale = false;
|
|
537
|
+
try {
|
|
538
|
+
const owner = readFileSync3(ownerPath, "utf8");
|
|
539
|
+
const pid = Number(owner.split(":", 1)[0]);
|
|
540
|
+
if (Number.isSafeInteger(pid) && pid > 0) {
|
|
541
|
+
try {
|
|
542
|
+
process.kill(pid, 0);
|
|
543
|
+
} catch (probeError) {
|
|
544
|
+
stale = probeError.code === "ESRCH";
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
} catch {
|
|
548
|
+
try {
|
|
549
|
+
stale = Date.now() - statSync2(lock).mtimeMs > 30000;
|
|
550
|
+
} catch {
|
|
551
|
+
stale = true;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (stale) {
|
|
555
|
+
rmSync(lock, { recursive: true, force: true });
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (Date.now() >= deadline)
|
|
559
|
+
throw new Error(`timed out waiting for AnyCode activation lock: ${lock}`);
|
|
560
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function recoverInterruptedActivation(installRoot, previous) {
|
|
565
|
+
if (existsSync3(installRoot))
|
|
566
|
+
return;
|
|
567
|
+
if (!existsSync3(previous))
|
|
568
|
+
return;
|
|
569
|
+
validateExistingInstall(previous);
|
|
570
|
+
renameSync(previous, installRoot);
|
|
571
|
+
}
|
|
572
|
+
function validateLauncherOwnership(launcher, root, staged, executable, platform) {
|
|
573
|
+
let existing;
|
|
574
|
+
try {
|
|
575
|
+
existing = lstatSync3(launcher);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
if (error.code === "ENOENT")
|
|
578
|
+
return;
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
if (existing.isSymbolicLink()) {
|
|
582
|
+
const target = resolve2(dirname2(launcher), readlinkSync2(launcher));
|
|
583
|
+
const path = relative3(resolve2(root), target).replaceAll("\\", "/");
|
|
584
|
+
if (path === `bin/${executable}`)
|
|
585
|
+
return;
|
|
586
|
+
try {
|
|
587
|
+
validateExistingInstall(root);
|
|
588
|
+
if (path === `current/bin/${executable}` && realpathSync(target) === realpathSync(join3(root, "current", "bin", executable)))
|
|
589
|
+
return;
|
|
590
|
+
} catch {}
|
|
591
|
+
} else if (platform === "win32" && existing.isFile()) {
|
|
592
|
+
const bytes = readFileSync3(launcher);
|
|
593
|
+
for (const candidate of [join3(staged, "bin", executable), join3(root, "bin", executable)]) {
|
|
594
|
+
if (existsSync3(candidate) && bytes.equals(readFileSync3(candidate)))
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
throw new Error(`refusing to replace an unmanaged anycode launcher: ${launcher}`);
|
|
599
|
+
}
|
|
600
|
+
function linkLauncher(source, launcherPath, platform) {
|
|
601
|
+
mkdirSync2(dirname2(launcherPath), { recursive: true });
|
|
602
|
+
const next = `${launcherPath}.${process.pid}.next`;
|
|
603
|
+
rmSync(next, { force: true });
|
|
604
|
+
if (platform === "win32")
|
|
605
|
+
copyFileSync(source, next);
|
|
606
|
+
else
|
|
607
|
+
symlinkSync(source, next);
|
|
608
|
+
renameSync(next, launcherPath);
|
|
609
|
+
}
|
|
610
|
+
// runtime/providers/src/installation/release-trust.ts
|
|
611
|
+
import { verify } from "node:crypto";
|
|
612
|
+
import { basename } from "node:path";
|
|
613
|
+
|
|
614
|
+
// runtime/providers/src/installation/release-key.generated.ts
|
|
615
|
+
var RELEASE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
616
|
+
MCowBQYDK2VwAyEA/hfqP5bUwAMqzY3QS+j9GIGjbYpyrtcheixQGJt93zo=
|
|
617
|
+
-----END PUBLIC KEY-----
|
|
618
|
+
`;
|
|
619
|
+
|
|
620
|
+
// runtime/providers/src/installation/release-trust.ts
|
|
621
|
+
function verifyReleaseChecksums(sums, encodedSignature, publicKey = RELEASE_PUBLIC_KEY_PEM) {
|
|
622
|
+
const encoded = encodedSignature.trim();
|
|
623
|
+
const signature = Buffer.from(encoded, "base64");
|
|
624
|
+
if (signature.toString("base64") !== encoded || signature.byteLength !== 64)
|
|
625
|
+
throw new Error("AnyCode SHA256SUMS 签名格式无效");
|
|
626
|
+
if (!verify(null, sums, publicKey, signature))
|
|
627
|
+
throw new Error("AnyCode SHA256SUMS Ed25519 签名验证失败");
|
|
628
|
+
return parseReleaseChecksums(Buffer.from(sums).toString("utf8"));
|
|
629
|
+
}
|
|
630
|
+
function parseReleaseChecksums(value) {
|
|
631
|
+
const result = new Map;
|
|
632
|
+
for (const line of value.split(/\r?\n/u)) {
|
|
633
|
+
if (!line.trim())
|
|
634
|
+
continue;
|
|
635
|
+
const match = /^([a-f0-9]{64})\s+[* ]?([^\s]+)$/u.exec(line.trim());
|
|
636
|
+
if (!match || basename(match[2]) !== match[2] || result.has(match[2]))
|
|
637
|
+
throw new Error("已签名 SHA256SUMS 格式无效");
|
|
638
|
+
result.set(match[2], match[1]);
|
|
639
|
+
}
|
|
640
|
+
if (!result.size)
|
|
641
|
+
throw new Error("已签名 SHA256SUMS 不能为空");
|
|
642
|
+
return result;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// runtime/providers/src/installation/update.ts
|
|
646
|
+
import { createHash as createHash2, createPublicKey } from "node:crypto";
|
|
647
|
+
var PLATFORM = new Map([
|
|
648
|
+
["darwin-arm64", "darwin-arm64"],
|
|
649
|
+
["darwin-x64", "darwin-x64"],
|
|
650
|
+
["linux-arm64", "linux-arm64"],
|
|
651
|
+
["linux-x64", "linux-x64"],
|
|
652
|
+
["win32-arm64", "windows-arm64"],
|
|
653
|
+
["win32-x64", "windows-x64"]
|
|
654
|
+
]);
|
|
655
|
+
var RELEASE_PUBLIC_KEY_DER_SHA256 = createHash2("sha256").update(createPublicKey(RELEASE_PUBLIC_KEY_PEM).export({ type: "spki", format: "der" })).digest("hex");
|
|
656
|
+
// npm/src/cli.ts
|
|
657
|
+
var HARNESS_IDS = ["opencode-v1", "opencode-v2", "claude", "pi"];
|
|
658
|
+
var HARNESS_ENTRYPOINTS = {
|
|
659
|
+
"opencode-v1": "bin/harness/opencode-1.x.js",
|
|
660
|
+
"opencode-v2": "bin/harness/opencode-2.x.js",
|
|
661
|
+
claude: "bin/harness/claude.js",
|
|
662
|
+
pi: "bin/harness/pi.js"
|
|
663
|
+
};
|
|
664
|
+
function isRecord2(value) {
|
|
665
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
666
|
+
}
|
|
667
|
+
function parsePackageMetadata(value) {
|
|
668
|
+
if (!isRecord2(value) || typeof value["version"] !== "string" || !value["version"].trim()) {
|
|
669
|
+
throw new Error("package.json must contain a non-empty version");
|
|
670
|
+
}
|
|
671
|
+
return { version: value["version"] };
|
|
672
|
+
}
|
|
673
|
+
var metadata = parsePackageMetadata(JSON.parse(await readFile(new URL("../../package.json", import.meta.url), "utf8")));
|
|
674
|
+
var PACKAGE_VERSION = metadata.version;
|
|
675
|
+
var RELEASE_BASE_URL = (process.env["ANYCODE_RELEASE_BASE_URL"] ?? "https://anycode.me").replace(/\/$/u, "");
|
|
676
|
+
var homeDirectory = homedir();
|
|
677
|
+
var INSTALL_ROOT = join4(process.env["ANYCODE_INSTALL_ROOT"] ?? join4(homeDirectory, ".local", "share", "anycode"));
|
|
678
|
+
var BIN_DIR = process.env["ANYCODE_BIN_DIR"] ?? join4(homeDirectory, ".local", "bin");
|
|
679
|
+
var NODE_MINIMUM_MAJOR = 20;
|
|
680
|
+
var DOWNLOAD_TIMEOUT_MS = 60000;
|
|
681
|
+
function usage() {
|
|
682
|
+
console.log(`AnyCode ${PACKAGE_VERSION}
|
|
683
|
+
|
|
684
|
+
Usage:
|
|
685
|
+
curl -fsSL https://anycode.me/install.sh | sh
|
|
686
|
+
curl -fsSL https://anycode.me/install.sh | ANYCODE_VERSION=<version> sh
|
|
687
|
+
|
|
688
|
+
After installation:
|
|
689
|
+
anycode doctor
|
|
690
|
+
anycode help [topic] [--json]
|
|
691
|
+
anycode <native-anycode-command> [...args]
|
|
692
|
+
|
|
693
|
+
Install downloads and verifies the immutable GitHub release. Other commands are
|
|
694
|
+
forwarded to the activated native AnyCode runtime.
|
|
695
|
+
|
|
696
|
+
Node.js ${NODE_MINIMUM_MAJOR} or newer is required. Git and a Harness (OpenCode,
|
|
697
|
+
Claude Code, or Pi) are separate optional integrations.
|
|
698
|
+
Supported Harness adapters: opencode-v1, opencode-v2, claude, pi.`);
|
|
699
|
+
}
|
|
700
|
+
function normalizedVersion(value) {
|
|
701
|
+
const version = value.startsWith("v") ? value.slice(1) : value;
|
|
702
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(version)) {
|
|
703
|
+
throw new Error(`invalid AnyCode version: ${value}`);
|
|
704
|
+
}
|
|
705
|
+
return version;
|
|
706
|
+
}
|
|
707
|
+
function option(args, name, fallback) {
|
|
708
|
+
const index = args.indexOf(name);
|
|
709
|
+
if (index < 0)
|
|
710
|
+
return fallback;
|
|
711
|
+
const value = args[index + 1];
|
|
712
|
+
if (!value || value.startsWith("--"))
|
|
713
|
+
throw new Error(`${name} requires a value`);
|
|
714
|
+
return value;
|
|
715
|
+
}
|
|
716
|
+
function ensureNodeVersion() {
|
|
717
|
+
const major = Number.parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
718
|
+
if (!Number.isInteger(major) || major < NODE_MINIMUM_MAJOR) {
|
|
719
|
+
throw new Error(`AnyCode requires Node.js >= ${NODE_MINIMUM_MAJOR}; detected ${process.versions.node}. Install or upgrade Node.js before continuing.`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
async function verifyArtifact(artifact, checksumFile) {
|
|
723
|
+
const expected = (await readFile(checksumFile, "utf8")).trim().split(/\s+/).at(-1);
|
|
724
|
+
const actual = createHash3("sha256").update(await readFile(artifact)).digest("hex");
|
|
725
|
+
const normalized = expected?.replace(/^sha256:/, "");
|
|
726
|
+
if (!normalized || normalized !== actual) {
|
|
727
|
+
throw new Error("release artifact sha256 does not match its checksum");
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async function verifySignedRelease(version, target, artifact, options = {}) {
|
|
731
|
+
const root = `${(options.baseUrl ?? RELEASE_BASE_URL).replace(/\/$/u, "")}/releases/v${version}`;
|
|
732
|
+
if (!root.startsWith("https://"))
|
|
733
|
+
throw new Error("AnyCode release base URL must use HTTPS");
|
|
734
|
+
const fetcher = options.fetcher ?? fetch;
|
|
735
|
+
const [manifestResponse, sumsResponse, signatureResponse] = await Promise.all([
|
|
736
|
+
fetcher(`${root}/latest.json`, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) }),
|
|
737
|
+
fetcher(`${root}/SHA256SUMS`, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) }),
|
|
738
|
+
fetcher(`${root}/SHA256SUMS.sig`, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) })
|
|
739
|
+
]);
|
|
740
|
+
if (!manifestResponse.ok || !sumsResponse.ok || !signatureResponse.ok)
|
|
741
|
+
throw new Error("AnyCode release trust metadata is unavailable");
|
|
742
|
+
const manifest = await manifestResponse.json();
|
|
743
|
+
if (manifest.version !== version)
|
|
744
|
+
throw new Error("AnyCode release manifest version does not match the requested version");
|
|
745
|
+
const platform = `${target.os}-${target.arch}`;
|
|
746
|
+
const name = `anycode-${version}-${platform}.tar.gz`;
|
|
747
|
+
const file = manifest.files?.find((candidate) => candidate.platform === platform && candidate.name === name);
|
|
748
|
+
if (!file || typeof file.sha256 !== "string")
|
|
749
|
+
throw new Error("AnyCode release manifest has no unique platform asset");
|
|
750
|
+
const sums = await sumsResponse.text();
|
|
751
|
+
const signedHash = verifyReleaseChecksums(Buffer.from(sums), await signatureResponse.text(), options.publicKeyPem ?? RELEASE_PUBLIC_KEY_PEM).get(name);
|
|
752
|
+
if (signedHash !== file.sha256)
|
|
753
|
+
throw new Error("AnyCode manifest does not match signed SHA256SUMS");
|
|
754
|
+
const actual = createHash3("sha256").update(await readFile(artifact)).digest("hex");
|
|
755
|
+
if (actual !== signedHash)
|
|
756
|
+
throw new Error("AnyCode release artifact does not match signed SHA256SUMS");
|
|
757
|
+
}
|
|
758
|
+
var RELEASE_TARGETS = {
|
|
759
|
+
"linux-x64": { os: "linux", arch: "x64", executable: "anycode" },
|
|
760
|
+
"linux-arm64": { os: "linux", arch: "arm64", executable: "anycode" },
|
|
761
|
+
"darwin-x64": { os: "darwin", arch: "x64", executable: "anycode" },
|
|
762
|
+
"darwin-arm64": { os: "darwin", arch: "arm64", executable: "anycode" },
|
|
763
|
+
"win32-x64": { os: "windows", arch: "x64", executable: "anycode.exe" }
|
|
764
|
+
};
|
|
765
|
+
function releaseTarget() {
|
|
766
|
+
const target = RELEASE_TARGETS[`${process.platform}-${process.arch}`];
|
|
767
|
+
if (target === undefined)
|
|
768
|
+
throw new Error(`AnyCode has no native release for ${process.platform}-${process.arch}`);
|
|
769
|
+
return target;
|
|
770
|
+
}
|
|
771
|
+
function harnessOption(args) {
|
|
772
|
+
const value = option(args, "--harness", "");
|
|
773
|
+
if (!value)
|
|
774
|
+
return;
|
|
775
|
+
if (!HARNESS_IDS.includes(value))
|
|
776
|
+
throw new Error(`unsupported Harness: ${value}; choose ${HARNESS_IDS.join(", ")}`);
|
|
777
|
+
return value;
|
|
778
|
+
}
|
|
779
|
+
async function install(version, harness, options = {}) {
|
|
780
|
+
ensureNodeVersion();
|
|
781
|
+
const target = options.target ?? releaseTarget();
|
|
782
|
+
const installRoot = options.installRoot ?? INSTALL_ROOT;
|
|
783
|
+
const binDirectory = options.binDirectory ?? BIN_DIR;
|
|
784
|
+
const baseUrl = (options.baseUrl ?? RELEASE_BASE_URL).replace(/\/$/u, "");
|
|
785
|
+
if (!baseUrl.startsWith("https://"))
|
|
786
|
+
throw new Error("AnyCode release base URL must use HTTPS");
|
|
787
|
+
const fetcher = options.fetcher ?? fetch;
|
|
788
|
+
const requestOptions = { redirect: "follow", signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) };
|
|
789
|
+
await mkdir(dirname3(installRoot), { recursive: true });
|
|
790
|
+
const temporary = await mkdtemp(join4(dirname3(installRoot), ".anycode-npm-"));
|
|
791
|
+
const artifact = join4(temporary, `anycode-${version}-${target.os}-${target.arch}.tar.gz`);
|
|
792
|
+
const checksum = `${artifact}.sha256`;
|
|
793
|
+
const source = join4(temporary, "source");
|
|
794
|
+
try {
|
|
795
|
+
await downloadWith(fetcher, `${baseUrl}/releases/v${version}/anycode-${version}-${target.os}-${target.arch}.tar.gz`, artifact, requestOptions);
|
|
796
|
+
await downloadWith(fetcher, `${baseUrl}/releases/v${version}/anycode-${version}-${target.os}-${target.arch}.tar.gz.sha256`, checksum, requestOptions);
|
|
797
|
+
await verifyArtifact(artifact, checksum);
|
|
798
|
+
await verifySignedRelease(version, target, artifact, options);
|
|
799
|
+
await mkdir(source, { recursive: true });
|
|
800
|
+
extractTarGzip(artifact, source);
|
|
801
|
+
const manifest = verifyInstalledPackage(source);
|
|
802
|
+
if (manifest.files[`bin/${target.executable}`] === undefined)
|
|
803
|
+
throw new Error(`release package is missing native executable bin/${target.executable}`);
|
|
804
|
+
if (harness !== undefined && manifest.files[HARNESS_ENTRYPOINTS[harness]] === undefined) {
|
|
805
|
+
throw new Error(`release package does not contain the requested Harness adapter: ${harness}`);
|
|
806
|
+
}
|
|
807
|
+
activatePackage(source, installRoot, join4(binDirectory, target.executable), {
|
|
808
|
+
executable: target.executable,
|
|
809
|
+
...options.platform === undefined ? {} : { platform: options.platform }
|
|
810
|
+
});
|
|
811
|
+
console.log(`AnyCode ${version} installed at ${installRoot}`);
|
|
812
|
+
if (harness !== undefined)
|
|
813
|
+
console.log(`Register only ${HARNESS_ENTRYPOINTS[harness]} in the native ${harness} configuration.`);
|
|
814
|
+
if (!pathContains(binDirectory))
|
|
815
|
+
console.log(`Add ${binDirectory} to PATH before using the anycode command in a new shell.`);
|
|
816
|
+
} finally {
|
|
817
|
+
await rm(temporary, { recursive: true, force: true });
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async function downloadWith(fetcher, url, destination, requestOptions) {
|
|
821
|
+
const response = await fetcher(url, requestOptions);
|
|
822
|
+
if (!response.ok)
|
|
823
|
+
throw new Error(`download failed: ${response.status} ${response.statusText}`);
|
|
824
|
+
await writeFile(destination, Buffer.from(await response.arrayBuffer()));
|
|
825
|
+
}
|
|
826
|
+
function pathContains(directory) {
|
|
827
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
828
|
+
return (process.env["PATH"] ?? "").split(separator).includes(directory);
|
|
829
|
+
}
|
|
830
|
+
function compareVersions(left, right) {
|
|
831
|
+
const parse = (value) => {
|
|
832
|
+
const [base, suffix] = value.split("-", 2);
|
|
833
|
+
return { core: base.split(".").map(Number), suffix: suffix?.split(".") };
|
|
834
|
+
};
|
|
835
|
+
const a = parse(left);
|
|
836
|
+
const b = parse(right);
|
|
837
|
+
for (let index = 0;index < 3; index += 1) {
|
|
838
|
+
const difference = (a.core[index] ?? 0) - (b.core[index] ?? 0);
|
|
839
|
+
if (difference)
|
|
840
|
+
return Math.sign(difference);
|
|
841
|
+
}
|
|
842
|
+
if (!a.suffix && !b.suffix)
|
|
843
|
+
return 0;
|
|
844
|
+
if (!a.suffix)
|
|
845
|
+
return 1;
|
|
846
|
+
if (!b.suffix)
|
|
847
|
+
return -1;
|
|
848
|
+
for (let index = 0;index < Math.max(a.suffix.length, b.suffix.length); index += 1) {
|
|
849
|
+
const x = a.suffix[index];
|
|
850
|
+
const y = b.suffix[index];
|
|
851
|
+
if (x === undefined)
|
|
852
|
+
return -1;
|
|
853
|
+
if (y === undefined)
|
|
854
|
+
return 1;
|
|
855
|
+
if (x === y)
|
|
856
|
+
continue;
|
|
857
|
+
const nx = /^\d+$/u.test(x);
|
|
858
|
+
const ny = /^\d+$/u.test(y);
|
|
859
|
+
if (nx && ny)
|
|
860
|
+
return Number(x) < Number(y) ? -1 : 1;
|
|
861
|
+
if (nx !== ny)
|
|
862
|
+
return nx ? -1 : 1;
|
|
863
|
+
return x.localeCompare(y);
|
|
864
|
+
}
|
|
865
|
+
return 0;
|
|
866
|
+
}
|
|
867
|
+
function newerNativeExecutable() {
|
|
868
|
+
const path = join4(INSTALL_ROOT, "bin", releaseTarget().executable);
|
|
869
|
+
const probe = spawnSync(path, ["version", "--json"], { encoding: "utf8", timeout: 5000 });
|
|
870
|
+
if (probe.status !== 0)
|
|
871
|
+
return;
|
|
872
|
+
try {
|
|
873
|
+
const value = JSON.parse(probe.stdout);
|
|
874
|
+
if (!isRecord2(value) || typeof value["version"] !== "string" || compareVersions(value["version"], PACKAGE_VERSION) <= 0)
|
|
875
|
+
return;
|
|
876
|
+
return { path, version: value["version"] };
|
|
877
|
+
} catch {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
async function main(args) {
|
|
882
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h" || args[0] === "install" && (args.includes("--help") || args.includes("-h"))) {
|
|
883
|
+
usage();
|
|
884
|
+
return 0;
|
|
885
|
+
}
|
|
886
|
+
if (args.includes("--version") && args.length === 1) {
|
|
887
|
+
const native = newerNativeExecutable();
|
|
888
|
+
console.log(native ? `${native.version} (${native.path})` : `${PACKAGE_VERSION} (${fileURLToPath(import.meta.url)})`);
|
|
889
|
+
return 0;
|
|
890
|
+
}
|
|
891
|
+
if (args[0] === "install") {
|
|
892
|
+
const version = normalizedVersion(option(args, "--version", PACKAGE_VERSION));
|
|
893
|
+
await install(version, harnessOption(args));
|
|
894
|
+
return 0;
|
|
895
|
+
}
|
|
896
|
+
const executable = newerNativeExecutable()?.path ?? join4(INSTALL_ROOT, "bin", releaseTarget().executable);
|
|
897
|
+
const result = spawnSync(executable, args, { stdio: "inherit" });
|
|
898
|
+
if (result.error) {
|
|
899
|
+
throw new Error(`AnyCode is not installed at ${INSTALL_ROOT}; run: curl -fsSL https://anycode.me/install.sh | sh`);
|
|
900
|
+
}
|
|
901
|
+
return result.status ?? 1;
|
|
902
|
+
}
|
|
903
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
904
|
+
main(process.argv.slice(2)).then((code) => {
|
|
905
|
+
process.exitCode = code;
|
|
906
|
+
}).catch((error) => {
|
|
907
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
908
|
+
console.error(`anycode: ${message}`);
|
|
909
|
+
process.exitCode = 1;
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
export {
|
|
913
|
+
install
|
|
914
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langgexyz/anycode",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.6",
|
|
4
|
+
"description": "AnyCode distribution bootstrap for the native runtime",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"anycode": "npm/dist/cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"npm/dist/cli.mjs",
|
|
11
|
+
"npm/README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/bun": "1.4.0",
|
|
21
|
+
"@types/node": "26.6.1",
|
|
22
|
+
"jsonc-parser": "3.3.1",
|
|
23
|
+
"typescript": "7.0.2"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "bun scripts/generate-release-key.ts --check && bun build npm/src/cli.ts --outfile npm/dist/cli.mjs --target node",
|
|
27
|
+
"build:binary": "bun scripts/build-binary.ts",
|
|
28
|
+
"build:binary-package": "bun scripts/build-binary-package.ts",
|
|
29
|
+
"build:git-gate": "bun build scripts/git-gate.ts --outfile scripts/git-gate.mjs --target node",
|
|
30
|
+
"check:provider-bundle": "bun scripts/check-provider-bundle.ts",
|
|
31
|
+
"catalog:generate": "bun scripts/generate-capability-catalog-doc.ts",
|
|
32
|
+
"catalog:check": "bun scripts/generate-capability-catalog-doc.ts --check",
|
|
33
|
+
"build:release": "bun scripts/build-release.ts",
|
|
34
|
+
"test": "bun scripts/run-bounded-test.ts npm/tests/*.test.ts",
|
|
35
|
+
"test:providers": "bun scripts/run-bounded-test.ts runtime/providers/tests/*.test.ts",
|
|
36
|
+
"test:opencode": "bun run --cwd runtime/opencode/v1 typecheck && bun run --cwd runtime/opencode/v1 test && bun run --cwd runtime/opencode/v2 typecheck && bun run --cwd runtime/opencode/v2 test",
|
|
37
|
+
"test:opencode:v1-loader": "bun scripts/run-opencode-v1-registration-e2e.ts",
|
|
38
|
+
"test:opencode:e2e": "bun scripts/run-opencode-v2-e2e.ts",
|
|
39
|
+
"architecture:doctor": "bun scripts/architecture-doctor.ts",
|
|
40
|
+
"typecheck": "bun scripts/typecheck-runtime.ts",
|
|
41
|
+
"prepublishOnly": "npm run typecheck && npm run build && npm test",
|
|
42
|
+
"test:runtime": "bun run --cwd runtime/opencode/common test",
|
|
43
|
+
"test:modules": "bun run --cwd runtime/model-policy test && bun run --cwd runtime/provider-routing test && bun scripts/run-bounded-test.ts runtime/core/tests/*.test.ts runtime/contracts/tests/*.test.ts runtime/archive/tests/*.test.ts runtime/behavior/tests/*.test.ts runtime/credentials/tests/*.test.ts runtime/evidence/tests/*.test.ts runtime/harness-context/tests/*.test.ts runtime/usage/tests/*.test.ts runtime/claude/tests/*.test.ts runtime/pi/tests/*.test.ts scripts/*.test.ts",
|
|
44
|
+
"verify:native-runtime": "bun scripts/verify-native-runtime.ts",
|
|
45
|
+
"test:native-runtime:e2e": "bun scripts/run-native-runtime-e2e.ts",
|
|
46
|
+
"ci:test": "bun run architecture:doctor && bun run typecheck && bun run test:opencode && bun run test:opencode:v1-loader && bun run test:runtime && bun run test:modules && bun run test && bun run test:providers && bun run check:provider-bundle && bun run catalog:check && bash scripts/test-release-workflow.sh && bash scripts/test-release-publish.sh && bash scripts/test-wait-for-ci.sh && bun run test:opencode:e2e && OPENCODE_BINARY=\"$PWD/.cache/opencode-cli/node_modules/.bin/opencode\" bun run test:native-runtime:e2e"
|
|
47
|
+
}
|
|
48
|
+
}
|