@nema-project/nema 0.0.0-stage → 0.1.4

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.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunks/chunk-HEIQINX3.js +825 -0
  3. package/dist/chunks/chunk-WT3XUFWU.js +86 -0
  4. package/dist/claude/adapter.d.ts +19 -0
  5. package/dist/claude/adapter.js +258 -0
  6. package/dist/claude/events.d.ts +29 -0
  7. package/dist/claude/events.js +75 -0
  8. package/dist/claude/launch.d.ts +22 -0
  9. package/dist/claude/launch.js +80 -0
  10. package/dist/claude/token.d.ts +9 -0
  11. package/dist/claude/token.js +87 -0
  12. package/dist/codex/adapter.d.ts +17 -0
  13. package/dist/codex/adapter.js +151 -0
  14. package/dist/codex/events.d.ts +16 -0
  15. package/dist/codex/events.js +47 -0
  16. package/dist/codex/isolation.d.ts +33 -0
  17. package/dist/codex/isolation.js +115 -0
  18. package/dist/config.d.ts +29 -0
  19. package/dist/config.js +75 -0
  20. package/dist/configCommands.d.ts +13 -0
  21. package/dist/configCommands.js +69 -0
  22. package/dist/daemon/cleanup.d.ts +5 -0
  23. package/dist/daemon/cleanup.js +49 -0
  24. package/dist/daemon/connection.d.ts +22 -0
  25. package/dist/daemon/connection.js +166 -0
  26. package/dist/daemon/rpc.d.ts +14 -0
  27. package/dist/daemon/rpc.js +20 -0
  28. package/dist/daemon/runner.d.ts +34 -0
  29. package/dist/daemon/runner.js +94 -0
  30. package/dist/daemon/setup.d.ts +39 -0
  31. package/dist/daemon/setup.js +95 -0
  32. package/dist/direct/adapter.d.ts +30 -0
  33. package/dist/direct/adapter.js +190 -0
  34. package/dist/direct/authCommands.d.ts +54 -0
  35. package/dist/direct/authCommands.js +268 -0
  36. package/dist/direct/authStore.d.ts +120 -0
  37. package/dist/direct/authStore.js +351 -0
  38. package/dist/direct/byok.d.ts +27 -0
  39. package/dist/direct/byok.js +67 -0
  40. package/dist/direct/codexLogin.d.ts +34 -0
  41. package/dist/direct/codexLogin.js +93 -0
  42. package/dist/direct/jwt.d.ts +4 -0
  43. package/dist/direct/jwt.js +22 -0
  44. package/dist/direct/lock.d.ts +16 -0
  45. package/dist/direct/lock.js +134 -0
  46. package/dist/direct/loop.d.ts +33 -0
  47. package/dist/direct/loop.js +110 -0
  48. package/dist/direct/siwc.d.ts +63 -0
  49. package/dist/direct/siwc.js +403 -0
  50. package/dist/direct/tools.d.ts +17 -0
  51. package/dist/direct/tools.js +104 -0
  52. package/dist/direct/transport.d.ts +177 -0
  53. package/dist/direct/transport.js +437 -0
  54. package/dist/doctor.d.ts +28 -0
  55. package/dist/doctor.js +178 -0
  56. package/dist/extensions/handlers.d.ts +2 -0
  57. package/dist/extensions/handlers.js +10 -0
  58. package/dist/extensions/store.d.ts +11 -0
  59. package/dist/extensions/store.js +132 -0
  60. package/dist/github/client.d.ts +47 -0
  61. package/dist/github/client.js +383 -0
  62. package/dist/github/gh.d.ts +56 -0
  63. package/dist/github/gh.js +385 -0
  64. package/dist/index.d.ts +2 -0
  65. package/dist/index.js +144 -0
  66. package/dist/localSettings.d.ts +8 -0
  67. package/dist/localSettings.js +26 -0
  68. package/dist/mcp/server.d.ts +5 -0
  69. package/dist/mcp/server.js +83 -0
  70. package/dist/ownedProcess.d.ts +39 -0
  71. package/dist/ownedProcess.js +229 -0
  72. package/dist/platform.d.ts +10 -0
  73. package/dist/platform.js +60 -0
  74. package/dist/report/commands.d.ts +41 -0
  75. package/dist/report/commands.js +159 -0
  76. package/dist/report/desktop.d.ts +3 -0
  77. package/dist/report/desktop.js +123 -0
  78. package/dist/report/git.d.ts +5 -0
  79. package/dist/report/git.js +42 -0
  80. package/dist/report/install.d.ts +22 -0
  81. package/dist/report/install.js +183 -0
  82. package/dist/report/send.d.ts +35 -0
  83. package/dist/report/send.js +208 -0
  84. package/dist/report/token.d.ts +5 -0
  85. package/dist/report/token.js +86 -0
  86. package/dist/repos/git.d.ts +48 -0
  87. package/dist/repos/git.js +370 -0
  88. package/dist/repos/graphify.d.ts +32 -0
  89. package/dist/repos/graphify.js +292 -0
  90. package/dist/repos/handlers.d.ts +20 -0
  91. package/dist/repos/handlers.js +255 -0
  92. package/dist/repos/indexState.d.ts +10 -0
  93. package/dist/repos/indexState.js +45 -0
  94. package/dist/repos/pickDirectory.d.ts +11 -0
  95. package/dist/repos/pickDirectory.js +140 -0
  96. package/dist/repos/reportCheck.d.ts +13 -0
  97. package/dist/repos/reportCheck.js +268 -0
  98. package/dist/repos/watcher.d.ts +64 -0
  99. package/dist/repos/watcher.js +202 -0
  100. package/dist/room/client.d.ts +26 -0
  101. package/dist/room/client.js +76 -0
  102. package/dist/secrets/file.d.ts +12 -0
  103. package/dist/secrets/file.js +89 -0
  104. package/dist/secrets/github.d.ts +10 -0
  105. package/dist/secrets/github.js +29 -0
  106. package/dist/secrets/permissions.d.ts +7 -0
  107. package/dist/secrets/permissions.js +86 -0
  108. package/dist/systemGit.d.ts +14 -0
  109. package/dist/systemGit.js +75 -0
  110. package/dist/wp/commands.d.ts +15 -0
  111. package/dist/wp/commands.js +103 -0
  112. package/package.json +26 -4
  113. package/src/report/SKILL.md +21 -0
  114. package/README.md +0 -3
@@ -0,0 +1,86 @@
1
+ // ../../../packages/shared/dist/ownedHelper.js
2
+ import { execFile } from "node:child_process";
3
+ async function runHelperFile(command, args, options = {}, scope) {
4
+ if (!scope) {
5
+ return new Promise((resolve) => {
6
+ execFile(command, args, options, (error2, stdout2, stderr2) => {
7
+ const out2 = Buffer.isBuffer(stdout2) ? stdout2 : Buffer.from(stdout2 ?? "");
8
+ const err2 = Buffer.isBuffer(stderr2) ? stderr2 : Buffer.from(stderr2 ?? "");
9
+ const childError = error2;
10
+ resolve({
11
+ error: error2 ?? null,
12
+ stdout: out2,
13
+ stderr: err2,
14
+ code: childError && typeof childError.code === "number" ? childError.code : childError ? null : 0,
15
+ signal: childError?.signal ?? null,
16
+ timedOut: Boolean(childError?.killed)
17
+ });
18
+ });
19
+ });
20
+ }
21
+ const maxBuffer = options.maxBuffer ?? 1024 * 1024;
22
+ let child;
23
+ try {
24
+ child = await scope.spawn(command, args, {
25
+ cwd: options.cwd,
26
+ env: options.env,
27
+ stdio: [options.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
28
+ windowsHide: true,
29
+ signal: options.signal
30
+ });
31
+ } catch (cause) {
32
+ const error2 = cause instanceof Error ? cause : new Error(String(cause));
33
+ const code = cause && typeof cause === "object" && "code" in cause ? cause.code : void 0;
34
+ return {
35
+ error: error2,
36
+ stdout: Buffer.alloc(0),
37
+ stderr: Buffer.alloc(0),
38
+ code: typeof code === "number" ? code : null,
39
+ signal: null,
40
+ timedOut: false
41
+ };
42
+ }
43
+ const stdout = [];
44
+ const stderr = [];
45
+ let outSize = 0;
46
+ let errSize = 0;
47
+ let overflow = false;
48
+ let timedOut = false;
49
+ const collect = (target, chunk, isOut) => {
50
+ if (overflow)
51
+ return;
52
+ if (isOut)
53
+ outSize += chunk.length;
54
+ else
55
+ errSize += chunk.length;
56
+ if (outSize + errSize > maxBuffer) {
57
+ overflow = true;
58
+ child.kill("SIGTERM");
59
+ return;
60
+ }
61
+ target.push(Buffer.from(chunk));
62
+ };
63
+ child.stdout?.on("data", (chunk) => collect(stdout, chunk, true));
64
+ child.stderr?.on("data", (chunk) => collect(stderr, chunk, false));
65
+ if (options.input !== void 0 && child.stdin) {
66
+ child.stdin.on("error", () => void 0);
67
+ child.stdin.end(options.input);
68
+ }
69
+ let timer;
70
+ if (options.timeout && options.timeout > 0)
71
+ timer = setTimeout(() => {
72
+ timedOut = true;
73
+ child.kill("SIGTERM");
74
+ }, options.timeout);
75
+ const closed = await child.closed;
76
+ if (timer)
77
+ clearTimeout(timer);
78
+ const out = Buffer.concat(stdout);
79
+ const err = Buffer.concat(stderr);
80
+ const error = overflow ? Object.assign(new Error(`maxBuffer exceeded: ${maxBuffer} bytes`), { code: "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" }) : timedOut ? Object.assign(new Error("Command timed out"), { code: "ETIMEDOUT", killed: true }) : closed.code === 0 ? null : Object.assign(new Error(err.toString("utf8").trim() || `${command} exited ${closed.code ?? closed.signal ?? "unknown"}`), { code: closed.code, signal: closed.signal });
81
+ return { error, stdout: out, stderr: err, code: closed.code, signal: closed.signal, timedOut };
82
+ }
83
+
84
+ export {
85
+ runHelperFile
86
+ };
@@ -0,0 +1,19 @@
1
+ import type { TaskAssign, TaskEvent } from "@nema/shared";
2
+ import type { AgentAdapter, AgentResult } from "../daemon/runner.js";
3
+ import type { OwnedHelperScope } from "@nema/shared/ownedHelper";
4
+ export type ClaudeCodeAdapterOptions = {
5
+ nemaHome: string;
6
+ cliEntry: string;
7
+ claudeBin?: string;
8
+ parentEnv?: NodeJS.ProcessEnv;
9
+ execPath?: string;
10
+ timeoutMs?: number;
11
+ ownedHelpers?: OwnedHelperScope;
12
+ };
13
+ export declare class ClaudeCodeAdapter implements AgentAdapter {
14
+ private readonly opts;
15
+ readonly name = "claude-code";
16
+ constructor(opts: ClaudeCodeAdapterOptions);
17
+ run(task: TaskAssign, onEvent: (e: TaskEvent) => void, signal?: AbortSignal): Promise<AgentResult>;
18
+ private execute;
19
+ }
@@ -0,0 +1,258 @@
1
+ // claude/adapter.js
2
+ import { spawn, spawnSync } from "node:child_process";
3
+ import { rmSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { commandSpec } from "../platform.js";
6
+ import { configErrorMessage, loadConfig } from "../config.js";
7
+ import { parseClaudeLine, roomToolsOk } from "./events.js";
8
+ import { claudeAllowedToolNames } from "../direct/tools.js";
9
+ import { prepareClaudeLaunch } from "./launch.js";
10
+ import { readClaudeToken } from "./token.js";
11
+ var NOT_FOUND = "\u627E\u4E0D\u5230 claude\u3002\u8BF7\u5148\u5B89\u88C5 Claude Code \u5E76\u628A\u5B83\u653E\u5230 PATH \u4E0A\u3002";
12
+ var START_FAILED = "\u65E0\u6CD5\u542F\u52A8 Claude\u3002";
13
+ var toolLimitMessage = (n) => `Claude \u5DE5\u5177\u8C03\u7528\u8D85\u8FC7 ${n} \u6B21\uFF0C\u4EFB\u52A1\u5DF2\u4E2D\u6B62\u3002`;
14
+ var timedOutMessage = (minutes) => `Claude \u8FD0\u884C\u8D85\u8FC7 ${minutes} \u5206\u949F\uFF0C\u4EFB\u52A1\u5DF2\u4E2D\u6B62\u3002`;
15
+ var NO_INIT = "Claude \u6CA1\u6709\u7ED9\u51FA\u5DE5\u5177\u9762\u5C31\u9000\u51FA\u4E86\u3002";
16
+ var NO_REPLY = "Claude \u6CA1\u6709\u8FD4\u56DE\u56DE\u590D\u3002";
17
+ var RUN_FAILED = "Claude \u8FD0\u884C\u5931\u8D25\uFF0C\u4EFB\u52A1\u5DF2\u4E2D\u6B62\u3002";
18
+ var LOGIN_FAILED = "Claude \u767B\u5F55\u5931\u8D25\u3002\u8BF7\u91CD\u65B0\u8FD0\u884C claude setup-token\uFF0C\u518D\u628A\u4EE4\u724C\u4ECE\u6807\u51C6\u8F93\u5165\u4EA4\u7ED9 nema auth claude\u3002";
19
+ var MAX_TOOL_CALLS = 60;
20
+ var STDERR_CAP = 8 * 1024;
21
+ var AUTH_STDERR = /authentication|not logged in|login|oauth|setup-token|401/i;
22
+ function message(e) {
23
+ return e instanceof Error ? e.message : String(e);
24
+ }
25
+ function userMessage(prompt) {
26
+ return `${JSON.stringify({
27
+ type: "user",
28
+ message: { role: "user", content: [{ type: "text", text: prompt }] }
29
+ })}
30
+ `;
31
+ }
32
+ var ClaudeCodeAdapter = class {
33
+ opts;
34
+ name = "claude-code";
35
+ constructor(opts) {
36
+ this.opts = opts;
37
+ }
38
+ async run(task, onEvent, signal) {
39
+ if (signal?.aborted)
40
+ return { ok: false, error: "Claude \u8FD0\u884C\u5DF2\u4E2D\u6B62\u3002" };
41
+ let token;
42
+ try {
43
+ token = readClaudeToken(this.opts.nemaHome);
44
+ } catch (e) {
45
+ return { ok: false, error: message(e) };
46
+ }
47
+ let model;
48
+ try {
49
+ model = loadConfig(this.opts.nemaHome).model;
50
+ } catch (e) {
51
+ return { ok: false, error: configErrorMessage(e) };
52
+ }
53
+ let launch;
54
+ try {
55
+ launch = prepareClaudeLaunch({
56
+ nemaHome: this.opts.nemaHome,
57
+ taskId: task.taskId,
58
+ hubUrl: task.hubUrl,
59
+ runToken: task.runToken,
60
+ token,
61
+ model,
62
+ cliEntry: this.opts.cliEntry,
63
+ execPath: this.opts.execPath ?? process.execPath,
64
+ parentEnv: this.opts.parentEnv ?? process.env,
65
+ pluginTools: task.pluginTools,
66
+ ...task.limits ? { maxTurns: task.limits.maxModelCalls } : {}
67
+ });
68
+ } catch (e) {
69
+ return { ok: false, error: message(e) };
70
+ }
71
+ try {
72
+ return await this.execute(task, launch, onEvent, signal);
73
+ } finally {
74
+ rmSync(launch.runDir, { recursive: true, force: true });
75
+ }
76
+ }
77
+ async execute(task, launch, onEvent, signal) {
78
+ const bin = this.opts.claudeBin ?? "claude";
79
+ const timeoutMs = task.limits?.timeoutMs ?? this.opts.timeoutMs ?? 6e5;
80
+ const maxToolCalls = task.limits?.maxToolCalls ?? MAX_TOOL_CALLS;
81
+ const minutes = task.limits ? Math.round(task.limits.timeoutMs / 6e4) : 10;
82
+ const allowed = new Set(claudeAllowedToolNames(task.pluginTools));
83
+ const command = commandSpec(bin, launch.args, launch.env);
84
+ let child;
85
+ try {
86
+ child = this.opts.ownedHelpers ? await this.opts.ownedHelpers.spawn(command.bin, command.args, {
87
+ cwd: path.join(launch.runDir, "work"),
88
+ env: launch.env,
89
+ stdio: ["pipe", "pipe", "pipe"],
90
+ windowsHide: true,
91
+ signal
92
+ }) : spawn(command.bin, command.args, {
93
+ cwd: path.join(launch.runDir, "work"),
94
+ env: launch.env,
95
+ stdio: ["pipe", "pipe", "pipe"],
96
+ detached: true,
97
+ windowsHide: true
98
+ });
99
+ } catch (error) {
100
+ if (error.name === "AbortError")
101
+ return { ok: false, error: "Claude \u8FD0\u884C\u5DF2\u4E2D\u6B62\u3002" };
102
+ return { ok: false, error: error.code === "ENOENT" ? NOT_FOUND : START_FAILED };
103
+ }
104
+ const owned = "closed" in child;
105
+ const closedPromise = owned ? child.closed : new Promise((resolveClosed) => {
106
+ child.once("close", (code, closeSignal) => resolveClosed({ code, signal: closeSignal }));
107
+ });
108
+ return new Promise((resolve) => {
109
+ let settled = false;
110
+ let closed = false;
111
+ let initOk = false;
112
+ let initSeen = false;
113
+ let toolCount = 0;
114
+ let buf = "";
115
+ let stderrBuf = "";
116
+ const killTree = () => {
117
+ if (owned) {
118
+ child.kill("SIGKILL");
119
+ return;
120
+ }
121
+ const pid = child.pid;
122
+ if (pid === void 0 || pid <= 0 || pid === process.pid)
123
+ return;
124
+ if (process.platform === "win32") {
125
+ spawnSync("taskkill.exe", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
126
+ return;
127
+ }
128
+ try {
129
+ process.kill(-pid, "SIGKILL");
130
+ } catch (e) {
131
+ const code = e.code;
132
+ if (code !== "ESRCH" && code !== "EPERM")
133
+ throw e;
134
+ }
135
+ };
136
+ const finish = (result) => {
137
+ if (settled)
138
+ return;
139
+ settled = true;
140
+ clearTimeout(timer);
141
+ signal?.removeEventListener("abort", cancel);
142
+ killTree();
143
+ if (closed)
144
+ return resolve(result);
145
+ void closedPromise.then(() => resolve(result));
146
+ };
147
+ const cancel = () => finish({ ok: false, error: "Claude \u8FD0\u884C\u5DF2\u4E2D\u6B62\u3002" });
148
+ const handle = (line) => {
149
+ if (settled)
150
+ return;
151
+ const ev = parseClaudeLine(line);
152
+ if (ev.kind === "ignore")
153
+ return;
154
+ if (ev.kind === "tools") {
155
+ const outside = ev.names.find((name) => !allowed.has(name));
156
+ if (outside !== void 0) {
157
+ return finish({ ok: false, error: `Claude \u8C03\u7528\u4E86 Room \u4EE5\u5916\u7684\u5DE5\u5177 ${outside}\uFF0C\u4EFB\u52A1\u5DF2\u4E2D\u6B62\u3002` });
158
+ }
159
+ }
160
+ if (ev.kind === "failure")
161
+ return finish({ ok: false, error: RUN_FAILED });
162
+ if (ev.kind === "init") {
163
+ if (initSeen)
164
+ return;
165
+ initSeen = true;
166
+ const check = roomToolsOk(ev.tools, ev.mcpServers, allowed);
167
+ if (!check.ok)
168
+ return finish({ ok: false, error: check.error });
169
+ initOk = true;
170
+ return;
171
+ }
172
+ if (!initOk)
173
+ return;
174
+ if (ev.kind === "text")
175
+ return onEvent({ type: "progress", text: ev.text });
176
+ if (ev.kind === "tools") {
177
+ for (const name of ev.names) {
178
+ toolCount += 1;
179
+ if (toolCount > maxToolCalls)
180
+ return finish({ ok: false, error: toolLimitMessage(maxToolCalls) });
181
+ onEvent({ type: "tool_call", text: name });
182
+ }
183
+ return;
184
+ }
185
+ finish({ ok: true, reply: ev.text });
186
+ };
187
+ const take = (chunk) => {
188
+ if (settled)
189
+ return;
190
+ buf += chunk;
191
+ let nl;
192
+ while (!settled && (nl = buf.indexOf("\n")) >= 0) {
193
+ const line = buf.slice(0, nl);
194
+ buf = buf.slice(nl + 1);
195
+ handle(line);
196
+ }
197
+ };
198
+ const timer = setTimeout(() => finish({ ok: false, error: timedOutMessage(minutes) }), timeoutMs);
199
+ signal?.addEventListener("abort", cancel, { once: true });
200
+ if (signal?.aborted)
201
+ cancel();
202
+ child.stdout?.setEncoding("utf8");
203
+ child.stderr?.setEncoding("utf8");
204
+ if (!owned) {
205
+ child.on("spawn", () => {
206
+ if (settled)
207
+ killTree();
208
+ });
209
+ child.on("error", (err) => {
210
+ if (settled)
211
+ return;
212
+ settled = true;
213
+ clearTimeout(timer);
214
+ signal?.removeEventListener("abort", cancel);
215
+ killTree();
216
+ resolve({ ok: false, error: err.code === "ENOENT" ? NOT_FOUND : START_FAILED });
217
+ });
218
+ }
219
+ child.stdout?.on("data", take);
220
+ child.stderr?.on("data", (chunk) => {
221
+ const room = STDERR_CAP - stderrBuf.length;
222
+ if (room <= 0)
223
+ return;
224
+ stderrBuf += chunk.length > room ? chunk.slice(0, room) : chunk;
225
+ });
226
+ child.stdin?.on("error", () => {
227
+ });
228
+ const onClose = () => {
229
+ closed = true;
230
+ killTree();
231
+ if (settled)
232
+ return;
233
+ if (buf.trim())
234
+ handle(buf);
235
+ buf = "";
236
+ if (!settled) {
237
+ let error = NO_INIT;
238
+ if (initOk)
239
+ error = NO_REPLY;
240
+ else if (AUTH_STDERR.test(stderrBuf))
241
+ error = LOGIN_FAILED;
242
+ finish({ ok: false, error });
243
+ }
244
+ };
245
+ if (owned)
246
+ void closedPromise.then(onClose);
247
+ else
248
+ child.once("close", onClose);
249
+ try {
250
+ child.stdin?.end(userMessage(task.prompt));
251
+ } catch {
252
+ }
253
+ });
254
+ }
255
+ };
256
+ export {
257
+ ClaudeCodeAdapter
258
+ };
@@ -0,0 +1,29 @@
1
+ export type McpServer = {
2
+ name: string;
3
+ status: string | null;
4
+ };
5
+ export type ClaudeLine = {
6
+ kind: "init";
7
+ tools: string[] | null;
8
+ mcpServers: McpServer[] | null;
9
+ } | {
10
+ kind: "text";
11
+ text: string;
12
+ } | {
13
+ kind: "tools";
14
+ names: string[];
15
+ } | {
16
+ kind: "result";
17
+ text: string;
18
+ } | {
19
+ kind: "failure";
20
+ } | {
21
+ kind: "ignore";
22
+ };
23
+ export declare function parseClaudeLine(line: string): ClaudeLine;
24
+ export declare function roomToolsOk(tools: string[] | null, mcpServers: McpServer[] | null, allowed?: ReadonlySet<string>): {
25
+ ok: true;
26
+ } | {
27
+ ok: false;
28
+ error: string;
29
+ };
@@ -0,0 +1,75 @@
1
+ // claude/events.js
2
+ import { CLAUDE_ROOM_TOOLS } from "./launch.js";
3
+ var FAILURE = "Claude \u7684\u5DE5\u5177\u9762\u65E0\u6CD5\u786E\u8BA4\u4E3A\u53EA\u6709 Room\uFF1A";
4
+ var roomOnly = new Set(CLAUDE_ROOM_TOOLS);
5
+ function isRecord(value) {
6
+ return value !== null && typeof value === "object" && !Array.isArray(value);
7
+ }
8
+ function strings(value) {
9
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
10
+ return null;
11
+ return value;
12
+ }
13
+ function serverList(value) {
14
+ if (!Array.isArray(value))
15
+ return null;
16
+ const servers = [];
17
+ for (const item of value) {
18
+ if (!isRecord(item) || typeof item.name !== "string")
19
+ return null;
20
+ servers.push({ name: item.name, status: typeof item.status === "string" ? item.status : null });
21
+ }
22
+ return servers;
23
+ }
24
+ function parseClaudeLine(line) {
25
+ let value;
26
+ try {
27
+ value = JSON.parse(line);
28
+ } catch {
29
+ return { kind: "ignore" };
30
+ }
31
+ if (!isRecord(value))
32
+ return { kind: "ignore" };
33
+ if (value.type === "system" && value.subtype === "init") {
34
+ return { kind: "init", tools: strings(value.tools), mcpServers: serverList(value.mcp_servers) };
35
+ }
36
+ if (value.type === "assistant" && isRecord(value.message) && Array.isArray(value.message.content)) {
37
+ let text;
38
+ const names = [];
39
+ for (const block of value.message.content) {
40
+ if (!isRecord(block))
41
+ continue;
42
+ if (block.type === "tool_use" && typeof block.name === "string") {
43
+ names.push(block.name);
44
+ continue;
45
+ }
46
+ if (text === void 0 && block.type === "text" && typeof block.text === "string")
47
+ text = block.text;
48
+ }
49
+ if (names.length > 0)
50
+ return { kind: "tools", names };
51
+ if (text !== void 0)
52
+ return { kind: "text", text };
53
+ }
54
+ if (value.type === "result") {
55
+ const subtype = value.subtype;
56
+ if (value.is_error === true || typeof subtype === "string" && subtype.startsWith("error_")) {
57
+ return { kind: "failure" };
58
+ }
59
+ if (typeof value.result === "string")
60
+ return { kind: "result", text: value.result };
61
+ }
62
+ return { kind: "ignore" };
63
+ }
64
+ function roomToolsOk(tools, mcpServers, allowed = roomOnly) {
65
+ const toolsOk = tools !== null && tools.every((name) => allowed.has(name)) && CLAUDE_ROOM_TOOLS.every((name) => tools.includes(name));
66
+ const room = mcpServers?.length === 1 ? mcpServers[0] : void 0;
67
+ const mcpOk = room?.name === "room" && room.status === "connected";
68
+ if (toolsOk && mcpOk)
69
+ return { ok: true };
70
+ return { ok: false, error: `${FAILURE}${JSON.stringify({ tools, mcpServers })}` };
71
+ }
72
+ export {
73
+ parseClaudeLine,
74
+ roomToolsOk
75
+ };
@@ -0,0 +1,22 @@
1
+ import type { PluginToolSpec } from "@nema/shared";
2
+ export declare const CLAUDE_ROOM_TOOLS: readonly ["mcp__room__list_entries", "mcp__room__read_entry", "mcp__room__upsert_entry", "mcp__room__open_thread"];
3
+ export type ClaudeLaunch = {
4
+ args: string[];
5
+ env: NodeJS.ProcessEnv;
6
+ runDir: string;
7
+ mcpPath: string;
8
+ };
9
+ export declare function prepareClaudeLaunch(o: {
10
+ nemaHome: string;
11
+ taskId: string;
12
+ hubUrl: string;
13
+ runToken: string;
14
+ token: string;
15
+ model?: string;
16
+ cliEntry: string;
17
+ execPath: string;
18
+ parentEnv: NodeJS.ProcessEnv;
19
+ pluginTools?: PluginToolSpec[];
20
+ /** `--max-turns`; defaults to 24. */
21
+ maxTurns?: number;
22
+ }): ClaudeLaunch;
@@ -0,0 +1,80 @@
1
+ // claude/launch.js
2
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import { claudeAllowedToolNames } from "../direct/tools.js";
6
+ import { windowsRuntimeEnv } from "../platform.js";
7
+ var CLAUDE_ROOM_TOOLS = [
8
+ "mcp__room__list_entries",
9
+ "mcp__room__read_entry",
10
+ "mcp__room__upsert_entry",
11
+ "mcp__room__open_thread"
12
+ ];
13
+ function prepareClaudeLaunch(o) {
14
+ if (!/^[A-Za-z0-9_-]+$/.test(o.taskId))
15
+ throw new Error("unsafe task id");
16
+ const runDir = path.join(o.nemaHome, "run", o.taskId);
17
+ const configDir = path.join(runDir, "claude-config");
18
+ const workDir = path.join(runDir, "work");
19
+ const mcpPath = path.join(runDir, "mcp.json");
20
+ rmSync(runDir, { recursive: true, force: true });
21
+ mkdirSync(configDir, { recursive: true, mode: 448 });
22
+ mkdirSync(workDir);
23
+ writeFileSync(mcpPath, JSON.stringify({
24
+ mcpServers: {
25
+ room: {
26
+ command: o.execPath,
27
+ args: [o.cliEntry, "mcp"],
28
+ env: { NEMA_HUB_URL: o.hubUrl, NEMA_RUN_TOKEN: o.runToken }
29
+ }
30
+ }
31
+ }), { mode: 384 });
32
+ const env = windowsRuntimeEnv(o.parentEnv);
33
+ for (const key of ["PATH", "LANG", "LC_ALL", "TERM"]) {
34
+ if (o.parentEnv[key] !== void 0)
35
+ env[key] = o.parentEnv[key];
36
+ }
37
+ Object.assign(env, {
38
+ TMPDIR: tmpdir(),
39
+ HOME: configDir,
40
+ CLAUDE_CONFIG_DIR: configDir,
41
+ CLAUDE_CODE_OAUTH_TOKEN: o.token
42
+ });
43
+ if (process.platform === "win32") {
44
+ env.USERPROFILE = configDir;
45
+ env.APPDATA = path.join(configDir, "AppData", "Roaming");
46
+ env.LOCALAPPDATA = path.join(configDir, "AppData", "Local");
47
+ env.PATH ??= Object.entries(o.parentEnv).find(([key]) => key.toLowerCase() === "path")?.[1];
48
+ mkdirSync(env.APPDATA, { recursive: true });
49
+ mkdirSync(env.LOCALAPPDATA, { recursive: true });
50
+ }
51
+ const args = [
52
+ "-p",
53
+ "--output-format",
54
+ "stream-json",
55
+ "--input-format",
56
+ "stream-json",
57
+ "--verbose",
58
+ "--strict-mcp-config",
59
+ "--mcp-config",
60
+ mcpPath,
61
+ "--setting-sources",
62
+ "",
63
+ // --allowedTools only pre-approves. Without this, init lists Bash, Read, Write and friends.
64
+ "--tools",
65
+ "",
66
+ "--allowedTools",
67
+ claudeAllowedToolNames(o.pluginTools ?? []).join(","),
68
+ "--disallowedTools",
69
+ "AskUserQuestion",
70
+ "--max-turns",
71
+ String(o.maxTurns ?? 24)
72
+ ];
73
+ if (o.model !== void 0)
74
+ args.push("--model", o.model);
75
+ return { args, env, runDir, mcpPath };
76
+ }
77
+ export {
78
+ CLAUDE_ROOM_TOOLS,
79
+ prepareClaudeLaunch
80
+ };
@@ -0,0 +1,9 @@
1
+ import type { Out } from "../direct/authCommands.js";
2
+ export declare const claudeTokenPath: (nemaHome: string) => string;
3
+ export declare function saveClaudeToken(nemaHome: string, token: string): void;
4
+ export declare function readClaudeToken(nemaHome: string): string;
5
+ export declare function authClaude(o: {
6
+ nemaHome: string;
7
+ out: Out;
8
+ stdin: NodeJS.ReadableStream;
9
+ }): Promise<number>;
@@ -0,0 +1,87 @@
1
+ // claude/token.js
2
+ import { randomUUID } from "node:crypto";
3
+ import { closeSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { createInterface } from "node:readline";
6
+ import { openPrivateFile, privateModeMatches, protectPrivatePath } from "../secrets/permissions.js";
7
+ var INVALID_TOKEN = "token \u5FC5\u987B\u662F\u4E00\u884C\u975E\u7A7A\u6587\u672C";
8
+ var SYMLINK_TOKEN = "Claude Code token \u6587\u4EF6\u662F\u7B26\u53F7\u94FE\u63A5\uFF0C\u5DF2\u62D2\u7EDD\u3002";
9
+ var MISSING_TOKEN = "\u6CA1\u6709 Claude Code token\u3002\u8BF7\u8FD0\u884C claude setup-token\uFF0C\u518D\u628A\u4EE4\u724C\u4ECE\u6807\u51C6\u8F93\u5165\u4EA4\u7ED9 nema auth claude\u3002";
10
+ var TOKEN_MODE = "Claude Code token \u6587\u4EF6\u6743\u9650\u5FC5\u987B\u662F 0600";
11
+ var claudeTokenPath = (nemaHome) => path.join(nemaHome, "claude-code-token");
12
+ function saveClaudeToken(nemaHome, token) {
13
+ if (token === "" || token.includes("\n") || token.includes("\r"))
14
+ throw new Error(INVALID_TOKEN);
15
+ const file = claudeTokenPath(nemaHome);
16
+ try {
17
+ if (lstatSync(file).isSymbolicLink())
18
+ throw new Error(SYMLINK_TOKEN);
19
+ } catch (e) {
20
+ if (e.code !== "ENOENT")
21
+ throw e;
22
+ }
23
+ mkdirSync(nemaHome, { recursive: true, mode: 448 });
24
+ protectPrivatePath(nemaHome, 448);
25
+ const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
26
+ const fd = openSync(tmp, "wx", 384);
27
+ try {
28
+ try {
29
+ writeSync(fd, token);
30
+ fsyncSync(fd);
31
+ } finally {
32
+ closeSync(fd);
33
+ }
34
+ protectPrivatePath(tmp, 384);
35
+ renameSync(tmp, file);
36
+ } catch (e) {
37
+ try {
38
+ unlinkSync(tmp);
39
+ } catch {
40
+ }
41
+ throw e;
42
+ }
43
+ }
44
+ function readClaudeToken(nemaHome) {
45
+ const file = claudeTokenPath(nemaHome);
46
+ let fd;
47
+ try {
48
+ fd = openPrivateFile(file);
49
+ } catch (e) {
50
+ if (e.code === "ENOENT")
51
+ throw new Error(MISSING_TOKEN);
52
+ if (e.code === "ELOOP")
53
+ throw new Error(SYMLINK_TOKEN);
54
+ throw e;
55
+ }
56
+ try {
57
+ const st = fstatSync(fd);
58
+ if (!st.isFile() || !privateModeMatches(st))
59
+ throw new Error(TOKEN_MODE);
60
+ return readFileSync(fd, "utf8");
61
+ } finally {
62
+ closeSync(fd);
63
+ }
64
+ }
65
+ async function authClaude(o) {
66
+ const lines = createInterface({ input: o.stdin });
67
+ let token = "";
68
+ for await (const line of lines) {
69
+ token = line.trim();
70
+ break;
71
+ }
72
+ lines.close();
73
+ try {
74
+ saveClaudeToken(o.nemaHome, token);
75
+ o.out.log("\u5DF2\u4FDD\u5B58 Claude Code token");
76
+ return 0;
77
+ } catch (e) {
78
+ o.out.error(e instanceof Error ? e.message : String(e));
79
+ return 1;
80
+ }
81
+ }
82
+ export {
83
+ authClaude,
84
+ claudeTokenPath,
85
+ readClaudeToken,
86
+ saveClaudeToken
87
+ };
@@ -0,0 +1,17 @@
1
+ import type { TaskAssign, TaskEvent } from "@nema/shared";
2
+ import type { AgentAdapter, AgentResult } from "../daemon/runner.js";
3
+ export type CodexAdapterOptions = {
4
+ codexBin?: string;
5
+ nemaHome: string;
6
+ userCodexHome: string;
7
+ cliEntry: string;
8
+ timeoutMs?: number;
9
+ };
10
+ export declare const CONFIDENTIAL_REFUSAL = "codex-cli \u9002\u914D\u5668\u5728\u4FDD\u5BC6 workspace \u4E2D\u88AB\u7981\u7528\uFF08\u9694\u79BB\u672A\u901A\u8FC7\u9A8C\u8BC1\uFF09\u3002\u8BF7\u6539\u7528 codex-direct\uFF1Anema config set adapter codex-direct\uFF0C\u6216\u5728 workspace \u8BBE\u7F6E\u4E2D\u5173\u95ED\u4FDD\u5BC6\u3002";
11
+ export declare class CodexAdapter implements AgentAdapter {
12
+ private readonly opts;
13
+ readonly name = "codex-cli";
14
+ constructor(opts: CodexAdapterOptions);
15
+ run(task: TaskAssign, onEvent: (e: TaskEvent) => void): Promise<AgentResult>;
16
+ private exec;
17
+ }