@akon-labs/nexus-cli 0.1.11-rc.2 → 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.js +588 -63
- 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/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/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 +135 -15
- package/dist/extensions/gitnexus/index.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/index.js +588 -63
- 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/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +35 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/rpc-entry.js +588 -63
- package/dist/vendor/ai.js +14 -14
- package/dist/vendor/tui.js +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { GitNexusConfig } from "./config.ts";
|
|
2
|
+
export interface PersonalBillingStatus {
|
|
3
|
+
active: boolean;
|
|
4
|
+
planSlug: string;
|
|
5
|
+
repoLimit: number | null;
|
|
6
|
+
}
|
|
7
|
+
export interface BillingStatusResponse {
|
|
8
|
+
personal: PersonalBillingStatus | null;
|
|
9
|
+
org?: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface HubRepoJob {
|
|
12
|
+
status: string;
|
|
13
|
+
progress: number;
|
|
14
|
+
phase: string | null;
|
|
15
|
+
failureClass: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface HubRepo {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
fullName: string;
|
|
21
|
+
cloneUrl: string;
|
|
22
|
+
branch: string;
|
|
23
|
+
resolvedBranch?: string | null;
|
|
24
|
+
isDefaultBranch?: boolean;
|
|
25
|
+
status: "pending" | "cloning" | "indexing" | "ready" | "failed";
|
|
26
|
+
error: string | null;
|
|
27
|
+
queuePosition: number | null;
|
|
28
|
+
failureClass: string | null;
|
|
29
|
+
job: HubRepoJob | null;
|
|
30
|
+
}
|
|
31
|
+
export interface AddGitHubRepoInput {
|
|
32
|
+
name: string;
|
|
33
|
+
fullName: string;
|
|
34
|
+
cloneUrl: string;
|
|
35
|
+
branch: string;
|
|
36
|
+
}
|
|
37
|
+
export interface AddGitHubRepoResult {
|
|
38
|
+
id: string;
|
|
39
|
+
}
|
|
40
|
+
export declare class GitNexusHubApiError extends Error {
|
|
41
|
+
readonly status: number;
|
|
42
|
+
readonly repoId: string | undefined;
|
|
43
|
+
constructor(message: string, status: number, repoId?: string);
|
|
44
|
+
}
|
|
45
|
+
export declare class GitNexusHubApi {
|
|
46
|
+
private readonly apiBase;
|
|
47
|
+
private readonly config;
|
|
48
|
+
constructor(config: GitNexusConfig);
|
|
49
|
+
billingStatus(signal?: AbortSignal): Promise<BillingStatusResponse>;
|
|
50
|
+
listRepos(signal?: AbortSignal): Promise<HubRepo[]>;
|
|
51
|
+
addGitHubRepo(input: AddGitHubRepoInput, signal?: AbortSignal): Promise<AddGitHubRepoResult>;
|
|
52
|
+
getRepo(id: string, signal?: AbortSignal): Promise<HubRepo>;
|
|
53
|
+
reindex(id: string, signal?: AbortSignal): Promise<void>;
|
|
54
|
+
private request;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=hub-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub-api.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/hub-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,qBAAqB;IACrC,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAAC;IACvC,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;IAChE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,GAAG,EAAE,UAAU,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,MAAM,CAAC;CACX;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAEpC,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAK3D;CACD;AAID,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAM;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IAExC,YAAY,MAAM,EAAE,cAAc,EAWjC;IAED,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAElE;IAED,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAElD;IAED,aAAa,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAE3F;IAED,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAE1D;IAEK,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7D;YAEa,OAAO;CA8DrB","sourcesContent":["import type { GitNexusConfig } from \"./config.ts\";\n\nexport interface PersonalBillingStatus {\n\tactive: boolean;\n\tplanSlug: string;\n\trepoLimit: number | null;\n}\n\nexport interface BillingStatusResponse {\n\tpersonal: PersonalBillingStatus | null;\n\torg?: unknown;\n}\n\nexport interface HubRepoJob {\n\tstatus: string;\n\tprogress: number;\n\tphase: string | null;\n\tfailureClass: string | null;\n}\n\nexport interface HubRepo {\n\tid: string;\n\tname: string;\n\tfullName: string;\n\tcloneUrl: string;\n\tbranch: string;\n\tresolvedBranch?: string | null;\n\tisDefaultBranch?: boolean;\n\tstatus: \"pending\" | \"cloning\" | \"indexing\" | \"ready\" | \"failed\";\n\terror: string | null;\n\tqueuePosition: number | null;\n\tfailureClass: string | null;\n\tjob: HubRepoJob | null;\n}\n\nexport interface AddGitHubRepoInput {\n\tname: string;\n\tfullName: string;\n\tcloneUrl: string;\n\tbranch: string;\n}\n\nexport interface AddGitHubRepoResult {\n\tid: string;\n}\n\nexport class GitNexusHubApiError extends Error {\n\treadonly status: number;\n\treadonly repoId: string | undefined;\n\n\tconstructor(message: string, status: number, repoId?: string) {\n\t\tsuper(message);\n\t\tthis.name = \"GitNexusHubApiError\";\n\t\tthis.status = status;\n\t\tthis.repoId = repoId;\n\t}\n}\n\nconst REQUEST_TIMEOUT_MS = 30_000;\n\nexport class GitNexusHubApi {\n\tprivate readonly apiBase: URL;\n\tprivate readonly config: GitNexusConfig;\n\n\tconstructor(config: GitNexusConfig) {\n\t\tconst hubUrl = new URL(config.hubUrl);\n\t\tconst pathname = hubUrl.pathname.replace(/\\/$/, \"\");\n\t\tif (!pathname.endsWith(\"/mcp\")) {\n\t\t\tthrow new Error(\"GitNexus hub URL must end in /mcp\");\n\t\t}\n\t\thubUrl.pathname = `${pathname.slice(0, -\"/mcp\".length)}/api/`;\n\t\thubUrl.search = \"\";\n\t\thubUrl.hash = \"\";\n\t\tthis.apiBase = hubUrl;\n\t\tthis.config = config;\n\t}\n\n\tbillingStatus(signal?: AbortSignal): Promise<BillingStatusResponse> {\n\t\treturn this.request(\"billing/status\", \"GET\", undefined, signal);\n\t}\n\n\tlistRepos(signal?: AbortSignal): Promise<HubRepo[]> {\n\t\treturn this.request(\"repos\", \"GET\", undefined, signal);\n\t}\n\n\taddGitHubRepo(input: AddGitHubRepoInput, signal?: AbortSignal): Promise<AddGitHubRepoResult> {\n\t\treturn this.request(\"repos\", \"POST\", input, signal);\n\t}\n\n\tgetRepo(id: string, signal?: AbortSignal): Promise<HubRepo> {\n\t\treturn this.request(`repos/${encodeURIComponent(id)}`, \"GET\", undefined, signal);\n\t}\n\n\tasync reindex(id: string, signal?: AbortSignal): Promise<void> {\n\t\tawait this.request(`repos/${encodeURIComponent(id)}/reindex`, \"POST\", undefined, signal);\n\t}\n\n\tprivate async request<T>(path: string, method: \"GET\" | \"POST\", body?: unknown, signal?: AbortSignal): Promise<T> {\n\t\tsignal?.throwIfAborted();\n\t\tconst controller = new AbortController();\n\t\tconst timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\t\tconst onOuterAbort = () => controller.abort(signal?.reason);\n\t\tsignal?.addEventListener(\"abort\", onOuterAbort, { once: true });\n\n\t\ttry {\n\t\t\tlet response: Response;\n\t\t\ttry {\n\t\t\t\tresponse = await fetch(new URL(path, this.apiBase).toString(), {\n\t\t\t\t\tmethod,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\t...(body === undefined ? {} : { \"content-type\": \"application/json\" }),\n\t\t\t\t\t\tauthorization: `Bearer ${this.config.token}`,\n\t\t\t\t\t\t...(this.config.deviceFingerprint ? { \"x-device-fingerprint\": this.config.deviceFingerprint } : {}),\n\t\t\t\t\t\t...(this.config.deviceName ? { \"x-device-name\": this.config.deviceName } : {}),\n\t\t\t\t\t},\n\t\t\t\t\t...(body === undefined ? {} : { body: JSON.stringify(body) }),\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (signal?.aborted) throw error;\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new GitNexusHubApiError(`Cannot reach GitNexus hub: ${message}`, 0);\n\t\t\t}\n\n\t\t\tlet payload: unknown;\n\t\t\ttry {\n\t\t\t\tpayload = await response.json();\n\t\t\t} catch (error) {\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (controller.signal.aborted) {\n\t\t\t\t\tthrow new GitNexusHubApiError(`Cannot reach GitNexus hub: ${message}`, 0);\n\t\t\t\t}\n\t\t\t\tthrow new GitNexusHubApiError(\n\t\t\t\t\t`GitNexus hub returned an invalid JSON response: ${message}`,\n\t\t\t\t\tresponse.status,\n\t\t\t\t);\n\t\t\t}\n\t\t\tsignal?.throwIfAborted();\n\t\t\tif (controller.signal.aborted) {\n\t\t\t\tthrow new GitNexusHubApiError(\"Cannot reach GitNexus hub: request timed out\", 0);\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorPayload = isRecord(payload) ? payload : undefined;\n\t\t\t\tconst message =\n\t\t\t\t\ttypeof errorPayload?.error === \"string\"\n\t\t\t\t\t\t? errorPayload.error\n\t\t\t\t\t\t: `GitNexus hub request failed with HTTP ${response.status}`;\n\t\t\t\tconst repoId = typeof errorPayload?.repoId === \"string\" ? errorPayload.repoId : undefined;\n\t\t\t\tthrow new GitNexusHubApiError(message, response.status, repoId);\n\t\t\t}\n\n\t\t\treturn payload as T;\n\t\t} finally {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onOuterAbort);\n\t\t}\n\t}\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export class GitNexusHubApiError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
repoId;
|
|
4
|
+
constructor(message, status, repoId) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "GitNexusHubApiError";
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.repoId = repoId;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
12
|
+
export class GitNexusHubApi {
|
|
13
|
+
apiBase;
|
|
14
|
+
config;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
const hubUrl = new URL(config.hubUrl);
|
|
17
|
+
const pathname = hubUrl.pathname.replace(/\/$/, "");
|
|
18
|
+
if (!pathname.endsWith("/mcp")) {
|
|
19
|
+
throw new Error("GitNexus hub URL must end in /mcp");
|
|
20
|
+
}
|
|
21
|
+
hubUrl.pathname = `${pathname.slice(0, -"/mcp".length)}/api/`;
|
|
22
|
+
hubUrl.search = "";
|
|
23
|
+
hubUrl.hash = "";
|
|
24
|
+
this.apiBase = hubUrl;
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
billingStatus(signal) {
|
|
28
|
+
return this.request("billing/status", "GET", undefined, signal);
|
|
29
|
+
}
|
|
30
|
+
listRepos(signal) {
|
|
31
|
+
return this.request("repos", "GET", undefined, signal);
|
|
32
|
+
}
|
|
33
|
+
addGitHubRepo(input, signal) {
|
|
34
|
+
return this.request("repos", "POST", input, signal);
|
|
35
|
+
}
|
|
36
|
+
getRepo(id, signal) {
|
|
37
|
+
return this.request(`repos/${encodeURIComponent(id)}`, "GET", undefined, signal);
|
|
38
|
+
}
|
|
39
|
+
async reindex(id, signal) {
|
|
40
|
+
await this.request(`repos/${encodeURIComponent(id)}/reindex`, "POST", undefined, signal);
|
|
41
|
+
}
|
|
42
|
+
async request(path, method, body, signal) {
|
|
43
|
+
signal?.throwIfAborted();
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
46
|
+
const onOuterAbort = () => controller.abort(signal?.reason);
|
|
47
|
+
signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
48
|
+
try {
|
|
49
|
+
let response;
|
|
50
|
+
try {
|
|
51
|
+
response = await fetch(new URL(path, this.apiBase).toString(), {
|
|
52
|
+
method,
|
|
53
|
+
headers: {
|
|
54
|
+
accept: "application/json",
|
|
55
|
+
...(body === undefined ? {} : { "content-type": "application/json" }),
|
|
56
|
+
authorization: `Bearer ${this.config.token}`,
|
|
57
|
+
...(this.config.deviceFingerprint ? { "x-device-fingerprint": this.config.deviceFingerprint } : {}),
|
|
58
|
+
...(this.config.deviceName ? { "x-device-name": this.config.deviceName } : {}),
|
|
59
|
+
},
|
|
60
|
+
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
61
|
+
signal: controller.signal,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (signal?.aborted)
|
|
66
|
+
throw error;
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
throw new GitNexusHubApiError(`Cannot reach GitNexus hub: ${message}`, 0);
|
|
69
|
+
}
|
|
70
|
+
let payload;
|
|
71
|
+
try {
|
|
72
|
+
payload = await response.json();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
signal?.throwIfAborted();
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
if (controller.signal.aborted) {
|
|
78
|
+
throw new GitNexusHubApiError(`Cannot reach GitNexus hub: ${message}`, 0);
|
|
79
|
+
}
|
|
80
|
+
throw new GitNexusHubApiError(`GitNexus hub returned an invalid JSON response: ${message}`, response.status);
|
|
81
|
+
}
|
|
82
|
+
signal?.throwIfAborted();
|
|
83
|
+
if (controller.signal.aborted) {
|
|
84
|
+
throw new GitNexusHubApiError("Cannot reach GitNexus hub: request timed out", 0);
|
|
85
|
+
}
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
const errorPayload = isRecord(payload) ? payload : undefined;
|
|
88
|
+
const message = typeof errorPayload?.error === "string"
|
|
89
|
+
? errorPayload.error
|
|
90
|
+
: `GitNexus hub request failed with HTTP ${response.status}`;
|
|
91
|
+
const repoId = typeof errorPayload?.repoId === "string" ? errorPayload.repoId : undefined;
|
|
92
|
+
throw new GitNexusHubApiError(message, response.status, repoId);
|
|
93
|
+
}
|
|
94
|
+
return payload;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
signal?.removeEventListener("abort", onOuterAbort);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function isRecord(value) {
|
|
103
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=hub-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub-api.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/hub-api.ts"],"names":[],"mappings":"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","sourcesContent":["import type { GitNexusConfig } from \"./config.ts\";\n\nexport interface PersonalBillingStatus {\n\tactive: boolean;\n\tplanSlug: string;\n\trepoLimit: number | null;\n}\n\nexport interface BillingStatusResponse {\n\tpersonal: PersonalBillingStatus | null;\n\torg?: unknown;\n}\n\nexport interface HubRepoJob {\n\tstatus: string;\n\tprogress: number;\n\tphase: string | null;\n\tfailureClass: string | null;\n}\n\nexport interface HubRepo {\n\tid: string;\n\tname: string;\n\tfullName: string;\n\tcloneUrl: string;\n\tbranch: string;\n\tresolvedBranch?: string | null;\n\tisDefaultBranch?: boolean;\n\tstatus: \"pending\" | \"cloning\" | \"indexing\" | \"ready\" | \"failed\";\n\terror: string | null;\n\tqueuePosition: number | null;\n\tfailureClass: string | null;\n\tjob: HubRepoJob | null;\n}\n\nexport interface AddGitHubRepoInput {\n\tname: string;\n\tfullName: string;\n\tcloneUrl: string;\n\tbranch: string;\n}\n\nexport interface AddGitHubRepoResult {\n\tid: string;\n}\n\nexport class GitNexusHubApiError extends Error {\n\treadonly status: number;\n\treadonly repoId: string | undefined;\n\n\tconstructor(message: string, status: number, repoId?: string) {\n\t\tsuper(message);\n\t\tthis.name = \"GitNexusHubApiError\";\n\t\tthis.status = status;\n\t\tthis.repoId = repoId;\n\t}\n}\n\nconst REQUEST_TIMEOUT_MS = 30_000;\n\nexport class GitNexusHubApi {\n\tprivate readonly apiBase: URL;\n\tprivate readonly config: GitNexusConfig;\n\n\tconstructor(config: GitNexusConfig) {\n\t\tconst hubUrl = new URL(config.hubUrl);\n\t\tconst pathname = hubUrl.pathname.replace(/\\/$/, \"\");\n\t\tif (!pathname.endsWith(\"/mcp\")) {\n\t\t\tthrow new Error(\"GitNexus hub URL must end in /mcp\");\n\t\t}\n\t\thubUrl.pathname = `${pathname.slice(0, -\"/mcp\".length)}/api/`;\n\t\thubUrl.search = \"\";\n\t\thubUrl.hash = \"\";\n\t\tthis.apiBase = hubUrl;\n\t\tthis.config = config;\n\t}\n\n\tbillingStatus(signal?: AbortSignal): Promise<BillingStatusResponse> {\n\t\treturn this.request(\"billing/status\", \"GET\", undefined, signal);\n\t}\n\n\tlistRepos(signal?: AbortSignal): Promise<HubRepo[]> {\n\t\treturn this.request(\"repos\", \"GET\", undefined, signal);\n\t}\n\n\taddGitHubRepo(input: AddGitHubRepoInput, signal?: AbortSignal): Promise<AddGitHubRepoResult> {\n\t\treturn this.request(\"repos\", \"POST\", input, signal);\n\t}\n\n\tgetRepo(id: string, signal?: AbortSignal): Promise<HubRepo> {\n\t\treturn this.request(`repos/${encodeURIComponent(id)}`, \"GET\", undefined, signal);\n\t}\n\n\tasync reindex(id: string, signal?: AbortSignal): Promise<void> {\n\t\tawait this.request(`repos/${encodeURIComponent(id)}/reindex`, \"POST\", undefined, signal);\n\t}\n\n\tprivate async request<T>(path: string, method: \"GET\" | \"POST\", body?: unknown, signal?: AbortSignal): Promise<T> {\n\t\tsignal?.throwIfAborted();\n\t\tconst controller = new AbortController();\n\t\tconst timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\t\tconst onOuterAbort = () => controller.abort(signal?.reason);\n\t\tsignal?.addEventListener(\"abort\", onOuterAbort, { once: true });\n\n\t\ttry {\n\t\t\tlet response: Response;\n\t\t\ttry {\n\t\t\t\tresponse = await fetch(new URL(path, this.apiBase).toString(), {\n\t\t\t\t\tmethod,\n\t\t\t\t\theaders: {\n\t\t\t\t\t\taccept: \"application/json\",\n\t\t\t\t\t\t...(body === undefined ? {} : { \"content-type\": \"application/json\" }),\n\t\t\t\t\t\tauthorization: `Bearer ${this.config.token}`,\n\t\t\t\t\t\t...(this.config.deviceFingerprint ? { \"x-device-fingerprint\": this.config.deviceFingerprint } : {}),\n\t\t\t\t\t\t...(this.config.deviceName ? { \"x-device-name\": this.config.deviceName } : {}),\n\t\t\t\t\t},\n\t\t\t\t\t...(body === undefined ? {} : { body: JSON.stringify(body) }),\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tif (signal?.aborted) throw error;\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new GitNexusHubApiError(`Cannot reach GitNexus hub: ${message}`, 0);\n\t\t\t}\n\n\t\t\tlet payload: unknown;\n\t\t\ttry {\n\t\t\t\tpayload = await response.json();\n\t\t\t} catch (error) {\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tif (controller.signal.aborted) {\n\t\t\t\t\tthrow new GitNexusHubApiError(`Cannot reach GitNexus hub: ${message}`, 0);\n\t\t\t\t}\n\t\t\t\tthrow new GitNexusHubApiError(\n\t\t\t\t\t`GitNexus hub returned an invalid JSON response: ${message}`,\n\t\t\t\t\tresponse.status,\n\t\t\t\t);\n\t\t\t}\n\t\t\tsignal?.throwIfAborted();\n\t\t\tif (controller.signal.aborted) {\n\t\t\t\tthrow new GitNexusHubApiError(\"Cannot reach GitNexus hub: request timed out\", 0);\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorPayload = isRecord(payload) ? payload : undefined;\n\t\t\t\tconst message =\n\t\t\t\t\ttypeof errorPayload?.error === \"string\"\n\t\t\t\t\t\t? errorPayload.error\n\t\t\t\t\t\t: `GitNexus hub request failed with HTTP ${response.status}`;\n\t\t\t\tconst repoId = typeof errorPayload?.repoId === \"string\" ? errorPayload.repoId : undefined;\n\t\t\t\tthrow new GitNexusHubApiError(message, response.status, repoId);\n\t\t\t}\n\n\t\t\treturn payload as T;\n\t\t} finally {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onOuterAbort);\n\t\t}\n\t}\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type AddGitHubRepoInput, type AddGitHubRepoResult, type BillingStatusResponse, type HubRepo } from "./hub-api.ts";
|
|
2
|
+
import type { GitHubRemote } from "./resolve.ts";
|
|
3
|
+
export interface GitNexusHubApiLike {
|
|
4
|
+
billingStatus(signal?: AbortSignal): Promise<BillingStatusResponse>;
|
|
5
|
+
listRepos(signal?: AbortSignal): Promise<HubRepo[]>;
|
|
6
|
+
addGitHubRepo(input: AddGitHubRepoInput, signal?: AbortSignal): Promise<AddGitHubRepoResult>;
|
|
7
|
+
getRepo(id: string, signal?: AbortSignal): Promise<HubRepo>;
|
|
8
|
+
reindex(id: string, signal?: AbortSignal): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface IndexRepoUi {
|
|
11
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
12
|
+
notify(message: string, level: "info" | "warning" | "error"): void;
|
|
13
|
+
setStatus(key: string, text: string | undefined): void;
|
|
14
|
+
}
|
|
15
|
+
export type IndexCommandResult = {
|
|
16
|
+
kind: "blocked";
|
|
17
|
+
} | {
|
|
18
|
+
kind: "cancelled";
|
|
19
|
+
} | {
|
|
20
|
+
kind: "ready";
|
|
21
|
+
} | {
|
|
22
|
+
kind: "monitor";
|
|
23
|
+
repoId: string;
|
|
24
|
+
};
|
|
25
|
+
export interface RunIndexRepoCommandOptions {
|
|
26
|
+
api: GitNexusHubApiLike;
|
|
27
|
+
remote: GitHubRemote;
|
|
28
|
+
branch?: string;
|
|
29
|
+
ui: IndexRepoUi;
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
export declare function runIndexRepoCommand(options: RunIndexRepoCommandOptions): Promise<IndexCommandResult>;
|
|
33
|
+
export interface MonitorRepoIndexOptions {
|
|
34
|
+
api: Pick<GitNexusHubApiLike, "getRepo">;
|
|
35
|
+
repoId: string;
|
|
36
|
+
ui: Pick<IndexRepoUi, "notify" | "setStatus">;
|
|
37
|
+
onReady: () => void | Promise<void>;
|
|
38
|
+
onProgress?: (progress: RepoIndexProgress | undefined) => void;
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
pollIntervalMs?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface RepoIndexProgress {
|
|
43
|
+
fullName: string;
|
|
44
|
+
branch: string;
|
|
45
|
+
phase: string;
|
|
46
|
+
percent: number | undefined;
|
|
47
|
+
queuePosition: number | undefined;
|
|
48
|
+
}
|
|
49
|
+
export declare function monitorRepoIndex(options: MonitorRepoIndexOptions): Promise<"ready" | "failed" | "aborted">;
|
|
50
|
+
//# sourceMappingURL=index-repo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-repo.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/index-repo.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAE1B,KAAK,OAAO,EACZ,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IAClC,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;IACpE,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACpD,aAAa,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC7F,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5D,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,WAAW;IAC3B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,IAAI,CAAC;IACnE,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;CACvD;AAED,MAAM,MAAM,kBAAkB,GAC3B;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,0BAA0B;IAC1C,GAAG,EAAE,kBAAkB,CAAC;IACxB,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,WAAW,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA+D1G;AAkDD,MAAM,WAAW,uBAAuB;IACvC,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,GAAG,WAAW,CAAC,CAAC;IAC9C,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,iBAAiB,GAAG,SAAS,KAAK,IAAI,CAAC;IAC/D,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC,CAiChH","sourcesContent":["import {\n\ttype AddGitHubRepoInput,\n\ttype AddGitHubRepoResult,\n\ttype BillingStatusResponse,\n\tGitNexusHubApiError,\n\ttype HubRepo,\n} from \"./hub-api.ts\";\nimport type { GitHubRemote } from \"./resolve.ts\";\n\nexport interface GitNexusHubApiLike {\n\tbillingStatus(signal?: AbortSignal): Promise<BillingStatusResponse>;\n\tlistRepos(signal?: AbortSignal): Promise<HubRepo[]>;\n\taddGitHubRepo(input: AddGitHubRepoInput, signal?: AbortSignal): Promise<AddGitHubRepoResult>;\n\tgetRepo(id: string, signal?: AbortSignal): Promise<HubRepo>;\n\treindex(id: string, signal?: AbortSignal): Promise<void>;\n}\n\nexport interface IndexRepoUi {\n\tconfirm(title: string, message: string): Promise<boolean>;\n\tnotify(message: string, level: \"info\" | \"warning\" | \"error\"): void;\n\tsetStatus(key: string, text: string | undefined): void;\n}\n\nexport type IndexCommandResult =\n\t| { kind: \"blocked\" }\n\t| { kind: \"cancelled\" }\n\t| { kind: \"ready\" }\n\t| { kind: \"monitor\"; repoId: string };\n\nexport interface RunIndexRepoCommandOptions {\n\tapi: GitNexusHubApiLike;\n\tremote: GitHubRemote;\n\tbranch?: string;\n\tui: IndexRepoUi;\n\tsignal?: AbortSignal;\n}\n\nexport async function runIndexRepoCommand(options: RunIndexRepoCommandOptions): Promise<IndexCommandResult> {\n\tconst branch = options.branch || \"default\";\n\tconst billing = await options.api.billingStatus(options.signal);\n\tconst personal = billing.personal;\n\tif (!personal?.active) {\n\t\toptions.ui.notify(\n\t\t\t\"GitNexus repository indexing requires an active subscription. Check account billing.\",\n\t\t\t\"error\",\n\t\t);\n\t\treturn { kind: \"blocked\" };\n\t}\n\tif (personal.repoLimit === null || !Number.isFinite(personal.repoLimit) || personal.repoLimit < 0) {\n\t\toptions.ui.notify(\"Your GitNexus subscription does not include a usable repository allowance.\", \"error\");\n\t\treturn { kind: \"blocked\" };\n\t}\n\n\tconst repos = await options.api.listRepos(options.signal);\n\tconst existing = repos.find(\n\t\t(repo) =>\n\t\t\trepo.fullName.toLowerCase() === options.remote.fullName.toLowerCase() &&\n\t\t\t(repo.branch === branch ||\n\t\t\t\t(branch === \"default\" && repo.isDefaultBranch === true) ||\n\t\t\t\t(repo.isDefaultBranch === true && repo.resolvedBranch === branch)),\n\t);\n\tif (existing) return handleExistingRepo(options, existing);\n\n\tconst used = repos.length;\n\tif (used >= personal.repoLimit) {\n\t\toptions.ui.notify(\n\t\t\t`Repository allowance exhausted (${used} of ${personal.repoLimit}). Delete a repository or update account billing.`,\n\t\t\t\"error\",\n\t\t);\n\t\treturn { kind: \"blocked\" };\n\t}\n\n\tconst projectedUsed = used + 1;\n\tconst projectedRemaining = Math.max(0, personal.repoLimit - projectedUsed);\n\tconst confirmed = await options.ui.confirm(\n\t\t\"Index repository?\",\n\t\t[\n\t\t\t`Repository: ${options.remote.fullName}`,\n\t\t\t`Branch: ${branch}`,\n\t\t\t`Subscription: ${formatPlanSlug(personal.planSlug)}`,\n\t\t\t`Repository usage: ${used} of ${personal.repoLimit}`,\n\t\t\t`After indexing: ${projectedUsed} of ${personal.repoLimit}`,\n\t\t\t`You can index ${projectedRemaining} more repositories after this.`,\n\t\t\t\"\",\n\t\t\t\"Proceed?\",\n\t\t].join(\"\\n\"),\n\t);\n\tif (!confirmed) return { kind: \"cancelled\" };\n\n\ttry {\n\t\tconst added = await options.api.addGitHubRepo({ ...options.remote, branch }, options.signal);\n\t\toptions.ui.notify(`Indexing started for ${options.remote.fullName} (${branch}).`, \"info\");\n\t\treturn { kind: \"monitor\", repoId: added.id };\n\t} catch (error) {\n\t\tif (error instanceof GitNexusHubApiError && error.status === 409 && error.repoId) {\n\t\t\toptions.ui.notify(`${options.remote.fullName} (${branch}) is already being indexed.`, \"info\");\n\t\t\treturn { kind: \"monitor\", repoId: error.repoId };\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nasync function handleExistingRepo(options: RunIndexRepoCommandOptions, repo: HubRepo): Promise<IndexCommandResult> {\n\tif (repo.status === \"ready\") {\n\t\tconst confirmed = await options.ui.confirm(\n\t\t\t\"Reindex repository?\",\n\t\t\t[\n\t\t\t\t`Repository: ${repo.fullName}`,\n\t\t\t\t`Indexed branch: ${repo.resolvedBranch ?? repo.branch}`,\n\t\t\t\t\"This repository is already indexed.\",\n\t\t\t\t\"Reindexing does not increase repository usage.\",\n\t\t\t\t\"\",\n\t\t\t\t\"Proceed?\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\tif (!confirmed) return { kind: \"ready\" };\n\t\tawait options.api.reindex(repo.id, options.signal);\n\t\toptions.ui.notify(`Reindexing started for ${repo.fullName} (${repo.resolvedBranch ?? repo.branch}).`, \"info\");\n\t\treturn { kind: \"monitor\", repoId: repo.id };\n\t}\n\tif (repo.status !== \"failed\") {\n\t\toptions.ui.notify(`${repo.fullName} (${repo.branch}) is already being indexed.`, \"info\");\n\t\treturn { kind: \"monitor\", repoId: repo.id };\n\t}\n\n\tconst confirmed = await options.ui.confirm(\n\t\t\"Retry repository indexing?\",\n\t\t[\n\t\t\t`Repository: ${repo.fullName}`,\n\t\t\t`Branch: ${repo.branch}`,\n\t\t\t\"Retrying this repository does not increase repository usage.\",\n\t\t\t...(repo.error ? [`Last error: ${repo.error}`] : []),\n\t\t\t\"\",\n\t\t\t\"Proceed?\",\n\t\t].join(\"\\n\"),\n\t);\n\tif (!confirmed) return { kind: \"cancelled\" };\n\tawait options.api.reindex(repo.id, options.signal);\n\toptions.ui.notify(`Retrying indexing for ${repo.fullName} (${repo.resolvedBranch ?? repo.branch}).`, \"info\");\n\treturn { kind: \"monitor\", repoId: repo.id };\n}\n\nfunction formatPlanSlug(planSlug: string): string {\n\treturn planSlug\n\t\t.split(/[-_]/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part[0]?.toUpperCase() + part.slice(1))\n\t\t.join(\" \");\n}\n\nexport interface MonitorRepoIndexOptions {\n\tapi: Pick<GitNexusHubApiLike, \"getRepo\">;\n\trepoId: string;\n\tui: Pick<IndexRepoUi, \"notify\" | \"setStatus\">;\n\tonReady: () => void | Promise<void>;\n\tonProgress?: (progress: RepoIndexProgress | undefined) => void;\n\tsignal?: AbortSignal;\n\tpollIntervalMs?: number;\n}\n\nexport interface RepoIndexProgress {\n\tfullName: string;\n\tbranch: string;\n\tphase: string;\n\tpercent: number | undefined;\n\tqueuePosition: number | undefined;\n}\n\nexport async function monitorRepoIndex(options: MonitorRepoIndexOptions): Promise<\"ready\" | \"failed\" | \"aborted\"> {\n\tconst pollIntervalMs = options.pollIntervalMs ?? 2_000;\n\ttry {\n\t\twhile (!options.signal?.aborted) {\n\t\t\tlet repo: HubRepo;\n\t\t\ttry {\n\t\t\t\trepo = await options.api.getRepo(options.repoId, options.signal);\n\t\t\t} catch (error) {\n\t\t\t\tif (options.signal?.aborted) return \"aborted\";\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\toptions.ui.notify(`GitNexus indexing monitor failed: ${message}`, \"error\");\n\t\t\t\treturn \"failed\";\n\t\t\t}\n\t\t\tif (options.signal?.aborted) return \"aborted\";\n\n\t\t\tif (repo.status === \"ready\") {\n\t\t\t\tawait options.onReady();\n\t\t\t\treturn \"ready\";\n\t\t\t}\n\t\t\tif (repo.status === \"failed\") {\n\t\t\t\toptions.ui.notify(`GitNexus indexing failed: ${repo.error || \"Unknown indexing error\"}`, \"error\");\n\t\t\t\treturn \"failed\";\n\t\t\t}\n\n\t\t\toptions.ui.setStatus(\"gitnexus-index\", formatRepoProgress(repo));\n\t\t\toptions.onProgress?.(repoIndexProgress(repo));\n\t\t\tawait waitForPoll(pollIntervalMs, options.signal);\n\t\t}\n\t\treturn \"aborted\";\n\t} finally {\n\t\toptions.ui.setStatus(\"gitnexus-index\", undefined);\n\t\toptions.onProgress?.(undefined);\n\t}\n}\n\nfunction repoIndexProgress(repo: HubRepo): RepoIndexProgress {\n\tconst queuePosition = repo.queuePosition ?? undefined;\n\treturn {\n\t\tfullName: repo.fullName,\n\t\tbranch: repo.branch,\n\t\tphase: queuePosition === undefined ? (repo.job?.phase ?? repo.status) : `Queued #${queuePosition}`,\n\t\tpercent: repo.job ? Math.round(repo.job.progress) : undefined,\n\t\tqueuePosition,\n\t};\n}\n\nfunction formatRepoProgress(repo: HubRepo): string {\n\tif (repo.queuePosition !== null) return `GitNexus: queued (#${repo.queuePosition})`;\n\tif (repo.job?.phase) return `GitNexus: ${repo.job.phase} (${Math.round(repo.job.progress)}%)`;\n\treturn `GitNexus: ${repo.status}`;\n}\n\nfunction waitForPoll(delayMs: number, signal?: AbortSignal): Promise<void> {\n\tif (signal?.aborted) return Promise.resolve();\n\treturn new Promise((resolve) => {\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\tresolve();\n\t\t};\n\t\tconst timeout = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, delayMs);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n"]}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { GitNexusHubApiError, } from "./hub-api.js";
|
|
2
|
+
export async function runIndexRepoCommand(options) {
|
|
3
|
+
const branch = options.branch || "default";
|
|
4
|
+
const billing = await options.api.billingStatus(options.signal);
|
|
5
|
+
const personal = billing.personal;
|
|
6
|
+
if (!personal?.active) {
|
|
7
|
+
options.ui.notify("GitNexus repository indexing requires an active subscription. Check account billing.", "error");
|
|
8
|
+
return { kind: "blocked" };
|
|
9
|
+
}
|
|
10
|
+
if (personal.repoLimit === null || !Number.isFinite(personal.repoLimit) || personal.repoLimit < 0) {
|
|
11
|
+
options.ui.notify("Your GitNexus subscription does not include a usable repository allowance.", "error");
|
|
12
|
+
return { kind: "blocked" };
|
|
13
|
+
}
|
|
14
|
+
const repos = await options.api.listRepos(options.signal);
|
|
15
|
+
const existing = repos.find((repo) => repo.fullName.toLowerCase() === options.remote.fullName.toLowerCase() &&
|
|
16
|
+
(repo.branch === branch ||
|
|
17
|
+
(branch === "default" && repo.isDefaultBranch === true) ||
|
|
18
|
+
(repo.isDefaultBranch === true && repo.resolvedBranch === branch)));
|
|
19
|
+
if (existing)
|
|
20
|
+
return handleExistingRepo(options, existing);
|
|
21
|
+
const used = repos.length;
|
|
22
|
+
if (used >= personal.repoLimit) {
|
|
23
|
+
options.ui.notify(`Repository allowance exhausted (${used} of ${personal.repoLimit}). Delete a repository or update account billing.`, "error");
|
|
24
|
+
return { kind: "blocked" };
|
|
25
|
+
}
|
|
26
|
+
const projectedUsed = used + 1;
|
|
27
|
+
const projectedRemaining = Math.max(0, personal.repoLimit - projectedUsed);
|
|
28
|
+
const confirmed = await options.ui.confirm("Index repository?", [
|
|
29
|
+
`Repository: ${options.remote.fullName}`,
|
|
30
|
+
`Branch: ${branch}`,
|
|
31
|
+
`Subscription: ${formatPlanSlug(personal.planSlug)}`,
|
|
32
|
+
`Repository usage: ${used} of ${personal.repoLimit}`,
|
|
33
|
+
`After indexing: ${projectedUsed} of ${personal.repoLimit}`,
|
|
34
|
+
`You can index ${projectedRemaining} more repositories after this.`,
|
|
35
|
+
"",
|
|
36
|
+
"Proceed?",
|
|
37
|
+
].join("\n"));
|
|
38
|
+
if (!confirmed)
|
|
39
|
+
return { kind: "cancelled" };
|
|
40
|
+
try {
|
|
41
|
+
const added = await options.api.addGitHubRepo({ ...options.remote, branch }, options.signal);
|
|
42
|
+
options.ui.notify(`Indexing started for ${options.remote.fullName} (${branch}).`, "info");
|
|
43
|
+
return { kind: "monitor", repoId: added.id };
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof GitNexusHubApiError && error.status === 409 && error.repoId) {
|
|
47
|
+
options.ui.notify(`${options.remote.fullName} (${branch}) is already being indexed.`, "info");
|
|
48
|
+
return { kind: "monitor", repoId: error.repoId };
|
|
49
|
+
}
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function handleExistingRepo(options, repo) {
|
|
54
|
+
if (repo.status === "ready") {
|
|
55
|
+
const confirmed = await options.ui.confirm("Reindex repository?", [
|
|
56
|
+
`Repository: ${repo.fullName}`,
|
|
57
|
+
`Indexed branch: ${repo.resolvedBranch ?? repo.branch}`,
|
|
58
|
+
"This repository is already indexed.",
|
|
59
|
+
"Reindexing does not increase repository usage.",
|
|
60
|
+
"",
|
|
61
|
+
"Proceed?",
|
|
62
|
+
].join("\n"));
|
|
63
|
+
if (!confirmed)
|
|
64
|
+
return { kind: "ready" };
|
|
65
|
+
await options.api.reindex(repo.id, options.signal);
|
|
66
|
+
options.ui.notify(`Reindexing started for ${repo.fullName} (${repo.resolvedBranch ?? repo.branch}).`, "info");
|
|
67
|
+
return { kind: "monitor", repoId: repo.id };
|
|
68
|
+
}
|
|
69
|
+
if (repo.status !== "failed") {
|
|
70
|
+
options.ui.notify(`${repo.fullName} (${repo.branch}) is already being indexed.`, "info");
|
|
71
|
+
return { kind: "monitor", repoId: repo.id };
|
|
72
|
+
}
|
|
73
|
+
const confirmed = await options.ui.confirm("Retry repository indexing?", [
|
|
74
|
+
`Repository: ${repo.fullName}`,
|
|
75
|
+
`Branch: ${repo.branch}`,
|
|
76
|
+
"Retrying this repository does not increase repository usage.",
|
|
77
|
+
...(repo.error ? [`Last error: ${repo.error}`] : []),
|
|
78
|
+
"",
|
|
79
|
+
"Proceed?",
|
|
80
|
+
].join("\n"));
|
|
81
|
+
if (!confirmed)
|
|
82
|
+
return { kind: "cancelled" };
|
|
83
|
+
await options.api.reindex(repo.id, options.signal);
|
|
84
|
+
options.ui.notify(`Retrying indexing for ${repo.fullName} (${repo.resolvedBranch ?? repo.branch}).`, "info");
|
|
85
|
+
return { kind: "monitor", repoId: repo.id };
|
|
86
|
+
}
|
|
87
|
+
function formatPlanSlug(planSlug) {
|
|
88
|
+
return planSlug
|
|
89
|
+
.split(/[-_]/)
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.map((part) => part[0]?.toUpperCase() + part.slice(1))
|
|
92
|
+
.join(" ");
|
|
93
|
+
}
|
|
94
|
+
export async function monitorRepoIndex(options) {
|
|
95
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2_000;
|
|
96
|
+
try {
|
|
97
|
+
while (!options.signal?.aborted) {
|
|
98
|
+
let repo;
|
|
99
|
+
try {
|
|
100
|
+
repo = await options.api.getRepo(options.repoId, options.signal);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (options.signal?.aborted)
|
|
104
|
+
return "aborted";
|
|
105
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
106
|
+
options.ui.notify(`GitNexus indexing monitor failed: ${message}`, "error");
|
|
107
|
+
return "failed";
|
|
108
|
+
}
|
|
109
|
+
if (options.signal?.aborted)
|
|
110
|
+
return "aborted";
|
|
111
|
+
if (repo.status === "ready") {
|
|
112
|
+
await options.onReady();
|
|
113
|
+
return "ready";
|
|
114
|
+
}
|
|
115
|
+
if (repo.status === "failed") {
|
|
116
|
+
options.ui.notify(`GitNexus indexing failed: ${repo.error || "Unknown indexing error"}`, "error");
|
|
117
|
+
return "failed";
|
|
118
|
+
}
|
|
119
|
+
options.ui.setStatus("gitnexus-index", formatRepoProgress(repo));
|
|
120
|
+
options.onProgress?.(repoIndexProgress(repo));
|
|
121
|
+
await waitForPoll(pollIntervalMs, options.signal);
|
|
122
|
+
}
|
|
123
|
+
return "aborted";
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
options.ui.setStatus("gitnexus-index", undefined);
|
|
127
|
+
options.onProgress?.(undefined);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function repoIndexProgress(repo) {
|
|
131
|
+
const queuePosition = repo.queuePosition ?? undefined;
|
|
132
|
+
return {
|
|
133
|
+
fullName: repo.fullName,
|
|
134
|
+
branch: repo.branch,
|
|
135
|
+
phase: queuePosition === undefined ? (repo.job?.phase ?? repo.status) : `Queued #${queuePosition}`,
|
|
136
|
+
percent: repo.job ? Math.round(repo.job.progress) : undefined,
|
|
137
|
+
queuePosition,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function formatRepoProgress(repo) {
|
|
141
|
+
if (repo.queuePosition !== null)
|
|
142
|
+
return `GitNexus: queued (#${repo.queuePosition})`;
|
|
143
|
+
if (repo.job?.phase)
|
|
144
|
+
return `GitNexus: ${repo.job.phase} (${Math.round(repo.job.progress)}%)`;
|
|
145
|
+
return `GitNexus: ${repo.status}`;
|
|
146
|
+
}
|
|
147
|
+
function waitForPoll(delayMs, signal) {
|
|
148
|
+
if (signal?.aborted)
|
|
149
|
+
return Promise.resolve();
|
|
150
|
+
return new Promise((resolve) => {
|
|
151
|
+
const onAbort = () => {
|
|
152
|
+
clearTimeout(timeout);
|
|
153
|
+
resolve();
|
|
154
|
+
};
|
|
155
|
+
const timeout = setTimeout(() => {
|
|
156
|
+
signal?.removeEventListener("abort", onAbort);
|
|
157
|
+
resolve();
|
|
158
|
+
}, delayMs);
|
|
159
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=index-repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-repo.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/index-repo.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype AddGitHubRepoInput,\n\ttype AddGitHubRepoResult,\n\ttype BillingStatusResponse,\n\tGitNexusHubApiError,\n\ttype HubRepo,\n} from \"./hub-api.ts\";\nimport type { GitHubRemote } from \"./resolve.ts\";\n\nexport interface GitNexusHubApiLike {\n\tbillingStatus(signal?: AbortSignal): Promise<BillingStatusResponse>;\n\tlistRepos(signal?: AbortSignal): Promise<HubRepo[]>;\n\taddGitHubRepo(input: AddGitHubRepoInput, signal?: AbortSignal): Promise<AddGitHubRepoResult>;\n\tgetRepo(id: string, signal?: AbortSignal): Promise<HubRepo>;\n\treindex(id: string, signal?: AbortSignal): Promise<void>;\n}\n\nexport interface IndexRepoUi {\n\tconfirm(title: string, message: string): Promise<boolean>;\n\tnotify(message: string, level: \"info\" | \"warning\" | \"error\"): void;\n\tsetStatus(key: string, text: string | undefined): void;\n}\n\nexport type IndexCommandResult =\n\t| { kind: \"blocked\" }\n\t| { kind: \"cancelled\" }\n\t| { kind: \"ready\" }\n\t| { kind: \"monitor\"; repoId: string };\n\nexport interface RunIndexRepoCommandOptions {\n\tapi: GitNexusHubApiLike;\n\tremote: GitHubRemote;\n\tbranch?: string;\n\tui: IndexRepoUi;\n\tsignal?: AbortSignal;\n}\n\nexport async function runIndexRepoCommand(options: RunIndexRepoCommandOptions): Promise<IndexCommandResult> {\n\tconst branch = options.branch || \"default\";\n\tconst billing = await options.api.billingStatus(options.signal);\n\tconst personal = billing.personal;\n\tif (!personal?.active) {\n\t\toptions.ui.notify(\n\t\t\t\"GitNexus repository indexing requires an active subscription. Check account billing.\",\n\t\t\t\"error\",\n\t\t);\n\t\treturn { kind: \"blocked\" };\n\t}\n\tif (personal.repoLimit === null || !Number.isFinite(personal.repoLimit) || personal.repoLimit < 0) {\n\t\toptions.ui.notify(\"Your GitNexus subscription does not include a usable repository allowance.\", \"error\");\n\t\treturn { kind: \"blocked\" };\n\t}\n\n\tconst repos = await options.api.listRepos(options.signal);\n\tconst existing = repos.find(\n\t\t(repo) =>\n\t\t\trepo.fullName.toLowerCase() === options.remote.fullName.toLowerCase() &&\n\t\t\t(repo.branch === branch ||\n\t\t\t\t(branch === \"default\" && repo.isDefaultBranch === true) ||\n\t\t\t\t(repo.isDefaultBranch === true && repo.resolvedBranch === branch)),\n\t);\n\tif (existing) return handleExistingRepo(options, existing);\n\n\tconst used = repos.length;\n\tif (used >= personal.repoLimit) {\n\t\toptions.ui.notify(\n\t\t\t`Repository allowance exhausted (${used} of ${personal.repoLimit}). Delete a repository or update account billing.`,\n\t\t\t\"error\",\n\t\t);\n\t\treturn { kind: \"blocked\" };\n\t}\n\n\tconst projectedUsed = used + 1;\n\tconst projectedRemaining = Math.max(0, personal.repoLimit - projectedUsed);\n\tconst confirmed = await options.ui.confirm(\n\t\t\"Index repository?\",\n\t\t[\n\t\t\t`Repository: ${options.remote.fullName}`,\n\t\t\t`Branch: ${branch}`,\n\t\t\t`Subscription: ${formatPlanSlug(personal.planSlug)}`,\n\t\t\t`Repository usage: ${used} of ${personal.repoLimit}`,\n\t\t\t`After indexing: ${projectedUsed} of ${personal.repoLimit}`,\n\t\t\t`You can index ${projectedRemaining} more repositories after this.`,\n\t\t\t\"\",\n\t\t\t\"Proceed?\",\n\t\t].join(\"\\n\"),\n\t);\n\tif (!confirmed) return { kind: \"cancelled\" };\n\n\ttry {\n\t\tconst added = await options.api.addGitHubRepo({ ...options.remote, branch }, options.signal);\n\t\toptions.ui.notify(`Indexing started for ${options.remote.fullName} (${branch}).`, \"info\");\n\t\treturn { kind: \"monitor\", repoId: added.id };\n\t} catch (error) {\n\t\tif (error instanceof GitNexusHubApiError && error.status === 409 && error.repoId) {\n\t\t\toptions.ui.notify(`${options.remote.fullName} (${branch}) is already being indexed.`, \"info\");\n\t\t\treturn { kind: \"monitor\", repoId: error.repoId };\n\t\t}\n\t\tthrow error;\n\t}\n}\n\nasync function handleExistingRepo(options: RunIndexRepoCommandOptions, repo: HubRepo): Promise<IndexCommandResult> {\n\tif (repo.status === \"ready\") {\n\t\tconst confirmed = await options.ui.confirm(\n\t\t\t\"Reindex repository?\",\n\t\t\t[\n\t\t\t\t`Repository: ${repo.fullName}`,\n\t\t\t\t`Indexed branch: ${repo.resolvedBranch ?? repo.branch}`,\n\t\t\t\t\"This repository is already indexed.\",\n\t\t\t\t\"Reindexing does not increase repository usage.\",\n\t\t\t\t\"\",\n\t\t\t\t\"Proceed?\",\n\t\t\t].join(\"\\n\"),\n\t\t);\n\t\tif (!confirmed) return { kind: \"ready\" };\n\t\tawait options.api.reindex(repo.id, options.signal);\n\t\toptions.ui.notify(`Reindexing started for ${repo.fullName} (${repo.resolvedBranch ?? repo.branch}).`, \"info\");\n\t\treturn { kind: \"monitor\", repoId: repo.id };\n\t}\n\tif (repo.status !== \"failed\") {\n\t\toptions.ui.notify(`${repo.fullName} (${repo.branch}) is already being indexed.`, \"info\");\n\t\treturn { kind: \"monitor\", repoId: repo.id };\n\t}\n\n\tconst confirmed = await options.ui.confirm(\n\t\t\"Retry repository indexing?\",\n\t\t[\n\t\t\t`Repository: ${repo.fullName}`,\n\t\t\t`Branch: ${repo.branch}`,\n\t\t\t\"Retrying this repository does not increase repository usage.\",\n\t\t\t...(repo.error ? [`Last error: ${repo.error}`] : []),\n\t\t\t\"\",\n\t\t\t\"Proceed?\",\n\t\t].join(\"\\n\"),\n\t);\n\tif (!confirmed) return { kind: \"cancelled\" };\n\tawait options.api.reindex(repo.id, options.signal);\n\toptions.ui.notify(`Retrying indexing for ${repo.fullName} (${repo.resolvedBranch ?? repo.branch}).`, \"info\");\n\treturn { kind: \"monitor\", repoId: repo.id };\n}\n\nfunction formatPlanSlug(planSlug: string): string {\n\treturn planSlug\n\t\t.split(/[-_]/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part[0]?.toUpperCase() + part.slice(1))\n\t\t.join(\" \");\n}\n\nexport interface MonitorRepoIndexOptions {\n\tapi: Pick<GitNexusHubApiLike, \"getRepo\">;\n\trepoId: string;\n\tui: Pick<IndexRepoUi, \"notify\" | \"setStatus\">;\n\tonReady: () => void | Promise<void>;\n\tonProgress?: (progress: RepoIndexProgress | undefined) => void;\n\tsignal?: AbortSignal;\n\tpollIntervalMs?: number;\n}\n\nexport interface RepoIndexProgress {\n\tfullName: string;\n\tbranch: string;\n\tphase: string;\n\tpercent: number | undefined;\n\tqueuePosition: number | undefined;\n}\n\nexport async function monitorRepoIndex(options: MonitorRepoIndexOptions): Promise<\"ready\" | \"failed\" | \"aborted\"> {\n\tconst pollIntervalMs = options.pollIntervalMs ?? 2_000;\n\ttry {\n\t\twhile (!options.signal?.aborted) {\n\t\t\tlet repo: HubRepo;\n\t\t\ttry {\n\t\t\t\trepo = await options.api.getRepo(options.repoId, options.signal);\n\t\t\t} catch (error) {\n\t\t\t\tif (options.signal?.aborted) return \"aborted\";\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\toptions.ui.notify(`GitNexus indexing monitor failed: ${message}`, \"error\");\n\t\t\t\treturn \"failed\";\n\t\t\t}\n\t\t\tif (options.signal?.aborted) return \"aborted\";\n\n\t\t\tif (repo.status === \"ready\") {\n\t\t\t\tawait options.onReady();\n\t\t\t\treturn \"ready\";\n\t\t\t}\n\t\t\tif (repo.status === \"failed\") {\n\t\t\t\toptions.ui.notify(`GitNexus indexing failed: ${repo.error || \"Unknown indexing error\"}`, \"error\");\n\t\t\t\treturn \"failed\";\n\t\t\t}\n\n\t\t\toptions.ui.setStatus(\"gitnexus-index\", formatRepoProgress(repo));\n\t\t\toptions.onProgress?.(repoIndexProgress(repo));\n\t\t\tawait waitForPoll(pollIntervalMs, options.signal);\n\t\t}\n\t\treturn \"aborted\";\n\t} finally {\n\t\toptions.ui.setStatus(\"gitnexus-index\", undefined);\n\t\toptions.onProgress?.(undefined);\n\t}\n}\n\nfunction repoIndexProgress(repo: HubRepo): RepoIndexProgress {\n\tconst queuePosition = repo.queuePosition ?? undefined;\n\treturn {\n\t\tfullName: repo.fullName,\n\t\tbranch: repo.branch,\n\t\tphase: queuePosition === undefined ? (repo.job?.phase ?? repo.status) : `Queued #${queuePosition}`,\n\t\tpercent: repo.job ? Math.round(repo.job.progress) : undefined,\n\t\tqueuePosition,\n\t};\n}\n\nfunction formatRepoProgress(repo: HubRepo): string {\n\tif (repo.queuePosition !== null) return `GitNexus: queued (#${repo.queuePosition})`;\n\tif (repo.job?.phase) return `GitNexus: ${repo.job.phase} (${Math.round(repo.job.progress)}%)`;\n\treturn `GitNexus: ${repo.status}`;\n}\n\nfunction waitForPoll(delayMs: number, signal?: AbortSignal): Promise<void> {\n\tif (signal?.aborted) return Promise.resolve();\n\treturn new Promise((resolve) => {\n\t\tconst onAbort = () => {\n\t\t\tclearTimeout(timeout);\n\t\t\tresolve();\n\t\t};\n\t\tconst timeout = setTimeout(() => {\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\tresolve();\n\t\t}, delayMs);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AA2BnE,2EAA2E;AAC3E,wBAAgB,wBAAwB,CACvC,SAAS,GAAE,MAAwB,EACnC,UAAU,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,GAChD,MAAM,GAAG,SAAS,CAMpB;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAoNhE","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 { 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 = 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\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.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AA8BnE,2EAA2E;AAC3E,wBAAgB,wBAAwB,CACvC,SAAS,GAAE,MAAwB,EACnC,UAAU,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,GAChD,MAAM,GAAG,SAAS,CAMpB;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA0UhE","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"]}
|