@akon-labs/nexus-cli 0.1.1 → 0.1.3

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 (57) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -2
  3. package/dist/cli/require-login.d.ts.map +1 -1
  4. package/dist/cli/require-login.js +1 -1
  5. package/dist/cli/require-login.js.map +1 -1
  6. package/dist/cli.js +404 -133
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +2 -2
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/auth-guidance.d.ts.map +1 -1
  11. package/dist/core/auth-guidance.js +3 -2
  12. package/dist/core/auth-guidance.js.map +1 -1
  13. package/dist/core/models-dev-catalog.d.ts.map +1 -1
  14. package/dist/core/models-dev-catalog.js +11 -7
  15. package/dist/core/models-dev-catalog.js.map +1 -1
  16. package/dist/core/sdk.d.ts +1 -1
  17. package/dist/core/sdk.d.ts.map +1 -1
  18. package/dist/core/sdk.js +1 -1
  19. package/dist/core/sdk.js.map +1 -1
  20. package/dist/core/tools/websearch.d.ts.map +1 -1
  21. package/dist/core/tools/websearch.js +52 -37
  22. package/dist/core/tools/websearch.js.map +1 -1
  23. package/dist/extensions/gitnexus/device-auth.d.ts.map +1 -1
  24. package/dist/extensions/gitnexus/device-auth.js +2 -1
  25. package/dist/extensions/gitnexus/device-auth.js.map +1 -1
  26. package/dist/extensions/gitnexus/gateway.d.ts +2 -0
  27. package/dist/extensions/gitnexus/gateway.d.ts.map +1 -1
  28. package/dist/extensions/gitnexus/gateway.js +31 -5
  29. package/dist/extensions/gitnexus/gateway.js.map +1 -1
  30. package/dist/extensions/gitnexus/index.d.ts.map +1 -1
  31. package/dist/extensions/gitnexus/index.js +31 -7
  32. package/dist/extensions/gitnexus/index.js.map +1 -1
  33. package/dist/extensions/index.d.ts.map +1 -1
  34. package/dist/extensions/index.js +2 -0
  35. package/dist/extensions/index.js.map +1 -1
  36. package/dist/extensions/web-access.d.ts +14 -0
  37. package/dist/extensions/web-access.d.ts.map +1 -0
  38. package/dist/extensions/web-access.js +110 -0
  39. package/dist/extensions/web-access.js.map +1 -0
  40. package/dist/extensions/web-search-concurrency.d.ts +14 -0
  41. package/dist/extensions/web-search-concurrency.d.ts.map +1 -0
  42. package/dist/extensions/web-search-concurrency.js +93 -0
  43. package/dist/extensions/web-search-concurrency.js.map +1 -0
  44. package/dist/index.d.ts +1 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +419 -146
  47. package/dist/index.js.map +1 -1
  48. package/dist/main.d.ts.map +1 -1
  49. package/dist/main.js +10 -1
  50. package/dist/main.js.map +1 -1
  51. package/dist/modes/interactive/components/command-palette.d.ts.map +1 -1
  52. package/dist/modes/interactive/components/command-palette.js +2 -2
  53. package/dist/modes/interactive/components/command-palette.js.map +1 -1
  54. package/dist/rpc-entry.js +404 -133
  55. package/docs/settings.md +1 -1
  56. package/npm-shrinkwrap.json +3 -3
  57. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.1.3] - 2026-08-20
6
+
5
7
  ### Breaking Changes
6
8
 
7
9
  - Changed JSON and RPC `message_update` events to emit only `assistantMessageEvent` deltas, removing the cumulative `message` and `assistantMessageEvent.partial` fields that caused quadratic output growth. Clients that need partial messages must assemble deltas between `message_start` and `message_end`; the latter remains authoritative ([#7290](https://github.com/earendil-works/pi/issues/7290)).
@@ -72,6 +74,8 @@
72
74
 
73
75
  ### Added
74
76
 
77
+ - Added the hosted Auto model and user-visible Nexus Credit balance without exposing provider names or dollar costs.
78
+ - Added default web access through the pinned `pi-web-access` package, with quota-aware provider fallback ending in keyless DuckDuckGo, optional self-hosted SearXNG, raw CLI results, the existing `websearch` tool name preserved, and concurrent searches capped at three.
75
79
  - Added built-in Baseten provider support with `BASETEN_API_KEY` authentication and `zai-org/GLM-5.2` as the default model.
76
80
  - Added `CredentialSynchronizationError` for credential changes that commit successfully but fail to synchronize local model state.
77
81
  - Added chainable `pi.registerMarkdownTransformer()` hooks for display-only transformation of user and assistant Markdown.
@@ -86,11 +90,19 @@
86
90
 
87
91
  ### Changed
88
92
 
93
+ - Forwarded stable, privacy-preserving session affinity through the Hub for provider prompt caching, and advertised hosted Auto as text-only to match the beta gateway.
89
94
  - Simplified interactive startup to show the Nexus wordmark and resource summary without onboarding copy, and removed session metrics duplicated by the fullscreen side rail from the footer.
90
95
  - Made model selection explicit: Nexus never falls back to another provider automatically, and `/byok` now configures a cloud provider/API key or an Ollama model.
91
96
 
92
97
  ### Fixed
93
98
 
99
+ - Fixed device reauthentication ignoring a custom Hub URL already stored in the Nexus credential.
100
+ - Fixed pasted-token login failures bypassing the user-facing error notification and stale credentials being described as a valid session.
101
+ - Fixed web search timeouts ending after response headers instead of covering a stalled response body.
102
+ - Fixed concurrent remote model-catalog refreshes sharing responses across different catalog URLs, and non-positive device-login backoff intervals.
103
+ - Fixed empty `NEXUS_PACKAGE_DIR` values suppressing the legacy package-dir fallback and corrected command-palette height budgeting.
104
+ - Fixed stale package-scope references in installation guidance, runtime fallback metadata, and the eval workspace.
105
+ - Fixed help and model-list commands downloading the default web extension, and made Cloudflare compatibility coverage resilient to live catalog rotation.
94
106
  - Fixed device login saving tokens without the required device fingerprint, causing successful browser approvals to fail with 401/403. Nexus now validates Hub access before saving credentials or creating model runtime, and uses `/login` as the user-facing sign-in command.
95
107
  - Fixed bundled GitNexus skills resolving to nonexistent `dist/skills`, which displayed a startup skill-conflict warning.
96
108
  - Removed remaining user-facing upstream Pi branding from npm startup, CLI help, update help, auth help, and errors.
package/README.md CHANGED
@@ -37,7 +37,7 @@ Built on a vendored fork of [pi](https://pi.dev) (`earendil-works/pi`, MIT). Pac
37
37
  ## Quick Start
38
38
 
39
39
  ```bash
40
- npm install -g --ignore-scripts @akonlabs/nexus-cli
40
+ npm install -g --ignore-scripts @akon-labs/nexus-cli
41
41
  ```
42
42
 
43
43
  `--ignore-scripts` disables dependency lifecycle scripts during install. Nexus does not require install scripts for normal npm installs.
@@ -674,4 +674,3 @@ MIT
674
674
  - [@earendil-works/pi-ai](https://www.npmjs.com/package/@earendil-works/pi-ai): Core LLM toolkit
675
675
  - [@earendil-works/pi-agent-core](https://www.npmjs.com/package/@earendil-works/pi-agent-core): Agent framework
676
676
  - [@earendil-works/pi-tui](https://www.npmjs.com/package/@earendil-works/pi-tui): Terminal UI components
677
-
@@ -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 ?? 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\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"]}
@@ -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 ?? DEFAULT_HUB_URL;
51
+ const hubUrl = process.env.GITNEXUS_HUB_URL ?? resolveGitNexusConfig()?.hubUrl ?? DEFAULT_HUB_URL;
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":"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","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 ?? 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\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"]}