dexto 1.5.6 → 1.5.7

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 (206) hide show
  1. package/README.md +13 -0
  2. package/dist/agents/coding-agent/coding-agent.yml +15 -0
  3. package/dist/agents/coding-agent/skills/code-review.md +46 -0
  4. package/dist/agents/explore-agent/explore-agent.yml +2 -0
  5. package/dist/analytics/events.d.ts +1 -1
  6. package/dist/analytics/events.d.ts.map +1 -1
  7. package/dist/api/server-hono.d.ts.map +1 -1
  8. package/dist/api/server-hono.js +55 -10
  9. package/dist/cli/assets/dexto-logo.svg +31 -0
  10. package/dist/cli/auth/api-client.d.ts +49 -0
  11. package/dist/cli/auth/api-client.d.ts.map +1 -0
  12. package/dist/cli/auth/api-client.js +127 -0
  13. package/dist/cli/auth/constants.d.ts +23 -0
  14. package/dist/cli/auth/constants.d.ts.map +1 -0
  15. package/dist/cli/auth/constants.js +24 -0
  16. package/dist/cli/auth/index.d.ts +5 -0
  17. package/dist/cli/auth/index.d.ts.map +1 -0
  18. package/dist/cli/auth/index.js +6 -0
  19. package/dist/cli/auth/oauth.d.ts +26 -0
  20. package/dist/cli/auth/oauth.d.ts.map +1 -0
  21. package/dist/cli/auth/oauth.js +327 -0
  22. package/dist/cli/auth/service.d.ts +20 -0
  23. package/dist/cli/auth/service.d.ts.map +1 -0
  24. package/dist/cli/auth/service.js +147 -0
  25. package/dist/cli/commands/auth/index.d.ts +4 -0
  26. package/dist/cli/commands/auth/index.d.ts.map +1 -0
  27. package/dist/cli/commands/auth/index.js +4 -0
  28. package/dist/cli/commands/auth/login.d.ts +9 -0
  29. package/dist/cli/commands/auth/login.d.ts.map +1 -0
  30. package/dist/cli/commands/auth/login.js +255 -0
  31. package/dist/cli/commands/auth/logout.d.ts +5 -0
  32. package/dist/cli/commands/auth/logout.d.ts.map +1 -0
  33. package/dist/cli/commands/auth/logout.js +51 -0
  34. package/dist/cli/commands/auth/status.d.ts +2 -0
  35. package/dist/cli/commands/auth/status.d.ts.map +1 -0
  36. package/dist/cli/commands/auth/status.js +22 -0
  37. package/dist/cli/commands/billing/index.d.ts +2 -0
  38. package/dist/cli/commands/billing/index.d.ts.map +1 -0
  39. package/dist/cli/commands/billing/index.js +2 -0
  40. package/dist/cli/commands/billing/status.d.ts +6 -0
  41. package/dist/cli/commands/billing/status.d.ts.map +1 -0
  42. package/dist/cli/commands/billing/status.js +60 -0
  43. package/dist/cli/commands/index.d.ts +3 -0
  44. package/dist/cli/commands/index.d.ts.map +1 -1
  45. package/dist/cli/commands/index.js +8 -0
  46. package/dist/cli/commands/interactive-commands/auth/index.d.ts +12 -0
  47. package/dist/cli/commands/interactive-commands/auth/index.d.ts.map +1 -0
  48. package/dist/cli/commands/interactive-commands/auth/index.js +20 -0
  49. package/dist/cli/commands/interactive-commands/command-parser.d.ts +5 -0
  50. package/dist/cli/commands/interactive-commands/command-parser.d.ts.map +1 -1
  51. package/dist/cli/commands/interactive-commands/command-parser.js +6 -0
  52. package/dist/cli/commands/interactive-commands/commands.d.ts +1 -0
  53. package/dist/cli/commands/interactive-commands/commands.d.ts.map +1 -1
  54. package/dist/cli/commands/interactive-commands/commands.js +8 -0
  55. package/dist/cli/commands/interactive-commands/mcp/index.d.ts +2 -2
  56. package/dist/cli/commands/interactive-commands/mcp/index.d.ts.map +1 -1
  57. package/dist/cli/commands/interactive-commands/mcp/index.js +4 -7
  58. package/dist/cli/commands/interactive-commands/model/index.d.ts +2 -2
  59. package/dist/cli/commands/interactive-commands/model/index.d.ts.map +1 -1
  60. package/dist/cli/commands/interactive-commands/model/index.js +4 -7
  61. package/dist/cli/commands/interactive-commands/plugin/index.d.ts +13 -0
  62. package/dist/cli/commands/interactive-commands/plugin/index.d.ts.map +1 -0
  63. package/dist/cli/commands/interactive-commands/plugin/index.js +18 -0
  64. package/dist/cli/commands/interactive-commands/prompt-commands.d.ts +3 -1
  65. package/dist/cli/commands/interactive-commands/prompt-commands.d.ts.map +1 -1
  66. package/dist/cli/commands/interactive-commands/prompt-commands.js +72 -36
  67. package/dist/cli/commands/plugin.d.ts +161 -0
  68. package/dist/cli/commands/plugin.d.ts.map +1 -0
  69. package/dist/cli/commands/plugin.js +376 -0
  70. package/dist/cli/commands/setup.d.ts +9 -9
  71. package/dist/cli/commands/setup.d.ts.map +1 -1
  72. package/dist/cli/commands/setup.js +228 -19
  73. package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
  74. package/dist/cli/ink-cli/InkCLIRefactored.js +2 -1
  75. package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts +1 -1
  76. package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts.map +1 -1
  77. package/dist/cli/ink-cli/components/ApprovalPrompt.js +80 -12
  78. package/dist/cli/ink-cli/components/Footer.d.ts +2 -1
  79. package/dist/cli/ink-cli/components/Footer.d.ts.map +1 -1
  80. package/dist/cli/ink-cli/components/Footer.js +6 -2
  81. package/dist/cli/ink-cli/components/SlashCommandAutocomplete.d.ts.map +1 -1
  82. package/dist/cli/ink-cli/components/SlashCommandAutocomplete.js +15 -7
  83. package/dist/cli/ink-cli/components/StatusBar.d.ts +5 -1
  84. package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
  85. package/dist/cli/ink-cli/components/StatusBar.js +2 -2
  86. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
  87. package/dist/cli/ink-cli/components/chat/MessageItem.js +14 -1
  88. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  89. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +4 -1
  90. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  91. package/dist/cli/ink-cli/components/modes/StaticCLI.js +4 -1
  92. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.d.ts +1 -0
  93. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.d.ts.map +1 -1
  94. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +30 -19
  95. package/dist/cli/ink-cli/components/overlays/MarketplaceAddPrompt.d.ts +20 -0
  96. package/dist/cli/ink-cli/components/overlays/MarketplaceAddPrompt.d.ts.map +1 -0
  97. package/dist/cli/ink-cli/components/overlays/MarketplaceAddPrompt.js +81 -0
  98. package/dist/cli/ink-cli/components/overlays/MarketplaceBrowser.d.ts +31 -0
  99. package/dist/cli/ink-cli/components/overlays/MarketplaceBrowser.d.ts.map +1 -0
  100. package/dist/cli/ink-cli/components/overlays/MarketplaceBrowser.js +297 -0
  101. package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.d.ts.map +1 -1
  102. package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.js +7 -1
  103. package/dist/cli/ink-cli/components/overlays/McpServerActions.d.ts +1 -1
  104. package/dist/cli/ink-cli/components/overlays/McpServerActions.d.ts.map +1 -1
  105. package/dist/cli/ink-cli/components/overlays/McpServerActions.js +9 -0
  106. package/dist/cli/ink-cli/components/overlays/McpServerList.d.ts.map +1 -1
  107. package/dist/cli/ink-cli/components/overlays/McpServerList.js +9 -2
  108. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
  109. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +15 -2
  110. package/dist/cli/ink-cli/components/overlays/PluginActions.d.ts +27 -0
  111. package/dist/cli/ink-cli/components/overlays/PluginActions.d.ts.map +1 -0
  112. package/dist/cli/ink-cli/components/overlays/PluginActions.js +66 -0
  113. package/dist/cli/ink-cli/components/overlays/PluginList.d.ts +21 -0
  114. package/dist/cli/ink-cli/components/overlays/PluginList.d.ts.map +1 -0
  115. package/dist/cli/ink-cli/components/overlays/PluginList.js +70 -0
  116. package/dist/cli/ink-cli/components/overlays/PluginManager.d.ts +21 -0
  117. package/dist/cli/ink-cli/components/overlays/PluginManager.d.ts.map +1 -0
  118. package/dist/cli/ink-cli/components/overlays/PluginManager.js +63 -0
  119. package/dist/cli/ink-cli/components/overlays/PromptList.d.ts.map +1 -1
  120. package/dist/cli/ink-cli/components/overlays/PromptList.js +4 -1
  121. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts +2 -1
  122. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts.map +1 -1
  123. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.js +61 -2
  124. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts +4 -2
  125. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts.map +1 -1
  126. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.js +4 -4
  127. package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
  128. package/dist/cli/ink-cli/containers/InputContainer.js +30 -3
  129. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  130. package/dist/cli/ink-cli/containers/OverlayContainer.js +256 -11
  131. package/dist/cli/ink-cli/hooks/index.d.ts +1 -0
  132. package/dist/cli/ink-cli/hooks/index.d.ts.map +1 -1
  133. package/dist/cli/ink-cli/hooks/index.js +1 -0
  134. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  135. package/dist/cli/ink-cli/hooks/useCLIState.js +2 -0
  136. package/dist/cli/ink-cli/hooks/useGitBranch.d.ts +13 -0
  137. package/dist/cli/ink-cli/hooks/useGitBranch.d.ts.map +1 -0
  138. package/dist/cli/ink-cli/hooks/useGitBranch.js +35 -0
  139. package/dist/cli/ink-cli/hooks/useInputOrchestrator.d.ts.map +1 -1
  140. package/dist/cli/ink-cli/hooks/useInputOrchestrator.js +47 -8
  141. package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
  142. package/dist/cli/ink-cli/services/processStream.js +42 -10
  143. package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
  144. package/dist/cli/ink-cli/state/initialState.js +2 -0
  145. package/dist/cli/ink-cli/state/types.d.ts +3 -1
  146. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  147. package/dist/cli/ink-cli/utils/commandOverlays.d.ts.map +1 -1
  148. package/dist/cli/ink-cli/utils/commandOverlays.js +1 -0
  149. package/dist/cli/ink-cli/utils/messageFormatting.d.ts +14 -1
  150. package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
  151. package/dist/cli/ink-cli/utils/messageFormatting.js +66 -6
  152. package/dist/cli/ink-cli/utils/toolUtils.d.ts +11 -0
  153. package/dist/cli/ink-cli/utils/toolUtils.d.ts.map +1 -1
  154. package/dist/cli/ink-cli/utils/toolUtils.js +17 -0
  155. package/dist/cli/mcp/index.d.ts +8 -0
  156. package/dist/cli/mcp/index.d.ts.map +1 -0
  157. package/dist/cli/mcp/index.js +7 -0
  158. package/dist/cli/mcp/oauth-factory.d.ts +6 -0
  159. package/dist/cli/mcp/oauth-factory.d.ts.map +1 -0
  160. package/dist/cli/mcp/oauth-factory.js +25 -0
  161. package/dist/cli/mcp/oauth-provider.d.ts +10 -0
  162. package/dist/cli/mcp/oauth-provider.d.ts.map +1 -0
  163. package/dist/cli/mcp/oauth-provider.js +77 -0
  164. package/dist/cli/mcp/oauth-redirect.d.ts +3 -0
  165. package/dist/cli/mcp/oauth-redirect.d.ts.map +1 -0
  166. package/dist/cli/mcp/oauth-redirect.js +4 -0
  167. package/dist/cli/mcp/oauth-server.d.ts +2 -0
  168. package/dist/cli/mcp/oauth-server.d.ts.map +1 -0
  169. package/dist/cli/mcp/oauth-server.js +70 -0
  170. package/dist/cli/mcp/oauth-store.d.ts +10 -0
  171. package/dist/cli/mcp/oauth-store.d.ts.map +1 -0
  172. package/dist/cli/mcp/oauth-store.js +27 -0
  173. package/dist/cli/mcp/oauth-ui.d.ts +2 -0
  174. package/dist/cli/mcp/oauth-ui.d.ts.map +1 -0
  175. package/dist/cli/mcp/oauth-ui.js +12 -0
  176. package/dist/cli/mcp/oauth-utils.d.ts +2 -0
  177. package/dist/cli/mcp/oauth-utils.d.ts.map +1 -0
  178. package/dist/cli/mcp/oauth-utils.js +17 -0
  179. package/dist/cli/utils/api-key-verification.d.ts.map +1 -1
  180. package/dist/cli/utils/api-key-verification.js +36 -0
  181. package/dist/cli/utils/config-validation.d.ts +3 -1
  182. package/dist/cli/utils/config-validation.d.ts.map +1 -1
  183. package/dist/cli/utils/config-validation.js +42 -19
  184. package/dist/cli/utils/dexto-auth-check.d.ts +53 -0
  185. package/dist/cli/utils/dexto-auth-check.d.ts.map +1 -0
  186. package/dist/cli/utils/dexto-auth-check.js +104 -0
  187. package/dist/cli/utils/dexto-setup.d.ts +8 -0
  188. package/dist/cli/utils/dexto-setup.d.ts.map +1 -0
  189. package/dist/cli/utils/dexto-setup.js +17 -0
  190. package/dist/cli/utils/options.d.ts.map +1 -1
  191. package/dist/cli/utils/options.js +5 -1
  192. package/dist/cli/utils/provider-setup.d.ts +4 -0
  193. package/dist/cli/utils/provider-setup.d.ts.map +1 -1
  194. package/dist/cli/utils/provider-setup.js +20 -0
  195. package/dist/config/cli-overrides.d.ts +17 -8
  196. package/dist/config/cli-overrides.d.ts.map +1 -1
  197. package/dist/config/cli-overrides.js +36 -22
  198. package/dist/config/effective-llm.d.ts +123 -0
  199. package/dist/config/effective-llm.d.ts.map +1 -0
  200. package/dist/config/effective-llm.js +171 -0
  201. package/dist/index.js +395 -99
  202. package/dist/webui/assets/index-C9JXwpvo.css +1 -0
  203. package/dist/webui/assets/{index-DVQWNLpT.js → index-Dl3mj53P.js} +217 -217
  204. package/dist/webui/index.html +2 -2
  205. package/package.json +9 -8
  206. package/dist/webui/assets/index-BglIVTSG.css +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/cli/auth/oauth.ts"],"names":[],"mappings":"AA4CA,UAAU,WAAW;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,IAAI,CAAC,EACC;QACI,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC7B,GACD,SAAS,CAAC;CACnB;AAiQD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CA2CjF;AAED;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,WAKlC,CAAC"}
@@ -0,0 +1,327 @@
1
+ // packages/cli/src/cli/auth/oauth.ts
2
+ // OAuth flow implementation with local callback server
3
+ //
4
+ // TODO: Add CSRF protection via Device Code Flow (RFC 8628)
5
+ // Current localhost callback pattern lacks CSRF protection. We attempted to add
6
+ // a `state` parameter but Supabase uses `state` internally for its own OAuth CSRF
7
+ // with Google, causing conflicts. The proper solution is Device Code Flow:
8
+ // 1. CLI requests device code from server
9
+ // 2. User visits URL and enters code
10
+ // 3. CLI polls for authorization completion
11
+ // This is what Supabase CLI, GitHub CLI, and others use for secure CLI auth.
12
+ import * as http from 'http';
13
+ import * as url from 'url';
14
+ import * as querystring from 'querystring';
15
+ import chalk from 'chalk';
16
+ import * as p from '@clack/prompts';
17
+ import { logger } from '@dexto/core';
18
+ import { readFileSync } from 'node:fs';
19
+ import { SUPABASE_URL, SUPABASE_ANON_KEY } from './constants.js';
20
+ // Track active OAuth callback servers by port for cleanup
21
+ const oauthStateStore = new Map();
22
+ const DEXTO_LOGO_DATA_URL = (() => {
23
+ try {
24
+ const svg = readFileSync(new URL('../assets/dexto-logo.svg', import.meta.url), 'utf-8');
25
+ return `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`;
26
+ }
27
+ catch (error) {
28
+ logger.warn(`Failed to load Dexto logo asset for OAuth screen: ${error instanceof Error ? error.message : String(error)}`);
29
+ return '';
30
+ }
31
+ })();
32
+ const LOGO_FALLBACK_TEXT = DEXTO_LOGO_DATA_URL ? '' : 'D';
33
+ const LOGO_HTML = `<div class="logo">${LOGO_FALLBACK_TEXT}</div>`;
34
+ // Pre-generate HTML strings with logo
35
+ const ERROR_HTML = `${LOGO_HTML}<div class="error-icon">✕</div><h1 class="error-title">Authentication Failed</h1><p>You can close this window and try again in your terminal.</p>`;
36
+ const SUCCESS_HTML = `${LOGO_HTML}<div class="success-icon">✓</div><h1 class="success-title">Login Successful!</h1><p>Welcome to Dexto! You can close this window and return to your terminal.</p>`;
37
+ const NO_DATA_HTML = `${LOGO_HTML}<div class="error-icon">✕</div><h1 class="error-title">No Authentication Data</h1><p>Please try the login process again in your terminal.</p>`;
38
+ // Fixed port for OAuth callback - must match Supabase redirect URL config
39
+ const OAUTH_CALLBACK_PORT = 48102;
40
+ /**
41
+ * Check if fixed port is available, throw if not
42
+ */
43
+ function ensurePortAvailable(port) {
44
+ return new Promise((resolve, reject) => {
45
+ const server = http.createServer();
46
+ server.listen(port, () => {
47
+ server.close(() => resolve(port));
48
+ });
49
+ server.on('error', (err) => {
50
+ if (err.code === 'EADDRINUSE') {
51
+ reject(new Error(`Port ${port} is already in use. Please close the application using it and try again.`));
52
+ }
53
+ else {
54
+ reject(err);
55
+ }
56
+ });
57
+ });
58
+ }
59
+ /**
60
+ * Start local HTTP server to receive OAuth callback
61
+ */
62
+ function startCallbackServer(port, config) {
63
+ return new Promise((resolve, reject) => {
64
+ const server = http.createServer(async (req, res) => {
65
+ try {
66
+ if (!req.url) {
67
+ res.writeHead(400);
68
+ res.end('Bad Request');
69
+ return;
70
+ }
71
+ const parsedUrl = url.parse(req.url, true);
72
+ if (req.method === 'GET') {
73
+ res.writeHead(200, { 'Content-Type': 'text/html' });
74
+ res.end(`
75
+ <html>
76
+ <head>
77
+ <meta charset="utf-8" />
78
+ <title>Dexto Authentication</title>
79
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
80
+ <link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet">
81
+ <style>
82
+ * { margin: 0; padding: 0; box-sizing: border-box; }
83
+ body {
84
+ font-family: 'Geist', -apple-system, BlinkMacSystemFont, sans-serif;
85
+ background: linear-gradient(135deg, #0f0f0f 0%, #1a1a1a 100%);
86
+ color: #fafafa;
87
+ min-height: 100vh;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ padding: 20px;
92
+ }
93
+ .container {
94
+ background: #141414;
95
+ border: 1px solid #262626;
96
+ padding: 48px;
97
+ border-radius: 12px;
98
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
99
+ max-width: 450px;
100
+ width: 100%;
101
+ text-align: center;
102
+ }
103
+ .logo {
104
+ width: 240px;
105
+ height: 70px;
106
+ margin: 0 auto 32px;
107
+ background-image: ${DEXTO_LOGO_DATA_URL ? `url("${DEXTO_LOGO_DATA_URL}")` : 'none'};
108
+ background-repeat: no-repeat;
109
+ background-position: center;
110
+ background-size: contain;
111
+ display: flex;
112
+ align-items: center;
113
+ justify-content: center;
114
+ font-size: 28px;
115
+ font-weight: 600;
116
+ color: #4ec9b0;
117
+ }
118
+ .spinner {
119
+ font-size: 48px;
120
+ margin-bottom: 24px;
121
+ animation: pulse 2s infinite;
122
+ }
123
+ @keyframes pulse {
124
+ 0%, 100% { opacity: 1; }
125
+ 50% { opacity: 0.6; }
126
+ }
127
+ h1 { font-size: 24px; font-weight: 600; margin-bottom: 12px; color: #fafafa; }
128
+ p { color: #a1a1aa; font-size: 16px; line-height: 1.5; }
129
+ .success-icon { color: #22c55e; font-size: 64px; margin-bottom: 24px; }
130
+ .error-icon { color: #dc2626; font-size: 64px; margin-bottom: 24px; }
131
+ .success-title { color: #22c55e; }
132
+ .error-title { color: #dc2626; }
133
+ </style>
134
+ </head>
135
+ <body>
136
+ <div class="container">
137
+ ${LOGO_HTML}
138
+ <div class="spinner">◐</div>
139
+ <h1>Processing Authentication...</h1>
140
+ <p>Please wait while we complete your Dexto login.</p>
141
+ </div>
142
+ <script>
143
+ const hashParams = new URLSearchParams(window.location.hash.substring(1));
144
+ const urlParams = new URLSearchParams(window.location.search);
145
+ const accessToken = hashParams.get('access_token') || urlParams.get('access_token');
146
+ const refreshToken = hashParams.get('refresh_token') || urlParams.get('refresh_token');
147
+ const expiresIn = hashParams.get('expires_in') || urlParams.get('expires_in');
148
+ const error = hashParams.get('error') || urlParams.get('error');
149
+
150
+ if (window.location.hash || window.location.search) {
151
+ window.history.replaceState(null, document.title, window.location.pathname);
152
+ }
153
+
154
+ if (error) {
155
+ fetch('/callback', {
156
+ method: 'POST',
157
+ headers: { 'Content-Type': 'application/json' },
158
+ body: JSON.stringify({ error: error })
159
+ }).then(() => {
160
+ document.querySelector('.container').innerHTML = ${JSON.stringify(ERROR_HTML)};
161
+ });
162
+ } else if (accessToken) {
163
+ fetch('/callback', {
164
+ method: 'POST',
165
+ headers: { 'Content-Type': 'application/json' },
166
+ body: JSON.stringify({
167
+ access_token: accessToken,
168
+ refresh_token: refreshToken,
169
+ expires_in: expiresIn ? parseInt(expiresIn) : undefined
170
+ })
171
+ }).then(() => {
172
+ document.querySelector('.container').innerHTML = ${JSON.stringify(SUCCESS_HTML)};
173
+ });
174
+ } else {
175
+ document.querySelector('.container').innerHTML = ${JSON.stringify(NO_DATA_HTML)};
176
+ }
177
+ </script>
178
+ </body>
179
+ </html>
180
+ `);
181
+ }
182
+ else if (req.method === 'POST' && parsedUrl.pathname === '/callback') {
183
+ let body = '';
184
+ const MAX_BODY_SIZE = 10 * 1024; // 10KB - plenty for OAuth tokens
185
+ req.on('data', (chunk) => {
186
+ if (body.length + chunk.length > MAX_BODY_SIZE) {
187
+ req.destroy();
188
+ res.writeHead(413);
189
+ res.end('Request too large');
190
+ return;
191
+ }
192
+ body += chunk.toString();
193
+ });
194
+ req.on('end', async () => {
195
+ try {
196
+ const data = JSON.parse(body);
197
+ if (data.error) {
198
+ res.writeHead(200);
199
+ res.end('OK');
200
+ server.close();
201
+ reject(new Error(`OAuth error: ${data.error}`));
202
+ return;
203
+ }
204
+ if (data.access_token) {
205
+ const userResponse = await fetch(`${config.authUrl}/auth/v1/user`, {
206
+ headers: {
207
+ Authorization: `Bearer ${data.access_token}`,
208
+ apikey: config.clientId,
209
+ },
210
+ });
211
+ const userData = userResponse.ok ? await userResponse.json() : null;
212
+ const result = {
213
+ accessToken: data.access_token,
214
+ refreshToken: data.refresh_token,
215
+ expiresIn: data.expires_in,
216
+ user: userData
217
+ ? {
218
+ id: userData.id,
219
+ email: userData.email,
220
+ name: userData.user_metadata?.full_name ||
221
+ userData.email,
222
+ }
223
+ : undefined,
224
+ };
225
+ res.writeHead(200);
226
+ res.end('OK');
227
+ server.close();
228
+ resolve(result);
229
+ }
230
+ else {
231
+ res.writeHead(400);
232
+ res.end('Missing tokens');
233
+ server.close();
234
+ reject(new Error('No access token received'));
235
+ }
236
+ }
237
+ catch (_err) {
238
+ res.writeHead(400);
239
+ res.end('Invalid data');
240
+ server.close();
241
+ reject(new Error('Invalid callback data'));
242
+ }
243
+ });
244
+ }
245
+ else {
246
+ res.writeHead(404);
247
+ res.end('Not Found');
248
+ }
249
+ }
250
+ catch (error) {
251
+ logger.error(`Callback server error: ${error}`);
252
+ res.writeHead(500);
253
+ res.end('Internal Server Error');
254
+ server.close();
255
+ oauthStateStore.delete(port);
256
+ reject(error);
257
+ }
258
+ });
259
+ const timeoutMs = 5 * 60 * 1000;
260
+ const timeoutHandle = setTimeout(() => {
261
+ server.close();
262
+ oauthStateStore.delete(port);
263
+ reject(new Error('Authentication timed out'));
264
+ }, timeoutMs);
265
+ const cleanup = () => {
266
+ clearTimeout(timeoutHandle);
267
+ oauthStateStore.delete(port);
268
+ };
269
+ server.listen(port, 'localhost', () => {
270
+ logger.debug(`OAuth callback server listening on http://localhost:${port}`);
271
+ });
272
+ server.on('close', cleanup);
273
+ server.on('error', (error) => {
274
+ cleanup();
275
+ reject(new Error(`Failed to start callback server: ${error.message}`));
276
+ });
277
+ });
278
+ }
279
+ /**
280
+ * Perform OAuth login flow with Supabase
281
+ */
282
+ export async function performOAuthLogin(config) {
283
+ try {
284
+ const port = await ensurePortAvailable(OAUTH_CALLBACK_PORT);
285
+ const redirectUri = `http://localhost:${port}`;
286
+ oauthStateStore.set(port, 'active');
287
+ logger.debug(`Registered OAuth callback server on port ${port}`);
288
+ const provider = config.provider || 'google';
289
+ const authParams = querystring.stringify({
290
+ redirect_to: redirectUri,
291
+ });
292
+ const authUrl = `${config.authUrl}/auth/v1/authorize?provider=${provider}&${authParams}`;
293
+ const tokenPromise = startCallbackServer(port, config);
294
+ console.log(chalk.cyan('🌐 Opening browser for authentication...'));
295
+ try {
296
+ const { default: open } = await import('open');
297
+ await open(authUrl);
298
+ console.log(chalk.green('✅ Browser opened'));
299
+ }
300
+ catch (_error) {
301
+ console.log(chalk.yellow(`💡 Please open manually: ${authUrl}`));
302
+ }
303
+ const spinner = p.spinner();
304
+ spinner.start('Waiting for authentication...');
305
+ try {
306
+ const result = await tokenPromise;
307
+ spinner.stop('Authentication successful!');
308
+ return result;
309
+ }
310
+ catch (error) {
311
+ spinner.stop('Authentication failed');
312
+ throw error;
313
+ }
314
+ }
315
+ catch (_error) {
316
+ throw new Error(`OAuth login failed: ${_error instanceof Error ? _error.message : String(_error)}`);
317
+ }
318
+ }
319
+ /**
320
+ * Default Supabase OAuth configuration for Dexto CLI
321
+ */
322
+ export const DEFAULT_OAUTH_CONFIG = {
323
+ authUrl: SUPABASE_URL,
324
+ clientId: SUPABASE_ANON_KEY,
325
+ provider: 'google',
326
+ scopes: ['openid', 'email', 'profile'],
327
+ };
@@ -0,0 +1,20 @@
1
+ export interface AuthConfig {
2
+ /** Supabase access token from OAuth login (optional if using --api-key) */
3
+ token?: string | undefined;
4
+ refreshToken?: string | undefined;
5
+ userId?: string | undefined;
6
+ email?: string | undefined;
7
+ expiresAt?: number | undefined;
8
+ createdAt: number;
9
+ /** Dexto API key for gateway access (from --api-key or provisioned after OAuth) */
10
+ dextoApiKey?: string | undefined;
11
+ dextoKeyId?: string | undefined;
12
+ }
13
+ export declare function storeAuth(config: AuthConfig): Promise<void>;
14
+ export declare function loadAuth(): Promise<AuthConfig | null>;
15
+ export declare function removeAuth(): Promise<void>;
16
+ export declare function isAuthenticated(): Promise<boolean>;
17
+ export declare function getAuthToken(): Promise<string | null>;
18
+ export declare function getDextoApiKey(): Promise<string | null>;
19
+ export declare function getAuthFilePath(): string;
20
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/cli/auth/service.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,UAAU;IACvB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAiBD,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjE;AAED,wBAAsB,QAAQ,IAAI,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA4B3D;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAOhD;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAGxD;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA4C3D;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQ7D;AA8CD,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
@@ -0,0 +1,147 @@
1
+ // packages/cli/src/cli/auth/service.ts
2
+ // Auth storage, token management, and refresh logic
3
+ import chalk from 'chalk';
4
+ import { existsSync, promises as fs } from 'fs';
5
+ import { z } from 'zod';
6
+ import { getDextoGlobalPath, logger } from '@dexto/core';
7
+ import { SUPABASE_URL, SUPABASE_ANON_KEY } from './constants.js';
8
+ const AUTH_CONFIG_FILE = 'auth.json';
9
+ const AuthConfigSchema = z
10
+ .object({
11
+ token: z.string().min(1).optional(),
12
+ refreshToken: z.string().optional(),
13
+ userId: z.string().optional(),
14
+ email: z.string().email().optional(),
15
+ expiresAt: z.number().optional(),
16
+ createdAt: z.number(),
17
+ dextoApiKey: z.string().optional(),
18
+ dextoKeyId: z.string().optional(),
19
+ })
20
+ .refine((data) => data.token || data.dextoApiKey, {
21
+ message: 'Either token (from OAuth) or dextoApiKey (from --api-key) is required',
22
+ });
23
+ export async function storeAuth(config) {
24
+ const authPath = getDextoGlobalPath('', AUTH_CONFIG_FILE);
25
+ const dextoDir = getDextoGlobalPath('', '');
26
+ await fs.mkdir(dextoDir, { recursive: true });
27
+ await fs.writeFile(authPath, JSON.stringify(config, null, 2), { mode: 0o600 });
28
+ logger.debug(`Stored auth config at: ${authPath}`);
29
+ }
30
+ export async function loadAuth() {
31
+ const authPath = getDextoGlobalPath('', AUTH_CONFIG_FILE);
32
+ if (!existsSync(authPath)) {
33
+ return null;
34
+ }
35
+ try {
36
+ const content = await fs.readFile(authPath, 'utf-8');
37
+ const config = JSON.parse(content);
38
+ const validated = AuthConfigSchema.parse(config);
39
+ if (validated.expiresAt && validated.expiresAt < Date.now()) {
40
+ // Only remove auth if there's no refresh token available
41
+ // If refresh token exists, return the expired auth and let getAuthToken() handle refresh
42
+ if (!validated.refreshToken) {
43
+ await removeAuth();
44
+ return null;
45
+ }
46
+ }
47
+ return validated;
48
+ }
49
+ catch (error) {
50
+ logger.warn(`Invalid auth config, removing: ${error}`);
51
+ await removeAuth();
52
+ return null;
53
+ }
54
+ }
55
+ export async function removeAuth() {
56
+ const authPath = getDextoGlobalPath('', AUTH_CONFIG_FILE);
57
+ if (existsSync(authPath)) {
58
+ await fs.unlink(authPath);
59
+ logger.debug(`Removed auth config from: ${authPath}`);
60
+ }
61
+ }
62
+ export async function isAuthenticated() {
63
+ const auth = await loadAuth();
64
+ return auth !== null;
65
+ }
66
+ export async function getAuthToken() {
67
+ const auth = await loadAuth();
68
+ if (!auth) {
69
+ return null;
70
+ }
71
+ const now = Date.now();
72
+ const fiveMinutes = 5 * 60 * 1000;
73
+ const isExpiringSoon = auth.expiresAt && auth.expiresAt < now + fiveMinutes;
74
+ if (!isExpiringSoon) {
75
+ return auth.token ?? null;
76
+ }
77
+ if (!auth.refreshToken) {
78
+ logger.debug('Token expired but no refresh token available');
79
+ await removeAuth();
80
+ return null;
81
+ }
82
+ logger.debug('Access token expired or expiring soon, refreshing...');
83
+ console.log(chalk.cyan('🔄 Access token expiring soon, refreshing...'));
84
+ const refreshResult = await refreshAccessToken(auth.refreshToken);
85
+ if (!refreshResult) {
86
+ logger.debug('Token refresh failed, removing auth');
87
+ console.log(chalk.red('❌ Token refresh failed. Please login again.'));
88
+ await removeAuth();
89
+ return null;
90
+ }
91
+ const newExpiresAt = Date.now() + refreshResult.expiresIn * 1000;
92
+ await storeAuth({
93
+ ...auth,
94
+ token: refreshResult.accessToken,
95
+ refreshToken: refreshResult.refreshToken,
96
+ expiresAt: newExpiresAt,
97
+ });
98
+ logger.debug('Token refreshed successfully');
99
+ console.log(chalk.green('✅ Access token refreshed successfully'));
100
+ return refreshResult.accessToken;
101
+ }
102
+ export async function getDextoApiKey() {
103
+ // Explicit env var takes priority (for CI, testing, account override)
104
+ if (process.env.DEXTO_API_KEY?.trim()) {
105
+ return process.env.DEXTO_API_KEY;
106
+ }
107
+ // Fall back to auth.json (from `dexto login`)
108
+ const auth = await loadAuth();
109
+ return auth?.dextoApiKey || null;
110
+ }
111
+ async function refreshAccessToken(refreshToken) {
112
+ try {
113
+ const response = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`, {
114
+ method: 'POST',
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ apikey: SUPABASE_ANON_KEY,
118
+ },
119
+ body: JSON.stringify({
120
+ refresh_token: refreshToken,
121
+ }),
122
+ signal: AbortSignal.timeout(10_000),
123
+ });
124
+ if (!response.ok) {
125
+ logger.debug(`Token refresh failed: ${response.status}`);
126
+ return null;
127
+ }
128
+ const data = await response.json();
129
+ if (!data.access_token || !data.refresh_token) {
130
+ logger.debug('Token refresh response missing required tokens');
131
+ return null;
132
+ }
133
+ logger.debug('Successfully refreshed access token');
134
+ return {
135
+ accessToken: data.access_token,
136
+ refreshToken: data.refresh_token,
137
+ expiresIn: data.expires_in || 3600,
138
+ };
139
+ }
140
+ catch (error) {
141
+ logger.debug(`Token refresh error: ${error instanceof Error ? error.message : String(error)}`);
142
+ return null;
143
+ }
144
+ }
145
+ export function getAuthFilePath() {
146
+ return getDextoGlobalPath('', AUTH_CONFIG_FILE);
147
+ }
@@ -0,0 +1,4 @@
1
+ export { handleLoginCommand, handleBrowserLogin } from './login.js';
2
+ export { handleLogoutCommand } from './logout.js';
3
+ export { handleStatusCommand } from './status.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/auth/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,4 @@
1
+ // packages/cli/src/cli/commands/auth/index.ts
2
+ export { handleLoginCommand, handleBrowserLogin } from './login.js';
3
+ export { handleLogoutCommand } from './logout.js';
4
+ export { handleStatusCommand } from './status.js';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Handle login command - multiple methods supported
3
+ */
4
+ export declare function handleLoginCommand(options?: {
5
+ apiKey?: string;
6
+ interactive?: boolean;
7
+ }): Promise<void>;
8
+ export declare function handleBrowserLogin(): Promise<void>;
9
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/auth/login.ts"],"names":[],"mappings":"AAcA;;GAEG;AACH,wBAAsB,kBAAkB,CACpC,OAAO,GAAE;IACL,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACpB,GACP,OAAO,CAAC,IAAI,CAAC,CAiEf;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAgCxD"}