@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
|
@@ -51,6 +51,14 @@ export interface RailRepoListEntry {
|
|
|
51
51
|
name: string;
|
|
52
52
|
symbols: string;
|
|
53
53
|
}
|
|
54
|
+
export interface RailIndexProgress {
|
|
55
|
+
fullName: string;
|
|
56
|
+
branch: string;
|
|
57
|
+
phase: string;
|
|
58
|
+
percent: number | undefined;
|
|
59
|
+
queuePosition: number | undefined;
|
|
60
|
+
}
|
|
61
|
+
export declare function formatCompactIndexPhase(phase: string): string;
|
|
54
62
|
export interface RailCommunity {
|
|
55
63
|
name: string;
|
|
56
64
|
members: number;
|
|
@@ -70,6 +78,7 @@ export interface GraphRailState {
|
|
|
70
78
|
repo?: RailRepoInfo;
|
|
71
79
|
currentIndexed: boolean;
|
|
72
80
|
repos: RailRepoListEntry[];
|
|
81
|
+
indexProgress?: RailIndexProgress;
|
|
73
82
|
observatory?: RailObservatory;
|
|
74
83
|
impact?: RailImpact;
|
|
75
84
|
query?: RailQuery;
|
|
@@ -83,6 +92,7 @@ export declare const graphRailStore: {
|
|
|
83
92
|
subscribe(listener: Listener): () => void;
|
|
84
93
|
setRepo(repo: RailRepoInfo | undefined, currentIndexed: boolean): void;
|
|
85
94
|
setRepoList(repos: RailRepoListEntry[]): void;
|
|
95
|
+
setIndexProgress(progress: RailIndexProgress | undefined): void;
|
|
86
96
|
setObservatory(observatory: RailObservatory): void;
|
|
87
97
|
recordGraphCall(): void;
|
|
88
98
|
setImpact(impact: RailImpact): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-rail-store.d.ts","sourceRoot":"","sources":["../../src/core/graph-rail-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;IAC1C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,QAAQ;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC/B,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AAsB3B,eAAO,MAAM,cAAc
|
|
1
|
+
{"version":3,"file":"graph-rail-store.d.ts","sourceRoot":"","sources":["../../src/core/graph-rail-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAA+D;IAC/D,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;IAC1C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,QAAQ;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;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,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,MAAM,WAAW,aAAa;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CACf;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC/B,WAAW,EAAE,aAAa,EAAE,CAAC;IAC7B,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AAsB3B,eAAO,MAAM,cAAc;;;;;;;;;;;;CA8D1B,CAAC","sourcesContent":["/**\n * Shared state behind the GitNexus side rail (fullscreen UI mode).\n *\n * The gitnexus extension writes here as tools run; the interactive mode's\n * GraphRailComponent reads and re-renders. Kept in core (not the extension)\n * so the UI never imports extension modules.\n */\n\nexport interface RailImpactNode {\n\tname: string;\n\tfilePath?: string;\n\trelationType?: string;\n\t/** Edge confidence 0–1; low-confidence nodes render dimmed. */\n\tconfidence?: number;\n}\n\nexport interface RailModule {\n\tname: string;\n\thits: number;\n\tdirect: boolean;\n}\n\nexport interface RailImpact {\n\ttarget: string;\n\tfilePath?: string;\n\trisk: string;\n\tbyDepth: Record<string, RailImpactNode[]>;\n\tprocesses: string[];\n\tmodules: RailModule[];\n\tat: number;\n}\n\nexport interface RailFlow {\n\tsummary: string;\n\tstepCount?: number;\n}\n\nexport interface RailQuery {\n\tquery: string;\n\tflows: RailFlow[];\n\tat: number;\n}\n\nexport interface RailChange {\n\tpath: string;\n\tadded: number;\n\tremoved: number;\n}\n\nexport interface RailRepoInfo {\n\tname: string;\n\tsymbols: string;\n\trelationships: string;\n\tindexedAt: string;\n\trepoCount: number;\n}\n\nexport interface RailRepoListEntry {\n\tname: string;\n\tsymbols: string;\n}\n\nexport interface RailIndexProgress {\n\tfullName: string;\n\tbranch: string;\n\tphase: string;\n\tpercent: number | undefined;\n\tqueuePosition: number | undefined;\n}\n\nexport function formatCompactIndexPhase(phase: string): string {\n\tconst detailSeparator = phase.indexOf(\": \");\n\treturn detailSeparator === -1 ? phase : phase.slice(0, detailSeparator);\n}\n\nexport interface RailCommunity {\n\tname: string;\n\tmembers: number;\n}\n\nexport interface RailCommunityEdge {\n\tsource: string;\n\ttarget: string;\n\tweight: number;\n}\n\n/** Startup graph observatory: community map of the current repo. */\nexport interface RailObservatory {\n\tcommunities: RailCommunity[];\n\tedges: RailCommunityEdge[];\n\tflowCount?: number;\n}\n\nexport interface GraphRailState {\n\trepo?: RailRepoInfo;\n\tcurrentIndexed: boolean;\n\trepos: RailRepoListEntry[];\n\tindexProgress?: RailIndexProgress;\n\tobservatory?: RailObservatory;\n\timpact?: RailImpact;\n\tquery?: RailQuery;\n\tchanges: RailChange[];\n\timpactChecks: number;\n\tgraphCalls: number;\n}\n\ntype Listener = () => void;\n\nconst state: GraphRailState = {\n\tcurrentIndexed: false,\n\trepos: [],\n\tchanges: [],\n\timpactChecks: 0,\n\tgraphCalls: 0,\n};\n\nconst listeners = new Set<Listener>();\n\nfunction emit(): void {\n\tfor (const listener of listeners) {\n\t\ttry {\n\t\t\tlistener();\n\t\t} catch {\n\t\t\t// rail must never break the session\n\t\t}\n\t}\n}\n\nexport const graphRailStore = {\n\tgetState(): Readonly<GraphRailState> {\n\t\treturn state;\n\t},\n\tsubscribe(listener: Listener): () => void {\n\t\tlisteners.add(listener);\n\t\treturn () => listeners.delete(listener);\n\t},\n\tsetRepo(repo: RailRepoInfo | undefined, currentIndexed: boolean): void {\n\t\tstate.repo = repo;\n\t\tstate.currentIndexed = currentIndexed;\n\t\temit();\n\t},\n\tsetRepoList(repos: RailRepoListEntry[]): void {\n\t\tstate.repos = repos;\n\t\temit();\n\t},\n\tsetIndexProgress(progress: RailIndexProgress | undefined): void {\n\t\tstate.indexProgress = progress;\n\t\temit();\n\t},\n\tsetObservatory(observatory: RailObservatory): void {\n\t\tstate.observatory = observatory;\n\t\temit();\n\t},\n\trecordGraphCall(): void {\n\t\tstate.graphCalls += 1;\n\t\temit();\n\t},\n\tsetImpact(impact: RailImpact): void {\n\t\tstate.impact = impact;\n\t\tstate.impactChecks += 1;\n\t\temit();\n\t},\n\tsetQuery(query: RailQuery): void {\n\t\tstate.query = query;\n\t\temit();\n\t},\n\trecordChange(path: string, added: number, removed: number): void {\n\t\tconst existing = state.changes.find((c) => c.path === path);\n\t\tif (existing) {\n\t\t\texisting.added += added;\n\t\t\texisting.removed += removed;\n\t\t} else {\n\t\t\tstate.changes.push({ path, added, removed });\n\t\t\tif (state.changes.length > 6) state.changes.shift();\n\t\t}\n\t\temit();\n\t},\n\tresetForTests(): void {\n\t\tstate.repo = undefined;\n\t\tstate.currentIndexed = false;\n\t\tstate.repos = [];\n\t\tstate.indexProgress = undefined;\n\t\tstate.observatory = undefined;\n\t\tstate.impact = undefined;\n\t\tstate.query = undefined;\n\t\tstate.changes = [];\n\t\tstate.impactChecks = 0;\n\t\tstate.graphCalls = 0;\n\t\tlisteners.clear();\n\t},\n};\n"]}
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* GraphRailComponent reads and re-renders. Kept in core (not the extension)
|
|
6
6
|
* so the UI never imports extension modules.
|
|
7
7
|
*/
|
|
8
|
+
export function formatCompactIndexPhase(phase) {
|
|
9
|
+
const detailSeparator = phase.indexOf(": ");
|
|
10
|
+
return detailSeparator === -1 ? phase : phase.slice(0, detailSeparator);
|
|
11
|
+
}
|
|
8
12
|
const state = {
|
|
9
13
|
currentIndexed: false,
|
|
10
14
|
repos: [],
|
|
@@ -40,6 +44,10 @@ export const graphRailStore = {
|
|
|
40
44
|
state.repos = repos;
|
|
41
45
|
emit();
|
|
42
46
|
},
|
|
47
|
+
setIndexProgress(progress) {
|
|
48
|
+
state.indexProgress = progress;
|
|
49
|
+
emit();
|
|
50
|
+
},
|
|
43
51
|
setObservatory(observatory) {
|
|
44
52
|
state.observatory = observatory;
|
|
45
53
|
emit();
|
|
@@ -74,6 +82,7 @@ export const graphRailStore = {
|
|
|
74
82
|
state.repo = undefined;
|
|
75
83
|
state.currentIndexed = false;
|
|
76
84
|
state.repos = [];
|
|
85
|
+
state.indexProgress = undefined;
|
|
77
86
|
state.observatory = undefined;
|
|
78
87
|
state.impact = undefined;
|
|
79
88
|
state.query = undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graph-rail-store.js","sourceRoot":"","sources":["../../src/core/graph-rail-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"graph-rail-store.js","sourceRoot":"","sources":["../../src/core/graph-rail-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared state behind the GitNexus side rail (fullscreen UI mode).\n *\n * The gitnexus extension writes here as tools run; the interactive mode's\n * GraphRailComponent reads and re-renders. Kept in core (not the extension)\n * so the UI never imports extension modules.\n */\n\nexport interface RailImpactNode {\n\tname: string;\n\tfilePath?: string;\n\trelationType?: string;\n\t/** Edge confidence 0–1; low-confidence nodes render dimmed. */\n\tconfidence?: number;\n}\n\nexport interface RailModule {\n\tname: string;\n\thits: number;\n\tdirect: boolean;\n}\n\nexport interface RailImpact {\n\ttarget: string;\n\tfilePath?: string;\n\trisk: string;\n\tbyDepth: Record<string, RailImpactNode[]>;\n\tprocesses: string[];\n\tmodules: RailModule[];\n\tat: number;\n}\n\nexport interface RailFlow {\n\tsummary: string;\n\tstepCount?: number;\n}\n\nexport interface RailQuery {\n\tquery: string;\n\tflows: RailFlow[];\n\tat: number;\n}\n\nexport interface RailChange {\n\tpath: string;\n\tadded: number;\n\tremoved: number;\n}\n\nexport interface RailRepoInfo {\n\tname: string;\n\tsymbols: string;\n\trelationships: string;\n\tindexedAt: string;\n\trepoCount: number;\n}\n\nexport interface RailRepoListEntry {\n\tname: string;\n\tsymbols: string;\n}\n\nexport interface RailIndexProgress {\n\tfullName: string;\n\tbranch: string;\n\tphase: string;\n\tpercent: number | undefined;\n\tqueuePosition: number | undefined;\n}\n\nexport function formatCompactIndexPhase(phase: string): string {\n\tconst detailSeparator = phase.indexOf(\": \");\n\treturn detailSeparator === -1 ? phase : phase.slice(0, detailSeparator);\n}\n\nexport interface RailCommunity {\n\tname: string;\n\tmembers: number;\n}\n\nexport interface RailCommunityEdge {\n\tsource: string;\n\ttarget: string;\n\tweight: number;\n}\n\n/** Startup graph observatory: community map of the current repo. */\nexport interface RailObservatory {\n\tcommunities: RailCommunity[];\n\tedges: RailCommunityEdge[];\n\tflowCount?: number;\n}\n\nexport interface GraphRailState {\n\trepo?: RailRepoInfo;\n\tcurrentIndexed: boolean;\n\trepos: RailRepoListEntry[];\n\tindexProgress?: RailIndexProgress;\n\tobservatory?: RailObservatory;\n\timpact?: RailImpact;\n\tquery?: RailQuery;\n\tchanges: RailChange[];\n\timpactChecks: number;\n\tgraphCalls: number;\n}\n\ntype Listener = () => void;\n\nconst state: GraphRailState = {\n\tcurrentIndexed: false,\n\trepos: [],\n\tchanges: [],\n\timpactChecks: 0,\n\tgraphCalls: 0,\n};\n\nconst listeners = new Set<Listener>();\n\nfunction emit(): void {\n\tfor (const listener of listeners) {\n\t\ttry {\n\t\t\tlistener();\n\t\t} catch {\n\t\t\t// rail must never break the session\n\t\t}\n\t}\n}\n\nexport const graphRailStore = {\n\tgetState(): Readonly<GraphRailState> {\n\t\treturn state;\n\t},\n\tsubscribe(listener: Listener): () => void {\n\t\tlisteners.add(listener);\n\t\treturn () => listeners.delete(listener);\n\t},\n\tsetRepo(repo: RailRepoInfo | undefined, currentIndexed: boolean): void {\n\t\tstate.repo = repo;\n\t\tstate.currentIndexed = currentIndexed;\n\t\temit();\n\t},\n\tsetRepoList(repos: RailRepoListEntry[]): void {\n\t\tstate.repos = repos;\n\t\temit();\n\t},\n\tsetIndexProgress(progress: RailIndexProgress | undefined): void {\n\t\tstate.indexProgress = progress;\n\t\temit();\n\t},\n\tsetObservatory(observatory: RailObservatory): void {\n\t\tstate.observatory = observatory;\n\t\temit();\n\t},\n\trecordGraphCall(): void {\n\t\tstate.graphCalls += 1;\n\t\temit();\n\t},\n\tsetImpact(impact: RailImpact): void {\n\t\tstate.impact = impact;\n\t\tstate.impactChecks += 1;\n\t\temit();\n\t},\n\tsetQuery(query: RailQuery): void {\n\t\tstate.query = query;\n\t\temit();\n\t},\n\trecordChange(path: string, added: number, removed: number): void {\n\t\tconst existing = state.changes.find((c) => c.path === path);\n\t\tif (existing) {\n\t\t\texisting.added += added;\n\t\t\texisting.removed += removed;\n\t\t} else {\n\t\t\tstate.changes.push({ path, added, removed });\n\t\t\tif (state.changes.length > 6) state.changes.shift();\n\t\t}\n\t\temit();\n\t},\n\tresetForTests(): void {\n\t\tstate.repo = undefined;\n\t\tstate.currentIndexed = false;\n\t\tstate.repos = [];\n\t\tstate.indexProgress = undefined;\n\t\tstate.observatory = undefined;\n\t\tstate.impact = undefined;\n\t\tstate.query = undefined;\n\t\tstate.changes = [];\n\t\tstate.impactChecks = 0;\n\t\tstate.graphCalls = 0;\n\t\tlisteners.clear();\n\t},\n};\n"]}
|
|
@@ -45,6 +45,7 @@ export interface AppKeybindings {
|
|
|
45
45
|
"app.tree.filter.cycleBackward": true;
|
|
46
46
|
}
|
|
47
47
|
export type AppKeybinding = keyof AppKeybindings;
|
|
48
|
+
export declare function getDefaultClearKey(platform: NodeJS.Platform): KeyId;
|
|
48
49
|
declare module "@earendil-works/pi-tui" {
|
|
49
50
|
interface Keybindings extends AppKeybindings {
|
|
50
51
|
}
|
|
@@ -211,7 +212,7 @@ export declare const KEYBINDINGS: {
|
|
|
211
212
|
readonly description: "Cancel or abort";
|
|
212
213
|
};
|
|
213
214
|
readonly "app.clear": {
|
|
214
|
-
readonly defaultKeys:
|
|
215
|
+
readonly defaultKeys: KeyId;
|
|
215
216
|
readonly description: "Clear editor";
|
|
216
217
|
};
|
|
217
218
|
readonly "app.exit": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keybindings.d.ts","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.copy\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.commands.palette\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Toggle tool output\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.copy\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Copy message to clipboard\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard (text fallback)\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.commands.palette\": { defaultKeys: \"ctrl+k\", description: \"Open the command palette\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+left\", \"ctrl+left\"] : [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+right\", \"ctrl+right\"] : [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: Record<string, unknown>): KeybindingsConfig {\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null) return undefined;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}
|
|
1
|
+
{"version":3,"file":"keybindings.d.ts","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.copy\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.commands.palette\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\nexport function getDefaultClearKey(platform: NodeJS.Platform): KeyId {\n\treturn platform === \"darwin\" ? \"super+c\" : \"ctrl+c\";\n}\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: getDefaultClearKey(process.platform), description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Toggle tool output\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.copy\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Copy message to clipboard\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard (text fallback)\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.commands.palette\": { defaultKeys: \"ctrl+k\", description: \"Open the command palette\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+left\", \"ctrl+left\"] : [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+right\", \"ctrl+right\"] : [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: Record<string, unknown>): KeybindingsConfig {\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null) return undefined;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}
|
package/dist/core/keybindings.js
CHANGED
|
@@ -2,10 +2,13 @@ import { TUI_KEYBINDINGS, KeybindingsManager as TuiKeybindingsManager, } from "@
|
|
|
2
2
|
import { existsSync, readFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { getAgentDir } from "../config.js";
|
|
5
|
+
export function getDefaultClearKey(platform) {
|
|
6
|
+
return platform === "darwin" ? "super+c" : "ctrl+c";
|
|
7
|
+
}
|
|
5
8
|
export const KEYBINDINGS = {
|
|
6
9
|
...TUI_KEYBINDINGS,
|
|
7
10
|
"app.interrupt": { defaultKeys: "escape", description: "Cancel or abort" },
|
|
8
|
-
"app.clear": { defaultKeys:
|
|
11
|
+
"app.clear": { defaultKeys: getDefaultClearKey(process.platform), description: "Clear editor" },
|
|
9
12
|
"app.exit": { defaultKeys: "ctrl+d", description: "Exit when editor is empty" },
|
|
10
13
|
"app.suspend": {
|
|
11
14
|
defaultKeys: process.platform === "win32" ? [] : "ctrl+z",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.copy\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.commands.palette\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Toggle tool output\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.copy\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Copy message to clipboard\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard (text fallback)\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.commands.palette\": { defaultKeys: \"ctrl+k\", description: \"Open the command palette\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+left\", \"ctrl+left\"] : [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+right\", \"ctrl+right\"] : [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: Record<string, unknown>): KeybindingsConfig {\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null) return undefined;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}
|
|
1
|
+
{"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"AAAA,OAAO,EAKN,eAAe,EACf,kBAAkB,IAAI,qBAAqB,GAC3C,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAkD3C,MAAM,UAAU,kBAAkB,CAAC,QAAyB,EAAS;IACpE,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;AAAA,CACpD;AAMD,MAAM,CAAC,MAAM,WAAW,GAAG;IAC1B,GAAG,eAAe;IAClB,eAAe,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,iBAAiB,EAAE;IAC1E,WAAW,EAAE,EAAE,WAAW,EAAE,kBAAkB,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE;IAC/F,UAAU,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,2BAA2B,EAAE;IAC/E,aAAa,EAAE;QACd,WAAW,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ;QACzD,WAAW,EAAE,uBAAuB;KACpC;IACD,oBAAoB,EAAE;QACrB,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE,sBAAsB;KACnC;IACD,wBAAwB,EAAE;QACzB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,qBAAqB;KAClC;IACD,yBAAyB,EAAE;QAC1B,WAAW,EAAE,cAAc;QAC3B,WAAW,EAAE,yBAAyB;KACtC;IACD,kBAAkB,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,qBAAqB,EAAE;IACjF,kBAAkB,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAE;IAChF,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,wBAAwB;KACrC;IACD,+BAA+B,EAAE;QAChC,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,6BAA6B;KAC1C;IACD,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,sBAAsB;KACnC;IACD,kBAAkB,EAAE;QACnB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,2BAA2B;KACxC;IACD,sBAAsB,EAAE;QACvB,WAAW,EAAE,WAAW;QACxB,WAAW,EAAE,yBAAyB;KACtC;IACD,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,yBAAyB;KACtC;IACD,0BAA0B,EAAE;QAC3B,WAAW,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ;QAC9D,WAAW,EAAE,4CAA4C;KACzD;IACD,iBAAiB,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,qBAAqB,EAAE;IAC1E,kBAAkB,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,mBAAmB,EAAE;IACzE,kBAAkB,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,sBAAsB,EAAE;IAC5E,oBAAoB,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,EAAE,kBAAkB,EAAE;IAC1E,sBAAsB,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,0BAA0B,EAAE;IAC1F,mBAAmB,EAAE;QACpB,WAAW,EAAE,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,UAAU,CAAC;QAClG,WAAW,EAAE,6BAA6B;KAC1C;IACD,uBAAuB,EAAE;QACxB,WAAW,EAAE,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC;QACtG,WAAW,EAAE,iCAAiC;KAC9C;IACD,oBAAoB,EAAE;QACrB,WAAW,EAAE,SAAS;QACtB,WAAW,EAAE,iBAAiB;KAC9B;IACD,+BAA+B,EAAE;QAChC,WAAW,EAAE,SAAS;QACtB,WAAW,EAAE,8BAA8B;KAC3C;IACD,wBAAwB,EAAE;QACzB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,6BAA6B;KAC1C;IACD,wBAAwB,EAAE;QACzB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,0BAA0B;KACvC;IACD,oBAAoB,EAAE;QACrB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,gBAAgB;KAC7B;IACD,oBAAoB,EAAE;QACrB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,gBAAgB;KAC7B;IACD,+BAA+B,EAAE;QAChC,WAAW,EAAE,gBAAgB;QAC7B,WAAW,EAAE,oCAAoC;KACjD;IACD,iBAAiB,EAAE;QAClB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,sBAAsB;KACnC;IACD,sBAAsB,EAAE;QACvB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,mBAAmB;KAChC;IACD,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,kBAAkB;KAC/B;IACD,2BAA2B,EAAE;QAC5B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,gCAAgC;KAC7C;IACD,sBAAsB,EAAE;QACvB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,wBAAwB;KACrC;IACD,wBAAwB,EAAE;QACzB,WAAW,EAAE,UAAU;QACvB,WAAW,EAAE,0BAA0B;KACvC;IACD,yBAAyB,EAAE;QAC1B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,2BAA2B;KACxC;IACD,yBAAyB,EAAE;QAC1B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,gCAAgC;KAC7C;IACD,0BAA0B,EAAE;QAC3B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,iCAAiC;KAC9C;IACD,6BAA6B,EAAE;QAC9B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,mCAAmC;KAChD;IACD,qBAAqB,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,+BAA+B;KAC5C;IACD,8BAA8B,EAAE;QAC/B,WAAW,EAAE,QAAQ;QACrB,WAAW,EAAE,4BAA4B;KACzC;IACD,+BAA+B,EAAE;QAChC,WAAW,EAAE,cAAc;QAC3B,WAAW,EAAE,6BAA6B;KAC1C;CACwC,CAAC;AAE3C,MAAM,0BAA0B,GAAG;IAClC,QAAQ,EAAE,qBAAqB;IAC/B,UAAU,EAAE,uBAAuB;IACnC,UAAU,EAAE,uBAAuB;IACnC,WAAW,EAAE,wBAAwB;IACrC,cAAc,EAAE,2BAA2B;IAC3C,eAAe,EAAE,4BAA4B;IAC7C,eAAe,EAAE,4BAA4B;IAC7C,aAAa,EAAE,0BAA0B;IACzC,WAAW,EAAE,wBAAwB;IACrC,YAAY,EAAE,yBAAyB;IACvC,MAAM,EAAE,mBAAmB;IAC3B,QAAQ,EAAE,qBAAqB;IAC/B,kBAAkB,EAAE,+BAA+B;IACnD,iBAAiB,EAAE,8BAA8B;IACjD,kBAAkB,EAAE,+BAA+B;IACnD,iBAAiB,EAAE,8BAA8B;IACjD,iBAAiB,EAAE,8BAA8B;IACjD,eAAe,EAAE,4BAA4B;IAC7C,IAAI,EAAE,iBAAiB;IACvB,OAAO,EAAE,oBAAoB;IAC7B,IAAI,EAAE,iBAAiB;IACvB,OAAO,EAAE,mBAAmB;IAC5B,MAAM,EAAE,kBAAkB;IAC1B,GAAG,EAAE,eAAe;IACpB,IAAI,EAAE,gBAAgB;IACtB,QAAQ,EAAE,eAAe;IACzB,UAAU,EAAE,iBAAiB;IAC7B,YAAY,EAAE,mBAAmB;IACjC,cAAc,EAAE,qBAAqB;IACrC,aAAa,EAAE,oBAAoB;IACnC,YAAY,EAAE,mBAAmB;IACjC,SAAS,EAAE,eAAe;IAC1B,KAAK,EAAE,WAAW;IAClB,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,aAAa;IACtB,kBAAkB,EAAE,oBAAoB;IACxC,iBAAiB,EAAE,wBAAwB;IAC3C,kBAAkB,EAAE,yBAAyB;IAC7C,WAAW,EAAE,kBAAkB;IAC/B,WAAW,EAAE,kBAAkB;IAC/B,cAAc,EAAE,qBAAqB;IACrC,wBAAwB,EAAE,+BAA+B;IACzD,cAAc,EAAE,qBAAqB;IACrC,QAAQ,EAAE,sBAAsB;IAChC,OAAO,EAAE,qBAAqB;IAC9B,UAAU,EAAE,0BAA0B;IACtC,UAAU,EAAE,iBAAiB;IAC7B,IAAI,EAAE,kBAAkB;IACxB,IAAI,EAAE,kBAAkB;IACxB,MAAM,EAAE,oBAAoB;IAC5B,YAAY,EAAE,mBAAmB;IACjC,gBAAgB,EAAE,uBAAuB;IACzC,aAAa,EAAE,oBAAoB;IACnC,wBAAwB,EAAE,+BAA+B;IACzD,iBAAiB,EAAE,wBAAwB;IAC3C,iBAAiB,EAAE,wBAAwB;IAC3C,aAAa,EAAE,oBAAoB;IACnC,aAAa,EAAE,oBAAoB;IACnC,wBAAwB,EAAE,+BAA+B;CACX,CAAC;AAEhD,SAAS,sBAAsB,CAAC,GAAW,EAAkD;IAC5F,OAAO,GAAG,IAAI,0BAA0B,CAAC;AAAA,CACzC;AAED,SAAS,mBAAmB,CAAC,KAA8B,EAAqB;IAC/E,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,CAAC,GAAG,OAAgB,CAAC;YAC/B,SAAS;QACV,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,EAAE,CAAC;YACnF,MAAM,CAAC,GAAG,CAAC,GAAG,OAAkB,CAAC;QAClC,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED,MAAM,UAAU,wBAAwB,CAAC,SAAkC,EAGzE;IACD,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,MAAM,OAAO,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACpF,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;YACrB,QAAQ,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,IAAI,GAAG,KAAK,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,CAAC;YAC1D,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS;QACV,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,CAAC;IACzB,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,sBAAsB,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC;AAAA,CAC5D;AAED,SAAS,sBAAsB,CAAC,MAA+B,EAA2B;IACzF,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACnD,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC;YACvC,OAAO,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAC1C,CAAC;IACF,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;SAChC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;SAC7C,IAAI,EAAE,CAAC;IACT,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,OAAO,CAAC;AAAA,CACf;AAED,SAAS,aAAa,CAAC,IAAY,EAAuC;IACzE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAY,CAAC;QAClE,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,SAAS,CAAC;QACpE,OAAO,MAAiC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AAAA,CACD;AAED,MAAM,OAAO,kBAAmB,SAAQ,qBAAqB;IACpD,UAAU,CAAqB;IAEvC,YAAY,YAAY,GAAsB,EAAE,EAAE,UAAmB,EAAE;QACtE,KAAK,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAAA,CAC7B;IAED,MAAM,CAAC,MAAM,CAAC,QAAQ,GAAW,WAAW,EAAE,EAAsB;QACnE,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QACtD,MAAM,YAAY,GAAG,kBAAkB,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;QACjE,OAAO,IAAI,kBAAkB,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IAAA,CACxD;IAED,MAAM,GAAS;QACd,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO;QAC7B,IAAI,CAAC,eAAe,CAAC,kBAAkB,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAAA,CACvE;IAED,kBAAkB,GAAsB;QACvC,OAAO,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAAA,CAClC;IAEO,MAAM,CAAC,YAAY,CAAC,IAAY,EAAqB;QAC5D,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1B,OAAO,mBAAmB,CAAC,wBAAwB,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;IAAA,CACvE;CACD","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.copy\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.commands.palette\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\nexport function getDefaultClearKey(platform: NodeJS.Platform): KeyId {\n\treturn platform === \"darwin\" ? \"super+c\" : \"ctrl+c\";\n}\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: getDefaultClearKey(process.platform), description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Toggle tool output\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.copy\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Copy message to clipboard\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard (text fallback)\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.commands.palette\": { defaultKeys: \"ctrl+k\", description: \"Open the command palette\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+left\", \"ctrl+left\"] : [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+right\", \"ctrl+right\"] : [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: Record<string, unknown>): KeybindingsConfig {\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(path, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null) return undefined;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeyId, KeybindingsConfig };\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAmBlD,MAAM,WAAW,aAAa;IAC7B,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IAExD,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,EAI1E;CACD;AAKD,qBAAa,cAAc;IAC1B,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IAExC,YAAY,MAAM,EAAE,cAAc,EAEjC;IAED,gFAAgF;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAIxG;IAED,gEAAgE;IAChE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAUzC;IAED,OAAO,CAAC,iBAAiB;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAmBlD,MAAM,WAAW,aAAa;IAC7B,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IAExD,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,EAI1E;CACD;AAKD,qBAAa,cAAc;IAC1B,OAAO,CAAC,SAAS,CAAqB;IACtC,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiB;IAExC,YAAY,MAAM,EAAE,cAAc,EAEjC;IAED,gFAAgF;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAIxG;IAED,gEAAgE;IAChE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAUzC;IAED,OAAO,CAAC,iBAAiB;YASX,UAAU;YAaV,MAAM;YAIN,OAAO;YAUP,IAAI;CA0FlB","sourcesContent":["/**\n * Minimal Streamable-HTTP MCP client for the GitNexus hub — and only the hub.\n *\n * This is intentionally NOT a general MCP client: it implements exactly the\n * subset nexus-cli needs against a single known server — initialize,\n * notifications/initialized, tools/call — over the Streamable HTTP transport\n * (JSON-RPC over POST; responses arrive as application/json or as a\n * text/event-stream carrying JSON-RPC messages).\n */\n\nimport { appendFileSync } from \"node:fs\";\nimport type { GitNexusConfig } from \"./config.ts\";\n\n/** File-based trace (NEXUS_TRACE=1): TUI captures stderr, so console won't do. */\nfunction clientTrace(message: string): void {\n\tif (!process.env.NEXUS_TRACE) return;\n\ttry {\n\t\tappendFileSync(\"/tmp/nexus-observatory-trace.log\", `${new Date().toISOString()} [client] ${message}\\n`);\n\t} catch {\n\t\t// best-effort\n\t}\n}\n\ninterface JsonRpcResponse {\n\tjsonrpc: \"2.0\";\n\tid?: number | string | null;\n\tresult?: unknown;\n\terror?: { code: number; message: string; data?: unknown };\n}\n\nexport interface McpToolResult {\n\tcontent?: Array<{ type: string; text?: string }>;\n\tisError?: boolean;\n\tstructuredContent?: unknown;\n}\n\nexport class GitNexusClientError extends Error {\n\treadonly kind: \"auth\" | \"network\" | \"protocol\" | \"tool\";\n\n\tconstructor(message: string, kind: \"auth\" | \"network\" | \"protocol\" | \"tool\") {\n\t\tsuper(message);\n\t\tthis.name = \"GitNexusClientError\";\n\t\tthis.kind = kind;\n\t}\n}\n\nconst PROTOCOL_VERSION = \"2025-06-18\";\nconst REQUEST_TIMEOUT_MS = 60_000;\n\nexport class GitNexusClient {\n\tprivate sessionId: string | undefined;\n\tprivate nextId = 1;\n\tprivate initializing: Promise<void> | undefined;\n\tprivate readonly config: GitNexusConfig;\n\n\tconstructor(config: GitNexusConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/** Call a hub tool by name. Initializes the MCP session lazily on first use. */\n\tasync callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<McpToolResult> {\n\t\tawait this.ensureInitialized(signal);\n\t\tconst result = await this.request(\"tools/call\", { name, arguments: args }, signal);\n\t\treturn (result ?? {}) as McpToolResult;\n\t}\n\n\t/** Extract the primary text payload from an MCP tool result. */\n\tstatic text(result: McpToolResult): string {\n\t\tconst parts = result.content ?? [];\n\t\tconst text = parts\n\t\t\t.filter((p) => p.type === \"text\" && typeof p.text === \"string\")\n\t\t\t.map((p) => p.text)\n\t\t\t.join(\"\\n\");\n\t\tif (result.isError) {\n\t\t\tthrow new GitNexusClientError(text || \"hub tool returned an error\", \"tool\");\n\t\t}\n\t\treturn text;\n\t}\n\n\tprivate ensureInitialized(signal?: AbortSignal): Promise<void> {\n\t\tthis.initializing ??= this.initialize(signal).catch((error) => {\n\t\t\tthis.initializing = undefined;\n\t\t\tthis.sessionId = undefined;\n\t\t\tthrow error;\n\t\t});\n\t\treturn this.initializing;\n\t}\n\n\tprivate async initialize(signal?: AbortSignal): Promise<void> {\n\t\tawait this.request(\n\t\t\t\"initialize\",\n\t\t\t{\n\t\t\t\tprotocolVersion: PROTOCOL_VERSION,\n\t\t\t\tcapabilities: {},\n\t\t\t\tclientInfo: { name: \"nexus-cli\", version: \"0.1.0\" },\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tawait this.notify(\"notifications/initialized\", signal);\n\t}\n\n\tprivate async notify(method: string, signal?: AbortSignal): Promise<void> {\n\t\tawait this.post({ jsonrpc: \"2.0\", method }, signal);\n\t}\n\n\tprivate async request(method: string, params: unknown, signal?: AbortSignal): Promise<unknown> {\n\t\tconst id = this.nextId++;\n\t\tconst response = await this.post({ jsonrpc: \"2.0\", id, method, params }, signal);\n\t\tif (!response) throw new GitNexusClientError(`empty response for ${method}`, \"protocol\");\n\t\tif (response.error) {\n\t\t\tthrow new GitNexusClientError(`hub ${method} failed: ${response.error.message}`, \"protocol\");\n\t\t}\n\t\treturn response.result;\n\t}\n\n\tprivate async post(body: unknown, signal?: AbortSignal): Promise<JsonRpcResponse | undefined> {\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\tconst throwIfTimedOut = () => {\n\t\t\tif (controller.signal.aborted) {\n\t\t\t\tthrow new GitNexusClientError(\"cannot reach GitNexus hub: request timed out\", \"network\");\n\t\t\t}\n\t\t};\n\t\tsignal?.addEventListener(\"abort\", onOuterAbort, { once: true });\n\t\ttry {\n\t\t\tconst method = (body as { method?: string }).method ?? \"?\";\n\t\t\tclientTrace(`${method}: fetch dispatch`);\n\t\t\tlet response: Response;\n\t\t\ttry {\n\t\t\t\tresponse = await fetch(this.config.hubUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t\t\taccept: \"application/json, text/event-stream\",\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\t...(this.sessionId ? { \"mcp-session-id\": this.sessionId } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tbody: 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\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new GitNexusClientError(`cannot reach GitNexus hub: ${reason}`, \"network\");\n\t\t\t}\n\t\t\tsignal?.throwIfAborted();\n\t\t\tthrowIfTimedOut();\n\n\t\t\tclientTrace(`${method}: status ${response.status} content-type ${response.headers.get(\"content-type\")}`);\n\t\t\tconst newSessionId = response.headers.get(\"mcp-session-id\");\n\n\t\t\tif (response.status === 401 || response.status === 403) {\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tthrow new GitNexusClientError(\"GitNexus hub rejected the token (401/403). Run /login.\", \"auth\");\n\t\t\t}\n\t\t\tif (response.status === 202) {\n\t\t\t\t// Drain the body — an unconsumed response pins its socket, and under a\n\t\t\t\t// connection-capped dispatcher that deadlocks every later hub call.\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tif (newSessionId) this.sessionId = newSessionId;\n\t\t\t\treturn undefined; // accepted notification\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tthrow new GitNexusClientError(`GitNexus hub HTTP ${response.status}`, \"protocol\");\n\t\t\t}\n\n\t\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\t\tlet text: string;\n\t\t\ttry {\n\t\t\t\ttext = await response.text();\n\t\t\t} catch (error) {\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new GitNexusClientError(`cannot reach GitNexus hub: ${reason}`, \"network\");\n\t\t\t}\n\t\t\tsignal?.throwIfAborted();\n\t\t\tthrowIfTimedOut();\n\t\t\tlet parsed: JsonRpcResponse | undefined;\n\t\t\tif (contentType.includes(\"text/event-stream\")) {\n\t\t\t\tclientTrace(`${method}: sse body ${text.length} bytes`);\n\t\t\t\tparsed = parseSseForResponse(text);\n\t\t\t} else {\n\t\t\t\tclientTrace(`${method}: json body ${text.length} bytes`);\n\t\t\t\tparsed = text ? (JSON.parse(text) as JsonRpcResponse) : undefined;\n\t\t\t}\n\t\t\tif (newSessionId && parsed) this.sessionId = newSessionId;\n\t\t\treturn parsed;\n\t\t} finally {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onOuterAbort);\n\t\t}\n\t}\n}\n\n/** Pull the (single) JSON-RPC response message out of an SSE body. */\nfunction parseSseForResponse(sse: string): JsonRpcResponse | undefined {\n\tlet last: JsonRpcResponse | undefined;\n\tfor (const rawEvent of sse.split(\"\\n\\n\")) {\n\t\tconst data = rawEvent\n\t\t\t.split(\"\\n\")\n\t\t\t.filter((line) => line.startsWith(\"data:\"))\n\t\t\t.map((line) => line.slice(5).trim())\n\t\t\t.join(\"\\n\");\n\t\tif (!data) continue;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(data) as JsonRpcResponse;\n\t\t\t// Responses carry an id; server-initiated notifications do not.\n\t\t\tif (parsed.id !== undefined && parsed.id !== null) last = parsed;\n\t\t} catch {\n\t\t\t// Ignore non-JSON keepalives.\n\t\t}\n\t}\n\treturn last;\n}\n"]}
|
|
@@ -58,6 +58,7 @@ export class GitNexusClient {
|
|
|
58
58
|
ensureInitialized(signal) {
|
|
59
59
|
this.initializing ??= this.initialize(signal).catch((error) => {
|
|
60
60
|
this.initializing = undefined;
|
|
61
|
+
this.sessionId = undefined;
|
|
61
62
|
throw error;
|
|
62
63
|
});
|
|
63
64
|
return this.initializing;
|
|
@@ -68,10 +69,10 @@ export class GitNexusClient {
|
|
|
68
69
|
capabilities: {},
|
|
69
70
|
clientInfo: { name: "nexus-cli", version: "0.1.0" },
|
|
70
71
|
}, signal);
|
|
71
|
-
await this.notify("notifications/initialized");
|
|
72
|
+
await this.notify("notifications/initialized", signal);
|
|
72
73
|
}
|
|
73
|
-
async notify(method) {
|
|
74
|
-
await this.post({ jsonrpc: "2.0", method },
|
|
74
|
+
async notify(method, signal) {
|
|
75
|
+
await this.post({ jsonrpc: "2.0", method }, signal);
|
|
75
76
|
}
|
|
76
77
|
async request(method, params, signal) {
|
|
77
78
|
const id = this.nextId++;
|
|
@@ -84,9 +85,15 @@ export class GitNexusClient {
|
|
|
84
85
|
return response.result;
|
|
85
86
|
}
|
|
86
87
|
async post(body, signal) {
|
|
88
|
+
signal?.throwIfAborted();
|
|
87
89
|
const controller = new AbortController();
|
|
88
90
|
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
89
|
-
const onOuterAbort = () => controller.abort();
|
|
91
|
+
const onOuterAbort = () => controller.abort(signal?.reason);
|
|
92
|
+
const throwIfTimedOut = () => {
|
|
93
|
+
if (controller.signal.aborted) {
|
|
94
|
+
throw new GitNexusClientError("cannot reach GitNexus hub: request timed out", "network");
|
|
95
|
+
}
|
|
96
|
+
};
|
|
90
97
|
signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
91
98
|
try {
|
|
92
99
|
const method = body.method ?? "?";
|
|
@@ -108,38 +115,62 @@ export class GitNexusClient {
|
|
|
108
115
|
});
|
|
109
116
|
}
|
|
110
117
|
catch (error) {
|
|
118
|
+
signal?.throwIfAborted();
|
|
119
|
+
throwIfTimedOut();
|
|
111
120
|
const reason = error instanceof Error ? error.message : String(error);
|
|
112
121
|
throw new GitNexusClientError(`cannot reach GitNexus hub: ${reason}`, "network");
|
|
113
122
|
}
|
|
123
|
+
signal?.throwIfAborted();
|
|
124
|
+
throwIfTimedOut();
|
|
114
125
|
clientTrace(`${method}: status ${response.status} content-type ${response.headers.get("content-type")}`);
|
|
115
126
|
const newSessionId = response.headers.get("mcp-session-id");
|
|
116
|
-
if (newSessionId)
|
|
117
|
-
this.sessionId = newSessionId;
|
|
118
127
|
if (response.status === 401 || response.status === 403) {
|
|
119
128
|
await response.body?.cancel().catch(() => { });
|
|
129
|
+
signal?.throwIfAborted();
|
|
130
|
+
throwIfTimedOut();
|
|
120
131
|
throw new GitNexusClientError("GitNexus hub rejected the token (401/403). Run /login.", "auth");
|
|
121
132
|
}
|
|
122
133
|
if (response.status === 202) {
|
|
123
134
|
// Drain the body — an unconsumed response pins its socket, and under a
|
|
124
135
|
// connection-capped dispatcher that deadlocks every later hub call.
|
|
125
136
|
await response.body?.cancel().catch(() => { });
|
|
137
|
+
signal?.throwIfAborted();
|
|
138
|
+
throwIfTimedOut();
|
|
139
|
+
if (newSessionId)
|
|
140
|
+
this.sessionId = newSessionId;
|
|
126
141
|
return undefined; // accepted notification
|
|
127
142
|
}
|
|
128
143
|
if (!response.ok) {
|
|
129
144
|
await response.body?.cancel().catch(() => { });
|
|
145
|
+
signal?.throwIfAborted();
|
|
146
|
+
throwIfTimedOut();
|
|
130
147
|
throw new GitNexusClientError(`GitNexus hub HTTP ${response.status}`, "protocol");
|
|
131
148
|
}
|
|
132
149
|
const contentType = response.headers.get("content-type") ?? "";
|
|
150
|
+
let text;
|
|
151
|
+
try {
|
|
152
|
+
text = await response.text();
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
signal?.throwIfAborted();
|
|
156
|
+
throwIfTimedOut();
|
|
157
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
158
|
+
throw new GitNexusClientError(`cannot reach GitNexus hub: ${reason}`, "network");
|
|
159
|
+
}
|
|
160
|
+
signal?.throwIfAborted();
|
|
161
|
+
throwIfTimedOut();
|
|
162
|
+
let parsed;
|
|
133
163
|
if (contentType.includes("text/event-stream")) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return parseSseForResponse(sse);
|
|
164
|
+
clientTrace(`${method}: sse body ${text.length} bytes`);
|
|
165
|
+
parsed = parseSseForResponse(text);
|
|
137
166
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
167
|
+
else {
|
|
168
|
+
clientTrace(`${method}: json body ${text.length} bytes`);
|
|
169
|
+
parsed = text ? JSON.parse(text) : undefined;
|
|
170
|
+
}
|
|
171
|
+
if (newSessionId && parsed)
|
|
172
|
+
this.sessionId = newSessionId;
|
|
173
|
+
return parsed;
|
|
143
174
|
}
|
|
144
175
|
finally {
|
|
145
176
|
clearTimeout(timeout);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGzC,kFAAkF;AAClF,SAAS,WAAW,CAAC,OAAe,EAAQ;IAC3C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW;QAAE,OAAO;IACrC,IAAI,CAAC;QACJ,cAAc,CAAC,kCAAkC,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,aAAa,OAAO,IAAI,CAAC,CAAC;IACzG,CAAC;IAAC,MAAM,CAAC;QACR,cAAc;IACf,CAAC;AAAA,CACD;AAeD,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACpC,IAAI,CAA2C;IAExD,YAAY,OAAe,EAAE,IAA8C,EAAE;QAC5E,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAAA,CACjB;CACD;AAED,MAAM,gBAAgB,GAAG,YAAY,CAAC;AACtC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,MAAM,OAAO,cAAc;IAClB,SAAS,CAAqB;IAC9B,MAAM,GAAG,CAAC,CAAC;IACX,YAAY,CAA4B;IAC/B,MAAM,CAAiB;IAExC,YAAY,MAAsB,EAAE;QACnC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAAA,CACrB;IAED,gFAAgF;IAChF,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,IAA6B,EAAE,MAAoB,EAA0B;QACzG,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;QACnF,OAAO,CAAC,MAAM,IAAI,EAAE,CAAkB,CAAC;IAAA,CACvC;IAED,gEAAgE;IAChE,MAAM,CAAC,IAAI,CAAC,MAAqB,EAAU;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,KAAK;aAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;aAC9D,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAClB,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,mBAAmB,CAAC,IAAI,IAAI,4BAA4B,EAAE,MAAM,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,IAAI,CAAC;IAAA,CACZ;IAEO,iBAAiB,CAAC,MAAoB,EAAiB;QAC9D,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;YAC9D,IAAI,CAAC,YAAY,GAAG,SAAS,CAAC;YAC9B,MAAM,KAAK,CAAC;QAAA,CACZ,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,YAAY,CAAC;IAAA,CACzB;IAEO,KAAK,CAAC,UAAU,CAAC,MAAoB,EAAiB;QAC7D,MAAM,IAAI,CAAC,OAAO,CACjB,YAAY,EACZ;YACC,eAAe,EAAE,gBAAgB;YACjC,YAAY,EAAE,EAAE;YAChB,UAAU,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE;SACnD,EACD,MAAM,CACN,CAAC;QACF,MAAM,IAAI,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC;IAAA,CAC/C;IAEO,KAAK,CAAC,MAAM,CAAC,MAAc,EAAiB;QACnD,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,CAAC,CAAC;IAAA,CACvD;IAEO,KAAK,CAAC,OAAO,CAAC,MAAc,EAAE,MAAe,EAAE,MAAoB,EAAoB;QAC9F,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;QACjF,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,mBAAmB,CAAC,sBAAsB,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC;QACzF,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YACpB,MAAM,IAAI,mBAAmB,CAAC,OAAO,MAAM,YAAY,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;QAC9F,CAAC;QACD,OAAO,QAAQ,CAAC,MAAM,CAAC;IAAA,CACvB;IAEO,KAAK,CAAC,IAAI,CAAC,IAAa,EAAE,MAAoB,EAAwC;QAC7F,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,kBAAkB,CAAC,CAAC;QACzE,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAC9C,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC;YACJ,MAAM,MAAM,GAAI,IAA4B,CAAC,MAAM,IAAI,GAAG,CAAC;YAC3D,WAAW,CAAC,GAAG,MAAM,kBAAkB,CAAC,CAAC;YACzC,IAAI,QAAkB,CAAC;YACvB,IAAI,CAAC;gBACJ,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;oBAC1C,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE;wBACR,cAAc,EAAE,kBAAkB;wBAClC,MAAM,EAAE,qCAAqC;wBAC7C,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;wBAC5C,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBACnG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC9E,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBAC/D;oBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;oBAC1B,MAAM,EAAE,UAAU,CAAC,MAAM;iBACzB,CAAC,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtE,MAAM,IAAI,mBAAmB,CAAC,8BAA8B,MAAM,EAAE,EAAE,SAAS,CAAC,CAAC;YAClF,CAAC;YAED,WAAW,CAAC,GAAG,MAAM,YAAY,QAAQ,CAAC,MAAM,iBAAiB,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;YACzG,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC5D,IAAI,YAAY;gBAAE,IAAI,CAAC,SAAS,GAAG,YAAY,CAAC;YAEhD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACxD,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;gBAC9C,MAAM,IAAI,mBAAmB,CAAC,wDAAwD,EAAE,MAAM,CAAC,CAAC;YACjG,CAAC;YACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC7B,yEAAuE;gBACvE,oEAAoE;gBACpE,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;gBAC9C,OAAO,SAAS,CAAC,CAAC,wBAAwB;YAC3C,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAClB,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC;gBAC9C,MAAM,IAAI,mBAAmB,CAAC,qBAAqB,QAAQ,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC;YACnF,CAAC;YAED,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;YAC/D,IAAI,WAAW,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;gBAC/C,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;gBAClC,WAAW,CAAC,GAAG,MAAM,cAAc,GAAG,CAAC,MAAM,QAAQ,CAAC,CAAC;gBACvD,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;YACjC,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,WAAW,CAAC,GAAG,MAAM,eAAe,IAAI,CAAC,MAAM,QAAQ,CAAC,CAAC;YACzD,IAAI,CAAC,IAAI;gBAAE,OAAO,SAAS,CAAC;YAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;QAC5C,CAAC;gBAAS,CAAC;YACV,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACpD,CAAC;IAAA,CACD;CACD;AAED,sEAAsE;AACtE,SAAS,mBAAmB,CAAC,GAAW,EAA+B;IACtE,IAAI,IAAiC,CAAC;IACtC,KAAK,MAAM,QAAQ,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAG,QAAQ;aACnB,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;aAC1C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACnC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAoB,CAAC;YACnD,gEAAgE;YAChE,IAAI,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,MAAM,CAAC,EAAE,KAAK,IAAI;gBAAE,IAAI,GAAG,MAAM,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACR,8BAA8B;QAC/B,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ","sourcesContent":["/**\n * Minimal Streamable-HTTP MCP client for the GitNexus hub — and only the hub.\n *\n * This is intentionally NOT a general MCP client: it implements exactly the\n * subset nexus-cli needs against a single known server — initialize,\n * notifications/initialized, tools/call — over the Streamable HTTP transport\n * (JSON-RPC over POST; responses arrive as application/json or as a\n * text/event-stream carrying JSON-RPC messages).\n */\n\nimport { appendFileSync } from \"node:fs\";\nimport type { GitNexusConfig } from \"./config.ts\";\n\n/** File-based trace (NEXUS_TRACE=1): TUI captures stderr, so console won't do. */\nfunction clientTrace(message: string): void {\n\tif (!process.env.NEXUS_TRACE) return;\n\ttry {\n\t\tappendFileSync(\"/tmp/nexus-observatory-trace.log\", `${new Date().toISOString()} [client] ${message}\\n`);\n\t} catch {\n\t\t// best-effort\n\t}\n}\n\ninterface JsonRpcResponse {\n\tjsonrpc: \"2.0\";\n\tid?: number | string | null;\n\tresult?: unknown;\n\terror?: { code: number; message: string; data?: unknown };\n}\n\nexport interface McpToolResult {\n\tcontent?: Array<{ type: string; text?: string }>;\n\tisError?: boolean;\n\tstructuredContent?: unknown;\n}\n\nexport class GitNexusClientError extends Error {\n\treadonly kind: \"auth\" | \"network\" | \"protocol\" | \"tool\";\n\n\tconstructor(message: string, kind: \"auth\" | \"network\" | \"protocol\" | \"tool\") {\n\t\tsuper(message);\n\t\tthis.name = \"GitNexusClientError\";\n\t\tthis.kind = kind;\n\t}\n}\n\nconst PROTOCOL_VERSION = \"2025-06-18\";\nconst REQUEST_TIMEOUT_MS = 60_000;\n\nexport class GitNexusClient {\n\tprivate sessionId: string | undefined;\n\tprivate nextId = 1;\n\tprivate initializing: Promise<void> | undefined;\n\tprivate readonly config: GitNexusConfig;\n\n\tconstructor(config: GitNexusConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/** Call a hub tool by name. Initializes the MCP session lazily on first use. */\n\tasync callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<McpToolResult> {\n\t\tawait this.ensureInitialized(signal);\n\t\tconst result = await this.request(\"tools/call\", { name, arguments: args }, signal);\n\t\treturn (result ?? {}) as McpToolResult;\n\t}\n\n\t/** Extract the primary text payload from an MCP tool result. */\n\tstatic text(result: McpToolResult): string {\n\t\tconst parts = result.content ?? [];\n\t\tconst text = parts\n\t\t\t.filter((p) => p.type === \"text\" && typeof p.text === \"string\")\n\t\t\t.map((p) => p.text)\n\t\t\t.join(\"\\n\");\n\t\tif (result.isError) {\n\t\t\tthrow new GitNexusClientError(text || \"hub tool returned an error\", \"tool\");\n\t\t}\n\t\treturn text;\n\t}\n\n\tprivate ensureInitialized(signal?: AbortSignal): Promise<void> {\n\t\tthis.initializing ??= this.initialize(signal).catch((error) => {\n\t\t\tthis.initializing = undefined;\n\t\t\tthrow error;\n\t\t});\n\t\treturn this.initializing;\n\t}\n\n\tprivate async initialize(signal?: AbortSignal): Promise<void> {\n\t\tawait this.request(\n\t\t\t\"initialize\",\n\t\t\t{\n\t\t\t\tprotocolVersion: PROTOCOL_VERSION,\n\t\t\t\tcapabilities: {},\n\t\t\t\tclientInfo: { name: \"nexus-cli\", version: \"0.1.0\" },\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tawait this.notify(\"notifications/initialized\");\n\t}\n\n\tprivate async notify(method: string): Promise<void> {\n\t\tawait this.post({ jsonrpc: \"2.0\", method }, undefined);\n\t}\n\n\tprivate async request(method: string, params: unknown, signal?: AbortSignal): Promise<unknown> {\n\t\tconst id = this.nextId++;\n\t\tconst response = await this.post({ jsonrpc: \"2.0\", id, method, params }, signal);\n\t\tif (!response) throw new GitNexusClientError(`empty response for ${method}`, \"protocol\");\n\t\tif (response.error) {\n\t\t\tthrow new GitNexusClientError(`hub ${method} failed: ${response.error.message}`, \"protocol\");\n\t\t}\n\t\treturn response.result;\n\t}\n\n\tprivate async post(body: unknown, signal?: AbortSignal): Promise<JsonRpcResponse | undefined> {\n\t\tconst controller = new AbortController();\n\t\tconst timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n\t\tconst onOuterAbort = () => controller.abort();\n\t\tsignal?.addEventListener(\"abort\", onOuterAbort, { once: true });\n\t\ttry {\n\t\t\tconst method = (body as { method?: string }).method ?? \"?\";\n\t\t\tclientTrace(`${method}: fetch dispatch`);\n\t\t\tlet response: Response;\n\t\t\ttry {\n\t\t\t\tresponse = await fetch(this.config.hubUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t\t\taccept: \"application/json, text/event-stream\",\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\t...(this.sessionId ? { \"mcp-session-id\": this.sessionId } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tbody: 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\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new GitNexusClientError(`cannot reach GitNexus hub: ${reason}`, \"network\");\n\t\t\t}\n\n\t\t\tclientTrace(`${method}: status ${response.status} content-type ${response.headers.get(\"content-type\")}`);\n\t\t\tconst newSessionId = response.headers.get(\"mcp-session-id\");\n\t\t\tif (newSessionId) this.sessionId = newSessionId;\n\n\t\t\tif (response.status === 401 || response.status === 403) {\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\tthrow new GitNexusClientError(\"GitNexus hub rejected the token (401/403). Run /login.\", \"auth\");\n\t\t\t}\n\t\t\tif (response.status === 202) {\n\t\t\t\t// Drain the body — an unconsumed response pins its socket, and under a\n\t\t\t\t// connection-capped dispatcher that deadlocks every later hub call.\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\treturn undefined; // accepted notification\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\tthrow new GitNexusClientError(`GitNexus hub HTTP ${response.status}`, \"protocol\");\n\t\t\t}\n\n\t\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\t\tif (contentType.includes(\"text/event-stream\")) {\n\t\t\t\tconst sse = await response.text();\n\t\t\t\tclientTrace(`${method}: sse body ${sse.length} bytes`);\n\t\t\t\treturn parseSseForResponse(sse);\n\t\t\t}\n\t\t\tconst text = await response.text();\n\t\t\tclientTrace(`${method}: json body ${text.length} bytes`);\n\t\t\tif (!text) return undefined;\n\t\t\treturn JSON.parse(text) as JsonRpcResponse;\n\t\t} finally {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onOuterAbort);\n\t\t}\n\t}\n}\n\n/** Pull the (single) JSON-RPC response message out of an SSE body. */\nfunction parseSseForResponse(sse: string): JsonRpcResponse | undefined {\n\tlet last: JsonRpcResponse | undefined;\n\tfor (const rawEvent of sse.split(\"\\n\\n\")) {\n\t\tconst data = rawEvent\n\t\t\t.split(\"\\n\")\n\t\t\t.filter((line) => line.startsWith(\"data:\"))\n\t\t\t.map((line) => line.slice(5).trim())\n\t\t\t.join(\"\\n\");\n\t\tif (!data) continue;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(data) as JsonRpcResponse;\n\t\t\t// Responses carry an id; server-initiated notifications do not.\n\t\t\tif (parsed.id !== undefined && parsed.id !== null) last = parsed;\n\t\t} catch {\n\t\t\t// Ignore non-JSON keepalives.\n\t\t}\n\t}\n\treturn last;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Minimal Streamable-HTTP MCP client for the GitNexus hub — and only the hub.\n *\n * This is intentionally NOT a general MCP client: it implements exactly the\n * subset nexus-cli needs against a single known server — initialize,\n * notifications/initialized, tools/call — over the Streamable HTTP transport\n * (JSON-RPC over POST; responses arrive as application/json or as a\n * text/event-stream carrying JSON-RPC messages).\n */\n\nimport { appendFileSync } from \"node:fs\";\nimport type { GitNexusConfig } from \"./config.ts\";\n\n/** File-based trace (NEXUS_TRACE=1): TUI captures stderr, so console won't do. */\nfunction clientTrace(message: string): void {\n\tif (!process.env.NEXUS_TRACE) return;\n\ttry {\n\t\tappendFileSync(\"/tmp/nexus-observatory-trace.log\", `${new Date().toISOString()} [client] ${message}\\n`);\n\t} catch {\n\t\t// best-effort\n\t}\n}\n\ninterface JsonRpcResponse {\n\tjsonrpc: \"2.0\";\n\tid?: number | string | null;\n\tresult?: unknown;\n\terror?: { code: number; message: string; data?: unknown };\n}\n\nexport interface McpToolResult {\n\tcontent?: Array<{ type: string; text?: string }>;\n\tisError?: boolean;\n\tstructuredContent?: unknown;\n}\n\nexport class GitNexusClientError extends Error {\n\treadonly kind: \"auth\" | \"network\" | \"protocol\" | \"tool\";\n\n\tconstructor(message: string, kind: \"auth\" | \"network\" | \"protocol\" | \"tool\") {\n\t\tsuper(message);\n\t\tthis.name = \"GitNexusClientError\";\n\t\tthis.kind = kind;\n\t}\n}\n\nconst PROTOCOL_VERSION = \"2025-06-18\";\nconst REQUEST_TIMEOUT_MS = 60_000;\n\nexport class GitNexusClient {\n\tprivate sessionId: string | undefined;\n\tprivate nextId = 1;\n\tprivate initializing: Promise<void> | undefined;\n\tprivate readonly config: GitNexusConfig;\n\n\tconstructor(config: GitNexusConfig) {\n\t\tthis.config = config;\n\t}\n\n\t/** Call a hub tool by name. Initializes the MCP session lazily on first use. */\n\tasync callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<McpToolResult> {\n\t\tawait this.ensureInitialized(signal);\n\t\tconst result = await this.request(\"tools/call\", { name, arguments: args }, signal);\n\t\treturn (result ?? {}) as McpToolResult;\n\t}\n\n\t/** Extract the primary text payload from an MCP tool result. */\n\tstatic text(result: McpToolResult): string {\n\t\tconst parts = result.content ?? [];\n\t\tconst text = parts\n\t\t\t.filter((p) => p.type === \"text\" && typeof p.text === \"string\")\n\t\t\t.map((p) => p.text)\n\t\t\t.join(\"\\n\");\n\t\tif (result.isError) {\n\t\t\tthrow new GitNexusClientError(text || \"hub tool returned an error\", \"tool\");\n\t\t}\n\t\treturn text;\n\t}\n\n\tprivate ensureInitialized(signal?: AbortSignal): Promise<void> {\n\t\tthis.initializing ??= this.initialize(signal).catch((error) => {\n\t\t\tthis.initializing = undefined;\n\t\t\tthis.sessionId = undefined;\n\t\t\tthrow error;\n\t\t});\n\t\treturn this.initializing;\n\t}\n\n\tprivate async initialize(signal?: AbortSignal): Promise<void> {\n\t\tawait this.request(\n\t\t\t\"initialize\",\n\t\t\t{\n\t\t\t\tprotocolVersion: PROTOCOL_VERSION,\n\t\t\t\tcapabilities: {},\n\t\t\t\tclientInfo: { name: \"nexus-cli\", version: \"0.1.0\" },\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t\tawait this.notify(\"notifications/initialized\", signal);\n\t}\n\n\tprivate async notify(method: string, signal?: AbortSignal): Promise<void> {\n\t\tawait this.post({ jsonrpc: \"2.0\", method }, signal);\n\t}\n\n\tprivate async request(method: string, params: unknown, signal?: AbortSignal): Promise<unknown> {\n\t\tconst id = this.nextId++;\n\t\tconst response = await this.post({ jsonrpc: \"2.0\", id, method, params }, signal);\n\t\tif (!response) throw new GitNexusClientError(`empty response for ${method}`, \"protocol\");\n\t\tif (response.error) {\n\t\t\tthrow new GitNexusClientError(`hub ${method} failed: ${response.error.message}`, \"protocol\");\n\t\t}\n\t\treturn response.result;\n\t}\n\n\tprivate async post(body: unknown, signal?: AbortSignal): Promise<JsonRpcResponse | undefined> {\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\tconst throwIfTimedOut = () => {\n\t\t\tif (controller.signal.aborted) {\n\t\t\t\tthrow new GitNexusClientError(\"cannot reach GitNexus hub: request timed out\", \"network\");\n\t\t\t}\n\t\t};\n\t\tsignal?.addEventListener(\"abort\", onOuterAbort, { once: true });\n\t\ttry {\n\t\t\tconst method = (body as { method?: string }).method ?? \"?\";\n\t\t\tclientTrace(`${method}: fetch dispatch`);\n\t\t\tlet response: Response;\n\t\t\ttry {\n\t\t\t\tresponse = await fetch(this.config.hubUrl, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t\t\taccept: \"application/json, text/event-stream\",\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\t...(this.sessionId ? { \"mcp-session-id\": this.sessionId } : {}),\n\t\t\t\t\t},\n\t\t\t\t\tbody: 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\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new GitNexusClientError(`cannot reach GitNexus hub: ${reason}`, \"network\");\n\t\t\t}\n\t\t\tsignal?.throwIfAborted();\n\t\t\tthrowIfTimedOut();\n\n\t\t\tclientTrace(`${method}: status ${response.status} content-type ${response.headers.get(\"content-type\")}`);\n\t\t\tconst newSessionId = response.headers.get(\"mcp-session-id\");\n\n\t\t\tif (response.status === 401 || response.status === 403) {\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tthrow new GitNexusClientError(\"GitNexus hub rejected the token (401/403). Run /login.\", \"auth\");\n\t\t\t}\n\t\t\tif (response.status === 202) {\n\t\t\t\t// Drain the body — an unconsumed response pins its socket, and under a\n\t\t\t\t// connection-capped dispatcher that deadlocks every later hub call.\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tif (newSessionId) this.sessionId = newSessionId;\n\t\t\t\treturn undefined; // accepted notification\n\t\t\t}\n\t\t\tif (!response.ok) {\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tthrow new GitNexusClientError(`GitNexus hub HTTP ${response.status}`, \"protocol\");\n\t\t\t}\n\n\t\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\t\tlet text: string;\n\t\t\ttry {\n\t\t\t\ttext = await response.text();\n\t\t\t} catch (error) {\n\t\t\t\tsignal?.throwIfAborted();\n\t\t\t\tthrowIfTimedOut();\n\t\t\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new GitNexusClientError(`cannot reach GitNexus hub: ${reason}`, \"network\");\n\t\t\t}\n\t\t\tsignal?.throwIfAborted();\n\t\t\tthrowIfTimedOut();\n\t\t\tlet parsed: JsonRpcResponse | undefined;\n\t\t\tif (contentType.includes(\"text/event-stream\")) {\n\t\t\t\tclientTrace(`${method}: sse body ${text.length} bytes`);\n\t\t\t\tparsed = parseSseForResponse(text);\n\t\t\t} else {\n\t\t\t\tclientTrace(`${method}: json body ${text.length} bytes`);\n\t\t\t\tparsed = text ? (JSON.parse(text) as JsonRpcResponse) : undefined;\n\t\t\t}\n\t\t\tif (newSessionId && parsed) this.sessionId = newSessionId;\n\t\t\treturn parsed;\n\t\t} finally {\n\t\t\tclearTimeout(timeout);\n\t\t\tsignal?.removeEventListener(\"abort\", onOuterAbort);\n\t\t}\n\t}\n}\n\n/** Pull the (single) JSON-RPC response message out of an SSE body. */\nfunction parseSseForResponse(sse: string): JsonRpcResponse | undefined {\n\tlet last: JsonRpcResponse | undefined;\n\tfor (const rawEvent of sse.split(\"\\n\\n\")) {\n\t\tconst data = rawEvent\n\t\t\t.split(\"\\n\")\n\t\t\t.filter((line) => line.startsWith(\"data:\"))\n\t\t\t.map((line) => line.slice(5).trim())\n\t\t\t.join(\"\\n\");\n\t\tif (!data) continue;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(data) as JsonRpcResponse;\n\t\t\t// Responses carry an id; server-initiated notifications do not.\n\t\t\tif (parsed.id !== undefined && parsed.id !== null) last = parsed;\n\t\t} catch {\n\t\t\t// Ignore non-JSON keepalives.\n\t\t}\n\t}\n\treturn last;\n}\n"]}
|