@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":"require-login.d.ts","sourceRoot":"","sources":["../../src/cli/require-login.ts"],"names":[],"mappings":"AA2BA,wBAAgB,mBAAmB,IAAI,OAAO,CAG7C;AAED,MAAM,MAAM,eAAe,GACxB;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,gFAAgF;AAChF,wBAAsB,kBAAkB,CAAC,MAAM,GAAE,WAAyC,GAAG,OAAO,CAAC,eAAe,CAAC,CAepH;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,MAAM,WAAW,WAAW;IAC3B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oDAAoD;IACpD,KAAK,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,mDAAmD;IACnD,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAMD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,EAAE,GAAE,WAAuB,GAAG,OAAO,CAAC,OAAO,CAAC,CAmChF;AAED,0EAA0E;AAC1E,wBAAgB,0BAA0B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAUlE","sourcesContent":["import { APP_NAME, readAppEnv } from \"../config.ts\";\nimport { GitNexusClient, GitNexusClientError } from \"../extensions/gitnexus/client.ts\";\nimport {\n\tDEFAULT_HUB_URL,\n\tgitNexusConfigPath,\n\tresolveGitNexusConfig,\n\tsaveGitNexusConfig,\n} from \"../extensions/gitnexus/config.ts\";\nimport {\n\tdefaultDeviceName,\n\tdeviceFingerprint,\n\tpollDeviceAuth,\n\tstartDeviceAuth,\n} from \"../extensions/gitnexus/device-auth.ts\";\nimport { openBrowser } from \"../utils/open-browser.ts\";\n\n/**\n * Sign-in gate.\n *\n * Nexus is a GitNexus client, so it requires an account. Startup stops here\n * until the hub accepts its credential. Model and session initialization happen\n * only after this check, so a stale token cannot fall through to a local provider.\n */\n\n/** Escape hatch for CI and automated tests, which never have a credential. */\nconst SKIP_ENV_SUFFIX = \"SKIP_LOGIN\";\n\nexport function isLoginGateDisabled(): boolean {\n\tconst value = readAppEnv(SKIP_ENV_SUFFIX);\n\treturn value === \"1\" || value?.toLowerCase() === \"true\";\n}\n\nexport type LoginGateStatus =\n\t| { status: \"signed_in\" }\n\t| { status: \"sign_in_required\"; reason?: string }\n\t| { status: \"unavailable\"; reason: string };\n\n/** Validate configured GitNexus auth against the hub before runtime startup. */\nexport async function getLoginGateStatus(signal: AbortSignal = AbortSignal.timeout(15_000)): Promise<LoginGateStatus> {\n\tif (isLoginGateDisabled()) return { status: \"signed_in\" };\n\tconst config = resolveGitNexusConfig();\n\tif (!config) return { status: \"sign_in_required\" };\n\n\ttry {\n\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {}, signal);\n\t\treturn { status: \"signed_in\" };\n\t} catch (error) {\n\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\tif (error instanceof GitNexusClientError && error.kind === \"auth\") {\n\t\t\treturn { status: \"sign_in_required\", reason };\n\t\t}\n\t\treturn { status: \"unavailable\", reason };\n\t}\n}\n\nexport function loginUnavailableMessage(reason: string): string {\n\treturn `${APP_NAME}: GitNexus unavailable. Startup stopped.\\n\\n${reason}`;\n}\n\nexport interface LoginGateIo {\n\twrite(text: string): void;\n\t/** Injected in tests so no real waiting happens. */\n\tsleep?(ms: number): Promise<void>;\n\t/** Injected in tests so no browser is launched. */\n\topen?(url: string): void;\n}\n\nconst defaultIo: LoginGateIo = {\n\twrite: (text) => process.stdout.write(text),\n};\n\n/**\n * Run the browser device login and persist the token.\n *\n * Returns true once signed in. Any failure is reported and returns false, so\n * the caller decides whether to exit.\n */\nexport async function runLoginFlow(io: LoginGateIo = defaultIo): Promise<boolean> {\n\tconst hubUrl = process.env.GITNEXUS_HUB_URL ?? resolveGitNexusConfig()?.hubUrl ?? DEFAULT_HUB_URL;\n\n\tio.write(`\\nSign in to GitNexus to use ${APP_NAME}.\\n\\n`);\n\n\ttry {\n\t\tconst deviceName = defaultDeviceName();\n\t\tconst fingerprint = deviceFingerprint();\n\t\tconst start = await startDeviceAuth(hubUrl, { deviceName, deviceFingerprint: fingerprint });\n\t\tconst link = start.verificationUrlComplete ?? start.verificationUrl;\n\t\tio.write(` Code: ${start.userCode}\\n`);\n\t\tio.write(` Open: ${link}\\n\\n`);\n\t\t// Best effort: the launcher swallows its own failures, and the link is\n\t\t// printed above regardless, so a headless box still has a way through.\n\t\t(io.open ?? openBrowser)(link);\n\t\tio.write(\"Opening your browser… (ctrl+c to cancel)\\n\");\n\n\t\tconst result = await pollDeviceAuth(hubUrl, start, io.sleep ? { sleep: io.sleep } : {});\n\t\tconst config = {\n\t\t\thubUrl: result.hubUrl,\n\t\t\ttoken: result.token,\n\t\t\tdeviceFingerprint: fingerprint,\n\t\t\tdeviceName,\n\t\t};\n\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {}, AbortSignal.timeout(15_000));\n\t\tconst path = saveGitNexusConfig(config);\n\t\tio.write(`\\nSigned in. Credential saved to ${path}\\n\\n`);\n\t\treturn true;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tio.write(`\\nSign-in failed: ${message}\\n`);\n\t\tio.write(`Run ${APP_NAME} again to retry, or set GITNEXUS_TOKEN if you already have one.\\n`);\n\t\tio.write(\"Inside a session, /login → GitNexus account does the same thing.\\n\\n\");\n\t\treturn false;\n\t}\n}\n\n/** Message shown when a non-interactive run cannot prompt for sign-in. */\nexport function nonInteractiveLoginMessage(reason?: string): string {\n\treturn [\n\t\t`${APP_NAME}: not signed in.`,\n\t\t...(reason ? [``, reason] : []),\n\t\t\"\",\n\t\t`Run ${APP_NAME} with no arguments once to sign in through your browser,`,\n\t\t\"or set GITNEXUS_TOKEN for non-interactive use.\",\n\t\t\"\",\n\t\t`Credential location: ${gitNexusConfigPath()}`,\n\t].join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"require-login.d.ts","sourceRoot":"","sources":["../../src/cli/require-login.ts"],"names":[],"mappings":"AA2BA,wBAAgB,mBAAmB,IAAI,OAAO,CAG7C;AAED,MAAM,MAAM,eAAe,GACxB;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,gFAAgF;AAChF,wBAAsB,kBAAkB,CAAC,MAAM,GAAE,WAAyC,GAAG,OAAO,CAAC,eAAe,CAAC,CAepH;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,MAAM,WAAW,WAAW;IAC3B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oDAAoD;IACpD,KAAK,CAAC,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,mDAAmD;IACnD,IAAI,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAMD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,EAAE,GAAE,WAAuB,GAAG,OAAO,CAAC,OAAO,CAAC,CAmChF;AAED,0EAA0E;AAC1E,wBAAgB,0BAA0B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAUlE","sourcesContent":["import { APP_NAME, readAppEnv } from \"../config.ts\";\nimport { GitNexusClient, GitNexusClientError } from \"../extensions/gitnexus/client.ts\";\nimport {\n\tgitNexusConfigPath,\n\tresolveGitNexusConfig,\n\tresolveHubUrl,\n\tsaveGitNexusConfig,\n} from \"../extensions/gitnexus/config.ts\";\nimport {\n\tdefaultDeviceName,\n\tdeviceFingerprint,\n\tpollDeviceAuth,\n\tstartDeviceAuth,\n} from \"../extensions/gitnexus/device-auth.ts\";\nimport { openBrowser } from \"../utils/open-browser.ts\";\n\n/**\n * Sign-in gate.\n *\n * Nexus is a GitNexus client, so it requires an account. Startup stops here\n * until the hub accepts its credential. Model and session initialization happen\n * only after this check, so a stale token cannot fall through to a local provider.\n */\n\n/** Escape hatch for CI and automated tests, which never have a credential. */\nconst SKIP_ENV_SUFFIX = \"SKIP_LOGIN\";\n\nexport function isLoginGateDisabled(): boolean {\n\tconst value = readAppEnv(SKIP_ENV_SUFFIX);\n\treturn value === \"1\" || value?.toLowerCase() === \"true\";\n}\n\nexport type LoginGateStatus =\n\t| { status: \"signed_in\" }\n\t| { status: \"sign_in_required\"; reason?: string }\n\t| { status: \"unavailable\"; reason: string };\n\n/** Validate configured GitNexus auth against the hub before runtime startup. */\nexport async function getLoginGateStatus(signal: AbortSignal = AbortSignal.timeout(15_000)): Promise<LoginGateStatus> {\n\tif (isLoginGateDisabled()) return { status: \"signed_in\" };\n\tconst config = resolveGitNexusConfig();\n\tif (!config) return { status: \"sign_in_required\" };\n\n\ttry {\n\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {}, signal);\n\t\treturn { status: \"signed_in\" };\n\t} catch (error) {\n\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\tif (error instanceof GitNexusClientError && error.kind === \"auth\") {\n\t\t\treturn { status: \"sign_in_required\", reason };\n\t\t}\n\t\treturn { status: \"unavailable\", reason };\n\t}\n}\n\nexport function loginUnavailableMessage(reason: string): string {\n\treturn `${APP_NAME}: GitNexus unavailable. Startup stopped.\\n\\n${reason}`;\n}\n\nexport interface LoginGateIo {\n\twrite(text: string): void;\n\t/** Injected in tests so no real waiting happens. */\n\tsleep?(ms: number): Promise<void>;\n\t/** Injected in tests so no browser is launched. */\n\topen?(url: string): void;\n}\n\nconst defaultIo: LoginGateIo = {\n\twrite: (text) => process.stdout.write(text),\n};\n\n/**\n * Run the browser device login and persist the token.\n *\n * Returns true once signed in. Any failure is reported and returns false, so\n * the caller decides whether to exit.\n */\nexport async function runLoginFlow(io: LoginGateIo = defaultIo): Promise<boolean> {\n\tconst hubUrl = resolveHubUrl(resolveGitNexusConfig()?.hubUrl);\n\n\tio.write(`\\nSign in to GitNexus to use ${APP_NAME}.\\n\\n`);\n\n\ttry {\n\t\tconst deviceName = defaultDeviceName();\n\t\tconst fingerprint = deviceFingerprint();\n\t\tconst start = await startDeviceAuth(hubUrl, { deviceName, deviceFingerprint: fingerprint });\n\t\tconst link = start.verificationUrlComplete ?? start.verificationUrl;\n\t\tio.write(` Code: ${start.userCode}\\n`);\n\t\tio.write(` Open: ${link}\\n\\n`);\n\t\t// Best effort: the launcher swallows its own failures, and the link is\n\t\t// printed above regardless, so a headless box still has a way through.\n\t\t(io.open ?? openBrowser)(link);\n\t\tio.write(\"Opening your browser… (ctrl+c to cancel)\\n\");\n\n\t\tconst result = await pollDeviceAuth(hubUrl, start, io.sleep ? { sleep: io.sleep } : {});\n\t\tconst config = {\n\t\t\thubUrl: result.hubUrl,\n\t\t\ttoken: result.token,\n\t\t\tdeviceFingerprint: fingerprint,\n\t\t\tdeviceName,\n\t\t};\n\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {}, AbortSignal.timeout(15_000));\n\t\tconst path = saveGitNexusConfig(config);\n\t\tio.write(`\\nSigned in. Credential saved to ${path}\\n\\n`);\n\t\treturn true;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tio.write(`\\nSign-in failed: ${message}\\n`);\n\t\tio.write(`Run ${APP_NAME} again to retry, or set GITNEXUS_TOKEN if you already have one.\\n`);\n\t\tio.write(\"Inside a session, /login → GitNexus account does the same thing.\\n\\n\");\n\t\treturn false;\n\t}\n}\n\n/** Message shown when a non-interactive run cannot prompt for sign-in. */\nexport function nonInteractiveLoginMessage(reason?: string): string {\n\treturn [\n\t\t`${APP_NAME}: not signed in.`,\n\t\t...(reason ? [``, reason] : []),\n\t\t\"\",\n\t\t`Run ${APP_NAME} with no arguments once to sign in through your browser,`,\n\t\t\"or set GITNEXUS_TOKEN for non-interactive use.\",\n\t\t\"\",\n\t\t`Credential location: ${gitNexusConfigPath()}`,\n\t].join(\"\\n\");\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { APP_NAME, readAppEnv } from "../config.js";
2
2
  import { GitNexusClient, GitNexusClientError } from "../extensions/gitnexus/client.js";
3
- import { DEFAULT_HUB_URL, gitNexusConfigPath, resolveGitNexusConfig, saveGitNexusConfig, } from "../extensions/gitnexus/config.js";
3
+ import { gitNexusConfigPath, resolveGitNexusConfig, resolveHubUrl, saveGitNexusConfig, } from "../extensions/gitnexus/config.js";
4
4
  import { defaultDeviceName, deviceFingerprint, pollDeviceAuth, startDeviceAuth, } from "../extensions/gitnexus/device-auth.js";
5
5
  import { openBrowser } from "../utils/open-browser.js";
6
6
  /**
@@ -48,7 +48,7 @@ const defaultIo = {
48
48
  * the caller decides whether to exit.
49
49
  */
50
50
  export async function runLoginFlow(io = defaultIo) {
51
- const hubUrl = process.env.GITNEXUS_HUB_URL ?? resolveGitNexusConfig()?.hubUrl ?? DEFAULT_HUB_URL;
51
+ const hubUrl = resolveHubUrl(resolveGitNexusConfig()?.hubUrl);
52
52
  io.write(`\nSign in to GitNexus to use ${APP_NAME}.\n\n`);
53
53
  try {
54
54
  const deviceName = defaultDeviceName();
@@ -1 +1 @@
1
- {"version":3,"file":"require-login.js","sourceRoot":"","sources":["../../src/cli/require-login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvF,OAAO,EACN,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,GAClB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,eAAe,GACf,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD;;;;;;GAMG;AAEH,8EAA8E;AAC9E,MAAM,eAAe,GAAG,YAAY,CAAC;AAErC,MAAM,UAAU,mBAAmB,GAAY;IAC9C,MAAM,KAAK,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC;IAC1C,OAAO,KAAK,KAAK,GAAG,IAAI,KAAK,EAAE,WAAW,EAAE,KAAK,MAAM,CAAC;AAAA,CACxD;AAOD,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAM,GAAgB,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,EAA4B;IACrH,IAAI,mBAAmB,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAC1D,MAAM,MAAM,GAAG,qBAAqB,EAAE,CAAC;IACvC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAEnD,IAAI,CAAC;QACJ,MAAM,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;QACpE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,IAAI,KAAK,YAAY,mBAAmB,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACnE,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,EAAE,CAAC;QAC/C,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAC1C,CAAC;AAAA,CACD;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAc,EAAU;IAC/D,OAAO,GAAG,QAAQ,+CAA+C,MAAM,EAAE,CAAC;AAAA,CAC1E;AAUD,MAAM,SAAS,GAAgB;IAC9B,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;CAC3C,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAE,GAAgB,SAAS,EAAoB;IACjF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,qBAAqB,EAAE,EAAE,MAAM,IAAI,eAAe,CAAC;IAElG,EAAE,CAAC,KAAK,CAAC,gCAAgC,QAAQ,OAAO,CAAC,CAAC;IAE1D,IAAI,CAAC;QACJ,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;QACvC,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAAC;QAC5F,MAAM,IAAI,GAAG,KAAK,CAAC,uBAAuB,IAAI,KAAK,CAAC,eAAe,CAAC;QACpE,EAAE,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;QACxC,EAAE,CAAC,KAAK,CAAC,WAAW,IAAI,MAAM,CAAC,CAAC;QAChC,uEAAuE;QACvE,uEAAuE;QACvE,CAAC,EAAE,CAAC,IAAI,IAAI,WAAW,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/B,EAAE,CAAC,KAAK,CAAC,8CAA4C,CAAC,CAAC;QAEvD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxF,MAAM,MAAM,GAAG;YACd,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,iBAAiB,EAAE,WAAW;YAC9B,UAAU;SACV,CAAC;QACF,MAAM,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QACzF,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QACxC,EAAE,CAAC,KAAK,CAAC,oCAAoC,IAAI,MAAM,CAAC,CAAC;QACzD,OAAO,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,EAAE,CAAC,KAAK,CAAC,qBAAqB,OAAO,IAAI,CAAC,CAAC;QAC3C,EAAE,CAAC,KAAK,CAAC,OAAO,QAAQ,mEAAmE,CAAC,CAAC;QAC7F,EAAE,CAAC,KAAK,CAAC,wEAAsE,CAAC,CAAC;QACjF,OAAO,KAAK,CAAC;IACd,CAAC;AAAA,CACD;AAED,0EAA0E;AAC1E,MAAM,UAAU,0BAA0B,CAAC,MAAe,EAAU;IACnE,OAAO;QACN,GAAG,QAAQ,kBAAkB;QAC7B,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,EAAE;QACF,OAAO,QAAQ,0DAA0D;QACzE,gDAAgD;QAChD,EAAE;QACF,wBAAwB,kBAAkB,EAAE,EAAE;KAC9C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACb","sourcesContent":["import { APP_NAME, readAppEnv } from \"../config.ts\";\nimport { GitNexusClient, GitNexusClientError } from \"../extensions/gitnexus/client.ts\";\nimport {\n\tDEFAULT_HUB_URL,\n\tgitNexusConfigPath,\n\tresolveGitNexusConfig,\n\tsaveGitNexusConfig,\n} from \"../extensions/gitnexus/config.ts\";\nimport {\n\tdefaultDeviceName,\n\tdeviceFingerprint,\n\tpollDeviceAuth,\n\tstartDeviceAuth,\n} from \"../extensions/gitnexus/device-auth.ts\";\nimport { openBrowser } from \"../utils/open-browser.ts\";\n\n/**\n * Sign-in gate.\n *\n * Nexus is a GitNexus client, so it requires an account. Startup stops here\n * until the hub accepts its credential. Model and session initialization happen\n * only after this check, so a stale token cannot fall through to a local provider.\n */\n\n/** Escape hatch for CI and automated tests, which never have a credential. */\nconst SKIP_ENV_SUFFIX = \"SKIP_LOGIN\";\n\nexport function isLoginGateDisabled(): boolean {\n\tconst value = readAppEnv(SKIP_ENV_SUFFIX);\n\treturn value === \"1\" || value?.toLowerCase() === \"true\";\n}\n\nexport type LoginGateStatus =\n\t| { status: \"signed_in\" }\n\t| { status: \"sign_in_required\"; reason?: string }\n\t| { status: \"unavailable\"; reason: string };\n\n/** Validate configured GitNexus auth against the hub before runtime startup. */\nexport async function getLoginGateStatus(signal: AbortSignal = AbortSignal.timeout(15_000)): Promise<LoginGateStatus> {\n\tif (isLoginGateDisabled()) return { status: \"signed_in\" };\n\tconst config = resolveGitNexusConfig();\n\tif (!config) return { status: \"sign_in_required\" };\n\n\ttry {\n\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {}, signal);\n\t\treturn { status: \"signed_in\" };\n\t} catch (error) {\n\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\tif (error instanceof GitNexusClientError && error.kind === \"auth\") {\n\t\t\treturn { status: \"sign_in_required\", reason };\n\t\t}\n\t\treturn { status: \"unavailable\", reason };\n\t}\n}\n\nexport function loginUnavailableMessage(reason: string): string {\n\treturn `${APP_NAME}: GitNexus unavailable. Startup stopped.\\n\\n${reason}`;\n}\n\nexport interface LoginGateIo {\n\twrite(text: string): void;\n\t/** Injected in tests so no real waiting happens. */\n\tsleep?(ms: number): Promise<void>;\n\t/** Injected in tests so no browser is launched. */\n\topen?(url: string): void;\n}\n\nconst defaultIo: LoginGateIo = {\n\twrite: (text) => process.stdout.write(text),\n};\n\n/**\n * Run the browser device login and persist the token.\n *\n * Returns true once signed in. Any failure is reported and returns false, so\n * the caller decides whether to exit.\n */\nexport async function runLoginFlow(io: LoginGateIo = defaultIo): Promise<boolean> {\n\tconst hubUrl = process.env.GITNEXUS_HUB_URL ?? resolveGitNexusConfig()?.hubUrl ?? DEFAULT_HUB_URL;\n\n\tio.write(`\\nSign in to GitNexus to use ${APP_NAME}.\\n\\n`);\n\n\ttry {\n\t\tconst deviceName = defaultDeviceName();\n\t\tconst fingerprint = deviceFingerprint();\n\t\tconst start = await startDeviceAuth(hubUrl, { deviceName, deviceFingerprint: fingerprint });\n\t\tconst link = start.verificationUrlComplete ?? start.verificationUrl;\n\t\tio.write(` Code: ${start.userCode}\\n`);\n\t\tio.write(` Open: ${link}\\n\\n`);\n\t\t// Best effort: the launcher swallows its own failures, and the link is\n\t\t// printed above regardless, so a headless box still has a way through.\n\t\t(io.open ?? openBrowser)(link);\n\t\tio.write(\"Opening your browser… (ctrl+c to cancel)\\n\");\n\n\t\tconst result = await pollDeviceAuth(hubUrl, start, io.sleep ? { sleep: io.sleep } : {});\n\t\tconst config = {\n\t\t\thubUrl: result.hubUrl,\n\t\t\ttoken: result.token,\n\t\t\tdeviceFingerprint: fingerprint,\n\t\t\tdeviceName,\n\t\t};\n\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {}, AbortSignal.timeout(15_000));\n\t\tconst path = saveGitNexusConfig(config);\n\t\tio.write(`\\nSigned in. Credential saved to ${path}\\n\\n`);\n\t\treturn true;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tio.write(`\\nSign-in failed: ${message}\\n`);\n\t\tio.write(`Run ${APP_NAME} again to retry, or set GITNEXUS_TOKEN if you already have one.\\n`);\n\t\tio.write(\"Inside a session, /login → GitNexus account does the same thing.\\n\\n\");\n\t\treturn false;\n\t}\n}\n\n/** Message shown when a non-interactive run cannot prompt for sign-in. */\nexport function nonInteractiveLoginMessage(reason?: string): string {\n\treturn [\n\t\t`${APP_NAME}: not signed in.`,\n\t\t...(reason ? [``, reason] : []),\n\t\t\"\",\n\t\t`Run ${APP_NAME} with no arguments once to sign in through your browser,`,\n\t\t\"or set GITNEXUS_TOKEN for non-interactive use.\",\n\t\t\"\",\n\t\t`Credential location: ${gitNexusConfigPath()}`,\n\t].join(\"\\n\");\n}\n"]}
1
+ {"version":3,"file":"require-login.js","sourceRoot":"","sources":["../../src/cli/require-login.ts"],"names":[],"mappings":"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","sourcesContent":["import { APP_NAME, readAppEnv } from \"../config.ts\";\nimport { GitNexusClient, GitNexusClientError } from \"../extensions/gitnexus/client.ts\";\nimport {\n\tgitNexusConfigPath,\n\tresolveGitNexusConfig,\n\tresolveHubUrl,\n\tsaveGitNexusConfig,\n} from \"../extensions/gitnexus/config.ts\";\nimport {\n\tdefaultDeviceName,\n\tdeviceFingerprint,\n\tpollDeviceAuth,\n\tstartDeviceAuth,\n} from \"../extensions/gitnexus/device-auth.ts\";\nimport { openBrowser } from \"../utils/open-browser.ts\";\n\n/**\n * Sign-in gate.\n *\n * Nexus is a GitNexus client, so it requires an account. Startup stops here\n * until the hub accepts its credential. Model and session initialization happen\n * only after this check, so a stale token cannot fall through to a local provider.\n */\n\n/** Escape hatch for CI and automated tests, which never have a credential. */\nconst SKIP_ENV_SUFFIX = \"SKIP_LOGIN\";\n\nexport function isLoginGateDisabled(): boolean {\n\tconst value = readAppEnv(SKIP_ENV_SUFFIX);\n\treturn value === \"1\" || value?.toLowerCase() === \"true\";\n}\n\nexport type LoginGateStatus =\n\t| { status: \"signed_in\" }\n\t| { status: \"sign_in_required\"; reason?: string }\n\t| { status: \"unavailable\"; reason: string };\n\n/** Validate configured GitNexus auth against the hub before runtime startup. */\nexport async function getLoginGateStatus(signal: AbortSignal = AbortSignal.timeout(15_000)): Promise<LoginGateStatus> {\n\tif (isLoginGateDisabled()) return { status: \"signed_in\" };\n\tconst config = resolveGitNexusConfig();\n\tif (!config) return { status: \"sign_in_required\" };\n\n\ttry {\n\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {}, signal);\n\t\treturn { status: \"signed_in\" };\n\t} catch (error) {\n\t\tconst reason = error instanceof Error ? error.message : String(error);\n\t\tif (error instanceof GitNexusClientError && error.kind === \"auth\") {\n\t\t\treturn { status: \"sign_in_required\", reason };\n\t\t}\n\t\treturn { status: \"unavailable\", reason };\n\t}\n}\n\nexport function loginUnavailableMessage(reason: string): string {\n\treturn `${APP_NAME}: GitNexus unavailable. Startup stopped.\\n\\n${reason}`;\n}\n\nexport interface LoginGateIo {\n\twrite(text: string): void;\n\t/** Injected in tests so no real waiting happens. */\n\tsleep?(ms: number): Promise<void>;\n\t/** Injected in tests so no browser is launched. */\n\topen?(url: string): void;\n}\n\nconst defaultIo: LoginGateIo = {\n\twrite: (text) => process.stdout.write(text),\n};\n\n/**\n * Run the browser device login and persist the token.\n *\n * Returns true once signed in. Any failure is reported and returns false, so\n * the caller decides whether to exit.\n */\nexport async function runLoginFlow(io: LoginGateIo = defaultIo): Promise<boolean> {\n\tconst hubUrl = resolveHubUrl(resolveGitNexusConfig()?.hubUrl);\n\n\tio.write(`\\nSign in to GitNexus to use ${APP_NAME}.\\n\\n`);\n\n\ttry {\n\t\tconst deviceName = defaultDeviceName();\n\t\tconst fingerprint = deviceFingerprint();\n\t\tconst start = await startDeviceAuth(hubUrl, { deviceName, deviceFingerprint: fingerprint });\n\t\tconst link = start.verificationUrlComplete ?? start.verificationUrl;\n\t\tio.write(` Code: ${start.userCode}\\n`);\n\t\tio.write(` Open: ${link}\\n\\n`);\n\t\t// Best effort: the launcher swallows its own failures, and the link is\n\t\t// printed above regardless, so a headless box still has a way through.\n\t\t(io.open ?? openBrowser)(link);\n\t\tio.write(\"Opening your browser… (ctrl+c to cancel)\\n\");\n\n\t\tconst result = await pollDeviceAuth(hubUrl, start, io.sleep ? { sleep: io.sleep } : {});\n\t\tconst config = {\n\t\t\thubUrl: result.hubUrl,\n\t\t\ttoken: result.token,\n\t\t\tdeviceFingerprint: fingerprint,\n\t\t\tdeviceName,\n\t\t};\n\t\tawait new GitNexusClient(config).callTool(\"list_repos\", {}, AbortSignal.timeout(15_000));\n\t\tconst path = saveGitNexusConfig(config);\n\t\tio.write(`\\nSigned in. Credential saved to ${path}\\n\\n`);\n\t\treturn true;\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tio.write(`\\nSign-in failed: ${message}\\n`);\n\t\tio.write(`Run ${APP_NAME} again to retry, or set GITNEXUS_TOKEN if you already have one.\\n`);\n\t\tio.write(\"Inside a session, /login → GitNexus account does the same thing.\\n\\n\");\n\t\treturn false;\n\t}\n}\n\n/** Message shown when a non-interactive run cannot prompt for sign-in. */\nexport function nonInteractiveLoginMessage(reason?: string): string {\n\treturn [\n\t\t`${APP_NAME}: not signed in.`,\n\t\t...(reason ? [``, reason] : []),\n\t\t\"\",\n\t\t`Run ${APP_NAME} with no arguments once to sign in through your browser,`,\n\t\t\"or set GITNEXUS_TOKEN for non-interactive use.\",\n\t\t\"\",\n\t\t`Credential location: ${gitNexusConfigPath()}`,\n\t].join(\"\\n\");\n}\n"]}