@akon-labs/nexus-cli 0.1.10 → 0.1.11
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/dist/cli/require-login.d.ts.map +1 -1
- package/dist/cli/require-login.js +2 -2
- package/dist/cli/require-login.js.map +1 -1
- package/dist/cli.js +2492 -1088
- package/dist/core/agent-session.d.ts +3 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +6 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +7 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +14 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +18 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/graph-rail-store.d.ts +10 -0
- package/dist/core/graph-rail-store.d.ts.map +1 -1
- package/dist/core/graph-rail-store.js +9 -0
- package/dist/core/graph-rail-store.js.map +1 -1
- package/dist/core/keybindings.d.ts +2 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +4 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/extensions/gitnexus/client.d.ts.map +1 -1
- package/dist/extensions/gitnexus/client.js +45 -14
- package/dist/extensions/gitnexus/client.js.map +1 -1
- package/dist/extensions/gitnexus/config.d.ts +15 -1
- package/dist/extensions/gitnexus/config.d.ts.map +1 -1
- package/dist/extensions/gitnexus/config.js +86 -2
- package/dist/extensions/gitnexus/config.js.map +1 -1
- package/dist/extensions/gitnexus/hub-api.d.ts +56 -0
- package/dist/extensions/gitnexus/hub-api.d.ts.map +1 -0
- package/dist/extensions/gitnexus/hub-api.js +105 -0
- package/dist/extensions/gitnexus/hub-api.js.map +1 -0
- package/dist/extensions/gitnexus/index-repo.d.ts +50 -0
- package/dist/extensions/gitnexus/index-repo.d.ts.map +1 -0
- package/dist/extensions/gitnexus/index-repo.js +162 -0
- package/dist/extensions/gitnexus/index-repo.js.map +1 -0
- package/dist/extensions/gitnexus/index.d.ts.map +1 -1
- package/dist/extensions/gitnexus/index.js +137 -17
- package/dist/extensions/gitnexus/index.js.map +1 -1
- package/dist/extensions/gitnexus/observatory.d.ts.map +1 -1
- package/dist/extensions/gitnexus/observatory.js +2 -2
- package/dist/extensions/gitnexus/observatory.js.map +1 -1
- package/dist/extensions/gitnexus/resolve.d.ts +9 -0
- package/dist/extensions/gitnexus/resolve.d.ts.map +1 -1
- package/dist/extensions/gitnexus/resolve.js +52 -2
- package/dist/extensions/gitnexus/resolve.js.map +1 -1
- package/dist/extensions/index.d.ts.map +1 -1
- package/dist/extensions/index.js +3 -0
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/mcp/commands.d.ts +22 -2
- package/dist/extensions/mcp/commands.d.ts.map +1 -1
- package/dist/extensions/mcp/commands.js +201 -9
- package/dist/extensions/mcp/commands.js.map +1 -1
- package/dist/extensions/mcp/config.d.ts +2 -0
- package/dist/extensions/mcp/config.d.ts.map +1 -1
- package/dist/extensions/mcp/config.js +6 -1
- package/dist/extensions/mcp/config.js.map +1 -1
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +54 -5
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/mcp-setup-panel.d.ts +63 -2
- package/dist/extensions/mcp/mcp-setup-panel.d.ts.map +1 -1
- package/dist/extensions/mcp/mcp-setup-panel.js +563 -77
- package/dist/extensions/mcp/mcp-setup-panel.js.map +1 -1
- package/dist/extensions/subagent/index.d.ts +2 -1
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +19 -6
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent-render.d.ts +3 -0
- package/dist/extensions/subagent-render.d.ts.map +1 -0
- package/dist/extensions/subagent-render.js +39 -0
- package/dist/extensions/subagent-render.js.map +1 -0
- package/dist/index.js +2524 -1120
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +2 -0
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/command-palette.d.ts +2 -1
- package/dist/modes/interactive/components/command-palette.d.ts.map +1 -1
- package/dist/modes/interactive/components/command-palette.js +11 -4
- package/dist/modes/interactive/components/command-palette.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +9 -3
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/graph-rail.d.ts.map +1 -1
- package/dist/modes/interactive/components/graph-rail.js +18 -3
- package/dist/modes/interactive/components/graph-rail.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +1 -0
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +7 -1
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/landing-panel.d.ts.map +1 -1
- package/dist/modes/interactive/components/landing-panel.js +15 -2
- package/dist/modes/interactive/components/landing-panel.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +3 -2
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +42 -8
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/rpc-entry.js +2492 -1088
- package/dist/utils/hidden-spawn.d.ts +29 -0
- package/dist/utils/hidden-spawn.d.ts.map +1 -0
- package/dist/utils/hidden-spawn.js +101 -0
- package/dist/utils/hidden-spawn.js.map +1 -0
- package/dist/vendor/ai.js +233 -233
- package/dist/vendor/tui.js +1 -1
- package/native/win32/build.mjs +95 -0
- package/native/win32/prebuilds/win32-x64/hidden-spawn.exe +0 -0
- package/native/win32/src/hidden-spawn.c +157 -0
- package/npm-shrinkwrap.json +2 -37
- package/package.json +7 -3
|
@@ -8,13 +8,15 @@ import { applyNexusSubagentEnvAliases, isDelegatedWorkerProcess, isExternalDeleg
|
|
|
8
8
|
import { registerAugmentedGrep } from "./augment.js";
|
|
9
9
|
import { registerChangeTracking } from "./changes.js";
|
|
10
10
|
import { GitNexusClient, GitNexusClientError } from "./client.js";
|
|
11
|
-
import {
|
|
11
|
+
import { resolveGitNexusConfig, resolveHubUrl, saveGitNexusConfig } from "./config.js";
|
|
12
12
|
import { defaultDeviceName, deviceFingerprint, pollDeviceAuth, startDeviceAuth } from "./device-auth.js";
|
|
13
13
|
import { formatCreditBalance, readGatewayCache, refreshGatewayPolicy, registerAutoProvider } from "./gateway.js";
|
|
14
|
+
import { GitNexusHubApi } from "./hub-api.js";
|
|
15
|
+
import { monitorRepoIndex, runIndexRepoCommand } from "./index-repo.js";
|
|
14
16
|
import { spawnObservatoryFetch } from "./observatory.js";
|
|
15
17
|
import { buildGitNexusPrompt } from "./prompt.js";
|
|
16
18
|
import { withGitNexusRenderer } from "./render.js";
|
|
17
|
-
import { gitRemoteSlug, matchCurrentRepo, parseRepoInventory, } from "./resolve.js";
|
|
19
|
+
import { currentGitHubRemote, gitRemoteSlug, matchCurrentRepo, parseRepoInventory, } from "./resolve.js";
|
|
18
20
|
import { createGitNexusTools } from "./tools.js";
|
|
19
21
|
/** Resolve workflow skills in source mode and bundled dist/cli.js mode. */
|
|
20
22
|
export function resolveGitNexusSkillsDir(moduleUrl = import.meta.url, pathExists = existsSync) {
|
|
@@ -41,6 +43,7 @@ export default function gitnexusExtension(pi) {
|
|
|
41
43
|
let delegationRegistered = false;
|
|
42
44
|
let startupNotice;
|
|
43
45
|
let loginRequired = false;
|
|
46
|
+
let indexMonitorController;
|
|
44
47
|
const cwd = process.cwd();
|
|
45
48
|
// Nexus Agent must also register at factory time: the initial model is
|
|
46
49
|
// resolved before session_start fires, and a provider that appears after
|
|
@@ -69,31 +72,47 @@ export default function gitnexusExtension(pi) {
|
|
|
69
72
|
// behaves as plain grep until connect() provides a client + current repo.
|
|
70
73
|
registerAugmentedGrep(pi, cwd, () => activeClient, () => current?.name);
|
|
71
74
|
registerChangeTracking(pi, cwd);
|
|
72
|
-
|
|
75
|
+
function clearConnectionState(notice, requiresLogin = false) {
|
|
73
76
|
inventory = undefined;
|
|
74
77
|
current = undefined;
|
|
75
78
|
activeClient = undefined;
|
|
76
|
-
startupNotice =
|
|
77
|
-
loginRequired =
|
|
79
|
+
startupNotice = notice;
|
|
80
|
+
loginRequired = requiresLogin;
|
|
81
|
+
}
|
|
82
|
+
async function connect(signal) {
|
|
83
|
+
signal?.throwIfAborted();
|
|
78
84
|
const config = resolveGitNexusConfig();
|
|
79
|
-
if (!config)
|
|
85
|
+
if (!config) {
|
|
86
|
+
clearConnectionState();
|
|
80
87
|
return;
|
|
88
|
+
}
|
|
81
89
|
const client = new GitNexusClient(config);
|
|
90
|
+
let nextInventory;
|
|
82
91
|
try {
|
|
83
|
-
const listResult = await client.callTool("list_repos", {});
|
|
84
|
-
|
|
92
|
+
const listResult = await client.callTool("list_repos", {}, signal);
|
|
93
|
+
signal?.throwIfAborted();
|
|
94
|
+
nextInventory = parseRepoInventory(GitNexusClient.text(listResult));
|
|
85
95
|
}
|
|
86
96
|
catch (error) {
|
|
97
|
+
signal?.throwIfAborted();
|
|
87
98
|
const reason = error instanceof GitNexusClientError ? error.message : String(error);
|
|
88
99
|
if (error instanceof GitNexusClientError && error.kind === "auth") {
|
|
89
|
-
|
|
100
|
+
clearConnectionState(undefined, true);
|
|
90
101
|
return;
|
|
91
102
|
}
|
|
92
|
-
|
|
103
|
+
clearConnectionState(`GitNexus unavailable: ${reason}`);
|
|
93
104
|
return;
|
|
94
105
|
}
|
|
95
|
-
|
|
106
|
+
const nextCurrent = matchCurrentRepo(nextInventory, gitRemoteSlug(cwd));
|
|
107
|
+
const nextStartupNotice = nextCurrent
|
|
108
|
+
? `GitNexus: connected — ${nextCurrent.name} (${nextCurrent.symbols} symbols, indexed ${nextCurrent.indexedAt}), ${nextInventory.repos.length} repos available.`
|
|
109
|
+
: `GitNexus: connected — current directory is not indexed; ${nextInventory.repos.length} indexed repos remain queryable. Run /gitnexus-index to index this repo and unlock local graph context.`;
|
|
110
|
+
signal?.throwIfAborted();
|
|
111
|
+
inventory = nextInventory;
|
|
112
|
+
current = nextCurrent;
|
|
96
113
|
activeClient = client;
|
|
114
|
+
startupNotice = nextStartupNotice;
|
|
115
|
+
loginRequired = false;
|
|
97
116
|
graphRailStore.setRepoList(inventory.repos.map((repo) => ({ name: repo.name, symbols: repo.symbols })));
|
|
98
117
|
// Observatory (community map) rides a detached subprocess + temp-file
|
|
99
118
|
// handoff — in-process hub calls made after startup can starve in some
|
|
@@ -122,15 +141,108 @@ export default function gitnexusExtension(pi) {
|
|
|
122
141
|
pi.registerTool(withGitNexusRenderer(tool));
|
|
123
142
|
toolsRegistered = true;
|
|
124
143
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
144
|
+
}
|
|
145
|
+
async function requestRepositoryIndex(targetCwd, branch, ui) {
|
|
146
|
+
if (/\s/.test(branch)) {
|
|
147
|
+
const message = "Branch names containing whitespace are not supported.";
|
|
148
|
+
ui.notify(message, "error");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const config = resolveGitNexusConfig();
|
|
152
|
+
if (!config) {
|
|
153
|
+
const message = "GitNexus is not signed in. Run /login.";
|
|
154
|
+
ui.notify(message, "warning");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const remote = currentGitHubRemote(targetCwd);
|
|
158
|
+
if (!remote) {
|
|
159
|
+
const message = `No supported github.com origin was found in ${targetCwd}.`;
|
|
160
|
+
ui.notify(message, "error");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
indexMonitorController?.abort();
|
|
164
|
+
graphRailStore.setIndexProgress(undefined);
|
|
165
|
+
const controller = new AbortController();
|
|
166
|
+
indexMonitorController = controller;
|
|
167
|
+
const releaseController = () => {
|
|
168
|
+
if (indexMonitorController === controller)
|
|
169
|
+
indexMonitorController = undefined;
|
|
170
|
+
};
|
|
171
|
+
try {
|
|
172
|
+
const api = new GitNexusHubApi(config);
|
|
173
|
+
const result = await runIndexRepoCommand({ api, remote, branch, ui, signal: controller.signal });
|
|
174
|
+
if (controller.signal.aborted || indexMonitorController !== controller) {
|
|
175
|
+
releaseController();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (result.kind === "blocked") {
|
|
179
|
+
releaseController();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (result.kind === "cancelled") {
|
|
183
|
+
releaseController();
|
|
184
|
+
ui.notify("Repository indexing cancelled.", "info");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (result.kind === "ready") {
|
|
188
|
+
const message = `${remote.fullName} is already indexed; no reindex was started.`;
|
|
189
|
+
ui.notify(message, "info");
|
|
190
|
+
await connect(controller.signal);
|
|
191
|
+
releaseController();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
void monitorRepoIndex({
|
|
195
|
+
api,
|
|
196
|
+
repoId: result.repoId,
|
|
197
|
+
ui: {
|
|
198
|
+
notify: (message, level) => ui.notify(message, level),
|
|
199
|
+
setStatus: (key, text) => {
|
|
200
|
+
if (indexMonitorController !== controller)
|
|
201
|
+
return;
|
|
202
|
+
ui.setStatus(key, text);
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
signal: controller.signal,
|
|
206
|
+
onProgress: (progress) => {
|
|
207
|
+
if (indexMonitorController !== controller)
|
|
208
|
+
return;
|
|
209
|
+
graphRailStore.setIndexProgress(progress);
|
|
210
|
+
},
|
|
211
|
+
onReady: async () => {
|
|
212
|
+
if (controller.signal.aborted || indexMonitorController !== controller)
|
|
213
|
+
return;
|
|
214
|
+
await connect(controller.signal);
|
|
215
|
+
if (controller.signal.aborted || indexMonitorController !== controller)
|
|
216
|
+
return;
|
|
217
|
+
ui.notify(`${remote.fullName} (${branch}) is indexed and ready.`, "info");
|
|
218
|
+
},
|
|
219
|
+
})
|
|
220
|
+
.catch((error) => {
|
|
221
|
+
if (controller.signal.aborted)
|
|
222
|
+
return;
|
|
223
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
224
|
+
ui.notify(`GitNexus indexing monitor failed: ${message}`, "error");
|
|
225
|
+
})
|
|
226
|
+
.finally(releaseController);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
releaseController();
|
|
231
|
+
if (controller.signal.aborted)
|
|
232
|
+
return;
|
|
233
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
234
|
+
const message = `GitNexus repository indexing failed: ${reason}`;
|
|
235
|
+
ui.notify(message, "error");
|
|
236
|
+
}
|
|
128
237
|
}
|
|
129
238
|
pi.on("session_start", async (_event, ctx) => {
|
|
130
239
|
await connect();
|
|
131
240
|
updateCreditStatus(ctx);
|
|
132
|
-
if (startupNotice)
|
|
133
|
-
ctx.ui.notify(startupNotice,
|
|
241
|
+
if (startupNotice && !inventory)
|
|
242
|
+
ctx.ui.notify(startupNotice, "warning");
|
|
243
|
+
});
|
|
244
|
+
pi.on("session_shutdown", () => {
|
|
245
|
+
indexMonitorController?.abort();
|
|
134
246
|
});
|
|
135
247
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
136
248
|
if (!gatewayPolicy || ctx.model?.provider !== "gitnexus")
|
|
@@ -164,7 +276,7 @@ export default function gitnexusExtension(pi) {
|
|
|
164
276
|
pi.registerCommand("gitnexus-login", {
|
|
165
277
|
description: "Sign in to GitNexus (implementation of /login → GitNexus account; accepts a pasted token)",
|
|
166
278
|
handler: async (args, ctx) => {
|
|
167
|
-
const hubUrl =
|
|
279
|
+
const hubUrl = resolveHubUrl();
|
|
168
280
|
const pasted = args.trim();
|
|
169
281
|
// Pasting a token stays supported for CI and headless installs.
|
|
170
282
|
if (pasted) {
|
|
@@ -211,6 +323,7 @@ export default function gitnexusExtension(pi) {
|
|
|
211
323
|
description: "Show GitNexus connection status",
|
|
212
324
|
handler: async (_args, ctx) => {
|
|
213
325
|
const config = resolveGitNexusConfig();
|
|
326
|
+
await connect();
|
|
214
327
|
if (config)
|
|
215
328
|
gatewayPolicy = await refreshGatewayPolicy(config);
|
|
216
329
|
updateCreditStatus(ctx);
|
|
@@ -226,5 +339,12 @@ export default function gitnexusExtension(pi) {
|
|
|
226
339
|
ctx.ui.notify(`GitNexus: connected — ${inventory.repos.length} repos, ${repoLine}${creditLine ? `, ${creditLine}` : ""}`, "info");
|
|
227
340
|
},
|
|
228
341
|
});
|
|
342
|
+
pi.registerCommand("gitnexus-index", {
|
|
343
|
+
description: "Index the current repository in GitNexus (optional branch)",
|
|
344
|
+
handler: async (args, ctx) => {
|
|
345
|
+
const branch = args.trim() || "default";
|
|
346
|
+
await requestRepositoryIndex(cwd, branch, ctx.ui);
|
|
347
|
+
},
|
|
348
|
+
});
|
|
229
349
|
}
|
|
230
350
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { openBrowser } from \"../../utils/open-browser.ts\";\nimport {\n\tapplyNexusSubagentEnvAliases,\n\tisDelegatedWorkerProcess,\n\tisExternalDelegationMode,\n\tregisterDelegateTaskTool,\n} from \"../subagent/index.ts\";\nimport { registerAugmentedGrep } from \"./augment.ts\";\nimport { registerChangeTracking } from \"./changes.ts\";\nimport { GitNexusClient, GitNexusClientError } from \"./client.ts\";\nimport { DEFAULT_HUB_URL, resolveGitNexusConfig, saveGitNexusConfig } from \"./config.ts\";\nimport { defaultDeviceName, deviceFingerprint, pollDeviceAuth, startDeviceAuth } from \"./device-auth.ts\";\nimport { formatCreditBalance, readGatewayCache, refreshGatewayPolicy, registerAutoProvider } from \"./gateway.ts\";\nimport { spawnObservatoryFetch } from \"./observatory.ts\";\nimport { buildGitNexusPrompt } from \"./prompt.ts\";\nimport { withGitNexusRenderer } from \"./render.ts\";\nimport {\n\tgitRemoteSlug,\n\ttype IndexedRepo,\n\tmatchCurrentRepo,\n\tparseRepoInventory,\n\ttype RepoInventory,\n} from \"./resolve.ts\";\nimport { createGitNexusTools } from \"./tools.ts\";\n\n/** Resolve workflow skills in source mode and bundled dist/cli.js mode. */\nexport function resolveGitNexusSkillsDir(\n\tmoduleUrl: string = import.meta.url,\n\tpathExists: (path: string) => boolean = existsSync,\n): string | undefined {\n\tconst candidates = [\n\t\tfileURLToPath(new URL(\"./skills/\", moduleUrl)),\n\t\tfileURLToPath(new URL(\"./extensions/gitnexus/skills/\", moduleUrl)),\n\t];\n\treturn candidates.find(pathExists);\n}\n\n/**\n * GitNexus built-in extension: connects nexus-cli to the GitNexus hub.\n *\n * - session_start: resolve config + repo inventory, register the six graph\n * tools and the augmented grep. No token / hub down / repo unindexed all\n * degrade to a plain coding agent (graph is additive, never load-bearing).\n * - before_agent_start: append the GitNexus guidance block (ported from the\n * CLAUDE.md generator) to the system prompt.\n */\nexport default function gitnexusExtension(pi: ExtensionAPI): void {\n\tlet inventory: RepoInventory | undefined;\n\tlet current: IndexedRepo | undefined;\n\tlet activeClient: GitNexusClient | undefined;\n\tlet toolsRegistered = false;\n\tlet delegationRegistered = false;\n\tlet startupNotice: string | undefined;\n\tlet loginRequired = false;\n\n\tconst cwd = process.cwd();\n\n\t// Nexus Agent must also register at factory time: the initial model is\n\t// resolved before session_start fires, and a provider that appears after\n\t// that is invisible to it. Reads the policy cache the startup gate wrote.\n\tapplyNexusSubagentEnvAliases();\n\tlet gatewayPolicy = readGatewayCache();\n\tconst initialConfig = resolveGitNexusConfig();\n\tregisterAutoProvider(pi, initialConfig, gatewayPolicy);\n\n\tfunction registerDelegationIfAvailable(): void {\n\t\tif (\n\t\t\tdelegationRegistered ||\n\t\t\tisDelegatedWorkerProcess() ||\n\t\t\tisExternalDelegationMode() ||\n\t\t\t!resolveGitNexusConfig() ||\n\t\t\t!gatewayPolicy?.delegation\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tregisterDelegateTaskTool(pi, gatewayPolicy.delegation);\n\t\tdelegationRegistered = true;\n\t}\n\n\tregisterDelegationIfAvailable();\n\n\tfunction updateCreditStatus(ctx: { ui: { setStatus(key: string, text: string | undefined): void } }): void {\n\t\tctx.ui.setStatus(\"nexus-credits\", formatCreditBalance(gatewayPolicy));\n\t}\n\n\t// Built-in tool overrides must happen at factory time (like the bash-spawn-hook\n\t// example) — session_start registrations only add NEW names reliably. The wrapper\n\t// behaves as plain grep until connect() provides a client + current repo.\n\tregisterAugmentedGrep(\n\t\tpi,\n\t\tcwd,\n\t\t() => activeClient,\n\t\t() => current?.name,\n\t);\n\tregisterChangeTracking(pi, cwd);\n\n\tasync function connect(): Promise<void> {\n\t\tinventory = undefined;\n\t\tcurrent = undefined;\n\t\tactiveClient = undefined;\n\t\tstartupNotice = undefined;\n\t\tloginRequired = false;\n\t\tconst config = resolveGitNexusConfig();\n\t\tif (!config) return;\n\t\tconst client = new GitNexusClient(config);\n\t\ttry {\n\t\t\tconst listResult = await client.callTool(\"list_repos\", {});\n\t\t\tinventory = parseRepoInventory(GitNexusClient.text(listResult));\n\t\t} catch (error) {\n\t\t\tconst reason = error instanceof GitNexusClientError ? error.message : String(error);\n\t\t\tif (error instanceof GitNexusClientError && error.kind === \"auth\") {\n\t\t\t\tloginRequired = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tstartupNotice = `GitNexus unavailable: ${reason}`;\n\t\t\treturn;\n\t\t}\n\n\t\tcurrent = matchCurrentRepo(inventory, gitRemoteSlug(cwd));\n\t\tactiveClient = client;\n\t\tgraphRailStore.setRepoList(inventory.repos.map((repo) => ({ name: repo.name, symbols: repo.symbols })));\n\t\t// Observatory (community map) rides a detached subprocess + temp-file\n\t\t// handoff — in-process hub calls made after startup can starve in some\n\t\t// environments, a fresh process never does. The UI picks the file up\n\t\t// lazily via tryLoadObservatoryCache.\n\t\tif (current) spawnObservatoryFetch(config, current.name);\n\t\tgraphRailStore.setRepo(\n\t\t\tcurrent\n\t\t\t\t? {\n\t\t\t\t\t\tname: current.name,\n\t\t\t\t\t\tsymbols: current.symbols,\n\t\t\t\t\t\trelationships: current.relationships,\n\t\t\t\t\t\tindexedAt: current.indexedAt,\n\t\t\t\t\t\trepoCount: inventory.repos.length,\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tname: \"\",\n\t\t\t\t\t\tsymbols: \"0\",\n\t\t\t\t\t\trelationships: \"0\",\n\t\t\t\t\t\tindexedAt: \"\",\n\t\t\t\t\t\trepoCount: inventory.repos.length,\n\t\t\t\t\t},\n\t\t\tcurrent !== undefined,\n\t\t);\n\t\tif (!toolsRegistered) {\n\t\t\tconst ctx = { client, currentRepo: () => current?.name, cwd };\n\t\t\tfor (const tool of createGitNexusTools(ctx)) pi.registerTool(withGitNexusRenderer(tool));\n\t\t\ttoolsRegistered = true;\n\t\t}\n\n\t\tstartupNotice = current\n\t\t\t? `GitNexus: connected — ${current.name} (${current.symbols} symbols, indexed ${current.indexedAt}), ${inventory.repos.length} repos available.`\n\t\t\t: `GitNexus: connected — current directory is not indexed; ${inventory.repos.length} indexed repos remain queryable. Index this repo from the Hub dashboard to unlock local graph context.`;\n\t}\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tawait connect();\n\t\tupdateCreditStatus(ctx);\n\t\tif (startupNotice) ctx.ui.notify(startupNotice, inventory ? \"info\" : \"warning\");\n\t});\n\n\tpi.on(\"agent_settled\", async (_event, ctx) => {\n\t\tif (!gatewayPolicy || ctx.model?.provider !== \"gitnexus\") return;\n\t\tconst config = resolveGitNexusConfig();\n\t\tif (config) gatewayPolicy = await refreshGatewayPolicy(config);\n\t\tregisterDelegationIfAvailable();\n\t\tupdateCreditStatus(ctx);\n\t});\n\n\t// Bundled GitNexus workflow skills (debugging, exploring, impact-analysis,\n\t// refactoring, pr-review, guide) — same SKILL.md content the Claude plugin\n\t// ships, adapted for hub-based indexing. Loaded on-demand by pi's skill system.\n\t// Skipped entirely when the user already has gitnexus skills (project or\n\t// global) so the startup screen doesn't report shadowed duplicates.\n\tpi.on(\"resources_discover\", (event) => {\n\t\tconst hasOwnGitnexusSkills =\n\t\t\texistsSync(join(event.cwd, \".agents\", \"skills\", \"gitnexus\")) ||\n\t\t\texistsSync(join(homedir(), \".agents\", \"skills\", \"gitnexus-debugging\"));\n\t\tif (hasOwnGitnexusSkills) return;\n\t\tconst skillsDir = resolveGitNexusSkillsDir();\n\t\tif (!skillsDir) return;\n\t\treturn { skillPaths: [skillsDir] };\n\t});\n\n\tpi.on(\"before_agent_start\", (event) => {\n\t\tif (!inventory) return;\n\t\treturn { systemPrompt: `${event.systemPrompt}\\n\\n${buildGitNexusPrompt(inventory, current)}` };\n\t});\n\n\tpi.registerCommand(\"gitnexus-login\", {\n\t\tdescription: \"Sign in to GitNexus (implementation of /login → GitNexus account; accepts a pasted token)\",\n\t\thandler: async (args, ctx) => {\n\t\t\tconst hubUrl = process.env.GITNEXUS_HUB_URL ?? DEFAULT_HUB_URL;\n\t\t\tconst pasted = args.trim();\n\n\t\t\t// Pasting a token stays supported for CI and headless installs.\n\t\t\tif (pasted) {\n\t\t\t\ttry {\n\t\t\t\t\tawait new GitNexusClient({ hubUrl, token: pasted }).callTool(\"list_repos\", {});\n\t\t\t\t\tconst path = saveGitNexusConfig({ hubUrl, token: pasted });\n\t\t\t\t\tawait connect();\n\t\t\t\t\tctx.ui.notify(`GitNexus token saved to ${path}. ${startupNotice ?? \"\"}`, \"info\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tctx.ui.notify(`GitNexus login failed: ${message}`, \"error\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst deviceName = defaultDeviceName();\n\t\t\t\tconst fingerprint = deviceFingerprint();\n\t\t\t\tconst start = await startDeviceAuth(hubUrl, { deviceName, deviceFingerprint: fingerprint });\n\t\t\t\tconst link = start.verificationUrlComplete ?? start.verificationUrl;\n\t\t\t\t// The link carries the code, so an opened browser needs no typing.\n\t\t\t\t// Print it too: the launcher is best effort and can be headless.\n\t\t\t\tctx.ui.notify(`Code ${start.userCode} — opening ${link}`, \"info\");\n\t\t\t\topenBrowser(link);\n\n\t\t\t\tconst result = await pollDeviceAuth(hubUrl, start);\n\t\t\t\tconst config = {\n\t\t\t\t\thubUrl: result.hubUrl,\n\t\t\t\t\ttoken: result.token,\n\t\t\t\t\tdeviceFingerprint: fingerprint,\n\t\t\t\t\tdeviceName,\n\t\t\t\t};\n\t\t\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {});\n\t\t\t\tconst path = saveGitNexusConfig(config);\n\t\t\t\tawait connect();\n\t\t\t\tctx.ui.notify(`Signed in. Token saved to ${path}. ${startupNotice ?? \"\"}`, \"info\");\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(`GitNexus login failed: ${message}`, \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"gitnexus-status\", {\n\t\tdescription: \"Show GitNexus connection status\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tconst config = resolveGitNexusConfig();\n\t\t\tif (config) gatewayPolicy = await refreshGatewayPolicy(config);\n\t\t\tupdateCreditStatus(ctx);\n\t\t\tif (!inventory) {\n\t\t\t\tconst message = loginRequired\n\t\t\t\t\t? \"GitNexus session expired. Run /login to sign in again.\"\n\t\t\t\t\t: (startupNotice ?? \"GitNexus: not signed in. Run /login.\");\n\t\t\t\tctx.ui.notify(message, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst repoLine = current ? `current repo: ${current.name}` : \"current repo: not indexed\";\n\t\t\tconst creditLine = formatCreditBalance(gatewayPolicy);\n\t\t\tctx.ui.notify(\n\t\t\t\t`GitNexus: connected — ${inventory.repos.length} repos, ${repoLine}${creditLine ? `, ${creditLine}` : \"\"}`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { ExtensionAPI } from \"../../core/extensions/types.ts\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { openBrowser } from \"../../utils/open-browser.ts\";\nimport {\n\tapplyNexusSubagentEnvAliases,\n\tisDelegatedWorkerProcess,\n\tisExternalDelegationMode,\n\tregisterDelegateTaskTool,\n} from \"../subagent/index.ts\";\nimport { registerAugmentedGrep } from \"./augment.ts\";\nimport { registerChangeTracking } from \"./changes.ts\";\nimport { GitNexusClient, GitNexusClientError } from \"./client.ts\";\nimport { resolveGitNexusConfig, resolveHubUrl, saveGitNexusConfig } from \"./config.ts\";\nimport { defaultDeviceName, deviceFingerprint, pollDeviceAuth, startDeviceAuth } from \"./device-auth.ts\";\nimport { formatCreditBalance, readGatewayCache, refreshGatewayPolicy, registerAutoProvider } from \"./gateway.ts\";\nimport { GitNexusHubApi } from \"./hub-api.ts\";\nimport { type IndexRepoUi, monitorRepoIndex, runIndexRepoCommand } from \"./index-repo.ts\";\nimport { spawnObservatoryFetch } from \"./observatory.ts\";\nimport { buildGitNexusPrompt } from \"./prompt.ts\";\nimport { withGitNexusRenderer } from \"./render.ts\";\nimport {\n\tcurrentGitHubRemote,\n\tgitRemoteSlug,\n\ttype IndexedRepo,\n\tmatchCurrentRepo,\n\tparseRepoInventory,\n\ttype RepoInventory,\n} from \"./resolve.ts\";\nimport { createGitNexusTools } from \"./tools.ts\";\n\n/** Resolve workflow skills in source mode and bundled dist/cli.js mode. */\nexport function resolveGitNexusSkillsDir(\n\tmoduleUrl: string = import.meta.url,\n\tpathExists: (path: string) => boolean = existsSync,\n): string | undefined {\n\tconst candidates = [\n\t\tfileURLToPath(new URL(\"./skills/\", moduleUrl)),\n\t\tfileURLToPath(new URL(\"./extensions/gitnexus/skills/\", moduleUrl)),\n\t];\n\treturn candidates.find(pathExists);\n}\n\n/**\n * GitNexus built-in extension: connects nexus-cli to the GitNexus hub.\n *\n * - session_start: resolve config + repo inventory, register the six graph\n * tools and the augmented grep. No token / hub down / repo unindexed all\n * degrade to a plain coding agent (graph is additive, never load-bearing).\n * - before_agent_start: append the GitNexus guidance block (ported from the\n * CLAUDE.md generator) to the system prompt.\n */\nexport default function gitnexusExtension(pi: ExtensionAPI): void {\n\tlet inventory: RepoInventory | undefined;\n\tlet current: IndexedRepo | undefined;\n\tlet activeClient: GitNexusClient | undefined;\n\tlet toolsRegistered = false;\n\tlet delegationRegistered = false;\n\tlet startupNotice: string | undefined;\n\tlet loginRequired = false;\n\tlet indexMonitorController: AbortController | undefined;\n\n\tconst cwd = process.cwd();\n\n\t// Nexus Agent must also register at factory time: the initial model is\n\t// resolved before session_start fires, and a provider that appears after\n\t// that is invisible to it. Reads the policy cache the startup gate wrote.\n\tapplyNexusSubagentEnvAliases();\n\tlet gatewayPolicy = readGatewayCache();\n\tconst initialConfig = resolveGitNexusConfig();\n\tregisterAutoProvider(pi, initialConfig, gatewayPolicy);\n\n\tfunction registerDelegationIfAvailable(): void {\n\t\tif (\n\t\t\tdelegationRegistered ||\n\t\t\tisDelegatedWorkerProcess() ||\n\t\t\tisExternalDelegationMode() ||\n\t\t\t!resolveGitNexusConfig() ||\n\t\t\t!gatewayPolicy?.delegation\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tregisterDelegateTaskTool(pi, gatewayPolicy.delegation);\n\t\tdelegationRegistered = true;\n\t}\n\n\tregisterDelegationIfAvailable();\n\n\tfunction updateCreditStatus(ctx: { ui: { setStatus(key: string, text: string | undefined): void } }): void {\n\t\tctx.ui.setStatus(\"nexus-credits\", formatCreditBalance(gatewayPolicy));\n\t}\n\n\t// Built-in tool overrides must happen at factory time (like the bash-spawn-hook\n\t// example) — session_start registrations only add NEW names reliably. The wrapper\n\t// behaves as plain grep until connect() provides a client + current repo.\n\tregisterAugmentedGrep(\n\t\tpi,\n\t\tcwd,\n\t\t() => activeClient,\n\t\t() => current?.name,\n\t);\n\tregisterChangeTracking(pi, cwd);\n\n\tfunction clearConnectionState(notice?: string, requiresLogin = false): void {\n\t\tinventory = undefined;\n\t\tcurrent = undefined;\n\t\tactiveClient = undefined;\n\t\tstartupNotice = notice;\n\t\tloginRequired = requiresLogin;\n\t}\n\n\tasync function connect(signal?: AbortSignal): Promise<void> {\n\t\tsignal?.throwIfAborted();\n\t\tconst config = resolveGitNexusConfig();\n\t\tif (!config) {\n\t\t\tclearConnectionState();\n\t\t\treturn;\n\t\t}\n\t\tconst client = new GitNexusClient(config);\n\t\tlet nextInventory: RepoInventory;\n\t\ttry {\n\t\t\tconst listResult = await client.callTool(\"list_repos\", {}, signal);\n\t\t\tsignal?.throwIfAborted();\n\t\t\tnextInventory = parseRepoInventory(GitNexusClient.text(listResult));\n\t\t} catch (error) {\n\t\t\tsignal?.throwIfAborted();\n\t\t\tconst reason = error instanceof GitNexusClientError ? error.message : String(error);\n\t\t\tif (error instanceof GitNexusClientError && error.kind === \"auth\") {\n\t\t\t\tclearConnectionState(undefined, true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tclearConnectionState(`GitNexus unavailable: ${reason}`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst nextCurrent = matchCurrentRepo(nextInventory, gitRemoteSlug(cwd));\n\t\tconst nextStartupNotice = nextCurrent\n\t\t\t? `GitNexus: connected — ${nextCurrent.name} (${nextCurrent.symbols} symbols, indexed ${nextCurrent.indexedAt}), ${nextInventory.repos.length} repos available.`\n\t\t\t: `GitNexus: connected — current directory is not indexed; ${nextInventory.repos.length} indexed repos remain queryable. Run /gitnexus-index to index this repo and unlock local graph context.`;\n\t\tsignal?.throwIfAborted();\n\t\tinventory = nextInventory;\n\t\tcurrent = nextCurrent;\n\t\tactiveClient = client;\n\t\tstartupNotice = nextStartupNotice;\n\t\tloginRequired = false;\n\t\tgraphRailStore.setRepoList(inventory.repos.map((repo) => ({ name: repo.name, symbols: repo.symbols })));\n\t\t// Observatory (community map) rides a detached subprocess + temp-file\n\t\t// handoff — in-process hub calls made after startup can starve in some\n\t\t// environments, a fresh process never does. The UI picks the file up\n\t\t// lazily via tryLoadObservatoryCache.\n\t\tif (current) spawnObservatoryFetch(config, current.name);\n\t\tgraphRailStore.setRepo(\n\t\t\tcurrent\n\t\t\t\t? {\n\t\t\t\t\t\tname: current.name,\n\t\t\t\t\t\tsymbols: current.symbols,\n\t\t\t\t\t\trelationships: current.relationships,\n\t\t\t\t\t\tindexedAt: current.indexedAt,\n\t\t\t\t\t\trepoCount: inventory.repos.length,\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tname: \"\",\n\t\t\t\t\t\tsymbols: \"0\",\n\t\t\t\t\t\trelationships: \"0\",\n\t\t\t\t\t\tindexedAt: \"\",\n\t\t\t\t\t\trepoCount: inventory.repos.length,\n\t\t\t\t\t},\n\t\t\tcurrent !== undefined,\n\t\t);\n\t\tif (!toolsRegistered) {\n\t\t\tconst ctx = { client, currentRepo: () => current?.name, cwd };\n\t\t\tfor (const tool of createGitNexusTools(ctx)) pi.registerTool(withGitNexusRenderer(tool));\n\t\t\ttoolsRegistered = true;\n\t\t}\n\t}\n\n\tasync function requestRepositoryIndex(targetCwd: string, branch: string, ui: IndexRepoUi): Promise<void> {\n\t\tif (/\\s/.test(branch)) {\n\t\t\tconst message = \"Branch names containing whitespace are not supported.\";\n\t\t\tui.notify(message, \"error\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = resolveGitNexusConfig();\n\t\tif (!config) {\n\t\t\tconst message = \"GitNexus is not signed in. Run /login.\";\n\t\t\tui.notify(message, \"warning\");\n\t\t\treturn;\n\t\t}\n\t\tconst remote = currentGitHubRemote(targetCwd);\n\t\tif (!remote) {\n\t\t\tconst message = `No supported github.com origin was found in ${targetCwd}.`;\n\t\t\tui.notify(message, \"error\");\n\t\t\treturn;\n\t\t}\n\n\t\tindexMonitorController?.abort();\n\t\tgraphRailStore.setIndexProgress(undefined);\n\t\tconst controller = new AbortController();\n\t\tindexMonitorController = controller;\n\t\tconst releaseController = () => {\n\t\t\tif (indexMonitorController === controller) indexMonitorController = undefined;\n\t\t};\n\n\t\ttry {\n\t\t\tconst api = new GitNexusHubApi(config);\n\t\t\tconst result = await runIndexRepoCommand({ api, remote, branch, ui, signal: controller.signal });\n\t\t\tif (controller.signal.aborted || indexMonitorController !== controller) {\n\t\t\t\treleaseController();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (result.kind === \"blocked\") {\n\t\t\t\treleaseController();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (result.kind === \"cancelled\") {\n\t\t\t\treleaseController();\n\t\t\t\tui.notify(\"Repository indexing cancelled.\", \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (result.kind === \"ready\") {\n\t\t\t\tconst message = `${remote.fullName} is already indexed; no reindex was started.`;\n\t\t\t\tui.notify(message, \"info\");\n\t\t\t\tawait connect(controller.signal);\n\t\t\t\treleaseController();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid monitorRepoIndex({\n\t\t\t\tapi,\n\t\t\t\trepoId: result.repoId,\n\t\t\t\tui: {\n\t\t\t\t\tnotify: (message, level) => ui.notify(message, level),\n\t\t\t\t\tsetStatus: (key, text) => {\n\t\t\t\t\t\tif (indexMonitorController !== controller) return;\n\t\t\t\t\t\tui.setStatus(key, text);\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tsignal: controller.signal,\n\t\t\t\tonProgress: (progress) => {\n\t\t\t\t\tif (indexMonitorController !== controller) return;\n\t\t\t\t\tgraphRailStore.setIndexProgress(progress);\n\t\t\t\t},\n\t\t\t\tonReady: async () => {\n\t\t\t\t\tif (controller.signal.aborted || indexMonitorController !== controller) return;\n\t\t\t\t\tawait connect(controller.signal);\n\t\t\t\t\tif (controller.signal.aborted || indexMonitorController !== controller) return;\n\t\t\t\t\tui.notify(`${remote.fullName} (${branch}) is indexed and ready.`, \"info\");\n\t\t\t\t},\n\t\t\t})\n\t\t\t\t.catch((error: unknown) => {\n\t\t\t\t\tif (controller.signal.aborted) return;\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tui.notify(`GitNexus indexing monitor failed: ${message}`, \"error\");\n\t\t\t\t})\n\t\t\t\t.finally(releaseController);\n\t\t\treturn;\n\t\t} catch (error) {\n\t\t\treleaseController();\n\t\t\tif (controller.signal.aborted) return;\n\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\tconst message = `GitNexus repository indexing failed: ${reason}`;\n\t\t\tui.notify(message, \"error\");\n\t\t}\n\t}\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tawait connect();\n\t\tupdateCreditStatus(ctx);\n\t\tif (startupNotice && !inventory) ctx.ui.notify(startupNotice, \"warning\");\n\t});\n\n\tpi.on(\"session_shutdown\", () => {\n\t\tindexMonitorController?.abort();\n\t});\n\n\tpi.on(\"agent_settled\", async (_event, ctx) => {\n\t\tif (!gatewayPolicy || ctx.model?.provider !== \"gitnexus\") return;\n\t\tconst config = resolveGitNexusConfig();\n\t\tif (config) gatewayPolicy = await refreshGatewayPolicy(config);\n\t\tregisterDelegationIfAvailable();\n\t\tupdateCreditStatus(ctx);\n\t});\n\n\t// Bundled GitNexus workflow skills (debugging, exploring, impact-analysis,\n\t// refactoring, pr-review, guide) — same SKILL.md content the Claude plugin\n\t// ships, adapted for hub-based indexing. Loaded on-demand by pi's skill system.\n\t// Skipped entirely when the user already has gitnexus skills (project or\n\t// global) so the startup screen doesn't report shadowed duplicates.\n\tpi.on(\"resources_discover\", (event) => {\n\t\tconst hasOwnGitnexusSkills =\n\t\t\texistsSync(join(event.cwd, \".agents\", \"skills\", \"gitnexus\")) ||\n\t\t\texistsSync(join(homedir(), \".agents\", \"skills\", \"gitnexus-debugging\"));\n\t\tif (hasOwnGitnexusSkills) return;\n\t\tconst skillsDir = resolveGitNexusSkillsDir();\n\t\tif (!skillsDir) return;\n\t\treturn { skillPaths: [skillsDir] };\n\t});\n\n\tpi.on(\"before_agent_start\", (event) => {\n\t\tif (!inventory) return;\n\t\treturn { systemPrompt: `${event.systemPrompt}\\n\\n${buildGitNexusPrompt(inventory, current)}` };\n\t});\n\n\tpi.registerCommand(\"gitnexus-login\", {\n\t\tdescription: \"Sign in to GitNexus (implementation of /login → GitNexus account; accepts a pasted token)\",\n\t\thandler: async (args, ctx) => {\n\t\t\tconst hubUrl = resolveHubUrl();\n\t\t\tconst pasted = args.trim();\n\n\t\t\t// Pasting a token stays supported for CI and headless installs.\n\t\t\tif (pasted) {\n\t\t\t\ttry {\n\t\t\t\t\tawait new GitNexusClient({ hubUrl, token: pasted }).callTool(\"list_repos\", {});\n\t\t\t\t\tconst path = saveGitNexusConfig({ hubUrl, token: pasted });\n\t\t\t\t\tawait connect();\n\t\t\t\t\tctx.ui.notify(`GitNexus token saved to ${path}. ${startupNotice ?? \"\"}`, \"info\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\t\tctx.ui.notify(`GitNexus login failed: ${message}`, \"error\");\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst deviceName = defaultDeviceName();\n\t\t\t\tconst fingerprint = deviceFingerprint();\n\t\t\t\tconst start = await startDeviceAuth(hubUrl, { deviceName, deviceFingerprint: fingerprint });\n\t\t\t\tconst link = start.verificationUrlComplete ?? start.verificationUrl;\n\t\t\t\t// The link carries the code, so an opened browser needs no typing.\n\t\t\t\t// Print it too: the launcher is best effort and can be headless.\n\t\t\t\tctx.ui.notify(`Code ${start.userCode} — opening ${link}`, \"info\");\n\t\t\t\topenBrowser(link);\n\n\t\t\t\tconst result = await pollDeviceAuth(hubUrl, start);\n\t\t\t\tconst config = {\n\t\t\t\t\thubUrl: result.hubUrl,\n\t\t\t\t\ttoken: result.token,\n\t\t\t\t\tdeviceFingerprint: fingerprint,\n\t\t\t\t\tdeviceName,\n\t\t\t\t};\n\t\t\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {});\n\t\t\t\tconst path = saveGitNexusConfig(config);\n\t\t\t\tawait connect();\n\t\t\t\tctx.ui.notify(`Signed in. Token saved to ${path}. ${startupNotice ?? \"\"}`, \"info\");\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tctx.ui.notify(`GitNexus login failed: ${message}`, \"error\");\n\t\t\t}\n\t\t},\n\t});\n\n\tpi.registerCommand(\"gitnexus-status\", {\n\t\tdescription: \"Show GitNexus connection status\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tconst config = resolveGitNexusConfig();\n\t\t\tawait connect();\n\t\t\tif (config) gatewayPolicy = await refreshGatewayPolicy(config);\n\t\t\tupdateCreditStatus(ctx);\n\t\t\tif (!inventory) {\n\t\t\t\tconst message = loginRequired\n\t\t\t\t\t? \"GitNexus session expired. Run /login to sign in again.\"\n\t\t\t\t\t: (startupNotice ?? \"GitNexus: not signed in. Run /login.\");\n\t\t\t\tctx.ui.notify(message, \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst repoLine = current ? `current repo: ${current.name}` : \"current repo: not indexed\";\n\t\t\tconst creditLine = formatCreditBalance(gatewayPolicy);\n\t\t\tctx.ui.notify(\n\t\t\t\t`GitNexus: connected — ${inventory.repos.length} repos, ${repoLine}${creditLine ? `, ${creditLine}` : \"\"}`,\n\t\t\t\t\"info\",\n\t\t\t);\n\t\t},\n\t});\n\n\tpi.registerCommand(\"gitnexus-index\", {\n\t\tdescription: \"Index the current repository in GitNexus (optional branch)\",\n\t\thandler: async (args, ctx) => {\n\t\t\tconst branch = args.trim() || \"default\";\n\t\t\tawait requestRepositoryIndex(cwd, branch, ctx.ui);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observatory.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/observatory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkClD,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAiBxD;AAeD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B1F;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBhF","sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"observatory.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/observatory.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkClD,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAiBxD;AAeD,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B1F;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBhF","sourcesContent":["import { appendFileSync } from \"node:fs\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { observatoryCachePath } from \"../../core/observatory-cache.ts\";\nimport { spawnWithoutConsole } from \"../../utils/hidden-spawn.ts\";\nimport { GitNexusClient } from \"./client.ts\";\nimport type { GitNexusConfig } from \"./config.ts\";\n\nfunction trace(message: string): void {\n\tif (!process.env.NEXUS_TRACE) return;\n\ttry {\n\t\tappendFileSync(\"/tmp/nexus-observatory-trace.log\", `${new Date().toISOString()} ${message}\\n`);\n\t} catch {\n\t\t// tracing is best-effort\n\t}\n}\n\n/**\n * Startup graph observatory: fetches the community map of the current repo\n * (community sizes, cross-community call edges, flow count) and feeds the\n * rail store. Fire-and-forget — failures leave the observatory empty and\n * never surface to the user.\n */\n\nconst COMMUNITY_QUERY =\n\t\"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) \" +\n\t\"RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\";\n\nconst EDGE_QUERY =\n\t\"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), \" +\n\t\"(b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), \" +\n\t\"(a)-[r:CodeRelation {type:'CALLS'}]->(b) \" +\n\t\"WHERE ca.heuristicLabel <> cb.heuristicLabel \" +\n\t\"RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight \" +\n\t\"ORDER BY weight DESC LIMIT 16\";\n\nconst FLOW_QUERY = \"MATCH (p:Process) RETURN count(p) AS flows\";\n\nconst TIMEOUT_MS = 20_000;\n\n/** Parse the hub cypher result: `{\"markdown\": \"| a | b |\\n| --- |...\", \"row_count\": n}`. */\nexport function parseCypherRows(text: string): string[][] {\n\tlet markdown: string;\n\ttry {\n\t\tconst parsed = JSON.parse(text.split(\"\\n\\n---\")[0]?.trim() ?? \"\") as { markdown?: string };\n\t\tif (typeof parsed.markdown !== \"string\") return [];\n\t\tmarkdown = parsed.markdown;\n\t} catch {\n\t\treturn [];\n\t}\n\tconst lines = markdown.split(\"\\n\").filter((line) => line.trim().startsWith(\"|\"));\n\t// Drop header + separator rows.\n\treturn lines.slice(2).map((line) =>\n\t\tline\n\t\t\t.split(\"|\")\n\t\t\t.slice(1, -1)\n\t\t\t.map((cell) => cell.trim()),\n\t);\n}\n\nasync function runQuery(client: GitNexusClient, repo: string, query: string, label: string): Promise<string[][]> {\n\tconst start = Date.now();\n\ttrace(`${label}: start`);\n\ttry {\n\t\tconst result = await client.callTool(\"cypher\", { query, repo }, AbortSignal.timeout(TIMEOUT_MS));\n\t\ttrace(`${label}: ok in ${Date.now() - start}ms`);\n\t\treturn parseCypherRows(GitNexusClient.text(result));\n\t} catch (error) {\n\t\ttrace(`${label}: failed in ${Date.now() - start}ms: ${String(error)}`);\n\t\tthrow error;\n\t}\n}\n\nexport async function fetchObservatory(client: GitNexusClient, repo: string): Promise<void> {\n\ttry {\n\t\ttrace(`fetching for ${repo}`);\n\t\t// Sequential on purpose: parallel POSTs against one hub MCP session stall\n\t\t// behind per-session serialization; three sequential cyphers finish in ~2s.\n\t\tconst communityRows = await runQuery(client, repo, COMMUNITY_QUERY, \"communities\");\n\t\tconst edgeRows = await runQuery(client, repo, EDGE_QUERY, \"edges\");\n\t\tconst flowRows = await runQuery(client, repo, FLOW_QUERY, \"flows\");\n\t\ttrace(`rows: communities=${communityRows.length} edges=${edgeRows.length} flows=${flowRows.length}`);\n\t\tconst communities = communityRows\n\t\t\t.filter((row) => row[0])\n\t\t\t.map((row) => ({ name: row[0], members: Number(row[1]) || 0 }));\n\t\tif (communities.length === 0) return;\n\t\tconst known = new Set(communities.map((c) => c.name));\n\t\tconst edges = edgeRows\n\t\t\t.filter((row) => row[0] && row[1] && known.has(row[0]) && known.has(row[1]))\n\t\t\t.map((row) => ({ source: row[0], target: row[1], weight: Number(row[2]) || 0 }));\n\t\tconst flowCount = Number(flowRows[0]?.[0]);\n\t\tgraphRailStore.setObservatory({\n\t\t\tcommunities,\n\t\t\tedges,\n\t\t\t...(Number.isNaN(flowCount) ? {} : { flowCount }),\n\t\t});\n\t} catch (error) {\n\t\t// observatory is cosmetic — startup must never depend on it\n\t\ttrace(`fetch failed: ${String(error)}`);\n\t}\n}\n\n/**\n * Fetch the observatory in a detached child process and hand the result over\n * via a temp file (picked up lazily by the landing panel / rail). In-process\n * hub calls issued after startup can starve behind the session's own HTTP\n * machinery in some environments; a fresh process never does.\n */\nexport function spawnObservatoryFetch(config: GitNexusConfig, repo: string): void {\n\ttry {\n\t\tconst script = buildFetchScript();\n\t\tconst child = spawnWithoutConsole(process.execPath, [\"-e\", script], {\n\t\t\tdetached: true,\n\t\t\tstdio: \"ignore\",\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\tNEXUS_OBS_HUB_URL: config.hubUrl,\n\t\t\t\tNEXUS_OBS_TOKEN: config.token,\n\t\t\t\tNEXUS_OBS_REPO: repo,\n\t\t\t\tNEXUS_OBS_OUT: observatoryCachePath(repo),\n\t\t\t},\n\t\t});\n\t\tchild.unref();\n\t\ttrace(`spawned observatory fetcher pid=${child.pid ?? \"?\"} for ${repo}`);\n\t} catch (error) {\n\t\ttrace(`spawn failed: ${String(error)}`);\n\t}\n}\n\n/** Self-contained fetch script (plain node, no imports beyond fs). */\nfunction buildFetchScript(): string {\n\treturn `\nconst { writeFileSync } = require(\"node:fs\");\nconst hubUrl = process.env.NEXUS_OBS_HUB_URL;\nconst token = process.env.NEXUS_OBS_TOKEN;\nconst repo = process.env.NEXUS_OBS_REPO;\nconst out = process.env.NEXUS_OBS_OUT;\nlet sessionId;\nasync function post(body) {\n\tconst res = await fetch(hubUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"content-type\": \"application/json\",\n\t\t\taccept: \"application/json, text/event-stream\",\n\t\t\tauthorization: \"Bearer \" + token,\n\t\t\t...(sessionId ? { \"mcp-session-id\": sessionId } : {}),\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.timeout(20000),\n\t});\n\tconst sid = res.headers.get(\"mcp-session-id\");\n\tif (sid) sessionId = sid;\n\tconst text = await res.text();\n\tif (res.status === 202) return undefined;\n\tif (!res.ok) throw new Error(\"hub HTTP \" + res.status);\n\tif ((res.headers.get(\"content-type\") || \"\").includes(\"text/event-stream\")) {\n\t\tlet last;\n\t\tfor (const ev of text.split(\"\\\\n\\\\n\")) {\n\t\t\tconst data = ev.split(\"\\\\n\").filter((l) => l.startsWith(\"data:\")).map((l) => l.slice(5).trim()).join(\"\\\\n\");\n\t\t\tif (!data) continue;\n\t\t\ttry { const p = JSON.parse(data); if (p.id !== undefined && p.id !== null) last = p; } catch {}\n\t\t}\n\t\treturn last;\n\t}\n\treturn text ? JSON.parse(text) : undefined;\n}\nlet nextId = 1;\nasync function call(name, args) {\n\tconst r = await post({ jsonrpc: \"2.0\", id: nextId++, method: \"tools/call\", params: { name, arguments: args } });\n\tif (!r || r.error) throw new Error(r && r.error ? r.error.message : \"empty response\");\n\tconst parts = (r.result && r.result.content) || [];\n\treturn parts.filter((p) => p.type === \"text\").map((p) => p.text).join(\"\\\\n\");\n}\nfunction rows(text) {\n\tlet md;\n\ttry { md = JSON.parse((text.split(\"\\\\n\\\\n---\")[0] || \"\").trim()).markdown; } catch { return []; }\n\tif (typeof md !== \"string\") return [];\n\tconst lines = md.split(\"\\\\n\").filter((l) => l.trim().startsWith(\"|\"));\n\treturn lines.slice(2).map((l) => l.split(\"|\").slice(1, -1).map((c) => c.trim()));\n}\n(async () => {\n\tawait post({ jsonrpc: \"2.0\", id: nextId++, method: \"initialize\", params: { protocolVersion: \"2025-06-18\", capabilities: {}, clientInfo: { name: \"nexus-observatory\", version: \"0.1.0\" } } });\n\tawait post({ jsonrpc: \"2.0\", method: \"notifications/initialized\" });\n\tconst communityRows = rows(await call(\"cypher\", { repo, query: \"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\" }));\n\tconst edgeRows = rows(await call(\"cypher\", { repo, query: \"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), (b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), (a)-[r:CodeRelation {type:'CALLS'}]->(b) WHERE ca.heuristicLabel <> cb.heuristicLabel RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight ORDER BY weight DESC LIMIT 16\" }));\n\tconst flowRows = rows(await call(\"cypher\", { repo, query: \"MATCH (p:Process) RETURN count(p) AS flows\" }));\n\tconst communities = communityRows.filter((r) => r[0]).map((r) => ({ name: r[0], members: Number(r[1]) || 0 }));\n\tif (!communities.length) process.exit(0);\n\tconst known = new Set(communities.map((c) => c.name));\n\tconst edges = edgeRows.filter((r) => r[0] && r[1] && known.has(r[0]) && known.has(r[1])).map((r) => ({ source: r[0], target: r[1], weight: Number(r[2]) || 0 }));\n\tconst flowCount = Number(flowRows[0] && flowRows[0][0]);\n\tconst payload = { communities, edges, ...(Number.isNaN(flowCount) ? {} : { flowCount }), fetchedAt: Date.now() };\n\twriteFileSync(out, JSON.stringify(payload));\n\tprocess.exit(0);\n})().catch(() => process.exit(1));\n`;\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { appendFileSync } from "node:fs";
|
|
3
2
|
import { graphRailStore } from "../../core/graph-rail-store.js";
|
|
4
3
|
import { observatoryCachePath } from "../../core/observatory-cache.js";
|
|
4
|
+
import { spawnWithoutConsole } from "../../utils/hidden-spawn.js";
|
|
5
5
|
import { GitNexusClient } from "./client.js";
|
|
6
6
|
function trace(message) {
|
|
7
7
|
if (!process.env.NEXUS_TRACE)
|
|
@@ -100,7 +100,7 @@ export async function fetchObservatory(client, repo) {
|
|
|
100
100
|
export function spawnObservatoryFetch(config, repo) {
|
|
101
101
|
try {
|
|
102
102
|
const script = buildFetchScript();
|
|
103
|
-
const child =
|
|
103
|
+
const child = spawnWithoutConsole(process.execPath, ["-e", script], {
|
|
104
104
|
detached: true,
|
|
105
105
|
stdio: "ignore",
|
|
106
106
|
env: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observatory.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/observatory.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport { appendFileSync } from \"node:fs\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { observatoryCachePath } from \"../../core/observatory-cache.ts\";\nimport { GitNexusClient } from \"./client.ts\";\nimport type { GitNexusConfig } from \"./config.ts\";\n\nfunction trace(message: string): void {\n\tif (!process.env.NEXUS_TRACE) return;\n\ttry {\n\t\tappendFileSync(\"/tmp/nexus-observatory-trace.log\", `${new Date().toISOString()} ${message}\\n`);\n\t} catch {\n\t\t// tracing is best-effort\n\t}\n}\n\n/**\n * Startup graph observatory: fetches the community map of the current repo\n * (community sizes, cross-community call edges, flow count) and feeds the\n * rail store. Fire-and-forget — failures leave the observatory empty and\n * never surface to the user.\n */\n\nconst COMMUNITY_QUERY =\n\t\"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) \" +\n\t\"RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\";\n\nconst EDGE_QUERY =\n\t\"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), \" +\n\t\"(b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), \" +\n\t\"(a)-[r:CodeRelation {type:'CALLS'}]->(b) \" +\n\t\"WHERE ca.heuristicLabel <> cb.heuristicLabel \" +\n\t\"RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight \" +\n\t\"ORDER BY weight DESC LIMIT 16\";\n\nconst FLOW_QUERY = \"MATCH (p:Process) RETURN count(p) AS flows\";\n\nconst TIMEOUT_MS = 20_000;\n\n/** Parse the hub cypher result: `{\"markdown\": \"| a | b |\\n| --- |...\", \"row_count\": n}`. */\nexport function parseCypherRows(text: string): string[][] {\n\tlet markdown: string;\n\ttry {\n\t\tconst parsed = JSON.parse(text.split(\"\\n\\n---\")[0]?.trim() ?? \"\") as { markdown?: string };\n\t\tif (typeof parsed.markdown !== \"string\") return [];\n\t\tmarkdown = parsed.markdown;\n\t} catch {\n\t\treturn [];\n\t}\n\tconst lines = markdown.split(\"\\n\").filter((line) => line.trim().startsWith(\"|\"));\n\t// Drop header + separator rows.\n\treturn lines.slice(2).map((line) =>\n\t\tline\n\t\t\t.split(\"|\")\n\t\t\t.slice(1, -1)\n\t\t\t.map((cell) => cell.trim()),\n\t);\n}\n\nasync function runQuery(client: GitNexusClient, repo: string, query: string, label: string): Promise<string[][]> {\n\tconst start = Date.now();\n\ttrace(`${label}: start`);\n\ttry {\n\t\tconst result = await client.callTool(\"cypher\", { query, repo }, AbortSignal.timeout(TIMEOUT_MS));\n\t\ttrace(`${label}: ok in ${Date.now() - start}ms`);\n\t\treturn parseCypherRows(GitNexusClient.text(result));\n\t} catch (error) {\n\t\ttrace(`${label}: failed in ${Date.now() - start}ms: ${String(error)}`);\n\t\tthrow error;\n\t}\n}\n\nexport async function fetchObservatory(client: GitNexusClient, repo: string): Promise<void> {\n\ttry {\n\t\ttrace(`fetching for ${repo}`);\n\t\t// Sequential on purpose: parallel POSTs against one hub MCP session stall\n\t\t// behind per-session serialization; three sequential cyphers finish in ~2s.\n\t\tconst communityRows = await runQuery(client, repo, COMMUNITY_QUERY, \"communities\");\n\t\tconst edgeRows = await runQuery(client, repo, EDGE_QUERY, \"edges\");\n\t\tconst flowRows = await runQuery(client, repo, FLOW_QUERY, \"flows\");\n\t\ttrace(`rows: communities=${communityRows.length} edges=${edgeRows.length} flows=${flowRows.length}`);\n\t\tconst communities = communityRows\n\t\t\t.filter((row) => row[0])\n\t\t\t.map((row) => ({ name: row[0], members: Number(row[1]) || 0 }));\n\t\tif (communities.length === 0) return;\n\t\tconst known = new Set(communities.map((c) => c.name));\n\t\tconst edges = edgeRows\n\t\t\t.filter((row) => row[0] && row[1] && known.has(row[0]) && known.has(row[1]))\n\t\t\t.map((row) => ({ source: row[0], target: row[1], weight: Number(row[2]) || 0 }));\n\t\tconst flowCount = Number(flowRows[0]?.[0]);\n\t\tgraphRailStore.setObservatory({\n\t\t\tcommunities,\n\t\t\tedges,\n\t\t\t...(Number.isNaN(flowCount) ? {} : { flowCount }),\n\t\t});\n\t} catch (error) {\n\t\t// observatory is cosmetic — startup must never depend on it\n\t\ttrace(`fetch failed: ${String(error)}`);\n\t}\n}\n\n/**\n * Fetch the observatory in a detached child process and hand the result over\n * via a temp file (picked up lazily by the landing panel / rail). In-process\n * hub calls issued after startup can starve behind the session's own HTTP\n * machinery in some environments; a fresh process never does.\n */\nexport function spawnObservatoryFetch(config: GitNexusConfig, repo: string): void {\n\ttry {\n\t\tconst script = buildFetchScript();\n\t\tconst child = spawn(process.execPath, [\"-e\", script], {\n\t\t\tdetached: true,\n\t\t\tstdio: \"ignore\",\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\tNEXUS_OBS_HUB_URL: config.hubUrl,\n\t\t\t\tNEXUS_OBS_TOKEN: config.token,\n\t\t\t\tNEXUS_OBS_REPO: repo,\n\t\t\t\tNEXUS_OBS_OUT: observatoryCachePath(repo),\n\t\t\t},\n\t\t});\n\t\tchild.unref();\n\t\ttrace(`spawned observatory fetcher pid=${child.pid ?? \"?\"} for ${repo}`);\n\t} catch (error) {\n\t\ttrace(`spawn failed: ${String(error)}`);\n\t}\n}\n\n/** Self-contained fetch script (plain node, no imports beyond fs). */\nfunction buildFetchScript(): string {\n\treturn `\nconst { writeFileSync } = require(\"node:fs\");\nconst hubUrl = process.env.NEXUS_OBS_HUB_URL;\nconst token = process.env.NEXUS_OBS_TOKEN;\nconst repo = process.env.NEXUS_OBS_REPO;\nconst out = process.env.NEXUS_OBS_OUT;\nlet sessionId;\nasync function post(body) {\n\tconst res = await fetch(hubUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"content-type\": \"application/json\",\n\t\t\taccept: \"application/json, text/event-stream\",\n\t\t\tauthorization: \"Bearer \" + token,\n\t\t\t...(sessionId ? { \"mcp-session-id\": sessionId } : {}),\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.timeout(20000),\n\t});\n\tconst sid = res.headers.get(\"mcp-session-id\");\n\tif (sid) sessionId = sid;\n\tconst text = await res.text();\n\tif (res.status === 202) return undefined;\n\tif (!res.ok) throw new Error(\"hub HTTP \" + res.status);\n\tif ((res.headers.get(\"content-type\") || \"\").includes(\"text/event-stream\")) {\n\t\tlet last;\n\t\tfor (const ev of text.split(\"\\\\n\\\\n\")) {\n\t\t\tconst data = ev.split(\"\\\\n\").filter((l) => l.startsWith(\"data:\")).map((l) => l.slice(5).trim()).join(\"\\\\n\");\n\t\t\tif (!data) continue;\n\t\t\ttry { const p = JSON.parse(data); if (p.id !== undefined && p.id !== null) last = p; } catch {}\n\t\t}\n\t\treturn last;\n\t}\n\treturn text ? JSON.parse(text) : undefined;\n}\nlet nextId = 1;\nasync function call(name, args) {\n\tconst r = await post({ jsonrpc: \"2.0\", id: nextId++, method: \"tools/call\", params: { name, arguments: args } });\n\tif (!r || r.error) throw new Error(r && r.error ? r.error.message : \"empty response\");\n\tconst parts = (r.result && r.result.content) || [];\n\treturn parts.filter((p) => p.type === \"text\").map((p) => p.text).join(\"\\\\n\");\n}\nfunction rows(text) {\n\tlet md;\n\ttry { md = JSON.parse((text.split(\"\\\\n\\\\n---\")[0] || \"\").trim()).markdown; } catch { return []; }\n\tif (typeof md !== \"string\") return [];\n\tconst lines = md.split(\"\\\\n\").filter((l) => l.trim().startsWith(\"|\"));\n\treturn lines.slice(2).map((l) => l.split(\"|\").slice(1, -1).map((c) => c.trim()));\n}\n(async () => {\n\tawait post({ jsonrpc: \"2.0\", id: nextId++, method: \"initialize\", params: { protocolVersion: \"2025-06-18\", capabilities: {}, clientInfo: { name: \"nexus-observatory\", version: \"0.1.0\" } } });\n\tawait post({ jsonrpc: \"2.0\", method: \"notifications/initialized\" });\n\tconst communityRows = rows(await call(\"cypher\", { repo, query: \"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\" }));\n\tconst edgeRows = rows(await call(\"cypher\", { repo, query: \"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), (b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), (a)-[r:CodeRelation {type:'CALLS'}]->(b) WHERE ca.heuristicLabel <> cb.heuristicLabel RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight ORDER BY weight DESC LIMIT 16\" }));\n\tconst flowRows = rows(await call(\"cypher\", { repo, query: \"MATCH (p:Process) RETURN count(p) AS flows\" }));\n\tconst communities = communityRows.filter((r) => r[0]).map((r) => ({ name: r[0], members: Number(r[1]) || 0 }));\n\tif (!communities.length) process.exit(0);\n\tconst known = new Set(communities.map((c) => c.name));\n\tconst edges = edgeRows.filter((r) => r[0] && r[1] && known.has(r[0]) && known.has(r[1])).map((r) => ({ source: r[0], target: r[1], weight: Number(r[2]) || 0 }));\n\tconst flowCount = Number(flowRows[0] && flowRows[0][0]);\n\tconst payload = { communities, edges, ...(Number.isNaN(flowCount) ? {} : { flowCount }), fetchedAt: Date.now() };\n\twriteFileSync(out, JSON.stringify(payload));\n\tprocess.exit(0);\n})().catch(() => process.exit(1));\n`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"observatory.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/observatory.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync } from \"node:fs\";\nimport { graphRailStore } from \"../../core/graph-rail-store.ts\";\nimport { observatoryCachePath } from \"../../core/observatory-cache.ts\";\nimport { spawnWithoutConsole } from \"../../utils/hidden-spawn.ts\";\nimport { GitNexusClient } from \"./client.ts\";\nimport type { GitNexusConfig } from \"./config.ts\";\n\nfunction trace(message: string): void {\n\tif (!process.env.NEXUS_TRACE) return;\n\ttry {\n\t\tappendFileSync(\"/tmp/nexus-observatory-trace.log\", `${new Date().toISOString()} ${message}\\n`);\n\t} catch {\n\t\t// tracing is best-effort\n\t}\n}\n\n/**\n * Startup graph observatory: fetches the community map of the current repo\n * (community sizes, cross-community call edges, flow count) and feeds the\n * rail store. Fire-and-forget — failures leave the observatory empty and\n * never surface to the user.\n */\n\nconst COMMUNITY_QUERY =\n\t\"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) \" +\n\t\"RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\";\n\nconst EDGE_QUERY =\n\t\"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), \" +\n\t\"(b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), \" +\n\t\"(a)-[r:CodeRelation {type:'CALLS'}]->(b) \" +\n\t\"WHERE ca.heuristicLabel <> cb.heuristicLabel \" +\n\t\"RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight \" +\n\t\"ORDER BY weight DESC LIMIT 16\";\n\nconst FLOW_QUERY = \"MATCH (p:Process) RETURN count(p) AS flows\";\n\nconst TIMEOUT_MS = 20_000;\n\n/** Parse the hub cypher result: `{\"markdown\": \"| a | b |\\n| --- |...\", \"row_count\": n}`. */\nexport function parseCypherRows(text: string): string[][] {\n\tlet markdown: string;\n\ttry {\n\t\tconst parsed = JSON.parse(text.split(\"\\n\\n---\")[0]?.trim() ?? \"\") as { markdown?: string };\n\t\tif (typeof parsed.markdown !== \"string\") return [];\n\t\tmarkdown = parsed.markdown;\n\t} catch {\n\t\treturn [];\n\t}\n\tconst lines = markdown.split(\"\\n\").filter((line) => line.trim().startsWith(\"|\"));\n\t// Drop header + separator rows.\n\treturn lines.slice(2).map((line) =>\n\t\tline\n\t\t\t.split(\"|\")\n\t\t\t.slice(1, -1)\n\t\t\t.map((cell) => cell.trim()),\n\t);\n}\n\nasync function runQuery(client: GitNexusClient, repo: string, query: string, label: string): Promise<string[][]> {\n\tconst start = Date.now();\n\ttrace(`${label}: start`);\n\ttry {\n\t\tconst result = await client.callTool(\"cypher\", { query, repo }, AbortSignal.timeout(TIMEOUT_MS));\n\t\ttrace(`${label}: ok in ${Date.now() - start}ms`);\n\t\treturn parseCypherRows(GitNexusClient.text(result));\n\t} catch (error) {\n\t\ttrace(`${label}: failed in ${Date.now() - start}ms: ${String(error)}`);\n\t\tthrow error;\n\t}\n}\n\nexport async function fetchObservatory(client: GitNexusClient, repo: string): Promise<void> {\n\ttry {\n\t\ttrace(`fetching for ${repo}`);\n\t\t// Sequential on purpose: parallel POSTs against one hub MCP session stall\n\t\t// behind per-session serialization; three sequential cyphers finish in ~2s.\n\t\tconst communityRows = await runQuery(client, repo, COMMUNITY_QUERY, \"communities\");\n\t\tconst edgeRows = await runQuery(client, repo, EDGE_QUERY, \"edges\");\n\t\tconst flowRows = await runQuery(client, repo, FLOW_QUERY, \"flows\");\n\t\ttrace(`rows: communities=${communityRows.length} edges=${edgeRows.length} flows=${flowRows.length}`);\n\t\tconst communities = communityRows\n\t\t\t.filter((row) => row[0])\n\t\t\t.map((row) => ({ name: row[0], members: Number(row[1]) || 0 }));\n\t\tif (communities.length === 0) return;\n\t\tconst known = new Set(communities.map((c) => c.name));\n\t\tconst edges = edgeRows\n\t\t\t.filter((row) => row[0] && row[1] && known.has(row[0]) && known.has(row[1]))\n\t\t\t.map((row) => ({ source: row[0], target: row[1], weight: Number(row[2]) || 0 }));\n\t\tconst flowCount = Number(flowRows[0]?.[0]);\n\t\tgraphRailStore.setObservatory({\n\t\t\tcommunities,\n\t\t\tedges,\n\t\t\t...(Number.isNaN(flowCount) ? {} : { flowCount }),\n\t\t});\n\t} catch (error) {\n\t\t// observatory is cosmetic — startup must never depend on it\n\t\ttrace(`fetch failed: ${String(error)}`);\n\t}\n}\n\n/**\n * Fetch the observatory in a detached child process and hand the result over\n * via a temp file (picked up lazily by the landing panel / rail). In-process\n * hub calls issued after startup can starve behind the session's own HTTP\n * machinery in some environments; a fresh process never does.\n */\nexport function spawnObservatoryFetch(config: GitNexusConfig, repo: string): void {\n\ttry {\n\t\tconst script = buildFetchScript();\n\t\tconst child = spawnWithoutConsole(process.execPath, [\"-e\", script], {\n\t\t\tdetached: true,\n\t\t\tstdio: \"ignore\",\n\t\t\tenv: {\n\t\t\t\t...process.env,\n\t\t\t\tNEXUS_OBS_HUB_URL: config.hubUrl,\n\t\t\t\tNEXUS_OBS_TOKEN: config.token,\n\t\t\t\tNEXUS_OBS_REPO: repo,\n\t\t\t\tNEXUS_OBS_OUT: observatoryCachePath(repo),\n\t\t\t},\n\t\t});\n\t\tchild.unref();\n\t\ttrace(`spawned observatory fetcher pid=${child.pid ?? \"?\"} for ${repo}`);\n\t} catch (error) {\n\t\ttrace(`spawn failed: ${String(error)}`);\n\t}\n}\n\n/** Self-contained fetch script (plain node, no imports beyond fs). */\nfunction buildFetchScript(): string {\n\treturn `\nconst { writeFileSync } = require(\"node:fs\");\nconst hubUrl = process.env.NEXUS_OBS_HUB_URL;\nconst token = process.env.NEXUS_OBS_TOKEN;\nconst repo = process.env.NEXUS_OBS_REPO;\nconst out = process.env.NEXUS_OBS_OUT;\nlet sessionId;\nasync function post(body) {\n\tconst res = await fetch(hubUrl, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t\"content-type\": \"application/json\",\n\t\t\taccept: \"application/json, text/event-stream\",\n\t\t\tauthorization: \"Bearer \" + token,\n\t\t\t...(sessionId ? { \"mcp-session-id\": sessionId } : {}),\n\t\t},\n\t\tbody: JSON.stringify(body),\n\t\tsignal: AbortSignal.timeout(20000),\n\t});\n\tconst sid = res.headers.get(\"mcp-session-id\");\n\tif (sid) sessionId = sid;\n\tconst text = await res.text();\n\tif (res.status === 202) return undefined;\n\tif (!res.ok) throw new Error(\"hub HTTP \" + res.status);\n\tif ((res.headers.get(\"content-type\") || \"\").includes(\"text/event-stream\")) {\n\t\tlet last;\n\t\tfor (const ev of text.split(\"\\\\n\\\\n\")) {\n\t\t\tconst data = ev.split(\"\\\\n\").filter((l) => l.startsWith(\"data:\")).map((l) => l.slice(5).trim()).join(\"\\\\n\");\n\t\t\tif (!data) continue;\n\t\t\ttry { const p = JSON.parse(data); if (p.id !== undefined && p.id !== null) last = p; } catch {}\n\t\t}\n\t\treturn last;\n\t}\n\treturn text ? JSON.parse(text) : undefined;\n}\nlet nextId = 1;\nasync function call(name, args) {\n\tconst r = await post({ jsonrpc: \"2.0\", id: nextId++, method: \"tools/call\", params: { name, arguments: args } });\n\tif (!r || r.error) throw new Error(r && r.error ? r.error.message : \"empty response\");\n\tconst parts = (r.result && r.result.content) || [];\n\treturn parts.filter((p) => p.type === \"text\").map((p) => p.text).join(\"\\\\n\");\n}\nfunction rows(text) {\n\tlet md;\n\ttry { md = JSON.parse((text.split(\"\\\\n\\\\n---\")[0] || \"\").trim()).markdown; } catch { return []; }\n\tif (typeof md !== \"string\") return [];\n\tconst lines = md.split(\"\\\\n\").filter((l) => l.trim().startsWith(\"|\"));\n\treturn lines.slice(2).map((l) => l.split(\"|\").slice(1, -1).map((c) => c.trim()));\n}\n(async () => {\n\tawait post({ jsonrpc: \"2.0\", id: nextId++, method: \"initialize\", params: { protocolVersion: \"2025-06-18\", capabilities: {}, clientInfo: { name: \"nexus-observatory\", version: \"0.1.0\" } } });\n\tawait post({ jsonrpc: \"2.0\", method: \"notifications/initialized\" });\n\tconst communityRows = rows(await call(\"cypher\", { repo, query: \"MATCH (f)-[:CodeRelation {type:'MEMBER_OF'}]->(c:Community) RETURN c.heuristicLabel AS name, count(f) AS members ORDER BY members DESC LIMIT 8\" }));\n\tconst edgeRows = rows(await call(\"cypher\", { repo, query: \"MATCH (a)-[:CodeRelation {type:'MEMBER_OF'}]->(ca:Community), (b)-[:CodeRelation {type:'MEMBER_OF'}]->(cb:Community), (a)-[r:CodeRelation {type:'CALLS'}]->(b) WHERE ca.heuristicLabel <> cb.heuristicLabel RETURN ca.heuristicLabel AS source, cb.heuristicLabel AS target, count(r) AS weight ORDER BY weight DESC LIMIT 16\" }));\n\tconst flowRows = rows(await call(\"cypher\", { repo, query: \"MATCH (p:Process) RETURN count(p) AS flows\" }));\n\tconst communities = communityRows.filter((r) => r[0]).map((r) => ({ name: r[0], members: Number(r[1]) || 0 }));\n\tif (!communities.length) process.exit(0);\n\tconst known = new Set(communities.map((c) => c.name));\n\tconst edges = edgeRows.filter((r) => r[0] && r[1] && known.has(r[0]) && known.has(r[1])).map((r) => ({ source: r[0], target: r[1], weight: Number(r[2]) || 0 }));\n\tconst flowCount = Number(flowRows[0] && flowRows[0][0]);\n\tconst payload = { communities, edges, ...(Number.isNaN(flowCount) ? {} : { flowCount }), fetchedAt: Date.now() };\n\twriteFileSync(out, JSON.stringify(payload));\n\tprocess.exit(0);\n})().catch(() => process.exit(1));\n`;\n}\n"]}
|
|
@@ -13,8 +13,17 @@ export interface RepoInventory {
|
|
|
13
13
|
repos: IndexedRepo[];
|
|
14
14
|
groups: IndexedGroup[];
|
|
15
15
|
}
|
|
16
|
+
export interface GitHubRemote {
|
|
17
|
+
name: string;
|
|
18
|
+
fullName: string;
|
|
19
|
+
cloneUrl: string;
|
|
20
|
+
}
|
|
16
21
|
/** Parse the markdown returned by the hub list_repos tool. */
|
|
17
22
|
export declare function parseRepoInventory(markdown: string): RepoInventory;
|
|
23
|
+
/** Parse a supported GitHub origin without preserving credentials or URL parameters. */
|
|
24
|
+
export declare function parseGitHubRemote(remoteUrl: string): GitHubRemote | undefined;
|
|
25
|
+
/** Resolve the current repository's origin as a canonical GitHub remote. */
|
|
26
|
+
export declare function currentGitHubRemote(cwd: string): GitHubRemote | undefined;
|
|
18
27
|
/** Read the git remote of cwd and normalize to "owner/name" (undefined outside a repo). */
|
|
19
28
|
export declare function gitRemoteSlug(cwd: string): string | undefined;
|
|
20
29
|
/** Match the cwd's remote against the hub inventory. Exact slug first, then name suffix. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/resolve.ts"],"names":[],"mappings":"AAEA,iEAAiE;AACjE,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAuBlE;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/resolve.ts"],"names":[],"mappings":"AAEA,iEAAiE;AACjE,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,8DAA8D;AAC9D,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAuBlE;AAaD,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAwB7E;AAUD,4EAA4E;AAC5E,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAGzE;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM7D;AAED,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAO5G","sourcesContent":["import { spawnSync } from \"node:child_process\";\n\n/** One indexed repo as reported by the hub's list_repos tool. */\nexport interface IndexedRepo {\n\tname: string;\n\tsymbols: string;\n\trelationships: string;\n\tindexedAt: string;\n}\n\nexport interface IndexedGroup {\n\tname: string;\n\trepoCount: string;\n}\n\nexport interface RepoInventory {\n\trepos: IndexedRepo[];\n\tgroups: IndexedGroup[];\n}\n\nexport interface GitHubRemote {\n\tname: string;\n\tfullName: string;\n\tcloneUrl: string;\n}\n\n/** Parse the markdown returned by the hub list_repos tool. */\nexport function parseRepoInventory(markdown: string): RepoInventory {\n\tconst repos: IndexedRepo[] = [];\n\tconst groups: IndexedGroup[] = [];\n\tconst repoRe = /^- \\*\\*([^*]+)\\*\\* \\(([\\d,]+) symbols, ([\\d,]+) relationships\\) — indexed (.+)$/;\n\tconst groupRe = /^- \\*\\*(@[^*]+)\\*\\* \\((\\d+) repos?/;\n\tfor (const line of markdown.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tconst repoMatch = repoRe.exec(trimmed);\n\t\tif (repoMatch?.[1] && repoMatch[2] && repoMatch[3] && repoMatch[4]) {\n\t\t\trepos.push({\n\t\t\t\tname: repoMatch[1],\n\t\t\t\tsymbols: repoMatch[2],\n\t\t\t\trelationships: repoMatch[3],\n\t\t\t\tindexedAt: repoMatch[4],\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\t\tconst groupMatch = groupRe.exec(trimmed);\n\t\tif (groupMatch?.[1] && groupMatch[2]) {\n\t\t\tgroups.push({ name: groupMatch[1], repoCount: groupMatch[2] });\n\t\t}\n\t}\n\treturn { repos, groups };\n}\n\nfunction gitOriginUrl(cwd: string): string | undefined {\n\tconst result = spawnSync(\"git\", [\"remote\", \"get-url\", \"origin\"], {\n\t\tcwd,\n\t\tencoding: \"utf8\",\n\t\ttimeout: 3000,\n\t});\n\tif (result.status !== 0) return undefined;\n\tconst url = (result.stdout ?? \"\").trim();\n\treturn url || undefined;\n}\n\n/** Parse a supported GitHub origin without preserving credentials or URL parameters. */\nexport function parseGitHubRemote(remoteUrl: string): GitHubRemote | undefined {\n\tif (!remoteUrl || /\\s/.test(remoteUrl)) return undefined;\n\n\tconst scpMatch = /^git@github\\.com:([^/]+)\\/([^/]+)$/.exec(remoteUrl);\n\tif (scpMatch?.[1] && scpMatch[2]) return normalizeGitHubRemote(scpMatch[1], scpMatch[2]);\n\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(remoteUrl);\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (parsed.hostname !== \"github.com\" || parsed.port || parsed.search || parsed.hash) return undefined;\n\tif (parsed.protocol === \"https:\") {\n\t\tif (parsed.username || parsed.password) return undefined;\n\t} else if (parsed.protocol === \"ssh:\") {\n\t\tif (parsed.username !== \"git\" || parsed.password) return undefined;\n\t} else {\n\t\treturn undefined;\n\t}\n\n\tconst pathMatch = /^\\/([^/]+)\\/([^/]+)$/.exec(parsed.pathname);\n\tif (!pathMatch?.[1] || !pathMatch[2]) return undefined;\n\treturn normalizeGitHubRemote(pathMatch[1], pathMatch[2]);\n}\n\nfunction normalizeGitHubRemote(owner: string, rawName: string): GitHubRemote | undefined {\n\tconst name = rawName.endsWith(\".git\") ? rawName.slice(0, -4) : rawName;\n\tif (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(owner)) return undefined;\n\tif (name.length > 100 || !/^[A-Za-z0-9._-]+$/.test(name) || name === \".\" || name === \"..\") return undefined;\n\tconst fullName = `${owner}/${name}`;\n\treturn { name, fullName, cloneUrl: `https://github.com/${fullName}.git` };\n}\n\n/** Resolve the current repository's origin as a canonical GitHub remote. */\nexport function currentGitHubRemote(cwd: string): GitHubRemote | undefined {\n\tconst remoteUrl = gitOriginUrl(cwd);\n\treturn remoteUrl ? parseGitHubRemote(remoteUrl) : undefined;\n}\n\n/** Read the git remote of cwd and normalize to \"owner/name\" (undefined outside a repo). */\nexport function gitRemoteSlug(cwd: string): string | undefined {\n\tconst url = gitOriginUrl(cwd);\n\tif (!url) return undefined;\n\t// https://github.com/owner/name(.git) | git@github.com:owner/name(.git)\n\tconst match = /[/:]([^/:]+\\/[^/:]+?)(?:\\.git)?$/.exec(url);\n\treturn match?.[1];\n}\n\n/** Match the cwd's remote against the hub inventory. Exact slug first, then name suffix. */\nexport function matchCurrentRepo(inventory: RepoInventory, slug: string | undefined): IndexedRepo | undefined {\n\tif (!slug) return undefined;\n\tconst lower = slug.toLowerCase();\n\tconst exact = inventory.repos.find((r) => r.name.toLowerCase() === lower);\n\tif (exact) return exact;\n\tconst name = lower.split(\"/\")[1] ?? lower;\n\treturn inventory.repos.find((r) => r.name.toLowerCase().endsWith(`/${name}`));\n}\n"]}
|
|
@@ -24,8 +24,7 @@ export function parseRepoInventory(markdown) {
|
|
|
24
24
|
}
|
|
25
25
|
return { repos, groups };
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
export function gitRemoteSlug(cwd) {
|
|
27
|
+
function gitOriginUrl(cwd) {
|
|
29
28
|
const result = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
30
29
|
cwd,
|
|
31
30
|
encoding: "utf8",
|
|
@@ -34,6 +33,57 @@ export function gitRemoteSlug(cwd) {
|
|
|
34
33
|
if (result.status !== 0)
|
|
35
34
|
return undefined;
|
|
36
35
|
const url = (result.stdout ?? "").trim();
|
|
36
|
+
return url || undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Parse a supported GitHub origin without preserving credentials or URL parameters. */
|
|
39
|
+
export function parseGitHubRemote(remoteUrl) {
|
|
40
|
+
if (!remoteUrl || /\s/.test(remoteUrl))
|
|
41
|
+
return undefined;
|
|
42
|
+
const scpMatch = /^git@github\.com:([^/]+)\/([^/]+)$/.exec(remoteUrl);
|
|
43
|
+
if (scpMatch?.[1] && scpMatch[2])
|
|
44
|
+
return normalizeGitHubRemote(scpMatch[1], scpMatch[2]);
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = new URL(remoteUrl);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
if (parsed.hostname !== "github.com" || parsed.port || parsed.search || parsed.hash)
|
|
53
|
+
return undefined;
|
|
54
|
+
if (parsed.protocol === "https:") {
|
|
55
|
+
if (parsed.username || parsed.password)
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
else if (parsed.protocol === "ssh:") {
|
|
59
|
+
if (parsed.username !== "git" || parsed.password)
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const pathMatch = /^\/([^/]+)\/([^/]+)$/.exec(parsed.pathname);
|
|
66
|
+
if (!pathMatch?.[1] || !pathMatch[2])
|
|
67
|
+
return undefined;
|
|
68
|
+
return normalizeGitHubRemote(pathMatch[1], pathMatch[2]);
|
|
69
|
+
}
|
|
70
|
+
function normalizeGitHubRemote(owner, rawName) {
|
|
71
|
+
const name = rawName.endsWith(".git") ? rawName.slice(0, -4) : rawName;
|
|
72
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(owner))
|
|
73
|
+
return undefined;
|
|
74
|
+
if (name.length > 100 || !/^[A-Za-z0-9._-]+$/.test(name) || name === "." || name === "..")
|
|
75
|
+
return undefined;
|
|
76
|
+
const fullName = `${owner}/${name}`;
|
|
77
|
+
return { name, fullName, cloneUrl: `https://github.com/${fullName}.git` };
|
|
78
|
+
}
|
|
79
|
+
/** Resolve the current repository's origin as a canonical GitHub remote. */
|
|
80
|
+
export function currentGitHubRemote(cwd) {
|
|
81
|
+
const remoteUrl = gitOriginUrl(cwd);
|
|
82
|
+
return remoteUrl ? parseGitHubRemote(remoteUrl) : undefined;
|
|
83
|
+
}
|
|
84
|
+
/** Read the git remote of cwd and normalize to "owner/name" (undefined outside a repo). */
|
|
85
|
+
export function gitRemoteSlug(cwd) {
|
|
86
|
+
const url = gitOriginUrl(cwd);
|
|
37
87
|
if (!url)
|
|
38
88
|
return undefined;
|
|
39
89
|
// https://github.com/owner/name(.git) | git@github.com:owner/name(.git)
|