napbox 0.0.0-stage → 0.1.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.
@@ -0,0 +1,210 @@
1
+ // napboxd 本地 socket 协议的 TypeScript 这一份,对应 native/Sources/NapboxProtocol/Protocol.swift。
2
+ // 类型都由 schema 推出来,发回的内容也按同一份 schema 核对,类型和实际校验不会各写各的;
3
+ // 这份和 Swift 对不对得上由 test/contract.test.ts 逐字段比较。字段含义看 Protocol.swift,这里不重复。
4
+ import { z } from "zod";
5
+ const isoDate = z.iso.datetime();
6
+ const base64 = z.base64();
7
+ /** 错误码,napboxd 原样给什么这里就是什么 */
8
+ export const errorCodes = [
9
+ "daemon_unavailable",
10
+ "bad_request",
11
+ "unknown_method",
12
+ "protocol_error",
13
+ "usage_error",
14
+ "internal_error",
15
+ "compose_not_found",
16
+ "compose_invalid",
17
+ "compose_unsupported",
18
+ "not_in_repository",
19
+ "port_unavailable",
20
+ "state_corrupted",
21
+ "environment_not_found",
22
+ "runtime_unavailable",
23
+ "image_unavailable",
24
+ "image_build_failed",
25
+ "service_failed",
26
+ "service_not_found",
27
+ "volume_not_found",
28
+ "healthcheck_timeout",
29
+ "address_unavailable",
30
+ "name_taken",
31
+ "owner_mismatch",
32
+ ];
33
+ export const methods = {
34
+ status: "status",
35
+ up: "up",
36
+ down: "down",
37
+ sleep: "sleep",
38
+ egress: "egress",
39
+ environment: "environment",
40
+ logs: "logs",
41
+ exec: "exec",
42
+ subscribe: "subscribe",
43
+ run: "run",
44
+ reset: "reset",
45
+ imageImport: "image.import",
46
+ create: "create",
47
+ wake: "wake",
48
+ runs: "runs",
49
+ };
50
+ // code 不收窄成 errorCodes:新版 napboxd 加的错误码也要原样带给调用方
51
+ export const NapboxErrorBody = z.object({ code: z.string(), message: z.string() });
52
+ export const EnvironmentState = z.enum(["running", "sleeping"]);
53
+ export const EgressMode = z.enum(["open", "none"]);
54
+ export const ServiceExitRestart = z.enum(["none", "restarting", "gave_up"]);
55
+ export const RunPurpose = z.enum(["up", "wake", "reset", "restart"]);
56
+ export const Readiness = z.enum(["healthcheck", "port", "process"]);
57
+ export const OutputStream = z.enum(["stdout", "stderr"]);
58
+ export const EventKind = z.enum([
59
+ "started",
60
+ "start_failed",
61
+ "slept",
62
+ "service_exited",
63
+ "destroyed",
64
+ ]);
65
+ export const SleepReason = z.enum(["idle", "request", "host_address", "shutdown", "vm_lost"]);
66
+ export const PortInfo = z.object({
67
+ service: z.string(),
68
+ containerPort: z.int(),
69
+ hostPort: z.int(),
70
+ variable: z.string(),
71
+ });
72
+ export const ServiceExit = z.object({
73
+ service: z.string(),
74
+ exitCode: z.int(),
75
+ at: isoDate,
76
+ restart: ServiceExitRestart,
77
+ });
78
+ export const EnvironmentInfo = z.object({
79
+ name: z.string(),
80
+ owner: z.string().optional(),
81
+ state: EnvironmentState,
82
+ worktree: z.string(),
83
+ ports: z.array(PortInfo),
84
+ egress: EgressMode,
85
+ variables: z.record(z.string(), z.string()),
86
+ unsetVariables: z.array(z.string()),
87
+ lastError: z.string().optional(),
88
+ watchError: z.string().optional(),
89
+ serviceExits: z.array(ServiceExit),
90
+ });
91
+ export const StatusResult = z.object({
92
+ version: z.string(),
93
+ pid: z.int(),
94
+ startedAt: isoDate,
95
+ environments: z.array(EnvironmentInfo),
96
+ });
97
+ /** 操作哪个环境:worktree 持有的写 `{ cwd }`(它的临时环境再带上 `temp`),调用方持有的写 `{ name, owner }` */
98
+ export const EnvironmentTarget = z.union([
99
+ z.object({ cwd: z.string(), temp: z.string().optional() }),
100
+ z.object({ name: z.string(), owner: z.string() }),
101
+ ]);
102
+ export const UpParams = z.object({
103
+ cwd: z.string(),
104
+ files: z.array(z.string()),
105
+ profiles: z.array(z.string()),
106
+ environment: z.record(z.string(), z.string()),
107
+ build: z.boolean(),
108
+ temp: z.string().optional(),
109
+ });
110
+ export const RunParams = z.object({
111
+ cwd: z.string(),
112
+ files: z.array(z.string()),
113
+ profiles: z.array(z.string()),
114
+ environment: z.record(z.string(), z.string()),
115
+ build: z.boolean(),
116
+ egress: EgressMode.optional(),
117
+ service: z.string(),
118
+ command: z.array(z.string()),
119
+ noDeps: z.boolean(),
120
+ });
121
+ export const EnvironmentParams = z.object({ target: EnvironmentTarget });
122
+ export const CreateParams = z.object({
123
+ name: z.string(),
124
+ owner: z.string(),
125
+ directory: z.string(),
126
+ files: z.array(z.string()),
127
+ profiles: z.array(z.string()),
128
+ environment: z.record(z.string(), z.string()),
129
+ egress: EgressMode.optional(),
130
+ });
131
+ export const ResetParams = z.object({ target: EnvironmentTarget, volume: z.string() });
132
+ export const EgressParams = z.object({ target: EnvironmentTarget, mode: EgressMode });
133
+ export const DownResult = z.object({
134
+ name: z.string(),
135
+ releasedPorts: z.array(z.int()),
136
+ keptDataAt: z.string().optional(),
137
+ });
138
+ export const ImageImportParams = z.object({ reference: z.string() });
139
+ export const ImageImportResult = z.object({ reference: z.string(), digest: z.string() });
140
+ export const StreamOutput = z.object({ stream: OutputStream, data: base64 });
141
+ export const StdinMessage = z.object({ data: base64.optional(), end: z.boolean().optional() });
142
+ export const LogsParams = z.object({
143
+ target: EnvironmentTarget,
144
+ service: z.string(),
145
+ tail: z.int().optional(),
146
+ follow: z.boolean(),
147
+ });
148
+ export const LogsResult = z.object({ ended: z.string().optional() });
149
+ export const ExecParams = z.object({
150
+ target: EnvironmentTarget,
151
+ service: z.string(),
152
+ command: z.array(z.string()),
153
+ });
154
+ export const ExecResult = z.object({ exitCode: z.int() });
155
+ export const ServiceRun = z.object({
156
+ service: z.string(),
157
+ image: z.string(),
158
+ digest: z.string(),
159
+ platformDigest: z.string(),
160
+ skipped: z.boolean(),
161
+ startedAt: isoDate.optional(),
162
+ readyAt: isoDate.optional(),
163
+ readiness: Readiness.optional(),
164
+ });
165
+ export const RunRecord = z.object({
166
+ id: z.string(),
167
+ environment: z.string(),
168
+ purpose: RunPurpose,
169
+ startedAt: isoDate,
170
+ finishedAt: isoDate,
171
+ error: NapboxErrorBody.optional(),
172
+ definitionDigest: z.string(),
173
+ egress: EgressMode,
174
+ ports: z.array(PortInfo),
175
+ services: z.array(ServiceRun),
176
+ });
177
+ export const RunsResult = z.object({ runs: z.array(RunRecord) });
178
+ export const SubscribeParams = z.object({
179
+ owner: z.string().optional(),
180
+ names: z.array(z.string()).optional(),
181
+ });
182
+ export const EnvironmentEvent = z.object({
183
+ at: isoDate,
184
+ environment: z.string(),
185
+ owner: z.string().optional(),
186
+ type: EventKind,
187
+ purpose: RunPurpose.optional(),
188
+ runId: z.string().optional(),
189
+ error: NapboxErrorBody.optional(),
190
+ sleepReason: SleepReason.optional(),
191
+ reason: z.string().optional(),
192
+ service: z.string().optional(),
193
+ exitCode: z.int().optional(),
194
+ restart: ServiceExitRestart.optional(),
195
+ });
196
+ /** subscribe 正常结束时的结果(调用方先断开写端才会有,这个客户端不这样断开) */
197
+ export const EmptyResult = z.object({});
198
+ export const Request = z.object({
199
+ id: z.int(),
200
+ method: z.string(),
201
+ params: z.unknown().optional(),
202
+ });
203
+ export const Response = z.object({
204
+ id: z.int(),
205
+ result: z.unknown().optional(),
206
+ error: NapboxErrorBody.optional(),
207
+ output: StreamOutput.optional(),
208
+ subscribed: z.boolean().optional(),
209
+ event: EnvironmentEvent.optional(),
210
+ });
@@ -0,0 +1,79 @@
1
+ // 由 scripts/plugins.ts 从 scripts/server-launcher.mjs 生成。
2
+ // 启动固定版本的 napbox MCP 服务,就在这个进程里。这个版本只在第一次安装一次,装进 napbox 的缓存目录,
3
+ // 之后不需要 npm 和网络。npm 在缓存目录里运行,项目自己的 npm 设置和依赖不会影响安装结果。
4
+ import { spawn } from "node:child_process";
5
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import path from "node:path";
8
+ import { pathToFileURL } from "node:url";
9
+
10
+ const VERSION = "0.1.1";
11
+
12
+ // 和 src/agent/locations.ts 的 cacheDirectory 同一个目录
13
+ const versions = path.join(homedir(), "Library/Caches/napbox/versions");
14
+ const home = path.join(versions, VERSION);
15
+ const installed = path.join(home, "node_modules/napbox");
16
+ const manifest = path.join(installed, "package.json");
17
+
18
+ // 运行这个脚本的 Node 自带的 npm,没有就用 PATH 上的
19
+ const npm = () => {
20
+ const directory = path.dirname(process.execPath);
21
+ for (const cli of [
22
+ path.join(directory, "node_modules/npm/bin/npm-cli.js"),
23
+ path.join(directory, "../lib/node_modules/npm/bin/npm-cli.js"),
24
+ ])
25
+ if (existsSync(cli)) return [process.execPath, [cli]];
26
+ return ["npm", []];
27
+ };
28
+
29
+ // 装进单独的目录,完整了才改名到位:不会从装了一半的地方启动,两个同时启动也只留先改名的那个
30
+ async function install() {
31
+ mkdirSync(versions, { recursive: true, mode: 0o700 });
32
+ const staging = mkdtempSync(path.join(versions, `.${VERSION}-`));
33
+ process.stderr.write(`napbox:用 npm 安装 napbox@${VERSION}\n`);
34
+ const [command, prefix] = npm();
35
+ const args = [
36
+ ...prefix,
37
+ "install",
38
+ `napbox@${VERSION}`,
39
+ "--prefix",
40
+ staging,
41
+ "--prefer-online",
42
+ "--omit=dev",
43
+ "--no-audit",
44
+ "--no-fund",
45
+ "--no-update-notifier",
46
+ "--loglevel=error",
47
+ ];
48
+ await new Promise((resolve, reject) => {
49
+ const child = spawn(command, args, { cwd: staging, stdio: ["ignore", "pipe", "pipe"] });
50
+ let output = "";
51
+ child.stdout.on("data", (data) => (output += data));
52
+ child.stderr.on("data", (data) => (output += data));
53
+ child.on("error", reject);
54
+ child.on("close", (code) =>
55
+ code === 0 ? resolve() : reject(new Error(`npm 退出码 ${code}\n${output.trim()}`)),
56
+ );
57
+ }).catch((error) => {
58
+ rmSync(staging, { recursive: true, force: true });
59
+ throw error;
60
+ });
61
+ try {
62
+ renameSync(staging, home);
63
+ } catch (error) {
64
+ rmSync(staging, { recursive: true, force: true });
65
+ if (!existsSync(manifest)) throw error;
66
+ }
67
+ }
68
+
69
+ try {
70
+ if (!existsSync(manifest)) await install();
71
+ // 一周没启动过的版本会从缓存里删掉(src/agent/cache.ts)
72
+ writeFileSync(path.join(home, "last-used"), "");
73
+ const entry = path.join(installed, JSON.parse(readFileSync(manifest, "utf8")).bin.napbox);
74
+ process.argv = [process.argv[0], entry, "mcp"];
75
+ await import(pathToFileURL(entry).href);
76
+ } catch (error) {
77
+ process.stderr.write(`napbox:启动不了 ${VERSION}:${error?.message ?? error}\n`);
78
+ process.exit(1);
79
+ }
package/native/napbox ADDED
Binary file
Binary file
package/native/napboxd ADDED
@@ -0,0 +1,4 @@
1
+ [diffend] Oversized file quarantined before diffing.
2
+ name: package/native/napboxd
3
+ size: 48788320 bytes
4
+ sha256: 082a181fd142838ec8e0f60b475ccf5eeb569d56fb3ce09f55b90944f980e1ad
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "napbox",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Local dev environments for coding agents on Apple silicon Macs, without Docker: an isolated set of compose services for every git worktree.",
5
+ "type": "module",
6
+ "bin": {
7
+ "napbox": "dist/cli.js"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "native",
18
+ "NOTICE",
19
+ "THIRD_PARTY_NOTICES.txt",
20
+ "README.zh-CN.md"
21
+ ],
22
+ "license": "Apache-2.0",
23
+ "engines": {
24
+ "node": ">=22.22"
25
+ },
26
+ "os": [
27
+ "darwin"
28
+ ],
29
+ "cpu": [
30
+ "arm64"
31
+ ],
32
+ "dependencies": {
33
+ "zod": "4.4.3"
34
+ },
35
+ "homepage": "https://github.com/coffeecoproject/napbox",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/coffeecoproject/napbox.git"
39
+ },
40
+ "keywords": [
41
+ "docker-compose",
42
+ "worktree",
43
+ "dev-environment",
44
+ "mcp",
45
+ "claude-code",
46
+ "macos"
47
+ ]
48
+ }