@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,385 @@
1
+ import {
2
+ runHelperFile
3
+ } from "../chunks/chunk-WT3XUFWU.js";
4
+ import {
5
+ parseGhCommand
6
+ } from "../chunks/chunk-HEIQINX3.js";
7
+
8
+ // github/gh.js
9
+ import { commandSpec, windowsRuntimeEnv } from "../platform.js";
10
+ var OUTPUT_LIMIT = 65536;
11
+ var CALL_TIMEOUT_MS = 6e4;
12
+ var STATUS_TIMEOUT_MS = 1e4;
13
+ var STDERR_LIMIT = 2048;
14
+ var OWNER_OR_REPO = /^[A-Za-z0-9._-]+$/;
15
+ var QUERY = "query($owner:String!,$name:String!,$number:Int!,$first:Int!,$after:String){repository(owner:$owner,name:$name){pullRequest(number:$number){reviewThreads(first:$first,after:$after){pageInfo{endCursor hasNextPage} nodes{id isResolved isOutdated path line startLine originalLine originalStartLine comments(first:100){nodes{databaseId body author{login} createdAt url}}}}}}}";
16
+ var PARENT_QUERY = "query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){issue(number:$number){parent{number title url}}}}";
17
+ var ISSUE_QUERY = "query($owner:String!,$name:String!,$first:Int!,$after:String,$states:[IssueState!],$labels:[String!],$orderBy:IssueOrder,$filterBy:IssueFilters){repository(owner:$owner,name:$name){issues(first:$first,after:$after,states:$states,labels:$labels,orderBy:$orderBy,filterBy:$filterBy){pageInfo{endCursor hasNextPage} nodes{number title state url createdAt updatedAt labels(first:100){nodes{name}}}}}}";
18
+ function invalid(field) {
19
+ throw new Error(`GitHub: invalid ${field}`);
20
+ }
21
+ function requiredString(args, field, max = 2e3) {
22
+ const value = args[field];
23
+ if (typeof value !== "string" || value.length > max || /[\r\n\0]/.test(value))
24
+ return invalid(field);
25
+ return value;
26
+ }
27
+ function optionalString(args, field, max = 2e3) {
28
+ return args[field] === void 0 ? void 0 : requiredString(args, field, max);
29
+ }
30
+ function optionalNumber(args, field, max = 2147483647) {
31
+ const value = args[field];
32
+ if (value === void 0)
33
+ return void 0;
34
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > max)
35
+ return invalid(field);
36
+ return value;
37
+ }
38
+ function ownerRepo(args) {
39
+ const owner = requiredString(args, "owner", 100);
40
+ const repo = requiredString(args, "repo", 100);
41
+ if (!OWNER_OR_REPO.test(owner) || !OWNER_OR_REPO.test(repo) || repo === "." || repo === "..")
42
+ return invalid("repository");
43
+ return [owner, repo];
44
+ }
45
+ function pagination(args) {
46
+ const page = optionalNumber(args, "page");
47
+ const perPage = optionalNumber(args, "perPage", 100);
48
+ return { ...page === void 0 ? {} : { page: String(page) }, ...perPage === void 0 ? {} : { per_page: String(perPage) } };
49
+ }
50
+ function rejectPagination(args) {
51
+ if (args.page !== void 0 || args.perPage !== void 0)
52
+ invalid("pagination is unsupported for this method");
53
+ }
54
+ function fields(args, map) {
55
+ const out = {};
56
+ for (const [from, to] of Object.entries(map)) {
57
+ const value = optionalString(args, from);
58
+ if (value !== void 0)
59
+ out[to] = value;
60
+ }
61
+ return out;
62
+ }
63
+ function graph(query, variables) {
64
+ return { kind: "graphql", query, variables };
65
+ }
66
+ function allowKeys(args, keys) {
67
+ const allowed = new Set(keys);
68
+ for (const key of Object.keys(args))
69
+ if (!allowed.has(key))
70
+ invalid(key);
71
+ }
72
+ function githubApiRequest(tool, input) {
73
+ if (!input || typeof input !== "object" || Array.isArray(input))
74
+ throw new Error("GitHub: invalid arguments");
75
+ const args = input;
76
+ if (tool === "pull_request_read") {
77
+ allowKeys(args, ["owner", "repo", "pullNumber", "method", "page", "perPage", "after"]);
78
+ const [owner, repo] = ownerRepo(args);
79
+ const number = optionalNumber(args, "pullNumber");
80
+ if (!number)
81
+ return invalid("pullNumber");
82
+ const method = requiredString(args, "method", 40);
83
+ const endpoint = `repos/${owner}/${repo}/pulls/${number}`;
84
+ if (method !== "get_review_comments" && args.after !== void 0)
85
+ return invalid("after is supported only for get_review_comments");
86
+ switch (method) {
87
+ case "get":
88
+ rejectPagination(args);
89
+ return { kind: "rest", endpoint, fields: {} };
90
+ case "get_diff":
91
+ rejectPagination(args);
92
+ return { kind: "rest", endpoint, fields: {}, accept: "application/vnd.github.v3.diff" };
93
+ case "get_status":
94
+ rejectPagination(args);
95
+ return { kind: "pr_status", owner, repo, number, fields: {} };
96
+ case "get_check_runs":
97
+ return { kind: "check_runs", owner, repo, number, fields: pagination(args) };
98
+ case "get_files":
99
+ return { kind: "rest", endpoint: `${endpoint}/files`, fields: pagination(args) };
100
+ case "get_commits":
101
+ return { kind: "rest", endpoint: `${endpoint}/commits`, fields: pagination(args) };
102
+ case "get_reviews":
103
+ return { kind: "rest", endpoint: `${endpoint}/reviews`, fields: pagination(args) };
104
+ case "get_comments":
105
+ return { kind: "rest", endpoint: `repos/${owner}/${repo}/issues/${number}/comments`, fields: pagination(args) };
106
+ case "get_review_comments": {
107
+ const after = optionalString(args, "after", 1e3);
108
+ const first = optionalNumber(args, "perPage", 100) ?? 30;
109
+ if (args.page !== void 0)
110
+ return invalid("page (get_review_comments uses after cursor)");
111
+ return graph(QUERY, { owner, name: repo, number, first, after: after || null });
112
+ }
113
+ default:
114
+ throw new Error("GitHub: unsupported pull_request_read method");
115
+ }
116
+ }
117
+ if (tool === "list_pull_requests") {
118
+ allowKeys(args, ["owner", "repo", "state", "base", "head", "sort", "direction", "page", "perPage"]);
119
+ const [owner, repo] = ownerRepo(args);
120
+ const requestFields = { ...fields(args, { state: "state", base: "base", head: "head", sort: "sort", direction: "direction" }), ...pagination(args) };
121
+ if (requestFields.state && !["open", "closed", "all"].includes(requestFields.state))
122
+ return invalid("state");
123
+ if (requestFields.direction && !["asc", "desc"].includes(requestFields.direction))
124
+ return invalid("direction");
125
+ if (requestFields.sort && !["created", "updated", "popularity", "long-running"].includes(requestFields.sort))
126
+ return invalid("sort");
127
+ if (requestFields.base && !/^[A-Za-z0-9._/-]+$/.test(requestFields.base))
128
+ return invalid("base ref");
129
+ if (requestFields.head && !/^[A-Za-z0-9._:/-]+$/.test(requestFields.head))
130
+ return invalid("head ref");
131
+ return { kind: "rest", endpoint: `repos/${owner}/${repo}/pulls`, fields: requestFields };
132
+ }
133
+ if (tool === "search_pull_requests" || tool === "search_issues") {
134
+ allowKeys(args, ["query", "sort", "order", "page", "perPage"]);
135
+ const query = requiredString(args, "query");
136
+ const repoQualifiers = [...query.matchAll(/(^|\s)repo:([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+)(?=\s|$)/gi)];
137
+ if (repoQualifiers.length !== 1 || /\b(?:OR|NOT)\b|\b(?:org|user|owner):/i.test(query))
138
+ return invalid("query scope");
139
+ const requestFields = { q: tool === "search_pull_requests" ? `is:pr ${query}` : query, ...fields(args, { sort: "sort", order: "order" }), ...pagination(args) };
140
+ if (requestFields.order && !["asc", "desc"].includes(requestFields.order))
141
+ return invalid("order");
142
+ if (requestFields.sort && !["created", "updated", "comments"].includes(requestFields.sort))
143
+ return invalid("sort");
144
+ return { kind: "rest", endpoint: "search/issues", fields: requestFields };
145
+ }
146
+ if (tool === "issue_read") {
147
+ allowKeys(args, ["owner", "repo", "issue_number", "method", "page", "perPage"]);
148
+ const [owner, repo] = ownerRepo(args);
149
+ const number = optionalNumber(args, "issue_number");
150
+ if (!number)
151
+ return invalid("issue_number");
152
+ const method = requiredString(args, "method", 40);
153
+ const endpoint = `repos/${owner}/${repo}/issues/${number}`;
154
+ switch (method) {
155
+ case "get":
156
+ rejectPagination(args);
157
+ return { kind: "rest", endpoint, fields: {} };
158
+ case "get_comments":
159
+ return { kind: "rest", endpoint: `${endpoint}/comments`, fields: pagination(args) };
160
+ case "get_sub_issues":
161
+ return { kind: "rest", endpoint: `${endpoint}/sub_issues`, fields: pagination(args) };
162
+ case "get_parent":
163
+ rejectPagination(args);
164
+ return graph(PARENT_QUERY, { owner, name: repo, number });
165
+ case "get_labels":
166
+ return { kind: "rest", endpoint: `${endpoint}/labels`, fields: pagination(args) };
167
+ default:
168
+ throw new Error("GitHub: unsupported issue_read method");
169
+ }
170
+ }
171
+ if (tool === "list_issues") {
172
+ allowKeys(args, ["owner", "repo", "state", "labels", "orderBy", "direction", "since", "after", "perPage"]);
173
+ const [owner, repo] = ownerRepo(args);
174
+ const state = optionalString(args, "state", 20);
175
+ if (state && !["open", "closed", "all"].includes(state))
176
+ return invalid("state");
177
+ const labels = args.labels;
178
+ if (labels !== void 0 && (!Array.isArray(labels) || labels.some((label) => typeof label !== "string" || label.length > 200)))
179
+ return invalid("labels");
180
+ const after = optionalString(args, "after", 1e3);
181
+ const perPage = optionalNumber(args, "perPage", 100) ?? 30;
182
+ const orderBy = optionalString(args, "orderBy", 30);
183
+ const direction = optionalString(args, "direction", 10)?.toUpperCase();
184
+ if (direction && direction !== "ASC" && direction !== "DESC")
185
+ return invalid("direction");
186
+ const since = optionalString(args, "since", 64);
187
+ if (since && Number.isNaN(Date.parse(since)))
188
+ return invalid("since");
189
+ const order = orderBy ? { field: orderBy.toUpperCase(), direction: direction ?? "DESC" } : void 0;
190
+ if (order && !["CREATED_AT", "UPDATED_AT", "COMMENTS"].includes(order.field))
191
+ return invalid("orderBy");
192
+ return graph(ISSUE_QUERY, {
193
+ owner,
194
+ name: repo,
195
+ first: perPage,
196
+ after: after || null,
197
+ states: state === "all" ? void 0 : state ? [state.toUpperCase()] : void 0,
198
+ labels,
199
+ orderBy: order,
200
+ filterBy: since ? { since } : void 0
201
+ });
202
+ }
203
+ throw new Error("GitHub: unsupported tool");
204
+ }
205
+ var ENV_ALLOW = /* @__PURE__ */ new Set([
206
+ "HOME",
207
+ "USERPROFILE",
208
+ "APPDATA",
209
+ "LOCALAPPDATA",
210
+ "XDG_CONFIG_HOME",
211
+ "GH_CONFIG_DIR",
212
+ "PATH",
213
+ "PATHEXT",
214
+ "SYSTEMROOT",
215
+ "COMSPEC",
216
+ "TEMP",
217
+ "TMP",
218
+ "GH_TOKEN",
219
+ "GITHUB_TOKEN",
220
+ "HTTP_PROXY",
221
+ "HTTPS_PROXY",
222
+ "ALL_PROXY",
223
+ "NO_PROXY",
224
+ "http_proxy",
225
+ "https_proxy",
226
+ "all_proxy",
227
+ "no_proxy",
228
+ "SSL_CERT_FILE",
229
+ "SSL_CERT_DIR",
230
+ "CURL_CA_BUNDLE",
231
+ "NODE_EXTRA_CA_CERTS"
232
+ ]);
233
+ function ghEnvironment(parent) {
234
+ const env = { ...windowsRuntimeEnv(parent) };
235
+ for (const [key, value] of Object.entries(parent))
236
+ if (value !== void 0 && ENV_ALLOW.has(key.toUpperCase()) || value !== void 0 && ENV_ALLOW.has(key))
237
+ env[key] = value;
238
+ Object.assign(env, { GH_HOST: "github.com", GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1", GH_PAGER: "cat", PAGER: "cat" });
239
+ return env;
240
+ }
241
+ function argsFor(request) {
242
+ if (request.kind === "pr_status" || request.kind === "check_runs")
243
+ throw new Error("GitHub: derived request requires PR metadata");
244
+ if (request.kind === "graphql") {
245
+ return { args: ["api", "--hostname", "github.com", "graphql", "--input", "-"], input: Buffer.from(JSON.stringify({ query: request.query, variables: request.variables })) };
246
+ }
247
+ const args = ["api", "--hostname", "github.com", "--method", "GET", request.endpoint];
248
+ if (request.accept)
249
+ args.push("--header", `Accept: ${request.accept}`);
250
+ for (const [key, value] of Object.entries(request.fields))
251
+ args.push("-f", `${key}=${value}`);
252
+ return { args };
253
+ }
254
+ function redactSecrets(text) {
255
+ return text.replace(/\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})\b/g, "[redacted]");
256
+ }
257
+ function cap(text, bytes) {
258
+ if (Buffer.byteLength(text) <= bytes)
259
+ return text;
260
+ let out = "";
261
+ for (const char of text) {
262
+ if (Buffer.byteLength(out + char) > bytes)
263
+ break;
264
+ out += char;
265
+ }
266
+ return out;
267
+ }
268
+ var GithubGh = class {
269
+ options;
270
+ abort = new AbortController();
271
+ closed = false;
272
+ pending = /* @__PURE__ */ new Set();
273
+ constructor(options = {}) {
274
+ this.options = options;
275
+ }
276
+ get bin() {
277
+ return this.options.bin ?? "gh";
278
+ }
279
+ get env() {
280
+ return ghEnvironment(this.options.env ?? process.env);
281
+ }
282
+ async execute(args, options) {
283
+ if (this.closed)
284
+ throw new Error("GitHub provider stopped");
285
+ const command = commandSpec(this.bin, args, this.env);
286
+ const operation = runHelperFile(command.bin, command.args, {
287
+ env: this.env,
288
+ timeout: options.timeout,
289
+ maxBuffer: 1024 * 1024,
290
+ encoding: "utf8",
291
+ signal: this.abort.signal,
292
+ input: options.input
293
+ }, this.options.ownedHelpers);
294
+ this.pending.add(operation);
295
+ let result;
296
+ try {
297
+ result = await operation;
298
+ } finally {
299
+ this.pending.delete(operation);
300
+ }
301
+ return { error: result.error, stdout: cap(result.stdout.toString("utf8"), OUTPUT_LIMIT), stderr: cap(result.stderr.toString("utf8"), STDERR_LIMIT), code: result.code };
302
+ }
303
+ /** `github__gh_read` / `github__gh_write`: the hub already scoped the call; the table is applied again here. */
304
+ async command(tool, input) {
305
+ const { args, bound } = input;
306
+ const targets = Array.isArray(bound) ? bound.filter((item) => !!item && typeof item.owner === "string" && typeof item.repo === "string") : [];
307
+ const parsed = parseGhCommand(args, targets);
308
+ if (!parsed.ok)
309
+ throw new Error(`GitHub: ${parsed.error}`);
310
+ if (parsed.mode !== (tool === "gh_write" ? "write" : "read"))
311
+ throw new Error("GitHub: command does not match the tool");
312
+ const result = await this.execute(parsed.argv, { timeout: this.options.timeoutMs ?? CALL_TIMEOUT_MS });
313
+ if (result.error) {
314
+ if (result.error.message.includes("timed out"))
315
+ throw new Error("GitHub call timed out");
316
+ const detail = redactSecrets(result.stderr || result.stdout).trim();
317
+ throw new Error(`gh failed${result.code === null ? "" : ` (exit ${result.code})`}${detail ? `: ${detail}` : ""}`);
318
+ }
319
+ return result.stdout;
320
+ }
321
+ async call(tool, args) {
322
+ if (tool === "gh_read" || tool === "gh_write")
323
+ return this.command(tool, args);
324
+ const request = githubApiRequest(tool, args);
325
+ if (request.kind === "pr_status" || request.kind === "check_runs") {
326
+ const metadata = await this.execute(["api", "--hostname", "github.com", "--method", "GET", `repos/${request.owner}/${request.repo}/pulls/${request.number}`], { timeout: this.options.timeoutMs ?? CALL_TIMEOUT_MS });
327
+ if (metadata.error)
328
+ throw new Error("GitHub call failed");
329
+ let sha;
330
+ try {
331
+ sha = JSON.parse(metadata.stdout).head?.sha;
332
+ } catch {
333
+ }
334
+ if (typeof sha !== "string" || !/^[a-f0-9]{40,64}$/i.test(sha))
335
+ throw new Error("GitHub returned invalid pull request head SHA");
336
+ const endpoint = request.kind === "pr_status" ? `repos/${request.owner}/${request.repo}/commits/${sha}/status` : `repos/${request.owner}/${request.repo}/commits/${sha}/check-runs`;
337
+ const derived = await this.execute(["api", "--hostname", "github.com", "--method", "GET", endpoint, ...Object.entries(request.fields).flatMap(([key, value]) => ["-f", `${key}=${value}`])], { timeout: this.options.timeoutMs ?? CALL_TIMEOUT_MS });
338
+ if (derived.error)
339
+ throw new Error("GitHub call failed");
340
+ return derived.stdout;
341
+ }
342
+ const operation = argsFor(request);
343
+ const result = await this.execute(operation.args, { timeout: this.options.timeoutMs ?? CALL_TIMEOUT_MS, input: operation.input });
344
+ if (result.error)
345
+ throw new Error(result.error.message.includes("timed out") ? "GitHub call timed out" : "GitHub call failed");
346
+ return result.stdout;
347
+ }
348
+ async status() {
349
+ let version;
350
+ try {
351
+ version = await this.execute(["--version"], { timeout: STATUS_TIMEOUT_MS });
352
+ } catch {
353
+ return { installed: false, loggedIn: false, detail: "GitHub CLI unavailable" };
354
+ }
355
+ if (version.error)
356
+ return { installed: false, loggedIn: false, detail: "GitHub CLI unavailable" };
357
+ let auth;
358
+ try {
359
+ auth = await this.execute(["auth", "status", "--hostname", "github.com", "--json", "hostname,login,state,active", "--no-refresh"], { timeout: STATUS_TIMEOUT_MS });
360
+ } catch {
361
+ return { installed: true, loggedIn: false, detail: "GitHub CLI installed; login status unavailable" };
362
+ }
363
+ if (auth.error)
364
+ return { installed: true, loggedIn: false, detail: "GitHub CLI installed; no active github.com login or CLI upgrade required" };
365
+ try {
366
+ const payload = JSON.parse(auth.stdout);
367
+ const accounts = payload.hosts?.["github.com"] ?? [];
368
+ const loggedIn = accounts.some((account) => account.active === true && (!account.state || account.state === "success"));
369
+ return { installed: true, loggedIn, detail: loggedIn ? "GitHub CLI logged in to github.com" : "GitHub CLI installed; no active github.com login" };
370
+ } catch {
371
+ return { installed: true, loggedIn: false, detail: "GitHub CLI installed; login status unavailable (upgrade gh)" };
372
+ }
373
+ }
374
+ async close() {
375
+ if (this.closed)
376
+ return;
377
+ this.closed = true;
378
+ this.abort.abort();
379
+ await Promise.allSettled([...this.pending]);
380
+ }
381
+ };
382
+ export {
383
+ GithubGh,
384
+ githubApiRequest
385
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+
3
+ // index.js
4
+ import { realpathSync } from "node:fs";
5
+ import { readFile } from "node:fs/promises";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { Command, InvalidArgumentError } from "commander";
10
+ import { authClaude } from "./claude/token.js";
11
+ import { startDaemon } from "./daemon/setup.js";
12
+ import { startMcpServer } from "./mcp/server.js";
13
+ import { configErrorMessage, DEFAULT_CONFIG, loadConfig } from "./config.js";
14
+ import { configGet, configSet } from "./configCommands.js";
15
+ import { defaultTokenFile, runDoctor } from "./doctor.js";
16
+ import { authByok, authLogin, authLogout, authProbe, authStatus, consoleOut } from "./direct/authCommands.js";
17
+ import { defaultCodexHome } from "./direct/codexLogin.js";
18
+ import { cliVersion } from "./direct/siwc.js";
19
+ import { resolveHubTarget, runReport, runReportHook } from "./report/commands.js";
20
+ import { runWpGet, runWpList } from "./wp/commands.js";
21
+ import { authGithub } from "./secrets/github.js";
22
+ import { installReport, REPORT_AGENTS, uninstallReport } from "./report/install.js";
23
+ var nemaHomeDir = () => path.join(os.homedir(), ".nema");
24
+ var program = new Command().name("nema").version(cliVersion()).enablePositionalOptions();
25
+ program.command("daemon").description("Connect to the hub and run agent tasks").option("--hub <url>", "hub URL", "http://127.0.0.1:4400").option("--token-file <path>", "daemon token file", defaultTokenFile()).action(async (o) => {
26
+ const token = (await readFile(o.tokenFile, "utf8")).trim();
27
+ const nemaHome = path.join(os.homedir(), ".nema");
28
+ startDaemon({
29
+ hubUrl: o.hub,
30
+ token,
31
+ nemaHome,
32
+ userCodexHome: defaultCodexHome(),
33
+ version: cliVersion(),
34
+ cliEntry: realpathSync(process.argv[1] ?? fileURLToPath(import.meta.url))
35
+ });
36
+ console.log(`nema daemon connecting to ${o.hub}`);
37
+ });
38
+ program.command("mcp").description("Run the stdio Room MCP server (used by Codex; reads NEMA_HUB_URL / NEMA_RUN_TOKEN)").action(async () => {
39
+ const hubUrl = process.env.NEMA_HUB_URL;
40
+ const runToken = process.env.NEMA_RUN_TOKEN;
41
+ if (!hubUrl || !runToken) {
42
+ console.error("NEMA_HUB_URL and NEMA_RUN_TOKEN must be set");
43
+ process.exit(1);
44
+ }
45
+ await startMcpServer({ hubUrl, runToken });
46
+ });
47
+ program.command("doctor").description("Check git, graphify, direct-mode login or BYOK key and model, Codex CLI and its login, the Claude Code binary and token file, the hub, the daemon token, github-mcp-server and the GitHub token").option("--hub <url>", "hub URL", "http://127.0.0.1:4400").option("--token-file <path>", "daemon token file", defaultTokenFile()).option("--codex-bin <path>", "codex binary", process.env.NEMA_CODEX_BIN ?? "codex").action(async (o) => {
48
+ let adapter = DEFAULT_CONFIG.adapter;
49
+ let authMode = DEFAULT_CONFIG.authMode;
50
+ let model = DEFAULT_CONFIG.model;
51
+ let configError;
52
+ try {
53
+ ({ adapter, authMode, model } = loadConfig(nemaHomeDir()));
54
+ } catch (e) {
55
+ configError = configErrorMessage(e);
56
+ }
57
+ const results = await runDoctor({
58
+ codexBin: o.codexBin,
59
+ userCodexHome: defaultCodexHome(),
60
+ hubUrl: o.hub,
61
+ tokenFile: o.tokenFile,
62
+ nemaHome: nemaHomeDir(),
63
+ adapter,
64
+ authMode,
65
+ model
66
+ });
67
+ if (configError !== void 0)
68
+ results.unshift({ name: "\u914D\u7F6E\u6587\u4EF6", ok: false, detail: configError });
69
+ for (const r of results)
70
+ console.log(`${r.ok ? "\u2713" : "\u2717"} ${r.name}\uFF1A${r.detail}`);
71
+ if (results.some((r) => !r.ok))
72
+ process.exit(1);
73
+ });
74
+ var auth = program.command("auth").description("Direct-mode login: Sign in with ChatGPT, or (authMode=compat) the existing Codex login file");
75
+ auth.command("login").description("SIWC browser login, or confirm the Codex login file when authMode=compat").option("--port <port>", "loopback callback port (default 1455)", (v) => Number.parseInt(v, 10)).action(async (o) => {
76
+ process.exitCode = await authLogin({ nemaHome: nemaHomeDir(), out: consoleOut, port: o.port, codexHome: defaultCodexHome() });
77
+ });
78
+ auth.command("byok").description("Read a BYOK API key from standard input").action(async () => {
79
+ process.exitCode = await authByok({ nemaHome: nemaHomeDir(), out: consoleOut, stdin: process.stdin });
80
+ });
81
+ auth.command("claude").description("Read a Claude Code setup token from standard input").action(async () => {
82
+ process.exitCode = await authClaude({ nemaHome: nemaHomeDir(), out: consoleOut, stdin: process.stdin });
83
+ });
84
+ auth.command("github").description("Read a GitHub personal access token from standard input (for the read-only GitHub plugin)").action(async () => {
85
+ process.exitCode = await authGithub({ nemaHome: nemaHomeDir(), out: consoleOut, stdin: process.stdin });
86
+ });
87
+ auth.command("status").description("Show the login mode, account, expiry and last auth error (never prints tokens)").action(() => {
88
+ process.exitCode = authStatus({ nemaHome: nemaHomeDir(), out: consoleOut, codexHome: defaultCodexHome() });
89
+ });
90
+ auth.command("logout").description("Delete Nema's direct-mode credentials").action(async () => {
91
+ process.exitCode = await authLogout({ nemaHome: nemaHomeDir(), out: consoleOut });
92
+ });
93
+ auth.command("probe").description('List models and make one minimal Responses call ("\u53EA\u56DE\u590D ok")').option("--force-refresh", "refresh the access token first (debugging)").action(async (o) => {
94
+ process.exitCode = await authProbe({ nemaHome: nemaHomeDir(), out: consoleOut, forceRefresh: !!o.forceRefresh, codexHome: defaultCodexHome() });
95
+ });
96
+ var config = program.command("config").description("Show or change Nema's local settings (~/.nema/config.json)");
97
+ config.command("get [key]").description("Print one setting, or all of them (adapter, model, reasoningEffort, authMode)").action((key) => {
98
+ process.exitCode = configGet({ nemaHome: nemaHomeDir(), out: consoleOut, key });
99
+ });
100
+ config.command("set <key> <value>").description("Set adapter (codex-direct | claude-code), model, reasoningEffort (low|medium|high), authMode (siwc|compat|byok) or byokBaseUrl").action((key, value) => {
101
+ process.exitCode = configSet({ nemaHome: nemaHomeDir(), out: consoleOut, key, value });
102
+ });
103
+ var report = program.command("report").description("Report finished work to Nema (summary, commits, entry ids, files); run inside the repo").enablePositionalOptions().option("--summary <text>", "one-line summary of the finished work").option("--commits <shas>", "comma-separated commit shas").option("--entries <ids>", "comma-separated entry ids, e.g. R-7,D-12").option("--files <paths>", "comma-separated files that were changed").option("--task <n>", "the work package task number this report finishes (use with --entries W-n)").option("--agent <name>", "coding agent name").option("--json", "read the fields as a JSON object from standard input").option("--hub <url>", "hub URL (default: NEMA_HUB_URL, else a running Nema desktop app, else http://127.0.0.1:4400)").action(async (o) => {
104
+ const { hub, ...flags } = o;
105
+ process.exitCode = await runReport({ nemaHome: nemaHomeDir(), hubUrl: await resolveHubTarget(hub), cwd: process.cwd(), out: consoleOut, flags, stdin: process.stdin });
106
+ });
107
+ report.command("hook").description("Called by a coding agent's hook: sends only agent, cwd, git common dir, branch, HEAD, time and session id").requiredOption("--agent <name>", "coding agent name").option("--hub <url>", "hub URL (default: NEMA_HUB_URL, else a running Nema desktop app, else http://127.0.0.1:4400)").action(async (o) => {
108
+ process.exitCode = await runReportHook({ agent: o.agent, nemaHome: nemaHomeDir(), hubUrl: await resolveHubTarget(o.hub), stdin: process.stdin.isTTY ? null : process.stdin });
109
+ });
110
+ function reportAgent(v) {
111
+ if (!REPORT_AGENTS.includes(v))
112
+ throw new InvalidArgumentError(`--agent \u53EA\u80FD\u662F ${REPORT_AGENTS.join(" \u6216 ")}`);
113
+ return v;
114
+ }
115
+ report.command("install").description("Install the report hook (Stop, SessionEnd) and the nema-report skill for a coding agent").requiredOption("--agent <name>", "claude or codex", reportAgent).option("--home <dir>", "home dir holding the agent's settings (default: your home dir)").action((o) => {
116
+ process.exitCode = installReport({
117
+ agent: o.agent,
118
+ home: o.home ?? os.homedir(),
119
+ nemaHome: nemaHomeDir(),
120
+ out: consoleOut,
121
+ nodeBin: process.execPath,
122
+ cliEntry: realpathSync(process.argv[1] ?? fileURLToPath(import.meta.url))
123
+ });
124
+ });
125
+ report.command("uninstall").description("Remove Nema's report hook entries and the nema-report skill; the report token stays").requiredOption("--agent <name>", "claude or codex", reportAgent).option("--home <dir>", "home dir holding the agent's settings (default: your home dir)").action((o) => {
126
+ process.exitCode = uninstallReport({ agent: o.agent, home: o.home ?? os.homedir(), out: consoleOut });
127
+ });
128
+ var wp = program.command("wp").description("Work packages: list the approved ones for this repo, or fetch one for a coding agent");
129
+ wp.command("list").description("List the approved work packages that cover the current repo").option("--hub <url>", "hub URL (default: NEMA_HUB_URL, else a running Nema desktop app, else http://127.0.0.1:4400)").action(async (o) => {
130
+ process.exitCode = await runWpList({ nemaHome: nemaHomeDir(), hubUrl: await resolveHubTarget(o.hub), cwd: process.cwd(), out: consoleOut });
131
+ });
132
+ wp.command("get <id>").description("Write an approved work package's spec and plan to .nema/<W-n>/ (or print them with --stdout)").option("--dir <dir>", "directory to write .nema/<W-n>/ under (default: the current directory)").option("--stdout", "print the spec and plan instead of writing files").option("--force", "overwrite files that exist with different content").option("--hub <url>", "hub URL (default: NEMA_HUB_URL, else a running Nema desktop app, else http://127.0.0.1:4400)").action(async (id, o) => {
133
+ process.exitCode = await runWpGet({
134
+ nemaHome: nemaHomeDir(),
135
+ hubUrl: await resolveHubTarget(o.hub),
136
+ cwd: process.cwd(),
137
+ out: consoleOut,
138
+ id,
139
+ ...o.dir !== void 0 ? { dir: o.dir } : {},
140
+ stdout: !!o.stdout,
141
+ force: !!o.force
142
+ });
143
+ });
144
+ await program.parseAsync(process.argv);
@@ -0,0 +1,8 @@
1
+ /** Narrow fixed helpers used by the authenticated local desktop settings backend. */
2
+ export { authLogin, authLogout, authProbe, defaultOpenBrowser, listConfiguredModels, ModelsListError } from "./direct/authCommands.js";
3
+ export { AuthStore } from "./direct/authStore.js";
4
+ export { siwcRefresher } from "./direct/siwc.js";
5
+ export { byokKeyPath, saveByokKey } from "./direct/byok.js";
6
+ export { claudeTokenPath, saveClaudeToken } from "./claude/token.js";
7
+ export { githubTokenPath, saveGithubToken } from "./secrets/github.js";
8
+ export { defaultCodexHome, readCodexCliTokens } from "./direct/codexLogin.js";
@@ -0,0 +1,26 @@
1
+ // localSettings.js
2
+ import { authLogin, authLogout, authProbe, defaultOpenBrowser, listConfiguredModels, ModelsListError } from "./direct/authCommands.js";
3
+ import { AuthStore } from "./direct/authStore.js";
4
+ import { siwcRefresher } from "./direct/siwc.js";
5
+ import { byokKeyPath, saveByokKey } from "./direct/byok.js";
6
+ import { claudeTokenPath, saveClaudeToken } from "./claude/token.js";
7
+ import { githubTokenPath, saveGithubToken } from "./secrets/github.js";
8
+ import { defaultCodexHome, readCodexCliTokens } from "./direct/codexLogin.js";
9
+ export {
10
+ AuthStore,
11
+ ModelsListError,
12
+ authLogin,
13
+ authLogout,
14
+ authProbe,
15
+ byokKeyPath,
16
+ claudeTokenPath,
17
+ defaultCodexHome,
18
+ defaultOpenBrowser,
19
+ githubTokenPath,
20
+ listConfiguredModels,
21
+ readCodexCliTokens,
22
+ saveByokKey,
23
+ saveClaudeToken,
24
+ saveGithubToken,
25
+ siwcRefresher
26
+ };
@@ -0,0 +1,5 @@
1
+ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
2
+ export declare function startMcpServer(opts: {
3
+ hubUrl: string;
4
+ runToken: string;
5
+ }, transport?: Transport): Promise<void>;
@@ -0,0 +1,83 @@
1
+ // mcp/server.js
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ import { RoomClient } from "../room/client.js";
6
+ import { roomTools } from "../direct/tools.js";
7
+ function toToolResult(r) {
8
+ if (!r.ok)
9
+ return { content: [{ type: "text", text: r.error }], isError: true };
10
+ return { content: [{ type: "text", text: typeof r.json === "string" ? r.json : JSON.stringify(r.json) }] };
11
+ }
12
+ function parsePluginTools(json) {
13
+ if (!json || typeof json !== "object" || !Array.isArray(json.tools))
14
+ return null;
15
+ const tools = [];
16
+ for (const item of json.tools) {
17
+ if (!item || typeof item !== "object")
18
+ return null;
19
+ const tool = item;
20
+ if (typeof tool.name !== "string" || typeof tool.description !== "string")
21
+ return null;
22
+ if (!tool.parameters || typeof tool.parameters !== "object" || Array.isArray(tool.parameters))
23
+ return null;
24
+ tools.push({ name: tool.name, description: tool.description, parameters: tool.parameters });
25
+ }
26
+ return tools;
27
+ }
28
+ async function listedPluginTools(room) {
29
+ try {
30
+ const listed = await room.listPluginTools();
31
+ if (!listed.ok) {
32
+ console.error(`\u65E0\u6CD5\u5217\u51FA\u63D2\u4EF6\u51FD\u6570\uFF1A${listed.error}`);
33
+ return [];
34
+ }
35
+ const tools = parsePluginTools(listed.json);
36
+ if (!tools) {
37
+ console.error("\u65E0\u6CD5\u5217\u51FA\u63D2\u4EF6\u51FD\u6570\uFF1A\u54CD\u5E94\u7F3A\u5C11 tools");
38
+ return [];
39
+ }
40
+ return tools;
41
+ } catch (e) {
42
+ console.error(`\u65E0\u6CD5\u5217\u51FA\u63D2\u4EF6\u51FD\u6570\uFF1A${e instanceof Error ? e.message : String(e)}`);
43
+ return [];
44
+ }
45
+ }
46
+ async function startMcpServer(opts, transport) {
47
+ const room = new RoomClient(opts.hubUrl, opts.runToken);
48
+ const pluginTools = await listedPluginTools(room);
49
+ const tools = roomTools(pluginTools);
50
+ const pluginNames = new Set(pluginTools.map((tool) => tool.name));
51
+ const server = new Server({ name: "room", version: "0.0.0" }, { capabilities: { tools: {} } });
52
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
53
+ tools: tools.map((tool) => ({
54
+ name: tool.name,
55
+ description: tool.description,
56
+ inputSchema: tool.parameters
57
+ }))
58
+ }));
59
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
60
+ const name = request.params.name;
61
+ const args = request.params.arguments ?? {};
62
+ if (pluginNames.has(name))
63
+ return toToolResult(await room.callPlugin(name, args));
64
+ if (name === "list_entries")
65
+ return toToolResult(await room.listEntries());
66
+ if (name === "read_entry") {
67
+ const id = args.id;
68
+ if (typeof id !== "string" || id === "") {
69
+ return { content: [{ type: "text", text: "read_entry \u9700\u8981\u5B57\u7B26\u4E32\u53C2\u6570 id" }], isError: true };
70
+ }
71
+ return toToolResult(await room.readEntry(id));
72
+ }
73
+ if (name === "upsert_entry")
74
+ return toToolResult(await room.upsertEntry(args));
75
+ if (name === "open_thread")
76
+ return toToolResult(await room.openThread(args));
77
+ return { content: [{ type: "text", text: `\u672A\u77E5\u51FD\u6570 ${name}` }], isError: true };
78
+ });
79
+ await server.connect(transport ?? new StdioServerTransport());
80
+ }
81
+ export {
82
+ startMcpServer
83
+ };