@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.
- package/LICENSE +21 -0
- package/dist/chunks/chunk-HEIQINX3.js +825 -0
- package/dist/chunks/chunk-WT3XUFWU.js +86 -0
- package/dist/claude/adapter.d.ts +19 -0
- package/dist/claude/adapter.js +258 -0
- package/dist/claude/events.d.ts +29 -0
- package/dist/claude/events.js +75 -0
- package/dist/claude/launch.d.ts +22 -0
- package/dist/claude/launch.js +80 -0
- package/dist/claude/token.d.ts +9 -0
- package/dist/claude/token.js +87 -0
- package/dist/codex/adapter.d.ts +17 -0
- package/dist/codex/adapter.js +151 -0
- package/dist/codex/events.d.ts +16 -0
- package/dist/codex/events.js +47 -0
- package/dist/codex/isolation.d.ts +33 -0
- package/dist/codex/isolation.js +115 -0
- package/dist/config.d.ts +29 -0
- package/dist/config.js +75 -0
- package/dist/configCommands.d.ts +13 -0
- package/dist/configCommands.js +69 -0
- package/dist/daemon/cleanup.d.ts +5 -0
- package/dist/daemon/cleanup.js +49 -0
- package/dist/daemon/connection.d.ts +22 -0
- package/dist/daemon/connection.js +166 -0
- package/dist/daemon/rpc.d.ts +14 -0
- package/dist/daemon/rpc.js +20 -0
- package/dist/daemon/runner.d.ts +34 -0
- package/dist/daemon/runner.js +94 -0
- package/dist/daemon/setup.d.ts +39 -0
- package/dist/daemon/setup.js +95 -0
- package/dist/direct/adapter.d.ts +30 -0
- package/dist/direct/adapter.js +190 -0
- package/dist/direct/authCommands.d.ts +54 -0
- package/dist/direct/authCommands.js +268 -0
- package/dist/direct/authStore.d.ts +120 -0
- package/dist/direct/authStore.js +351 -0
- package/dist/direct/byok.d.ts +27 -0
- package/dist/direct/byok.js +67 -0
- package/dist/direct/codexLogin.d.ts +34 -0
- package/dist/direct/codexLogin.js +93 -0
- package/dist/direct/jwt.d.ts +4 -0
- package/dist/direct/jwt.js +22 -0
- package/dist/direct/lock.d.ts +16 -0
- package/dist/direct/lock.js +134 -0
- package/dist/direct/loop.d.ts +33 -0
- package/dist/direct/loop.js +110 -0
- package/dist/direct/siwc.d.ts +63 -0
- package/dist/direct/siwc.js +403 -0
- package/dist/direct/tools.d.ts +17 -0
- package/dist/direct/tools.js +104 -0
- package/dist/direct/transport.d.ts +177 -0
- package/dist/direct/transport.js +437 -0
- package/dist/doctor.d.ts +28 -0
- package/dist/doctor.js +178 -0
- package/dist/extensions/handlers.d.ts +2 -0
- package/dist/extensions/handlers.js +10 -0
- package/dist/extensions/store.d.ts +11 -0
- package/dist/extensions/store.js +132 -0
- package/dist/github/client.d.ts +47 -0
- package/dist/github/client.js +383 -0
- package/dist/github/gh.d.ts +56 -0
- package/dist/github/gh.js +385 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +144 -0
- package/dist/localSettings.d.ts +8 -0
- package/dist/localSettings.js +26 -0
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.js +83 -0
- package/dist/ownedProcess.d.ts +39 -0
- package/dist/ownedProcess.js +229 -0
- package/dist/platform.d.ts +10 -0
- package/dist/platform.js +60 -0
- package/dist/report/commands.d.ts +41 -0
- package/dist/report/commands.js +159 -0
- package/dist/report/desktop.d.ts +3 -0
- package/dist/report/desktop.js +123 -0
- package/dist/report/git.d.ts +5 -0
- package/dist/report/git.js +42 -0
- package/dist/report/install.d.ts +22 -0
- package/dist/report/install.js +183 -0
- package/dist/report/send.d.ts +35 -0
- package/dist/report/send.js +208 -0
- package/dist/report/token.d.ts +5 -0
- package/dist/report/token.js +86 -0
- package/dist/repos/git.d.ts +48 -0
- package/dist/repos/git.js +370 -0
- package/dist/repos/graphify.d.ts +32 -0
- package/dist/repos/graphify.js +292 -0
- package/dist/repos/handlers.d.ts +20 -0
- package/dist/repos/handlers.js +255 -0
- package/dist/repos/indexState.d.ts +10 -0
- package/dist/repos/indexState.js +45 -0
- package/dist/repos/pickDirectory.d.ts +11 -0
- package/dist/repos/pickDirectory.js +140 -0
- package/dist/repos/reportCheck.d.ts +13 -0
- package/dist/repos/reportCheck.js +268 -0
- package/dist/repos/watcher.d.ts +64 -0
- package/dist/repos/watcher.js +202 -0
- package/dist/room/client.d.ts +26 -0
- package/dist/room/client.js +76 -0
- package/dist/secrets/file.d.ts +12 -0
- package/dist/secrets/file.js +89 -0
- package/dist/secrets/github.d.ts +10 -0
- package/dist/secrets/github.js +29 -0
- package/dist/secrets/permissions.d.ts +7 -0
- package/dist/secrets/permissions.js +86 -0
- package/dist/systemGit.d.ts +14 -0
- package/dist/systemGit.js +75 -0
- package/dist/wp/commands.d.ts +15 -0
- package/dist/wp/commands.js +103 -0
- package/package.json +26 -4
- package/src/report/SKILL.md +21 -0
- package/README.md +0 -3
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// doctor.js
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { commandSpec } from "./platform.js";
|
|
8
|
+
import { requireSystemGit } from "./systemGit.js";
|
|
9
|
+
import { claudeTokenPath, readClaudeToken } from "./claude/token.js";
|
|
10
|
+
import { checkCodexAuth } from "./codex/isolation.js";
|
|
11
|
+
import { AuthStore, UNUSABLE_FILE_MESSAGE } from "./direct/authStore.js";
|
|
12
|
+
import { readByokKey } from "./direct/byok.js";
|
|
13
|
+
import { readCodexCliTokens } from "./direct/codexLogin.js";
|
|
14
|
+
import { jwtExp } from "./direct/jwt.js";
|
|
15
|
+
import { siwcRefresher } from "./direct/siwc.js";
|
|
16
|
+
import { graphifyInstallStatus } from "./repos/graphify.js";
|
|
17
|
+
import { REPORT_AGENTS, reportHookInstalled } from "./report/install.js";
|
|
18
|
+
import { reportTokenPath } from "./report/token.js";
|
|
19
|
+
import { githubTokenPath, readGithubToken } from "./secrets/github.js";
|
|
20
|
+
var execFileAsync = promisify(execFile);
|
|
21
|
+
function defaultTokenFile(env = process.env) {
|
|
22
|
+
const raw = env.NEMA_DATA_DIR || path.join(os.homedir(), ".nema", "data");
|
|
23
|
+
const dir = raw === "~" ? os.homedir() : raw.startsWith("~/") ? path.join(os.homedir(), raw.slice(2)) : raw;
|
|
24
|
+
return path.join(dir, "daemon-token");
|
|
25
|
+
}
|
|
26
|
+
var msg = (e) => e instanceof Error ? e.message : String(e);
|
|
27
|
+
async function version(bin, args) {
|
|
28
|
+
const command = commandSpec(bin, args);
|
|
29
|
+
const { stdout } = await execFileAsync(command.bin, command.args, { timeout: 1e4, windowsHide: true });
|
|
30
|
+
return stdout.trim().split("\n")[0] ?? "";
|
|
31
|
+
}
|
|
32
|
+
function directLoginCheck(opts) {
|
|
33
|
+
const name = "\u76F4\u8FDE\u767B\u5F55";
|
|
34
|
+
const required = opts.adapter === "codex-direct" && (opts.authMode ?? "siwc") !== "byok";
|
|
35
|
+
if (opts.authMode === "byok")
|
|
36
|
+
return { name, ok: true, detail: "\u672A\u68C0\u67E5\uFF08authMode=byok \u4F7F\u7528 BYOK key\uFF09" };
|
|
37
|
+
if ((opts.authMode ?? "siwc") === "compat") {
|
|
38
|
+
try {
|
|
39
|
+
const t = readCodexCliTokens(opts.userCodexHome);
|
|
40
|
+
const tail = t.accountId ? `\uFF0C\u8D26\u53F7 \u2026${t.accountId.slice(-4)}` : "";
|
|
41
|
+
const exp2 = t.exp === null ? "\u5230\u671F\u65F6\u95F4\u672A\u77E5" : `\u5230\u671F ${new Date(t.exp * 1e3).toISOString()}`;
|
|
42
|
+
return { name, ok: true, detail: `\u5DF2\u767B\u5F55\uFF08compat\uFF0C\u8BFB\u53D6 Codex \u767B\u5F55${tail}\uFF0C${exp2}\uFF09` };
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return { name, ok: !required, detail: e instanceof Error ? e.message : String(e) };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const { auth, reason } = new AuthStore(opts.nemaHome, siwcRefresher).inspect();
|
|
48
|
+
if (!auth)
|
|
49
|
+
return { name, ok: !required, detail: reason === "unusable" ? UNUSABLE_FILE_MESSAGE : "\u672A\u767B\u5F55" };
|
|
50
|
+
if (auth.tokens.refresh_token === "")
|
|
51
|
+
return { name, ok: !required, detail: "\u5DF2\u5931\u6548\uFF0C\u8BF7\u8FD0\u884C nema auth login" };
|
|
52
|
+
const exp = jwtExp(auth.tokens.access_token);
|
|
53
|
+
return { name, ok: true, detail: `\u5DF2\u767B\u5F55\uFF08${auth.mode}\uFF0C${exp === null ? "\u5230\u671F\u65F6\u95F4\u672A\u77E5" : `\u5230\u671F ${new Date(exp * 1e3).toISOString()}`}\uFF09` };
|
|
54
|
+
}
|
|
55
|
+
var CLAUDE_ONLY = "\u672A\u68C0\u67E5\uFF08\u4EC5 adapter=claude-code \u9700\u8981\uFF09";
|
|
56
|
+
var CLAUDE_MISSING = "\u627E\u4E0D\u5230 claude\u3002\u8BF7\u5148\u5B89\u88C5 Claude Code \u5E76\u628A\u5B83\u653E\u5230 PATH \u4E0A\u3002";
|
|
57
|
+
async function claudeBinaryCheck(opts) {
|
|
58
|
+
const name = "claude \u53EF\u7528";
|
|
59
|
+
if (opts.adapter !== "claude-code")
|
|
60
|
+
return { name, ok: true, detail: CLAUDE_ONLY };
|
|
61
|
+
try {
|
|
62
|
+
return { name, ok: true, detail: await version("claude", ["--version"]) };
|
|
63
|
+
} catch {
|
|
64
|
+
return { name, ok: false, detail: CLAUDE_MISSING };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function claudeTokenCheck(opts) {
|
|
68
|
+
const name = "Claude Code token";
|
|
69
|
+
if (opts.adapter !== "claude-code")
|
|
70
|
+
return { name, ok: true, detail: CLAUDE_ONLY };
|
|
71
|
+
try {
|
|
72
|
+
readClaudeToken(opts.nemaHome);
|
|
73
|
+
return { name, ok: true, detail: `${process.platform === "win32" ? "\u79C1\u6709 Windows ACL" : "\u6743\u9650\u4E3A 0600"}\uFF08${claudeTokenPath(opts.nemaHome)}\uFF09` };
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return { name, ok: false, detail: msg(e) };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function graphifyCheck(opts) {
|
|
79
|
+
const name = "graphify \u53EF\u7528";
|
|
80
|
+
try {
|
|
81
|
+
const status = await graphifyInstallStatus(opts.graphify);
|
|
82
|
+
return { name, ok: true, detail: status.detail };
|
|
83
|
+
} catch (e) {
|
|
84
|
+
return { name, ok: true, detail: msg(e) };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function byokCheck(opts) {
|
|
88
|
+
const name = "BYOK";
|
|
89
|
+
if (opts.adapter !== "codex-direct" || opts.authMode !== "byok") {
|
|
90
|
+
return { name, ok: true, detail: "\u672A\u542F\u7528\uFF08\u4EC5 authMode=byok \u9700\u8981\uFF09" };
|
|
91
|
+
}
|
|
92
|
+
const problems = [];
|
|
93
|
+
try {
|
|
94
|
+
readByokKey(opts.nemaHome);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
problems.push(msg(e));
|
|
97
|
+
}
|
|
98
|
+
if (!opts.model)
|
|
99
|
+
problems.push("\u672A\u8BBE\u7F6E model\uFF0C\u8BF7\u8FD0\u884C nema config set model <\u540D\u79F0>");
|
|
100
|
+
return {
|
|
101
|
+
name,
|
|
102
|
+
ok: problems.length === 0,
|
|
103
|
+
detail: problems.length === 0 ? `${process.platform === "win32" ? "key \u4F7F\u7528\u79C1\u6709 Windows ACL" : "key \u6587\u4EF6\u6743\u9650\u4E3A 0600"}\uFF0Cmodel \u5DF2\u8BBE\u7F6E` : problems.join("\uFF1B")
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
var GITHUB_MCP_SERVER_HINT = "https://github.com/github/github-mcp-server/releases";
|
|
107
|
+
async function githubMcpServerCheck(opts) {
|
|
108
|
+
const name = "github-mcp-server";
|
|
109
|
+
try {
|
|
110
|
+
return { name, ok: true, detail: `\u53EF\u7528\uFF1A${await version(opts.githubMcpServerBin ?? "github-mcp-server", ["--version"])}` };
|
|
111
|
+
} catch {
|
|
112
|
+
return { name, ok: true, detail: `\u672A\u5B89\u88C5\uFF1A\u8BF7\u4ECE ${GITHUB_MCP_SERVER_HINT} \u4E0B\u8F7D` };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function githubTokenCheck(opts) {
|
|
116
|
+
const name = "GitHub \u4EE4\u724C";
|
|
117
|
+
if (!existsSync(githubTokenPath(opts.nemaHome)))
|
|
118
|
+
return { name, ok: true, detail: "\u4E0D\u5B58\u5728\uFF08\u53EF\u8FD0\u884C nema auth github\uFF09" };
|
|
119
|
+
try {
|
|
120
|
+
readGithubToken(opts.nemaHome);
|
|
121
|
+
return { name, ok: true, detail: `\u5B58\u5728\uFF0C${process.platform === "win32" ? "\u79C1\u6709 Windows ACL" : "\u6743\u9650\u4E3A 0600"}\uFF08${githubTokenPath(opts.nemaHome)}\uFF09` };
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return { name, ok: true, detail: msg(e) };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function runDoctor(opts) {
|
|
127
|
+
const checks = [];
|
|
128
|
+
try {
|
|
129
|
+
checks.push({ name: "git \u53EF\u7528", ok: true, detail: await requireSystemGit() });
|
|
130
|
+
} catch (e) {
|
|
131
|
+
checks.push({ name: "git \u53EF\u7528", ok: false, detail: msg(e) });
|
|
132
|
+
}
|
|
133
|
+
const fileRequired = opts.adapter === "codex-direct" && (opts.authMode ?? "siwc") === "compat";
|
|
134
|
+
try {
|
|
135
|
+
checks.push({ name: "codex \u53EF\u7528", ok: true, detail: await version(opts.codexBin, ["--version"]) });
|
|
136
|
+
} catch (e) {
|
|
137
|
+
checks.push({ name: "codex \u53EF\u7528", ok: true, detail: `\u672A\u5B89\u88C5\uFF08Codex \u5DF2\u6539\u4E3A\u76F4\u8FDE\uFF0C\u4E0D\u542F\u52A8 CLI\uFF09\uFF1A${msg(e)}` });
|
|
138
|
+
}
|
|
139
|
+
const auth = checkCodexAuth(opts.userCodexHome);
|
|
140
|
+
checks.push({
|
|
141
|
+
name: "Codex \u767B\u5F55\u6587\u4EF6",
|
|
142
|
+
ok: auth.ok || !fileRequired,
|
|
143
|
+
detail: auth.ok ? auth.authPath : fileRequired ? auth.error : "\u672A\u627E\u5230\uFF08\u4EC5 authMode=compat \u9700\u8981\uFF09"
|
|
144
|
+
});
|
|
145
|
+
checks.push(directLoginCheck(opts));
|
|
146
|
+
checks.push(byokCheck(opts));
|
|
147
|
+
const statusUrl = `${opts.hubUrl.replace(/\/+$/, "")}/api/daemon/status`;
|
|
148
|
+
try {
|
|
149
|
+
const res = await fetch(statusUrl, { signal: AbortSignal.timeout(3e3) });
|
|
150
|
+
if (!res.ok)
|
|
151
|
+
throw new Error(`HTTP ${res.status}`);
|
|
152
|
+
const body = await res.json();
|
|
153
|
+
checks.push({ name: "hub \u53EF\u8FBE", ok: true, detail: `${opts.hubUrl}\uFF08daemon ${body.connected ? "\u5DF2\u8FDE\u63A5" : "\u672A\u8FDE\u63A5"}\uFF09` });
|
|
154
|
+
} catch (e) {
|
|
155
|
+
checks.push({ name: "hub \u53EF\u8FBE", ok: false, detail: `\u65E0\u6CD5\u8BBF\u95EE ${statusUrl}\uFF1A${msg(e)}\u3002\u8BF7\u5148\u8FD0\u884C npm run dev` });
|
|
156
|
+
}
|
|
157
|
+
const hasToken = existsSync(opts.tokenFile);
|
|
158
|
+
checks.push({
|
|
159
|
+
name: "daemon token \u6587\u4EF6",
|
|
160
|
+
ok: hasToken,
|
|
161
|
+
detail: hasToken ? opts.tokenFile : `\u627E\u4E0D\u5230 ${opts.tokenFile}\u3002\u8BF7\u5148\u542F\u52A8 hub\uFF08\u5B83\u4F1A\u751F\u6210\u8BE5\u6587\u4EF6\uFF09`
|
|
162
|
+
});
|
|
163
|
+
checks.push(await claudeBinaryCheck(opts));
|
|
164
|
+
checks.push(claudeTokenCheck(opts));
|
|
165
|
+
checks.push(await graphifyCheck(opts));
|
|
166
|
+
const agentHome = opts.agentHome ?? os.homedir();
|
|
167
|
+
for (const agent of REPORT_AGENTS) {
|
|
168
|
+
checks.push({ name: `\u4E0A\u62A5\u94A9\u5B50\uFF1A${agent}`, ok: true, detail: reportHookInstalled(agent, agentHome) ? "\u5DF2\u5B89\u88C5" : "\u672A\u5B89\u88C5" });
|
|
169
|
+
}
|
|
170
|
+
checks.push({ name: "\u4E0A\u62A5\u4EE4\u724C", ok: true, detail: existsSync(reportTokenPath(opts.nemaHome)) ? "\u5B58\u5728" : "\u4E0D\u5B58\u5728" });
|
|
171
|
+
checks.push(await githubMcpServerCheck(opts));
|
|
172
|
+
checks.push(githubTokenCheck(opts));
|
|
173
|
+
return checks;
|
|
174
|
+
}
|
|
175
|
+
export {
|
|
176
|
+
defaultTokenFile,
|
|
177
|
+
runDoctor
|
|
178
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// extensions/handlers.js
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { openExtensionStore } from "./store.js";
|
|
4
|
+
function registerExtensionHandlers(router, nemaHome) {
|
|
5
|
+
const store = openExtensionStore(path.join(nemaHome, "extensions"));
|
|
6
|
+
router.register("extensions.list", async () => store.list());
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
registerExtensionHandlers
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ExtensionRecord } from "@nema/shared";
|
|
2
|
+
export type { Availability, ExtensionKind, ExtensionRecord, InstallState, Provision, UseState } from "@nema/shared";
|
|
3
|
+
export { bundledExtensions } from "@nema/shared";
|
|
4
|
+
export declare function openExtensionStore(root: string): {
|
|
5
|
+
list(): ExtensionRecord[];
|
|
6
|
+
putManaged(record: ExtensionRecord): void;
|
|
7
|
+
removeManaged(id: string): void;
|
|
8
|
+
detach(id: string): void;
|
|
9
|
+
installManaged(record: ExtensionRecord, stagedDir: string): void;
|
|
10
|
+
cancelInstall(id: string): void;
|
|
11
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bundledExtensions
|
|
3
|
+
} from "../chunks/chunk-HEIQINX3.js";
|
|
4
|
+
|
|
5
|
+
// extensions/store.js
|
|
6
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
var ID = /^[a-z][a-z0-9-]{0,63}$/;
|
|
9
|
+
var BUNDLED_IDS = new Set(bundledExtensions().map((row) => row.id));
|
|
10
|
+
function assertId(id) {
|
|
11
|
+
if (!ID.test(id) || BUNDLED_IDS.has(id))
|
|
12
|
+
throw new Error(`invalid extension id: ${id}`);
|
|
13
|
+
}
|
|
14
|
+
function indexPath(root) {
|
|
15
|
+
return path.join(root, "index.json");
|
|
16
|
+
}
|
|
17
|
+
function readExtra(root) {
|
|
18
|
+
const file = indexPath(root);
|
|
19
|
+
if (!existsSync(file))
|
|
20
|
+
return [];
|
|
21
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
22
|
+
if (!Array.isArray(parsed))
|
|
23
|
+
return [];
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
function writeExtra(root, rows) {
|
|
27
|
+
mkdirSync(root, { recursive: true });
|
|
28
|
+
writeFileSync(indexPath(root), JSON.stringify(rows));
|
|
29
|
+
}
|
|
30
|
+
function managedPath(root, id) {
|
|
31
|
+
const extensions = path.resolve(root, "extensions");
|
|
32
|
+
const target = path.resolve(extensions, id);
|
|
33
|
+
if (path.dirname(target) !== extensions)
|
|
34
|
+
throw new Error("invalid extension id");
|
|
35
|
+
return { extensions, target };
|
|
36
|
+
}
|
|
37
|
+
function ownedDirectory(root, id) {
|
|
38
|
+
const { extensions, target } = managedPath(root, id);
|
|
39
|
+
mkdirSync(extensions, { recursive: true });
|
|
40
|
+
const realExtensions = realpathSync(extensions);
|
|
41
|
+
if (!existsSync(target))
|
|
42
|
+
return target;
|
|
43
|
+
const stat = lstatSync(target);
|
|
44
|
+
const real = realpathSync(target);
|
|
45
|
+
const relative = path.relative(realExtensions, real);
|
|
46
|
+
const inside = relative === id || relative.startsWith(id + path.sep);
|
|
47
|
+
if (stat.isSymbolicLink() || !inside)
|
|
48
|
+
throw new Error("extension directory points outside the managed root");
|
|
49
|
+
return real;
|
|
50
|
+
}
|
|
51
|
+
function openExtensionStore(root) {
|
|
52
|
+
return {
|
|
53
|
+
list() {
|
|
54
|
+
const extra = readExtra(root).filter((row) => !BUNDLED_IDS.has(row.id));
|
|
55
|
+
return [...bundledExtensions(), ...extra];
|
|
56
|
+
},
|
|
57
|
+
putManaged(record) {
|
|
58
|
+
assertId(record.id);
|
|
59
|
+
if (record.provision === "managed") {
|
|
60
|
+
const target = ownedDirectory(root, record.id);
|
|
61
|
+
if (!existsSync(target))
|
|
62
|
+
mkdirSync(target);
|
|
63
|
+
} else if (record.provision !== "external")
|
|
64
|
+
throw new Error("bundled extensions are not installed here");
|
|
65
|
+
const rows = readExtra(root).filter((row) => row.id !== record.id);
|
|
66
|
+
rows.push(record);
|
|
67
|
+
writeExtra(root, rows);
|
|
68
|
+
},
|
|
69
|
+
removeManaged(id) {
|
|
70
|
+
const current = readExtra(root).find((row) => row.id === id);
|
|
71
|
+
if (!current) {
|
|
72
|
+
if (BUNDLED_IDS.has(id))
|
|
73
|
+
throw new Error("bundled extensions cannot be removed");
|
|
74
|
+
throw new Error("extension is not managed");
|
|
75
|
+
}
|
|
76
|
+
if (current.provision === "bundled")
|
|
77
|
+
throw new Error("bundled extensions cannot be removed");
|
|
78
|
+
if (current.provision !== "managed")
|
|
79
|
+
throw new Error("extension is not managed");
|
|
80
|
+
const target = ownedDirectory(root, id);
|
|
81
|
+
if (existsSync(target))
|
|
82
|
+
rmSync(target, { recursive: true, force: true });
|
|
83
|
+
writeExtra(root, readExtra(root).filter((row) => row.id !== id));
|
|
84
|
+
},
|
|
85
|
+
detach(id) {
|
|
86
|
+
const current = readExtra(root).find((row) => row.id === id);
|
|
87
|
+
if (!current || current.provision !== "external")
|
|
88
|
+
throw new Error("extension is not external");
|
|
89
|
+
writeExtra(root, readExtra(root).filter((row) => row.id !== id));
|
|
90
|
+
},
|
|
91
|
+
installManaged(record, stagedDir) {
|
|
92
|
+
assertId(record.id);
|
|
93
|
+
if (record.provision !== "managed")
|
|
94
|
+
throw new Error("only a managed extension can be installed");
|
|
95
|
+
if (!existsSync(stagedDir) || !lstatSync(stagedDir).isDirectory()) {
|
|
96
|
+
throw new Error("staged extension directory is missing");
|
|
97
|
+
}
|
|
98
|
+
const { extensions, target } = managedPath(root, record.id);
|
|
99
|
+
mkdirSync(extensions, { recursive: true });
|
|
100
|
+
const incoming = path.join(extensions, `.${record.id}.incoming`);
|
|
101
|
+
rmSync(incoming, { recursive: true, force: true });
|
|
102
|
+
try {
|
|
103
|
+
cpSync(stagedDir, incoming, { recursive: true });
|
|
104
|
+
if (existsSync(target)) {
|
|
105
|
+
ownedDirectory(root, record.id);
|
|
106
|
+
rmSync(target, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
renameSync(incoming, target);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
rmSync(incoming, { recursive: true, force: true });
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
const rows = readExtra(root).filter((row) => row.id !== record.id);
|
|
114
|
+
rows.push({ ...record, provision: "managed", installState: "installed", error: null });
|
|
115
|
+
writeExtra(root, rows);
|
|
116
|
+
},
|
|
117
|
+
cancelInstall(id) {
|
|
118
|
+
assertId(id);
|
|
119
|
+
const current = readExtra(root).find((row) => row.id === id);
|
|
120
|
+
if (!current || current.installState !== "installing")
|
|
121
|
+
throw new Error("extension is not installing");
|
|
122
|
+
const target = ownedDirectory(root, id);
|
|
123
|
+
if (existsSync(target))
|
|
124
|
+
rmSync(target, { recursive: true, force: true });
|
|
125
|
+
writeExtra(root, readExtra(root).map((row) => row.id === id ? { ...row, installState: "absent", error: null } : row));
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
bundledExtensions,
|
|
131
|
+
openExtensionStore
|
|
132
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { OwnedHelperScope } from "@nema/shared/ownedHelper";
|
|
2
|
+
export declare const GITHUB_ARGS: readonly ["stdio", "--read-only", "--toolsets", "pull_requests,issues"];
|
|
3
|
+
/**
|
|
4
|
+
* The fixed tool list this MCP server provides, from the `github` manifest. Nothing else is ever called.
|
|
5
|
+
* The `gh_read` / `gh_write` command tools run through the `gh` CLI, not this server.
|
|
6
|
+
*/
|
|
7
|
+
export declare const GITHUB_TOOLS: readonly string[];
|
|
8
|
+
export type GithubMcpOpts = {
|
|
9
|
+
nemaHome: string;
|
|
10
|
+
/** Defaults to `github-mcp-server` from PATH. */
|
|
11
|
+
bin?: string;
|
|
12
|
+
/** Overrides the PATH the child sees; defaults to the daemon's PATH. */
|
|
13
|
+
spawnEnv?: {
|
|
14
|
+
PATH?: string;
|
|
15
|
+
};
|
|
16
|
+
/** For tests. */
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
retryDelayMs?: number;
|
|
19
|
+
warn?: (message: string) => void;
|
|
20
|
+
ownedHelpers?: OwnedHelperScope;
|
|
21
|
+
};
|
|
22
|
+
export declare class GithubMcp {
|
|
23
|
+
private readonly opts;
|
|
24
|
+
private conn;
|
|
25
|
+
private starting;
|
|
26
|
+
private startError;
|
|
27
|
+
private closed;
|
|
28
|
+
private closing?;
|
|
29
|
+
private startingTransport?;
|
|
30
|
+
constructor(opts: GithubMcpOpts);
|
|
31
|
+
private get bin();
|
|
32
|
+
private homeDir;
|
|
33
|
+
private basePath;
|
|
34
|
+
private warn;
|
|
35
|
+
/** `installed`: the binary runs. `loggedIn`: the token file is readable. Never starts the server. */
|
|
36
|
+
status(): Promise<{
|
|
37
|
+
installed: boolean;
|
|
38
|
+
loggedIn: boolean;
|
|
39
|
+
detail: string;
|
|
40
|
+
}>;
|
|
41
|
+
private version;
|
|
42
|
+
/** Calls one fixed read tool and returns its text content, capped at 64 KB. */
|
|
43
|
+
call(tool: string, args: object): Promise<unknown>;
|
|
44
|
+
private ensure;
|
|
45
|
+
private start;
|
|
46
|
+
close(): Promise<void>;
|
|
47
|
+
}
|