@nema-project/nema 0.0.0-stage → 0.1.3

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,39 @@
1
+ import { type SpawnOptions } from "node:child_process";
2
+ import type { Readable, Writable } from "node:stream";
3
+ export type OwnedExit = {
4
+ code: number | null;
5
+ signal: NodeJS.Signals | null;
6
+ };
7
+ export type OwnedClose = OwnedExit;
8
+ export type OwnedProcess = {
9
+ stdin: Writable | null;
10
+ stdout: Readable | null;
11
+ stderr: Readable | null;
12
+ /** Terminates the guardian by its ChildProcess handle; closing its Job kills its helpers. */
13
+ kill(signal?: NodeJS.Signals | number): boolean;
14
+ /** Remains settled even when the target exits before spawnOwned() returns. */
15
+ exited: Promise<OwnedExit>;
16
+ /** Remains settled after all forwarded stdio has closed. */
17
+ closed: Promise<OwnedClose>;
18
+ };
19
+ export type SpawnOwnedOptions = Pick<SpawnOptions, "cwd" | "env" | "stdio" | "windowsHide" | "windowsVerbatimArguments"> & {
20
+ signal?: AbortSignal;
21
+ };
22
+ export declare class OwnedSpawnError extends Error {
23
+ readonly path: string;
24
+ readonly spawnargs: string[];
25
+ readonly win32ErrorCode: number;
26
+ readonly code: string;
27
+ readonly syscall: string;
28
+ constructor(command: string, args: string[], win32ErrorCode: number);
29
+ }
30
+ export declare class OwnedGuardianError extends Error {
31
+ readonly path: string;
32
+ readonly code: string | undefined;
33
+ constructor(guardianPath: string, cause: NodeJS.ErrnoException);
34
+ }
35
+ /**
36
+ * Start one helper inside a Windows Job Object owned by the shipped guardian.
37
+ * This API is intentionally Windows-only; POSIX helper ownership is a separate gate.
38
+ */
39
+ export declare function spawnOwned(guardianPath: string, command: string, args: string[], options?: SpawnOwnedOptions): Promise<OwnedProcess>;
@@ -0,0 +1,229 @@
1
+ // ownedProcess.js
2
+ import { randomBytes } from "node:crypto";
3
+ import { spawn } from "node:child_process";
4
+ import { setTimeout as delay } from "node:timers/promises";
5
+ import net from "node:net";
6
+ var CONNECT_TIMEOUT_MS = 4e3;
7
+ var STARTUP_TIMEOUT_MS = 1e4;
8
+ var RETRY_MS = 25;
9
+ var OwnedSpawnError = class extends Error {
10
+ path;
11
+ spawnargs;
12
+ win32ErrorCode;
13
+ code;
14
+ syscall;
15
+ constructor(command, args, win32ErrorCode) {
16
+ const code = nodeCodeForWin32(win32ErrorCode);
17
+ super(`spawn ${command} ${code}`);
18
+ this.name = "OwnedSpawnError";
19
+ this.path = command;
20
+ this.spawnargs = [...args];
21
+ this.win32ErrorCode = win32ErrorCode;
22
+ this.code = code;
23
+ this.syscall = `spawn ${command}`;
24
+ }
25
+ };
26
+ var OwnedGuardianError = class extends Error {
27
+ path;
28
+ code;
29
+ constructor(guardianPath, cause) {
30
+ super(`Could not start owned-helper guardian: ${cause.code ?? "unknown error"}`);
31
+ this.name = "OwnedGuardianError";
32
+ this.path = guardianPath;
33
+ this.code = cause.code;
34
+ }
35
+ };
36
+ function nodeCodeForWin32(code) {
37
+ switch (code) {
38
+ case 2:
39
+ case 3:
40
+ case 126:
41
+ case 127:
42
+ return "ENOENT";
43
+ case 5:
44
+ case 740:
45
+ return "EACCES";
46
+ case 32:
47
+ case 33:
48
+ return "EBUSY";
49
+ case 193:
50
+ return "ENOEXEC";
51
+ default:
52
+ return "EIO";
53
+ }
54
+ }
55
+ function abortError() {
56
+ const error = new Error("The owned helper startup was aborted");
57
+ error.name = "AbortError";
58
+ Object.assign(error, { code: "ABORT_ERR" });
59
+ return error;
60
+ }
61
+ function isAbortError(value) {
62
+ return value instanceof Error && value.name === "AbortError";
63
+ }
64
+ function pipeName() {
65
+ return `\\\\.\\pipe\\nema-owned-${randomBytes(24).toString("hex")}`;
66
+ }
67
+ function retryablePipeError(error) {
68
+ return error.code === "ENOENT" || error.code === "ECONNREFUSED";
69
+ }
70
+ function childEventPromises(child) {
71
+ const exited = new Promise((resolve) => {
72
+ child.once("exit", (code, signal) => resolve({ code, signal }));
73
+ });
74
+ const closed = new Promise((resolve) => {
75
+ child.once("close", (code, signal) => resolve({ code, signal }));
76
+ });
77
+ return { exited, closed };
78
+ }
79
+ function raceAbort(promise, signal) {
80
+ if (!signal)
81
+ return promise;
82
+ if (signal.aborted)
83
+ return Promise.reject(abortError());
84
+ return new Promise((resolve, reject) => {
85
+ const onAbort = () => reject(abortError());
86
+ signal.addEventListener("abort", onAbort, { once: true });
87
+ promise.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
88
+ });
89
+ }
90
+ async function connectPipe(name, childFailure, signal) {
91
+ const deadline = Date.now() + CONNECT_TIMEOUT_MS;
92
+ let lastError;
93
+ while (Date.now() < deadline) {
94
+ if (signal?.aborted)
95
+ throw abortError();
96
+ const socket = net.createConnection(name);
97
+ try {
98
+ await raceAbort(Promise.race([
99
+ new Promise((resolve, reject) => {
100
+ socket.once("connect", resolve);
101
+ socket.once("error", reject);
102
+ }),
103
+ childFailure
104
+ ]), signal);
105
+ return socket;
106
+ } catch (error2) {
107
+ socket.destroy();
108
+ if (isAbortError(error2))
109
+ throw error2;
110
+ if (error2 instanceof OwnedGuardianError)
111
+ throw error2;
112
+ if (error2 instanceof Error && "code" in error2 && retryablePipeError(error2)) {
113
+ lastError = error2;
114
+ await raceAbort(Promise.race([delay(RETRY_MS), childFailure]), signal);
115
+ continue;
116
+ }
117
+ throw error2;
118
+ }
119
+ }
120
+ const error = new Error("Timed out connecting to the owned-helper lease pipe", { cause: lastError });
121
+ error.name = "OwnedGuardianTimeoutError";
122
+ throw error;
123
+ }
124
+ function readStartupStatus(socket, childFailure, signal) {
125
+ return new Promise((resolve, reject) => {
126
+ let buffer = Buffer.alloc(0);
127
+ const timer = setTimeout(() => finish(new Error("Timed out waiting for owned-helper startup result")), STARTUP_TIMEOUT_MS);
128
+ const onAbort = () => finish(abortError());
129
+ const onData = (chunk) => {
130
+ buffer = Buffer.concat([buffer, chunk]);
131
+ if (buffer.length < 5)
132
+ return;
133
+ const tag = String.fromCharCode(buffer[0]);
134
+ const code = buffer.readUInt32LE(1);
135
+ if (tag !== "R" && tag !== "E")
136
+ return finish(new Error("Invalid owned-helper startup result"));
137
+ finish(void 0, { tag, code });
138
+ };
139
+ const onError = (error) => finish(error);
140
+ const onEnd = () => finish(new Error("Owned-helper guardian closed its startup channel"));
141
+ const finish = (error, status) => {
142
+ clearTimeout(timer);
143
+ socket.off("data", onData);
144
+ socket.off("error", onError);
145
+ socket.off("end", onEnd);
146
+ signal?.removeEventListener("abort", onAbort);
147
+ if (error)
148
+ reject(error);
149
+ else
150
+ resolve(status);
151
+ };
152
+ socket.on("data", onData);
153
+ socket.once("error", onError);
154
+ socket.once("end", onEnd);
155
+ signal?.addEventListener("abort", onAbort, { once: true });
156
+ if (signal?.aborted)
157
+ onAbort();
158
+ childFailure.catch((error) => finish(error instanceof Error ? error : new Error(String(error))));
159
+ });
160
+ }
161
+ function writeLease(socket, childFailure, signal) {
162
+ if (signal?.aborted)
163
+ return Promise.reject(abortError());
164
+ return raceAbort(Promise.race([
165
+ new Promise((resolve, reject) => socket.write("LEASE", (error) => error ? reject(error) : resolve())),
166
+ childFailure
167
+ ]), signal);
168
+ }
169
+ async function terminateAndDrain(child, closed, socket) {
170
+ socket?.destroy();
171
+ if (child.exitCode === null && child.signalCode === null)
172
+ child.kill("SIGKILL");
173
+ await closed;
174
+ }
175
+ async function spawnOwned(guardianPath, command, args, options = {}) {
176
+ if (process.platform !== "win32")
177
+ throw new Error("spawnOwned is available only on Windows until POSIX ownership is proven");
178
+ if (options.signal?.aborted)
179
+ throw abortError();
180
+ const name = pipeName();
181
+ const guardianArgs = [name, options.windowsVerbatimArguments ? "verbatim" : "quoted", command, ...args];
182
+ const child = spawn(guardianPath, guardianArgs, {
183
+ cwd: options.cwd,
184
+ env: options.env,
185
+ stdio: options.stdio ?? "pipe",
186
+ windowsHide: options.windowsHide ?? true,
187
+ windowsVerbatimArguments: false
188
+ });
189
+ const { exited, closed } = childEventPromises(child);
190
+ const guardianFailure = new Promise((_, reject) => {
191
+ child.once("error", (error) => reject(new OwnedGuardianError(guardianPath, error)));
192
+ });
193
+ let socket;
194
+ const signal = options.signal;
195
+ try {
196
+ socket = await connectPipe(name, guardianFailure, signal);
197
+ await writeLease(socket, guardianFailure, signal);
198
+ const status = await readStartupStatus(socket, guardianFailure, signal);
199
+ if (status.tag === "E") {
200
+ await closed;
201
+ throw new OwnedSpawnError(command, args, status.code);
202
+ }
203
+ if (signal?.aborted)
204
+ throw abortError();
205
+ const kill = (requested = "SIGTERM") => {
206
+ if (child.exitCode !== null || child.signalCode !== null)
207
+ return false;
208
+ return child.kill(requested);
209
+ };
210
+ if (signal) {
211
+ const onAbort = () => {
212
+ kill("SIGTERM");
213
+ };
214
+ signal.addEventListener("abort", onAbort, { once: true });
215
+ void closed.then(() => signal.removeEventListener("abort", onAbort));
216
+ }
217
+ return { stdin: child.stdin, stdout: child.stdout, stderr: child.stderr, kill, exited, closed };
218
+ } catch (error) {
219
+ await terminateAndDrain(child, closed, socket);
220
+ if (signal?.aborted && !isAbortError(error))
221
+ throw abortError();
222
+ throw error;
223
+ }
224
+ }
225
+ export {
226
+ OwnedGuardianError,
227
+ OwnedSpawnError,
228
+ spawnOwned
229
+ };
@@ -0,0 +1,10 @@
1
+ export declare function windowsRuntimeEnv(parent: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
2
+ /** Resolve native executables and npm JS shims without passing arguments through cmd.exe. */
3
+ export declare function commandSpec(bin: string, args: string[], env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): {
4
+ bin: string;
5
+ args: string[];
6
+ };
7
+ export declare function browserCommand(url: string, platform?: NodeJS.Platform): {
8
+ bin: string;
9
+ args: string[];
10
+ };
@@ -0,0 +1,60 @@
1
+ // platform.js
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ function windowsRuntimeEnv(parent) {
5
+ const env = {};
6
+ if (process.platform !== "win32")
7
+ return env;
8
+ for (const name of ["SystemRoot", "ComSpec", "PATHEXT", "TEMP", "TMP"]) {
9
+ const key = Object.keys(parent).find((key2) => key2.toLowerCase() === name.toLowerCase());
10
+ if (key && parent[key] !== void 0)
11
+ env[name] = parent[key];
12
+ }
13
+ return env;
14
+ }
15
+ function commandSpec(bin, args, env = process.env, platform = process.platform) {
16
+ if (/\.[cm]?js$/i.test(bin) && existsSync(bin))
17
+ return { bin: process.execPath, args: [bin, ...args] };
18
+ if (platform === "win32" && !path.extname(bin) && existsSync(bin) && /^#![^\r\n]*\bnode\b/.test(readFileSync(bin, "utf8").slice(0, 256)))
19
+ return { bin: process.execPath, args: [bin, ...args] };
20
+ if (platform !== "win32")
21
+ return { bin, args };
22
+ const p = path.win32;
23
+ const searchPath = Object.entries(env).find(([key]) => key.toLowerCase() === "path")?.[1] ?? "";
24
+ const dirs = p.isAbsolute(bin) || /[/\\]/.test(bin) ? [""] : searchPath.split(";");
25
+ const extensions = p.extname(bin) ? [""] : [".exe", ".com", ".cmd", ".bat", ""];
26
+ for (const dir of dirs)
27
+ for (const ext of extensions) {
28
+ const candidate = dir ? p.join(dir, bin + ext) : bin + ext;
29
+ if (!existsSync(candidate))
30
+ continue;
31
+ const candidateExt = p.extname(candidate);
32
+ if (/\.(?:[cm]?js)$/i.test(candidateExt))
33
+ return { bin: process.execPath, args: [candidate, ...args] };
34
+ if (!candidateExt) {
35
+ const header = readFileSync(candidate, "utf8").slice(0, 256);
36
+ if (/^#![^\r\n]*\bnode\b/.test(header))
37
+ return { bin: process.execPath, args: [candidate, ...args] };
38
+ }
39
+ if (!/\.(cmd|bat)$/i.test(candidate))
40
+ return { bin: candidate, args };
41
+ const match = /"%(?:dp0%|~dp0)[\\/]([^"\r\n]+\.(?:[cm]?js))"/i.exec(readFileSync(candidate, "utf8"));
42
+ if (!match)
43
+ throw new Error(`Unsupported command shim: ${p.basename(candidate)}; install a native executable or a standard npm shim.`);
44
+ const entry = p.resolve(p.dirname(candidate), match[1]);
45
+ if (!existsSync(entry))
46
+ throw new Error(`Command entry missing: ${p.basename(candidate)}`);
47
+ return { bin: process.execPath, args: [entry, ...args] };
48
+ }
49
+ return { bin, args };
50
+ }
51
+ function browserCommand(url, platform = process.platform) {
52
+ if (platform === "win32")
53
+ return { bin: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url] };
54
+ return { bin: platform === "darwin" ? "open" : "xdg-open", args: [url] };
55
+ }
56
+ export {
57
+ browserCommand,
58
+ commandSpec,
59
+ windowsRuntimeEnv
60
+ };
@@ -0,0 +1,41 @@
1
+ import type { Out } from "../direct/authCommands.js";
2
+ import { type SendOpts } from "./send.js";
3
+ export declare const DEFAULT_HUB_URL = "http://127.0.0.1:4400";
4
+ /** `--hub`, else NEMA_HUB_URL, else the same default `nema daemon` uses. */
5
+ export declare function resolveHubUrl(flag?: string): string;
6
+ /**
7
+ * Like `resolveHubUrl`, but when neither `--hub` nor NEMA_HUB_URL is given it first looks for a running desktop
8
+ * instance whose identity it can verify. An explicit address always wins and is never probed.
9
+ */
10
+ export declare function resolveHubTarget(flag?: string, discover?: () => Promise<string | null>): Promise<string>;
11
+ export type HookRun = {
12
+ agent: string;
13
+ nemaHome: string;
14
+ hubUrl: string;
15
+ stdin: NodeJS.ReadableStream | null;
16
+ sendOpts?: SendOpts;
17
+ };
18
+ /**
19
+ * Always resolves 0 and never writes to stdout. Only `cwd` and `session_id` are taken from the agent's
20
+ * payload; nothing else is kept or forwarded.
21
+ */
22
+ export declare function runReportHook(o: HookRun): Promise<number>;
23
+ export type ReportFlags = {
24
+ summary?: string;
25
+ commits?: string;
26
+ entries?: string;
27
+ files?: string;
28
+ task?: string;
29
+ agent?: string;
30
+ json?: boolean;
31
+ };
32
+ export type ReportRun = {
33
+ nemaHome: string;
34
+ hubUrl: string;
35
+ cwd: string;
36
+ out: Out;
37
+ flags: ReportFlags;
38
+ stdin?: NodeJS.ReadableStream;
39
+ sendOpts?: SendOpts;
40
+ };
41
+ export declare function runReport(o: ReportRun): Promise<number>;
@@ -0,0 +1,159 @@
1
+ import {
2
+ HookSignal,
3
+ ReportInput
4
+ } from "../chunks/chunk-HEIQINX3.js";
5
+
6
+ // report/commands.js
7
+ import { repoInfo } from "./git.js";
8
+ import { discoverDesktopOrigin } from "./desktop.js";
9
+ import { appendSpool, flushSpool, HOOK_TIMEOUTS, postToHub, sendToHub } from "./send.js";
10
+ import { ensureReportToken } from "./token.js";
11
+ var STDIN_CAP = 1024 * 1024;
12
+ var DEFAULT_HUB_URL = "http://127.0.0.1:4400";
13
+ function resolveHubUrl(flag) {
14
+ return flag || process.env.NEMA_HUB_URL || DEFAULT_HUB_URL;
15
+ }
16
+ async function resolveHubTarget(flag, discover = discoverDesktopOrigin) {
17
+ if (flag || process.env.NEMA_HUB_URL)
18
+ return resolveHubUrl(flag);
19
+ return await discover() ?? DEFAULT_HUB_URL;
20
+ }
21
+ async function readCapped(stdin) {
22
+ const chunks = [];
23
+ let size = 0;
24
+ for await (const c of stdin) {
25
+ const b = Buffer.isBuffer(c) ? c : Buffer.from(String(c));
26
+ size += b.length;
27
+ if (size > STDIN_CAP)
28
+ return null;
29
+ chunks.push(b);
30
+ }
31
+ return Buffer.concat(chunks).toString("utf8");
32
+ }
33
+ function replay(hubUrl, token, opts) {
34
+ return (line) => sendToHub(hubUrl, line.path, token, line.body, opts);
35
+ }
36
+ async function runReportHook(o) {
37
+ try {
38
+ let cwd = process.cwd();
39
+ let sessionId = null;
40
+ const raw = o.stdin ? await readCapped(o.stdin) : null;
41
+ if (raw) {
42
+ try {
43
+ const payload = JSON.parse(raw);
44
+ if (payload && typeof payload === "object") {
45
+ if (typeof payload.cwd === "string" && payload.cwd !== "")
46
+ cwd = payload.cwd;
47
+ if (typeof payload.session_id === "string" && payload.session_id.length <= 200)
48
+ sessionId = payload.session_id;
49
+ }
50
+ } catch {
51
+ }
52
+ }
53
+ const info = await repoInfo(cwd);
54
+ if (!info)
55
+ return 0;
56
+ const parsed = HookSignal.safeParse({ agent: o.agent, cwd, gitCommonDir: info.gitCommonDir, branch: info.branch, head: info.head, at: (/* @__PURE__ */ new Date()).toISOString(), sessionId });
57
+ if (!parsed.success)
58
+ return 0;
59
+ const signal = parsed.data;
60
+ const token = ensureReportToken(o.nemaHome);
61
+ const opts = o.sendOpts ?? HOOK_TIMEOUTS;
62
+ const line = { path: "/report/hook", body: signal, spooledAt: signal.at };
63
+ if (await flushSpool(o.nemaHome, replay(o.hubUrl, token, opts)) === "failed") {
64
+ appendSpool(o.nemaHome, line);
65
+ return 0;
66
+ }
67
+ if (await sendToHub(o.hubUrl, "/report/hook", token, signal, opts) === "failed")
68
+ appendSpool(o.nemaHome, line);
69
+ } catch {
70
+ }
71
+ return 0;
72
+ }
73
+ var list = (s) => s === void 0 ? void 0 : s.split(",").map((x) => x.trim()).filter((x) => x !== "");
74
+ function hubError(text, status) {
75
+ try {
76
+ const v = JSON.parse(text);
77
+ if (typeof v.error === "string")
78
+ return v.error;
79
+ if (typeof v.message === "string")
80
+ return v.message;
81
+ } catch {
82
+ }
83
+ return text.trim() || `Nema \u62D2\u7EDD\u4E86\u8FD9\u6B21\u4E0A\u62A5\uFF08HTTP ${status}\uFF09`;
84
+ }
85
+ async function runReport(o) {
86
+ const info = await repoInfo(o.cwd);
87
+ if (!info) {
88
+ o.out.error("\u4E0D\u5728 git \u4ED3\u5E93\u91CC\uFF0C\u65E0\u6CD5\u4E0A\u62A5");
89
+ return 1;
90
+ }
91
+ let fromJson = {};
92
+ if (o.flags.json) {
93
+ const raw = o.stdin ? await readCapped(o.stdin) : null;
94
+ try {
95
+ const v = raw === null ? null : JSON.parse(raw);
96
+ if (!v || typeof v !== "object" || Array.isArray(v))
97
+ throw new Error();
98
+ fromJson = v;
99
+ } catch {
100
+ o.out.error("--json \u9700\u8981\u4ECE\u6807\u51C6\u8F93\u5165\u8BFB\u53D6\u4E00\u4E2A JSON \u5BF9\u8C61\uFF08\u4E0D\u8D85\u8FC7 1 MB\uFF09");
101
+ return 1;
102
+ }
103
+ }
104
+ const parsed = ReportInput.safeParse({
105
+ ...fromJson,
106
+ agent: o.flags.agent ?? fromJson.agent ?? "coding-agent",
107
+ gitCommonDir: info.gitCommonDir,
108
+ ...o.flags.summary !== void 0 ? { summary: o.flags.summary } : {},
109
+ ...o.flags.commits !== void 0 ? { commits: list(o.flags.commits) } : {},
110
+ ...o.flags.entries !== void 0 ? { entries: list(o.flags.entries) } : {},
111
+ ...o.flags.files !== void 0 ? { files: list(o.flags.files) } : {},
112
+ ...o.flags.task !== void 0 ? { task: /^\d+$/.test(o.flags.task) ? Number(o.flags.task) : o.flags.task } : {}
113
+ });
114
+ if (!parsed.success) {
115
+ o.out.error(`\u4E0A\u62A5\u5185\u5BB9\u4E0D\u5B8C\u6574\u6216\u683C\u5F0F\u4E0D\u5BF9\uFF1A${parsed.error.issues.map((i) => `${i.path.join(".") || "(\u6839)"}\uFF1A${i.message}`).join("\uFF1B")}`);
116
+ return 1;
117
+ }
118
+ const report = parsed.data;
119
+ let token;
120
+ try {
121
+ token = ensureReportToken(o.nemaHome);
122
+ } catch (e) {
123
+ o.out.error(e instanceof Error ? e.message : String(e));
124
+ return 1;
125
+ }
126
+ const opts = o.sendOpts ?? HOOK_TIMEOUTS;
127
+ const line = { path: "/report", body: report, spooledAt: (/* @__PURE__ */ new Date()).toISOString() };
128
+ const spool = () => {
129
+ if (appendSpool(o.nemaHome, line)) {
130
+ o.out.log("Nema \u6682\u65F6\u4E0D\u53EF\u8FBE\uFF0C\u5DF2\u6682\u5B58\uFF0C\u7A0D\u540E\u8865\u53D1");
131
+ return 0;
132
+ }
133
+ o.out.error("Nema \u6682\u65F6\u4E0D\u53EF\u8FBE\uFF0C\u4E14\u672C\u673A\u6682\u5B58\u5DF2\u6EE1\uFF085 MB\uFF09\uFF0C\u8FD9\u6B21\u4E0A\u62A5\u6CA1\u6709\u4FDD\u5B58");
134
+ return 1;
135
+ };
136
+ if (await flushSpool(o.nemaHome, replay(o.hubUrl, token, opts)) === "failed")
137
+ return spool();
138
+ const res = await postToHub(o.hubUrl, "/report", token, report, opts);
139
+ const result = res.status === 0 || res.status >= 500 ? "failed" : res.status >= 400 ? "dropped" : "ok";
140
+ if (result === "failed")
141
+ return spool();
142
+ if (result === "dropped") {
143
+ o.out.error(hubError(res.text, res.status));
144
+ return 1;
145
+ }
146
+ if (res.status === 204) {
147
+ o.out.error("\u8FD9\u4E2A\u4ED3\u5E93\u6CA1\u6709\u7ED1\u5B9A\u5230 Nema \u91CC\u4EFB\u4F55\u672A\u5F52\u6863\u7684 workspace\uFF0C\u4E0A\u62A5\u672A\u88AB\u63A5\u6536");
148
+ return 1;
149
+ }
150
+ o.out.log("\u5DF2\u4E0A\u62A5\u5230 Nema");
151
+ return 0;
152
+ }
153
+ export {
154
+ DEFAULT_HUB_URL,
155
+ resolveHubTarget,
156
+ resolveHubUrl,
157
+ runReport,
158
+ runReportHook
159
+ };
@@ -0,0 +1,3 @@
1
+ export declare const defaultDesktopHome: () => string;
2
+ /** The verified UI/API origin of the running desktop instance for this home, or null. Never throws. */
3
+ export declare function discoverDesktopOrigin(home?: string): Promise<string | null>;
@@ -0,0 +1,123 @@
1
+ // report/desktop.js
2
+ import { randomBytes } from "node:crypto";
3
+ import { closeSync, fstatSync, lstatSync, readFileSync, realpathSync } from "node:fs";
4
+ import http from "node:http";
5
+ import os from "node:os";
6
+ import path from "node:path";
7
+
8
+ // ../../../packages/shared/dist/localInstance.js
9
+ import { createHash, createHmac, timingSafeEqual } from "node:crypto";
10
+ var LOCAL_INSTANCE_PROTOCOL = "nema-local-v1";
11
+ var sha256 = (value) => createHash("sha256").update(value).digest();
12
+ var homeHashFor = (identity) => sha256(identity).toString("hex");
13
+ var controlPortFor = (identity) => 2e4 + sha256(identity).readUInt32BE(0) % 25e3;
14
+ var controlOriginFor = (port) => `http://127.0.0.1:${port}`;
15
+ var homeIdentityFor = (canonicalHome, platform = process.platform) => platform === "win32" ? canonicalHome.toLowerCase() : canonicalHome;
16
+ function instanceHmac(secret, tuple) {
17
+ return createHmac("sha256", Buffer.from(secret, "hex")).update(JSON.stringify(tuple)).digest();
18
+ }
19
+ function equalHex(left, right) {
20
+ if (!/^[0-9a-f]{64}$/i.test(left) || !/^[0-9a-f]{64}$/i.test(right))
21
+ return false;
22
+ return timingSafeEqual(Buffer.from(left, "hex"), Buffer.from(right, "hex"));
23
+ }
24
+
25
+ // report/desktop.js
26
+ import { openPrivateFile, privateModeMatches } from "../secrets/permissions.js";
27
+ var REQUEST_TIMEOUT_MS = 800;
28
+ var defaultDesktopHome = () => path.join(os.homedir(), ".nema", "desktop");
29
+ function loopbackOrigin(value) {
30
+ if (typeof value !== "string")
31
+ return false;
32
+ try {
33
+ const url = new URL(value);
34
+ return url.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname) && url.origin === value;
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+ function readDescriptor(file) {
40
+ try {
41
+ lstatSync(file);
42
+ } catch {
43
+ return null;
44
+ }
45
+ const fd = openPrivateFile(file);
46
+ try {
47
+ const stat = fstatSync(fd);
48
+ if (!stat.isFile() || !privateModeMatches(stat) || stat.size > 16384)
49
+ return null;
50
+ const raw = JSON.parse(readFileSync(fd, "utf8"));
51
+ if (raw.schemaVersion !== 1 || raw.protocol !== LOCAL_INSTANCE_PROTOCOL)
52
+ return null;
53
+ const { homeHash, instanceId, installationId, controlOrigin, origin, secret } = raw;
54
+ if (typeof homeHash !== "string" || !/^[0-9a-f]{64}$/.test(homeHash) || typeof instanceId !== "string" || !/^[0-9a-f-]{36}$/i.test(instanceId) || typeof installationId !== "string" || !/^[0-9a-f]{64}$/.test(installationId) || typeof controlOrigin !== "string" || !loopbackOrigin(origin) || typeof secret !== "string" || !/^[0-9a-f]{64}$/.test(secret))
55
+ return null;
56
+ return { homeHash, instanceId, installationId, controlOrigin, origin, secret };
57
+ } finally {
58
+ closeSync(fd);
59
+ }
60
+ }
61
+ function getProof(port, challenge) {
62
+ return new Promise((resolve) => {
63
+ const req = http.request({
64
+ host: "127.0.0.1",
65
+ port,
66
+ path: `/_nema/instance?challenge=${challenge}`,
67
+ method: "GET",
68
+ headers: { host: `127.0.0.1:${port}` },
69
+ timeout: REQUEST_TIMEOUT_MS
70
+ }, (res) => {
71
+ const chunks = [];
72
+ let size = 0;
73
+ res.on("data", (chunk) => {
74
+ size += chunk.length;
75
+ if (size > 4096)
76
+ res.destroy();
77
+ else
78
+ chunks.push(chunk);
79
+ });
80
+ res.on("error", () => resolve(null));
81
+ res.on("close", () => {
82
+ try {
83
+ resolve({ status: res.statusCode ?? 0, body: JSON.parse(Buffer.concat(chunks).toString("utf8")) });
84
+ } catch {
85
+ resolve(null);
86
+ }
87
+ });
88
+ });
89
+ req.on("timeout", () => {
90
+ req.destroy();
91
+ resolve(null);
92
+ });
93
+ req.on("error", () => resolve(null));
94
+ req.end();
95
+ });
96
+ }
97
+ async function discoverDesktopOrigin(home = defaultDesktopHome()) {
98
+ try {
99
+ const canonical = realpathSync.native(home);
100
+ const identity = homeIdentityFor(canonical);
101
+ const homeHash = homeHashFor(identity);
102
+ const port = controlPortFor(identity);
103
+ const controlOrigin = controlOriginFor(port);
104
+ const descriptor = readDescriptor(path.join(canonical, "instance.json"));
105
+ if (!descriptor || descriptor.homeHash !== homeHash || descriptor.controlOrigin !== controlOrigin)
106
+ return null;
107
+ const challenge = randomBytes(32).toString("hex");
108
+ const response = await getProof(port, challenge);
109
+ if (!response || response.status !== 200 || !response.body || typeof response.body !== "object")
110
+ return null;
111
+ const proof = response.body;
112
+ if (proof.protocol !== LOCAL_INSTANCE_PROTOCOL || proof.homeHash !== homeHash || proof.instanceId !== descriptor.instanceId || proof.installationId !== descriptor.installationId || proof.controlOrigin !== controlOrigin || proof.origin !== descriptor.origin || proof.challenge !== challenge || typeof proof.proof !== "string")
113
+ return null;
114
+ const expected = instanceHmac(descriptor.secret, [LOCAL_INSTANCE_PROTOCOL, homeHash, descriptor.instanceId, descriptor.installationId, controlOrigin, descriptor.origin, challenge]).toString("hex");
115
+ return equalHex(proof.proof, expected) ? descriptor.origin : null;
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+ export {
121
+ defaultDesktopHome,
122
+ discoverDesktopOrigin
123
+ };
@@ -0,0 +1,5 @@
1
+ export declare function repoInfo(cwd: string): Promise<{
2
+ gitCommonDir: string;
3
+ branch: string | null;
4
+ head: string;
5
+ } | null>;