@akon-labs/nexus-cli 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/cli/require-login.d.ts.map +1 -1
  2. package/dist/cli/require-login.js +2 -2
  3. package/dist/cli/require-login.js.map +1 -1
  4. package/dist/cli.js +2492 -1088
  5. package/dist/core/agent-session.d.ts +3 -1
  6. package/dist/core/agent-session.d.ts.map +1 -1
  7. package/dist/core/agent-session.js +4 -0
  8. package/dist/core/agent-session.js.map +1 -1
  9. package/dist/core/extensions/index.d.ts +1 -1
  10. package/dist/core/extensions/index.d.ts.map +1 -1
  11. package/dist/core/extensions/index.js.map +1 -1
  12. package/dist/core/extensions/loader.d.ts.map +1 -1
  13. package/dist/core/extensions/loader.js +6 -0
  14. package/dist/core/extensions/loader.js.map +1 -1
  15. package/dist/core/extensions/runner.d.ts +7 -1
  16. package/dist/core/extensions/runner.d.ts.map +1 -1
  17. package/dist/core/extensions/runner.js +14 -0
  18. package/dist/core/extensions/runner.js.map +1 -1
  19. package/dist/core/extensions/types.d.ts +18 -0
  20. package/dist/core/extensions/types.d.ts.map +1 -1
  21. package/dist/core/extensions/types.js.map +1 -1
  22. package/dist/core/graph-rail-store.d.ts +10 -0
  23. package/dist/core/graph-rail-store.d.ts.map +1 -1
  24. package/dist/core/graph-rail-store.js +9 -0
  25. package/dist/core/graph-rail-store.js.map +1 -1
  26. package/dist/core/keybindings.d.ts +2 -1
  27. package/dist/core/keybindings.d.ts.map +1 -1
  28. package/dist/core/keybindings.js +4 -1
  29. package/dist/core/keybindings.js.map +1 -1
  30. package/dist/core/tools/find.d.ts.map +1 -1
  31. package/dist/core/tools/find.js +1 -1
  32. package/dist/core/tools/find.js.map +1 -1
  33. package/dist/core/tools/grep.d.ts.map +1 -1
  34. package/dist/core/tools/grep.js +1 -1
  35. package/dist/core/tools/grep.js.map +1 -1
  36. package/dist/extensions/gitnexus/client.d.ts.map +1 -1
  37. package/dist/extensions/gitnexus/client.js +45 -14
  38. package/dist/extensions/gitnexus/client.js.map +1 -1
  39. package/dist/extensions/gitnexus/config.d.ts +15 -1
  40. package/dist/extensions/gitnexus/config.d.ts.map +1 -1
  41. package/dist/extensions/gitnexus/config.js +86 -2
  42. package/dist/extensions/gitnexus/config.js.map +1 -1
  43. package/dist/extensions/gitnexus/hub-api.d.ts +56 -0
  44. package/dist/extensions/gitnexus/hub-api.d.ts.map +1 -0
  45. package/dist/extensions/gitnexus/hub-api.js +105 -0
  46. package/dist/extensions/gitnexus/hub-api.js.map +1 -0
  47. package/dist/extensions/gitnexus/index-repo.d.ts +50 -0
  48. package/dist/extensions/gitnexus/index-repo.d.ts.map +1 -0
  49. package/dist/extensions/gitnexus/index-repo.js +162 -0
  50. package/dist/extensions/gitnexus/index-repo.js.map +1 -0
  51. package/dist/extensions/gitnexus/index.d.ts.map +1 -1
  52. package/dist/extensions/gitnexus/index.js +137 -17
  53. package/dist/extensions/gitnexus/index.js.map +1 -1
  54. package/dist/extensions/gitnexus/observatory.d.ts.map +1 -1
  55. package/dist/extensions/gitnexus/observatory.js +2 -2
  56. package/dist/extensions/gitnexus/observatory.js.map +1 -1
  57. package/dist/extensions/gitnexus/resolve.d.ts +9 -0
  58. package/dist/extensions/gitnexus/resolve.d.ts.map +1 -1
  59. package/dist/extensions/gitnexus/resolve.js +52 -2
  60. package/dist/extensions/gitnexus/resolve.js.map +1 -1
  61. package/dist/extensions/index.d.ts.map +1 -1
  62. package/dist/extensions/index.js +3 -0
  63. package/dist/extensions/index.js.map +1 -1
  64. package/dist/extensions/mcp/commands.d.ts +22 -2
  65. package/dist/extensions/mcp/commands.d.ts.map +1 -1
  66. package/dist/extensions/mcp/commands.js +201 -9
  67. package/dist/extensions/mcp/commands.js.map +1 -1
  68. package/dist/extensions/mcp/config.d.ts +2 -0
  69. package/dist/extensions/mcp/config.d.ts.map +1 -1
  70. package/dist/extensions/mcp/config.js +6 -1
  71. package/dist/extensions/mcp/config.js.map +1 -1
  72. package/dist/extensions/mcp/index.d.ts.map +1 -1
  73. package/dist/extensions/mcp/index.js +54 -5
  74. package/dist/extensions/mcp/index.js.map +1 -1
  75. package/dist/extensions/mcp/mcp-setup-panel.d.ts +63 -2
  76. package/dist/extensions/mcp/mcp-setup-panel.d.ts.map +1 -1
  77. package/dist/extensions/mcp/mcp-setup-panel.js +563 -77
  78. package/dist/extensions/mcp/mcp-setup-panel.js.map +1 -1
  79. package/dist/extensions/subagent/index.d.ts +2 -1
  80. package/dist/extensions/subagent/index.d.ts.map +1 -1
  81. package/dist/extensions/subagent/index.js +19 -6
  82. package/dist/extensions/subagent/index.js.map +1 -1
  83. package/dist/extensions/subagent-render.d.ts +3 -0
  84. package/dist/extensions/subagent-render.d.ts.map +1 -0
  85. package/dist/extensions/subagent-render.js +39 -0
  86. package/dist/extensions/subagent-render.js.map +1 -0
  87. package/dist/index.js +2524 -1120
  88. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  89. package/dist/modes/interactive/components/assistant-message.js +2 -0
  90. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  91. package/dist/modes/interactive/components/command-palette.d.ts +2 -1
  92. package/dist/modes/interactive/components/command-palette.d.ts.map +1 -1
  93. package/dist/modes/interactive/components/command-palette.js +11 -4
  94. package/dist/modes/interactive/components/command-palette.js.map +1 -1
  95. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/footer.js +9 -3
  97. package/dist/modes/interactive/components/footer.js.map +1 -1
  98. package/dist/modes/interactive/components/graph-rail.d.ts.map +1 -1
  99. package/dist/modes/interactive/components/graph-rail.js +18 -3
  100. package/dist/modes/interactive/components/graph-rail.js.map +1 -1
  101. package/dist/modes/interactive/components/keybinding-hints.d.ts +1 -0
  102. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/keybinding-hints.js +7 -1
  104. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  105. package/dist/modes/interactive/components/landing-panel.d.ts.map +1 -1
  106. package/dist/modes/interactive/components/landing-panel.js +15 -2
  107. package/dist/modes/interactive/components/landing-panel.js.map +1 -1
  108. package/dist/modes/interactive/components/tool-execution.d.ts +3 -2
  109. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/tool-execution.js +12 -2
  111. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  112. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  113. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  114. package/dist/modes/interactive/interactive-mode.js +42 -8
  115. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  116. package/dist/rpc-entry.js +2492 -1088
  117. package/dist/utils/hidden-spawn.d.ts +29 -0
  118. package/dist/utils/hidden-spawn.d.ts.map +1 -0
  119. package/dist/utils/hidden-spawn.js +101 -0
  120. package/dist/utils/hidden-spawn.js.map +1 -0
  121. package/dist/vendor/ai.js +233 -233
  122. package/dist/vendor/tui.js +1 -1
  123. package/native/win32/build.mjs +95 -0
  124. package/native/win32/prebuilds/win32-x64/hidden-spawn.exe +0 -0
  125. package/native/win32/src/hidden-spawn.c +157 -0
  126. package/npm-shrinkwrap.json +2 -37
  127. package/package.json +7 -3
@@ -1 +1 @@
1
- {"version":3,"file":"grep.js","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile as fsReadFile, stat as fsStat } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Text } from \"@earendil-works/pi-tui\";\nimport { spawn } from \"child_process\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { ensureTool } from \"../../utils/tools-manager.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.ts\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: async (p) => (await fsStat(p)).isDirectory(),\n\treadFile: (p) => fsReadFile(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: Theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\") : null;\n\tconst glob = str(args?.glob);\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"grep\")) +\n\t\t\" \" +\n\t\t(pattern === null ? invalidArg : theme.fg(\"accent\", `/${pattern || \"\"}/`)) +\n\t\ttheme.fg(\"toolOutput\", ` in ${path === null ? invalidArg : path}`);\n\tif (glob) text += theme.fg(\"toolOutput\", ` (${glob})`);\n\tif (limit !== undefined) text += theme.fg(\"toolOutput\", ` limit ${limit}`);\n\treturn text;\n}\n\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"ripgrep (rg) is not available and could not be downloaded\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [`${relativePath}:${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${relativePath}:${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${relativePath}-${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Format matches after streaming finishes so custom readFile() backends can be async.\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\tconst relativePath = formatPath(match.filePath);\n\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\toutputLines.push(`${relativePath}:${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\n"]}
1
+ {"version":3,"file":"grep.js","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile as fsReadFile, stat as fsStat } from \"node:fs/promises\";\nimport { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Text } from \"@earendil-works/pi-tui\";\nimport { spawn } from \"child_process\";\nimport path from \"path\";\nimport { type Static, Type } from \"typebox\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport { ensureTool } from \"../../utils/tools-manager.ts\";\nimport type { ToolDefinition, ToolRenderResultOptions } from \"../extensions/types.ts\";\nimport { resolveToCwd } from \"./path-utils.ts\";\nimport { getTextOutput, invalidArgText, shortenPath, str } from \"./render-utils.ts\";\nimport { wrapToolDefinition } from \"./tool-definition-wrapper.ts\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.ts\";\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nexport type GrepToolInput = Static<typeof grepSchema>;\nconst DEFAULT_LIMIT = 100;\n\nexport interface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\n/**\n * Pluggable operations for the grep tool.\n * Override these to delegate search to remote systems (for example SSH).\n */\nexport interface GrepOperations {\n\t/** Check if path is a directory. Throws if path does not exist. */\n\tisDirectory: (absolutePath: string) => Promise<boolean> | boolean;\n\t/** Read file contents for context lines */\n\treadFile: (absolutePath: string) => Promise<string> | string;\n}\n\nconst defaultGrepOperations: GrepOperations = {\n\tisDirectory: async (p) => (await fsStat(p)).isDirectory(),\n\treadFile: (p) => fsReadFile(p, \"utf-8\"),\n};\n\nexport interface GrepToolOptions {\n\t/** Custom operations for grep. Default: local filesystem plus ripgrep */\n\toperations?: GrepOperations;\n}\n\nfunction formatGrepCall(\n\targs: { pattern: string; path?: string; glob?: string; limit?: number } | undefined,\n\ttheme: Theme,\n): string {\n\tconst pattern = str(args?.pattern);\n\tconst rawPath = str(args?.path);\n\tconst path = rawPath !== null ? shortenPath(rawPath || \".\") : null;\n\tconst glob = str(args?.glob);\n\tconst limit = args?.limit;\n\tconst invalidArg = invalidArgText(theme);\n\tlet text =\n\t\ttheme.fg(\"toolTitle\", theme.bold(\"grep\")) +\n\t\t\" \" +\n\t\t(pattern === null ? invalidArg : theme.fg(\"accent\", `/${pattern || \"\"}/`)) +\n\t\ttheme.fg(\"toolOutput\", ` in ${path === null ? invalidArg : path}`);\n\tif (glob) text += theme.fg(\"toolOutput\", ` (${glob})`);\n\tif (limit !== undefined) text += theme.fg(\"toolOutput\", ` limit ${limit}`);\n\treturn text;\n}\n\nfunction formatGrepResult(\n\tresult: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tdetails?: GrepToolDetails;\n\t},\n\toptions: ToolRenderResultOptions,\n\ttheme: Theme,\n\tshowImages: boolean,\n): string {\n\tconst output = getTextOutput(result, showImages).trim();\n\tlet text = \"\";\n\tif (output) {\n\t\tconst lines = output.split(\"\\n\");\n\t\tconst maxLines = options.expanded ? lines.length : 15;\n\t\tconst displayLines = lines.slice(0, maxLines);\n\t\tconst remaining = lines.length - maxLines;\n\t\ttext += `\\n${displayLines.map((line) => theme.fg(\"toolOutput\", line)).join(\"\\n\")}`;\n\t\tif (remaining > 0) {\n\t\t\ttext += `${theme.fg(\"muted\", `\\n... (${remaining} more lines,`)} ${keyHint(\"app.tools.expand\", \"to expand\")}${theme.fg(\"muted\", \")\")}`;\n\t\t}\n\t}\n\n\tconst matchLimit = result.details?.matchLimitReached;\n\tconst truncation = result.details?.truncation;\n\tconst linesTruncated = result.details?.linesTruncated;\n\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\tconst warnings: string[] = [];\n\t\tif (matchLimit) warnings.push(`${matchLimit} matches limit`);\n\t\tif (truncation?.truncated) warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\tif (linesTruncated) warnings.push(\"some lines truncated\");\n\t\ttext += `\\n${theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`)}`;\n\t}\n\treturn text;\n}\n\nexport function createGrepToolDefinition(\n\tcwd: string,\n\toptions?: GrepToolOptions,\n): ToolDefinition<typeof grepSchema, GrepToolDetails | undefined> {\n\tconst customOps = options?.operations;\n\treturn {\n\t\tname: \"grep\",\n\t\tlabel: \"grep\",\n\t\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\t\tpromptSnippet: \"Search file contents for patterns (respects .gitignore)\",\n\t\tparameters: grepSchema,\n\t\tasync execute(\n\t\t\t_toolCallId,\n\t\t\t{\n\t\t\t\tpattern,\n\t\t\t\tpath: searchDir,\n\t\t\t\tglob,\n\t\t\t\tignoreCase,\n\t\t\t\tliteral,\n\t\t\t\tcontext,\n\t\t\t\tlimit,\n\t\t\t}: {\n\t\t\t\tpattern: string;\n\t\t\t\tpath?: string;\n\t\t\t\tglob?: string;\n\t\t\t\tignoreCase?: boolean;\n\t\t\t\tliteral?: boolean;\n\t\t\t\tcontext?: number;\n\t\t\t\tlimit?: number;\n\t\t\t},\n\t\t\tsignal?: AbortSignal,\n\t\t\t_onUpdate?,\n\t\t\t_ctx?,\n\t\t) {\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlet settled = false;\n\t\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\t\tif (!settled) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tfn();\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"ripgrep (rg) is not available and could not be downloaded\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst searchPath = resolveToCwd(searchDir || \".\", cwd);\n\t\t\t\t\t\tconst ops = customOps ?? defaultGrepOperations;\n\t\t\t\t\t\tlet isDirectory: boolean;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tisDirectory = await ops.isDirectory(searchPath);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\t\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\t\tconst getFileLines = async (filePath: string): Promise<string[]> => {\n\t\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\tconst content = await ops.readFile(filePath);\n\t\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn lines;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\t\t\t\t\t\tif (ignoreCase) args.push(\"--ignore-case\");\n\t\t\t\t\t\tif (literal) args.push(\"--fixed-strings\");\n\t\t\t\t\t\tif (glob) args.push(\"--glob\", glob);\n\t\t\t\t\t\targs.push(\"--\", pattern, searchPath);\n\n\t\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"], windowsHide: true });\n\t\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\t\tlet aborted = false;\n\t\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\t\trl.close();\n\t\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst stopChild = (dueToLimit = false) => {\n\t\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t};\n\t\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\t\tstopChild();\n\t\t\t\t\t\t};\n\t\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst formatBlock = async (filePath: string, lineNumber: number): Promise<string[]> => {\n\t\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\t\tconst lines = await getFileLines(filePath);\n\t\t\t\t\t\t\tif (!lines.length) return [`${relativePath}:${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\t\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\t\t\t\t\t\t\t\t// Truncate long lines so grep output stays compact.\n\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\tif (isMatchLine) block.push(`${relativePath}:${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t\telse block.push(`${relativePath}-${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn block;\n\t\t\t\t\t\t};\n\n\t\t\t\t\t\t// Collect matches during streaming, then format them after rg exits.\n\t\t\t\t\t\tconst matches: Array<{ filePath: string; lineNumber: number; lineText?: string }> = [];\n\t\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) return;\n\t\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\t\t\t\t\t\t\t\tconst lineText = event.data?.lines?.text;\n\t\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\")\n\t\t\t\t\t\t\t\t\tmatches.push({ filePath, lineNumber, lineText });\n\t\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t\t});\n\t\t\t\t\t\tchild.on(\"close\", async (code) => {\n\t\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Format matches after streaming finishes so custom readFile() backends can be async.\n\t\t\t\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t\t\t\tif (contextValue === 0 && match.lineText !== undefined) {\n\t\t\t\t\t\t\t\t\tconst relativePath = formatPath(match.filePath);\n\t\t\t\t\t\t\t\t\tconst sanitized = match.lineText\n\t\t\t\t\t\t\t\t\t\t.replace(/\\r\\n/g, \"\\n\")\n\t\t\t\t\t\t\t\t\t\t.replace(/\\r/g, \"\")\n\t\t\t\t\t\t\t\t\t\t.replace(/\\n$/, \"\");\n\t\t\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\t\t\tif (wasTruncated) linesTruncated = true;\n\t\t\t\t\t\t\t\t\toutputLines.push(`${relativePath}:${match.lineNumber}: ${truncatedText}`);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tconst block = await formatBlock(match.filePath, match.lineNumber);\n\t\t\t\t\t\t\t\t\toutputLines.push(...block);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\t\t// Apply byte truncation. There is no line limit here because the match limit already capped rows.\n\t\t\t\t\t\t\tconst truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\t\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\t\t\t\t\t\t\t// Build actionable notices for truncation and match limits.\n\t\t\t\t\t\t\tconst notices: string[] = [];\n\t\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (notices.length > 0) output += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t});\n\t\t},\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepCall(args, theme));\n\t\t\treturn text;\n\t\t},\n\t\trenderResult(result, options, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(formatGrepResult(result as any, options, theme, context.showImages));\n\t\t\treturn text;\n\t\t},\n\t};\n}\n\nexport function createGrepTool(cwd: string, options?: GrepToolOptions): AgentTool<typeof grepSchema> {\n\treturn wrapToolDefinition(createGrepToolDefinition(cwd, options));\n}\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;YAQX,UAAU;YAaV,MAAM;YAIN,OAAO;YAUP,IAAI;CA8DlB","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.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 }, undefined);
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
- const sse = await response.text();
135
- clientTrace(`${method}: sse body ${sse.length} bytes`);
136
- return parseSseForResponse(sse);
164
+ clientTrace(`${method}: sse body ${text.length} bytes`);
165
+ parsed = parseSseForResponse(text);
137
166
  }
138
- const text = await response.text();
139
- clientTrace(`${method}: json body ${text.length} bytes`);
140
- if (!text)
141
- return undefined;
142
- return JSON.parse(text);
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"]}
@@ -2,8 +2,13 @@
2
2
  * GitNexus hub connection settings.
3
3
  *
4
4
  * Resolution order (first hit wins):
5
- * 1. Environment: GITNEXUS_HUB_URL / GITNEXUS_TOKEN
5
+ * 1. Environment: GITNEXUS_HUB_URL, then HUB_URL (process env or a `.env` file)
6
6
  * 2. Config file: ~/.config/nexus/auth.json ({ hubUrl, token })
7
+ * 3. Production default
8
+ *
9
+ * HUB_URL is the hub origin (`https://host`). The MCP client needs the
10
+ * `.../mcp` endpoint, so a bare origin gets `/mcp` appended. GITNEXUS_HUB_URL
11
+ * may already be that endpoint. Token: GITNEXUS_TOKEN, then the config file.
7
12
  *
8
13
  * The hub endpoint is the Streamable-HTTP MCP endpoint (".../mcp"). Bearer
9
14
  * tokens are hub API tokens; an empty token allowlist on the hub means the
@@ -17,6 +22,15 @@ export interface GitNexusConfig {
17
22
  deviceName?: string | undefined;
18
23
  }
19
24
  export declare const DEFAULT_HUB_URL = "https://gitnexus-enterprise-production.up.railway.app/mcp";
25
+ /** MCP endpoint for a hub origin or an already-qualified `.../mcp` URL. */
26
+ export declare function toMcpHubUrl(value: string): string;
27
+ /**
28
+ * Hub MCP endpoint.
29
+ *
30
+ * GITNEXUS_HUB_URL, then HUB_URL from the environment or `.env`, then a saved
31
+ * auth.json value, then the production default.
32
+ */
33
+ export declare function resolveHubUrl(savedHubUrl?: string): string;
20
34
  /** Resolve hub settings. Returns undefined when no token is configured anywhere. */
21
35
  export declare function resolveGitNexusConfig(): GitNexusConfig | undefined;
22
36
  /** Persist hub settings from `/login`. */
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/config.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,eAAO,MAAM,eAAe,8DAA8D,CAAC;AAoB3F,oFAAoF;AACpF,wBAAgB,qBAAqB,IAAI,cAAc,GAAG,SAAS,CAQlE;AAED,0CAA0C;AAC1C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAIjE;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * GitNexus hub connection settings.\n *\n * Resolution order (first hit wins):\n * 1. Environment: GITNEXUS_HUB_URL / GITNEXUS_TOKEN\n * 2. Config file: ~/.config/nexus/auth.json ({ hubUrl, token })\n *\n * The hub endpoint is the Streamable-HTTP MCP endpoint (\".../mcp\"). Bearer\n * tokens are hub API tokens; an empty token allowlist on the hub means the\n * token can see every repo of its user identity.\n */\n\nexport interface GitNexusConfig {\n\thubUrl: string;\n\ttoken: string;\n\t/** Hub tokens can be device-bound; these headers must then match the binding. */\n\tdeviceFingerprint?: string | undefined;\n\tdeviceName?: string | undefined;\n}\n\nexport const DEFAULT_HUB_URL = \"https://gitnexus-enterprise-production.up.railway.app/mcp\";\n\nfunction configDir(): string {\n\treturn join(homedir(), \".config\", \"nexus\");\n}\n\nfunction configPath(): string {\n\treturn join(configDir(), \"auth.json\");\n}\n\nfunction readConfigFile(): Partial<GitNexusConfig> {\n\ttry {\n\t\tconst raw = readFileSync(configPath(), \"utf8\");\n\t\tconst parsed = JSON.parse(raw) as Partial<GitNexusConfig>;\n\t\treturn typeof parsed === \"object\" && parsed !== null ? parsed : {};\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/** Resolve hub settings. Returns undefined when no token is configured anywhere. */\nexport function resolveGitNexusConfig(): GitNexusConfig | undefined {\n\tconst file = readConfigFile();\n\tconst token = process.env.GITNEXUS_TOKEN ?? file.token;\n\tif (!token) return undefined;\n\tconst hubUrl = process.env.GITNEXUS_HUB_URL ?? file.hubUrl ?? DEFAULT_HUB_URL;\n\tconst deviceFingerprint = process.env.GITNEXUS_DEVICE_FP ?? file.deviceFingerprint;\n\tconst deviceName = process.env.GITNEXUS_DEVICE_NAME ?? file.deviceName;\n\treturn { hubUrl, token, deviceFingerprint, deviceName };\n}\n\n/** Persist hub settings from `/login`. */\nexport function saveGitNexusConfig(config: GitNexusConfig): string {\n\tif (!existsSync(configDir())) mkdirSync(configDir(), { recursive: true, mode: 0o700 });\n\twriteFileSync(configPath(), `${JSON.stringify(config, null, \"\\t\")}\\n`, { mode: 0o600 });\n\treturn configPath();\n}\n\nexport function gitNexusConfigPath(): string {\n\treturn configPath();\n}\n"]}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/extensions/gitnexus/config.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,eAAO,MAAM,eAAe,8DAA8D,CAAC;AAE3F,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIjD;AA0DD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAK1D;AAoBD,oFAAoF;AACpF,wBAAgB,qBAAqB,IAAI,cAAc,GAAG,SAAS,CAQlE;AAED,0CAA0C;AAC1C,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAIjE;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * GitNexus hub connection settings.\n *\n * Resolution order (first hit wins):\n * 1. Environment: GITNEXUS_HUB_URL, then HUB_URL (process env or a `.env` file)\n * 2. Config file: ~/.config/nexus/auth.json ({ hubUrl, token })\n * 3. Production default\n *\n * HUB_URL is the hub origin (`https://host`). The MCP client needs the\n * `.../mcp` endpoint, so a bare origin gets `/mcp` appended. GITNEXUS_HUB_URL\n * may already be that endpoint. Token: GITNEXUS_TOKEN, then the config file.\n *\n * The hub endpoint is the Streamable-HTTP MCP endpoint (\".../mcp\"). Bearer\n * tokens are hub API tokens; an empty token allowlist on the hub means the\n * token can see every repo of its user identity.\n */\n\nexport interface GitNexusConfig {\n\thubUrl: string;\n\ttoken: string;\n\t/** Hub tokens can be device-bound; these headers must then match the binding. */\n\tdeviceFingerprint?: string | undefined;\n\tdeviceName?: string | undefined;\n}\n\nexport const DEFAULT_HUB_URL = \"https://gitnexus-enterprise-production.up.railway.app/mcp\";\n\n/** MCP endpoint for a hub origin or an already-qualified `.../mcp` URL. */\nexport function toMcpHubUrl(value: string): string {\n\tconst trimmed = value.trim().replace(/\\/+$/, \"\");\n\tif (trimmed.endsWith(\"/mcp\")) return trimmed;\n\treturn `${trimmed}/mcp`;\n}\n\nfunction readHubUrlKey(path: string): string | undefined {\n\tlet raw: string;\n\ttry {\n\t\traw = readFileSync(path, \"utf8\");\n\t} catch {\n\t\treturn undefined;\n\t}\n\tfor (const line of raw.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed || trimmed.startsWith(\"#\")) continue;\n\t\tconst eq = trimmed.indexOf(\"=\");\n\t\tif (eq <= 0) continue;\n\t\tif (trimmed.slice(0, eq).trim() !== \"HUB_URL\") continue;\n\t\tlet value = trimmed.slice(eq + 1).trim();\n\t\tif ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n\t\t\tvalue = value.slice(1, -1);\n\t\t}\n\t\treturn value || undefined;\n\t}\n\treturn undefined;\n}\n\n/**\n * HUB_URL from a `.env` file next to this checkout.\n *\n * Walks up from the working directory and from this module so `npm start`\n * from the repo root and a launch from a nested package both find\n * `nexus-cli/.env`. Skipped under Vitest so a developer `.env` does not\n * change the default the tests assert.\n */\nfunction readDotEnvHubUrl(): string | undefined {\n\tif (process.env.VITEST) return undefined;\n\tconst starts = [process.cwd()];\n\ttry {\n\t\tstarts.push(dirname(fileURLToPath(import.meta.url)));\n\t} catch {\n\t\t// import.meta.url is always present in the ESM build; ignore if a test bundler drops it.\n\t}\n\tconst seen = new Set<string>();\n\tfor (const start of starts) {\n\t\tlet dir = start;\n\t\tfor (let depth = 0; depth < 8; depth++) {\n\t\t\tconst candidate = join(dir, \".env\");\n\t\t\tif (!seen.has(candidate)) {\n\t\t\t\tseen.add(candidate);\n\t\t\t\tconst value = readHubUrlKey(candidate);\n\t\t\t\tif (value) return value;\n\t\t\t}\n\t\t\tconst parent = dirname(dir);\n\t\t\tif (parent === dir) break;\n\t\t\tdir = parent;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Hub MCP endpoint.\n *\n * GITNEXUS_HUB_URL, then HUB_URL from the environment or `.env`, then a saved\n * auth.json value, then the production default.\n */\nexport function resolveHubUrl(savedHubUrl?: string): string {\n\tconst fromEnv = process.env.GITNEXUS_HUB_URL?.trim() || process.env.HUB_URL?.trim() || readDotEnvHubUrl();\n\tif (fromEnv) return toMcpHubUrl(fromEnv);\n\tif (savedHubUrl) return savedHubUrl;\n\treturn DEFAULT_HUB_URL;\n}\n\nfunction configDir(): string {\n\treturn join(homedir(), \".config\", \"nexus\");\n}\n\nfunction configPath(): string {\n\treturn join(configDir(), \"auth.json\");\n}\n\nfunction readConfigFile(): Partial<GitNexusConfig> {\n\ttry {\n\t\tconst raw = readFileSync(configPath(), \"utf8\");\n\t\tconst parsed = JSON.parse(raw) as Partial<GitNexusConfig>;\n\t\treturn typeof parsed === \"object\" && parsed !== null ? parsed : {};\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/** Resolve hub settings. Returns undefined when no token is configured anywhere. */\nexport function resolveGitNexusConfig(): GitNexusConfig | undefined {\n\tconst file = readConfigFile();\n\tconst token = process.env.GITNEXUS_TOKEN ?? file.token;\n\tif (!token) return undefined;\n\tconst hubUrl = resolveHubUrl(file.hubUrl);\n\tconst deviceFingerprint = process.env.GITNEXUS_DEVICE_FP ?? file.deviceFingerprint;\n\tconst deviceName = process.env.GITNEXUS_DEVICE_NAME ?? file.deviceName;\n\treturn { hubUrl, token, deviceFingerprint, deviceName };\n}\n\n/** Persist hub settings from `/login`. */\nexport function saveGitNexusConfig(config: GitNexusConfig): string {\n\tif (!existsSync(configDir())) mkdirSync(configDir(), { recursive: true, mode: 0o700 });\n\twriteFileSync(configPath(), `${JSON.stringify(config, null, \"\\t\")}\\n`, { mode: 0o600 });\n\treturn configPath();\n}\n\nexport function gitNexusConfigPath(): string {\n\treturn configPath();\n}\n"]}
@@ -1,7 +1,91 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
- import { join } from "node:path";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  export const DEFAULT_HUB_URL = "https://gitnexus-enterprise-production.up.railway.app/mcp";
6
+ /** MCP endpoint for a hub origin or an already-qualified `.../mcp` URL. */
7
+ export function toMcpHubUrl(value) {
8
+ const trimmed = value.trim().replace(/\/+$/, "");
9
+ if (trimmed.endsWith("/mcp"))
10
+ return trimmed;
11
+ return `${trimmed}/mcp`;
12
+ }
13
+ function readHubUrlKey(path) {
14
+ let raw;
15
+ try {
16
+ raw = readFileSync(path, "utf8");
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ for (const line of raw.split("\n")) {
22
+ const trimmed = line.trim();
23
+ if (!trimmed || trimmed.startsWith("#"))
24
+ continue;
25
+ const eq = trimmed.indexOf("=");
26
+ if (eq <= 0)
27
+ continue;
28
+ if (trimmed.slice(0, eq).trim() !== "HUB_URL")
29
+ continue;
30
+ let value = trimmed.slice(eq + 1).trim();
31
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
32
+ value = value.slice(1, -1);
33
+ }
34
+ return value || undefined;
35
+ }
36
+ return undefined;
37
+ }
38
+ /**
39
+ * HUB_URL from a `.env` file next to this checkout.
40
+ *
41
+ * Walks up from the working directory and from this module so `npm start`
42
+ * from the repo root and a launch from a nested package both find
43
+ * `nexus-cli/.env`. Skipped under Vitest so a developer `.env` does not
44
+ * change the default the tests assert.
45
+ */
46
+ function readDotEnvHubUrl() {
47
+ if (process.env.VITEST)
48
+ return undefined;
49
+ const starts = [process.cwd()];
50
+ try {
51
+ starts.push(dirname(fileURLToPath(import.meta.url)));
52
+ }
53
+ catch {
54
+ // import.meta.url is always present in the ESM build; ignore if a test bundler drops it.
55
+ }
56
+ const seen = new Set();
57
+ for (const start of starts) {
58
+ let dir = start;
59
+ for (let depth = 0; depth < 8; depth++) {
60
+ const candidate = join(dir, ".env");
61
+ if (!seen.has(candidate)) {
62
+ seen.add(candidate);
63
+ const value = readHubUrlKey(candidate);
64
+ if (value)
65
+ return value;
66
+ }
67
+ const parent = dirname(dir);
68
+ if (parent === dir)
69
+ break;
70
+ dir = parent;
71
+ }
72
+ }
73
+ return undefined;
74
+ }
75
+ /**
76
+ * Hub MCP endpoint.
77
+ *
78
+ * GITNEXUS_HUB_URL, then HUB_URL from the environment or `.env`, then a saved
79
+ * auth.json value, then the production default.
80
+ */
81
+ export function resolveHubUrl(savedHubUrl) {
82
+ const fromEnv = process.env.GITNEXUS_HUB_URL?.trim() || process.env.HUB_URL?.trim() || readDotEnvHubUrl();
83
+ if (fromEnv)
84
+ return toMcpHubUrl(fromEnv);
85
+ if (savedHubUrl)
86
+ return savedHubUrl;
87
+ return DEFAULT_HUB_URL;
88
+ }
5
89
  function configDir() {
6
90
  return join(homedir(), ".config", "nexus");
7
91
  }
@@ -24,7 +108,7 @@ export function resolveGitNexusConfig() {
24
108
  const token = process.env.GITNEXUS_TOKEN ?? file.token;
25
109
  if (!token)
26
110
  return undefined;
27
- const hubUrl = process.env.GITNEXUS_HUB_URL ?? file.hubUrl ?? DEFAULT_HUB_URL;
111
+ const hubUrl = resolveHubUrl(file.hubUrl);
28
112
  const deviceFingerprint = process.env.GITNEXUS_DEVICE_FP ?? file.deviceFingerprint;
29
113
  const deviceName = process.env.GITNEXUS_DEVICE_NAME ?? file.deviceName;
30
114
  return { hubUrl, token, deviceFingerprint, deviceName };
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAsBjC,MAAM,CAAC,MAAM,eAAe,GAAG,2DAA2D,CAAC;AAE3F,SAAS,SAAS,GAAW;IAC5B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAAA,CAC3C;AAED,SAAS,UAAU,GAAW;IAC7B,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,WAAW,CAAC,CAAC;AAAA,CACtC;AAED,SAAS,cAAc,GAA4B;IAClD,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AAAA,CACD;AAED,oFAAoF;AACpF,MAAM,UAAU,qBAAqB,GAA+B;IACnE,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,KAAK,CAAC;IACvD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI,CAAC,MAAM,IAAI,eAAe,CAAC;IAC9E,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAC,iBAAiB,CAAC;IACnF,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,IAAI,CAAC,UAAU,CAAC;IACvE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC;AAAA,CACxD;AAED,0CAA0C;AAC1C,MAAM,UAAU,kBAAkB,CAAC,MAAsB,EAAU;IAClE,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;QAAE,SAAS,CAAC,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACvF,aAAa,CAAC,UAAU,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,OAAO,UAAU,EAAE,CAAC;AAAA,CACpB;AAED,MAAM,UAAU,kBAAkB,GAAW;IAC5C,OAAO,UAAU,EAAE,CAAC;AAAA,CACpB","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * GitNexus hub connection settings.\n *\n * Resolution order (first hit wins):\n * 1. Environment: GITNEXUS_HUB_URL / GITNEXUS_TOKEN\n * 2. Config file: ~/.config/nexus/auth.json ({ hubUrl, token })\n *\n * The hub endpoint is the Streamable-HTTP MCP endpoint (\".../mcp\"). Bearer\n * tokens are hub API tokens; an empty token allowlist on the hub means the\n * token can see every repo of its user identity.\n */\n\nexport interface GitNexusConfig {\n\thubUrl: string;\n\ttoken: string;\n\t/** Hub tokens can be device-bound; these headers must then match the binding. */\n\tdeviceFingerprint?: string | undefined;\n\tdeviceName?: string | undefined;\n}\n\nexport const DEFAULT_HUB_URL = \"https://gitnexus-enterprise-production.up.railway.app/mcp\";\n\nfunction configDir(): string {\n\treturn join(homedir(), \".config\", \"nexus\");\n}\n\nfunction configPath(): string {\n\treturn join(configDir(), \"auth.json\");\n}\n\nfunction readConfigFile(): Partial<GitNexusConfig> {\n\ttry {\n\t\tconst raw = readFileSync(configPath(), \"utf8\");\n\t\tconst parsed = JSON.parse(raw) as Partial<GitNexusConfig>;\n\t\treturn typeof parsed === \"object\" && parsed !== null ? parsed : {};\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/** Resolve hub settings. Returns undefined when no token is configured anywhere. */\nexport function resolveGitNexusConfig(): GitNexusConfig | undefined {\n\tconst file = readConfigFile();\n\tconst token = process.env.GITNEXUS_TOKEN ?? file.token;\n\tif (!token) return undefined;\n\tconst hubUrl = process.env.GITNEXUS_HUB_URL ?? file.hubUrl ?? DEFAULT_HUB_URL;\n\tconst deviceFingerprint = process.env.GITNEXUS_DEVICE_FP ?? file.deviceFingerprint;\n\tconst deviceName = process.env.GITNEXUS_DEVICE_NAME ?? file.deviceName;\n\treturn { hubUrl, token, deviceFingerprint, deviceName };\n}\n\n/** Persist hub settings from `/login`. */\nexport function saveGitNexusConfig(config: GitNexusConfig): string {\n\tif (!existsSync(configDir())) mkdirSync(configDir(), { recursive: true, mode: 0o700 });\n\twriteFileSync(configPath(), `${JSON.stringify(config, null, \"\\t\")}\\n`, { mode: 0o600 });\n\treturn configPath();\n}\n\nexport function gitNexusConfigPath(): string {\n\treturn configPath();\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/extensions/gitnexus/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA2BzC,MAAM,CAAC,MAAM,eAAe,GAAG,2DAA2D,CAAC;AAE3F,2EAA2E;AAC3E,MAAM,UAAU,WAAW,CAAC,KAAa,EAAU;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,OAAO,CAAC;IAC7C,OAAO,GAAG,OAAO,MAAM,CAAC;AAAA,CACxB;AAED,SAAS,aAAa,CAAC,IAAY,EAAsB;IACxD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACJ,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAClD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS;QACtB,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,SAAS;YAAE,SAAS;QACxD,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACtG,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO,KAAK,IAAI,SAAS,CAAC;IAC3B,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,GAAuB;IAC/C,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IACzC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/B,IAAI,CAAC;QACJ,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACR,yFAAyF;IAC1F,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,IAAI,GAAG,GAAG,KAAK,CAAC;QAChB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;YACxC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACpB,MAAM,KAAK,GAAG,aAAa,CAAC,SAAS,CAAC,CAAC;gBACvC,IAAI,KAAK;oBAAE,OAAO,KAAK,CAAC;YACzB,CAAC;YACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,MAAM,KAAK,GAAG;gBAAE,MAAM;YAC1B,GAAG,GAAG,MAAM,CAAC;QACd,CAAC;IACF,CAAC;IACD,OAAO,SAAS,CAAC;AAAA,CACjB;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,WAAoB,EAAU;IAC3D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,gBAAgB,EAAE,CAAC;IAC1G,IAAI,OAAO;QAAE,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IACpC,OAAO,eAAe,CAAC;AAAA,CACvB;AAED,SAAS,SAAS,GAAW;IAC5B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAAA,CAC3C;AAED,SAAS,UAAU,GAAW;IAC7B,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,WAAW,CAAC,CAAC;AAAA,CACtC;AAED,SAAS,cAAc,GAA4B;IAClD,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;QAC1D,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AAAA,CACD;AAED,oFAAoF;AACpF,MAAM,UAAU,qBAAqB,GAA+B;IACnE,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,KAAK,CAAC;IACvD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1C,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAC,iBAAiB,CAAC;IACnF,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,IAAI,CAAC,UAAU,CAAC;IACvE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC;AAAA,CACxD;AAED,0CAA0C;AAC1C,MAAM,UAAU,kBAAkB,CAAC,MAAsB,EAAU;IAClE,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;QAAE,SAAS,CAAC,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACvF,aAAa,CAAC,UAAU,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,OAAO,UAAU,EAAE,CAAC;AAAA,CACpB;AAED,MAAM,UAAU,kBAAkB,GAAW;IAC5C,OAAO,UAAU,EAAE,CAAC;AAAA,CACpB","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * GitNexus hub connection settings.\n *\n * Resolution order (first hit wins):\n * 1. Environment: GITNEXUS_HUB_URL, then HUB_URL (process env or a `.env` file)\n * 2. Config file: ~/.config/nexus/auth.json ({ hubUrl, token })\n * 3. Production default\n *\n * HUB_URL is the hub origin (`https://host`). The MCP client needs the\n * `.../mcp` endpoint, so a bare origin gets `/mcp` appended. GITNEXUS_HUB_URL\n * may already be that endpoint. Token: GITNEXUS_TOKEN, then the config file.\n *\n * The hub endpoint is the Streamable-HTTP MCP endpoint (\".../mcp\"). Bearer\n * tokens are hub API tokens; an empty token allowlist on the hub means the\n * token can see every repo of its user identity.\n */\n\nexport interface GitNexusConfig {\n\thubUrl: string;\n\ttoken: string;\n\t/** Hub tokens can be device-bound; these headers must then match the binding. */\n\tdeviceFingerprint?: string | undefined;\n\tdeviceName?: string | undefined;\n}\n\nexport const DEFAULT_HUB_URL = \"https://gitnexus-enterprise-production.up.railway.app/mcp\";\n\n/** MCP endpoint for a hub origin or an already-qualified `.../mcp` URL. */\nexport function toMcpHubUrl(value: string): string {\n\tconst trimmed = value.trim().replace(/\\/+$/, \"\");\n\tif (trimmed.endsWith(\"/mcp\")) return trimmed;\n\treturn `${trimmed}/mcp`;\n}\n\nfunction readHubUrlKey(path: string): string | undefined {\n\tlet raw: string;\n\ttry {\n\t\traw = readFileSync(path, \"utf8\");\n\t} catch {\n\t\treturn undefined;\n\t}\n\tfor (const line of raw.split(\"\\n\")) {\n\t\tconst trimmed = line.trim();\n\t\tif (!trimmed || trimmed.startsWith(\"#\")) continue;\n\t\tconst eq = trimmed.indexOf(\"=\");\n\t\tif (eq <= 0) continue;\n\t\tif (trimmed.slice(0, eq).trim() !== \"HUB_URL\") continue;\n\t\tlet value = trimmed.slice(eq + 1).trim();\n\t\tif ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n\t\t\tvalue = value.slice(1, -1);\n\t\t}\n\t\treturn value || undefined;\n\t}\n\treturn undefined;\n}\n\n/**\n * HUB_URL from a `.env` file next to this checkout.\n *\n * Walks up from the working directory and from this module so `npm start`\n * from the repo root and a launch from a nested package both find\n * `nexus-cli/.env`. Skipped under Vitest so a developer `.env` does not\n * change the default the tests assert.\n */\nfunction readDotEnvHubUrl(): string | undefined {\n\tif (process.env.VITEST) return undefined;\n\tconst starts = [process.cwd()];\n\ttry {\n\t\tstarts.push(dirname(fileURLToPath(import.meta.url)));\n\t} catch {\n\t\t// import.meta.url is always present in the ESM build; ignore if a test bundler drops it.\n\t}\n\tconst seen = new Set<string>();\n\tfor (const start of starts) {\n\t\tlet dir = start;\n\t\tfor (let depth = 0; depth < 8; depth++) {\n\t\t\tconst candidate = join(dir, \".env\");\n\t\t\tif (!seen.has(candidate)) {\n\t\t\t\tseen.add(candidate);\n\t\t\t\tconst value = readHubUrlKey(candidate);\n\t\t\t\tif (value) return value;\n\t\t\t}\n\t\t\tconst parent = dirname(dir);\n\t\t\tif (parent === dir) break;\n\t\t\tdir = parent;\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * Hub MCP endpoint.\n *\n * GITNEXUS_HUB_URL, then HUB_URL from the environment or `.env`, then a saved\n * auth.json value, then the production default.\n */\nexport function resolveHubUrl(savedHubUrl?: string): string {\n\tconst fromEnv = process.env.GITNEXUS_HUB_URL?.trim() || process.env.HUB_URL?.trim() || readDotEnvHubUrl();\n\tif (fromEnv) return toMcpHubUrl(fromEnv);\n\tif (savedHubUrl) return savedHubUrl;\n\treturn DEFAULT_HUB_URL;\n}\n\nfunction configDir(): string {\n\treturn join(homedir(), \".config\", \"nexus\");\n}\n\nfunction configPath(): string {\n\treturn join(configDir(), \"auth.json\");\n}\n\nfunction readConfigFile(): Partial<GitNexusConfig> {\n\ttry {\n\t\tconst raw = readFileSync(configPath(), \"utf8\");\n\t\tconst parsed = JSON.parse(raw) as Partial<GitNexusConfig>;\n\t\treturn typeof parsed === \"object\" && parsed !== null ? parsed : {};\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/** Resolve hub settings. Returns undefined when no token is configured anywhere. */\nexport function resolveGitNexusConfig(): GitNexusConfig | undefined {\n\tconst file = readConfigFile();\n\tconst token = process.env.GITNEXUS_TOKEN ?? file.token;\n\tif (!token) return undefined;\n\tconst hubUrl = resolveHubUrl(file.hubUrl);\n\tconst deviceFingerprint = process.env.GITNEXUS_DEVICE_FP ?? file.deviceFingerprint;\n\tconst deviceName = process.env.GITNEXUS_DEVICE_NAME ?? file.deviceName;\n\treturn { hubUrl, token, deviceFingerprint, deviceName };\n}\n\n/** Persist hub settings from `/login`. */\nexport function saveGitNexusConfig(config: GitNexusConfig): string {\n\tif (!existsSync(configDir())) mkdirSync(configDir(), { recursive: true, mode: 0o700 });\n\twriteFileSync(configPath(), `${JSON.stringify(config, null, \"\\t\")}\\n`, { mode: 0o600 });\n\treturn configPath();\n}\n\nexport function gitNexusConfigPath(): string {\n\treturn configPath();\n}\n"]}