neoagent 2.3.1-beta.101 → 2.3.1-beta.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/manager.js CHANGED
@@ -786,10 +786,140 @@ async function pollDeviceCode({ pollUrl, pollBody, pollHeaders = {}, intervalMs,
786
786
  throw new Error('Authentication timed out after 15 minutes.');
787
787
  }
788
788
 
789
+ async function cmdLoginClaudeCode() {
790
+ heading('Claude Code Login');
791
+
792
+ // Check for Claude CLI credential file first (set by `claude login`)
793
+ const cliCredsPath = path.join(os.homedir(), '.claude', '.credentials.json');
794
+ if (fs.existsSync(cliCredsPath)) {
795
+ try {
796
+ const raw = fs.readFileSync(cliCredsPath, 'utf8');
797
+ const data = JSON.parse(raw);
798
+ const token = data?.claudeAiOauthTokens?.accessToken;
799
+ if (token) {
800
+ upsertEnvValue('CLAUDE_CODE_ACCESS_TOKEN', token);
801
+ logOk('Imported access token from Claude CLI credentials store');
802
+ logInfo('Restarting NeoAgent to apply credentials...');
803
+ cmdRestart();
804
+ return;
805
+ }
806
+ } catch { }
807
+ }
808
+
809
+ // Fall back to browser-based PKCE OAuth flow
810
+ const http = require('http');
811
+ const { URL: NodeURL } = require('url');
812
+
813
+ const clientId = '9d1c250a-e61b-48f7-87a4-b8a14c8a8f5e';
814
+ const redirectPort = 54321;
815
+ const redirectUri = `http://localhost:${redirectPort}/callback`;
816
+
817
+ // Generate PKCE verifier and challenge
818
+ const codeVerifier = crypto.randomBytes(48).toString('base64url');
819
+ const codeChallenge = crypto
820
+ .createHash('sha256')
821
+ .update(codeVerifier)
822
+ .digest('base64url');
823
+
824
+ const authUrl = new URL('https://claude.ai/oauth/authorize');
825
+ authUrl.searchParams.set('response_type', 'code');
826
+ authUrl.searchParams.set('client_id', clientId);
827
+ authUrl.searchParams.set('redirect_uri', redirectUri);
828
+ authUrl.searchParams.set('scope', 'openid profile email offline_access');
829
+ authUrl.searchParams.set('code_challenge', codeChallenge);
830
+ authUrl.searchParams.set('code_challenge_method', 'S256');
831
+
832
+ console.log(`\n ${COLORS.cyan}Opening browser for Claude Code authorization...${COLORS.reset}`);
833
+ console.log(` ${COLORS.dim}If the browser doesn't open, visit:${COLORS.reset}`);
834
+ console.log(` ${authUrl.toString()}\n`);
835
+
836
+ // Open browser
837
+ const openCmd = process.platform === 'darwin' ? 'open'
838
+ : process.platform === 'win32' ? 'start'
839
+ : 'xdg-open';
840
+ spawnSync(openCmd, [authUrl.toString()], { stdio: 'ignore' });
841
+
842
+ // Start local redirect server to capture authorization code
843
+ const authCode = await new Promise((resolve, reject) => {
844
+ const timeout = setTimeout(() => {
845
+ server.close();
846
+ reject(new Error('Claude Code authorization timed out after 5 minutes.'));
847
+ }, 5 * 60 * 1000);
848
+
849
+ const server = http.createServer((req, res) => {
850
+ try {
851
+ const reqUrl = new NodeURL(req.url, `http://localhost:${redirectPort}`);
852
+ const code = reqUrl.searchParams.get('code');
853
+ const error = reqUrl.searchParams.get('error');
854
+
855
+ if (error) {
856
+ res.writeHead(200, { 'Content-Type': 'text/html' });
857
+ res.end('<html><body><h2>Authorization failed.</h2><p>You can close this tab.</p></body></html>');
858
+ clearTimeout(timeout);
859
+ server.close();
860
+ reject(new Error(`OAuth error: ${error}`));
861
+ return;
862
+ }
863
+
864
+ if (code) {
865
+ res.writeHead(200, { 'Content-Type': 'text/html' });
866
+ res.end('<html><body><h2>Authorization successful!</h2><p>You can close this tab and return to the terminal.</p></body></html>');
867
+ clearTimeout(timeout);
868
+ server.close();
869
+ resolve(code);
870
+ }
871
+ } catch (err) {
872
+ res.writeHead(500);
873
+ res.end('Internal error');
874
+ }
875
+ });
876
+
877
+ server.listen(redirectPort, '127.0.0.1', () => {
878
+ logInfo(`Waiting for authorization callback on port ${redirectPort}...`);
879
+ });
880
+ server.on('error', (err) => {
881
+ clearTimeout(timeout);
882
+ reject(new Error(`Could not start local redirect server: ${err.message}`));
883
+ });
884
+ });
885
+
886
+ logInfo('Exchanging authorization code for access token...');
887
+ const tokenRes = await fetch('https://claude.ai/api/oauth/token', {
888
+ method: 'POST',
889
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
890
+ body: JSON.stringify({
891
+ grant_type: 'authorization_code',
892
+ code: authCode,
893
+ redirect_uri: redirectUri,
894
+ client_id: clientId,
895
+ code_verifier: codeVerifier,
896
+ }),
897
+ });
898
+
899
+ if (!tokenRes.ok) {
900
+ const text = await tokenRes.text().catch(() => 'Unknown error');
901
+ throw new Error(`Token exchange failed: HTTP ${tokenRes.status} — ${text}`);
902
+ }
903
+
904
+ const tokenData = await tokenRes.json();
905
+ const accessToken = tokenData.access_token;
906
+ if (!accessToken) {
907
+ throw new Error('Token exchange succeeded but no access_token was returned.');
908
+ }
909
+
910
+ upsertEnvValue('CLAUDE_CODE_ACCESS_TOKEN', accessToken);
911
+ if (tokenData.refresh_token) {
912
+ upsertEnvValue('CLAUDE_CODE_REFRESH_TOKEN', tokenData.refresh_token);
913
+ }
914
+ logOk('Saved Claude Code access token to .env');
915
+ logInfo('Restarting NeoAgent to apply credentials...');
916
+ cmdRestart();
917
+ }
918
+
789
919
  async function cmdLogin(args = []) {
790
920
  const provider = args[0];
791
- if (provider !== 'github-copilot' && provider !== 'openai-codex') {
792
- throw new Error(`Unsupported login provider: ${provider || 'none'}. Available: github-copilot, openai-codex`);
921
+ if (provider !== 'github-copilot' && provider !== 'openai-codex' && provider !== 'claude-code') {
922
+ throw new Error(`Unsupported login provider: ${provider || 'none'}. Available: github-copilot, openai-codex, claude-code`);
793
923
  }
794
924
 
795
925
  if (provider === 'github-copilot') {
@@ -893,6 +1023,8 @@ async function cmdLogin(args = []) {
893
1023
  logOk('Saved OpenAI Codex tokens to .env');
894
1024
  logInfo('Restarting NeoAgent to apply credentials...');
895
1025
  cmdRestart();
1026
+ } else if (provider === 'claude-code') {
1027
+ await cmdLoginClaudeCode();
896
1028
  }
897
1029
  }
898
1030
 
@@ -1471,6 +1603,7 @@ function printHelp() {
1471
1603
  row('update stable|beta', 'Update and switch channel');
1472
1604
  row('login github-copilot','Authenticate GitHub Copilot');
1473
1605
  row('login openai-codex', 'Authenticate OpenAI Codex');
1606
+ row('login claude-code', 'Authenticate Claude Code');
1474
1607
  console.log('');
1475
1608
 
1476
1609
  console.log(`${c.bold}Maintenance${c.reset}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.101",
3
+ "version": "2.3.1-beta.102",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -1 +1 @@
1
- 8b0dc1b848bc67887f399a80a15258e6
1
+ e8fb2753d39f91b30e2cefa61e11fab9
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "3696925671" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "4130560553" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
41
41
  }
42
42
  });
@@ -129338,7 +129338,7 @@ r===$&&A.b()
129338
129338
  o.push(A.ii(p,A.iY(!1,new A.a3(B.tM,A.dT(new A.cI(B.he,new A.a5V(r,p),p),p,p),p),!1,B.I,!0),p,p,0,0,0,p))}r=!1
129339
129339
  if(!s.ay)if(!s.ch){r=s.e
129340
129340
  r===$&&A.b()
129341
- r=B.b.t("mpa5woez-bed15cd").length!==0&&r.b}if(r){r=s.d
129341
+ r=B.b.t("mpabyxka-510c5db").length!==0&&r.b}if(r){r=s.d
129342
129342
  r===$&&A.b()
129343
129343
  r=r.ag&&!r.V?84:0
129344
129344
  q=s.e
@@ -134146,7 +134146,7 @@ $S:236}
134146
134146
  A.Ys.prototype={}
134147
134147
  A.Rr.prototype={
134148
134148
  mT(a){var s=this
134149
- if(B.b.t("mpa5woez-bed15cd").length===0||s.a!=null)return
134149
+ if(B.b.t("mpabyxka-510c5db").length===0||s.a!=null)return
134150
134150
  s.A5()
134151
134151
  s.a=A.q1(B.PP,new A.b58(s))},
134152
134152
  A5(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
@@ -134164,7 +134164,7 @@ if(!t.f.b(k)){s=1
134164
134164
  break}i=J.Z(k,"buildId")
134165
134165
  h=i==null?null:B.b.t(J.r(i))
134166
134166
  j=h==null?"":h
134167
- if(J.bm(j)===0||J.d(j,"mpa5woez-bed15cd")){s=1
134167
+ if(J.bm(j)===0||J.d(j,"mpabyxka-510c5db")){s=1
134168
134168
  break}n.b=!0
134169
134169
  n.D()
134170
134170
  p=2
@@ -134181,7 +134181,7 @@ case 2:return A.i(o.at(-1),r)}})
134181
134181
  return A.k($async$A5,r)},
134182
134182
  vb(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
134183
134183
  var $async$vb=A.h(function(a2,a3){if(a2===1){o.push(a3)
134184
- s=p}for(;;)switch(s){case 0:if(B.b.t("mpa5woez-bed15cd").length===0||n.c){s=1
134184
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mpabyxka-510c5db").length===0||n.c){s=1
134185
134185
  break}n.c=!0
134186
134186
  n.D()
134187
134187
  p=4
@@ -5,6 +5,7 @@ const { OllamaProvider } = require('./providers/ollama');
5
5
  const { OpenAIProvider } = require('./providers/openai');
6
6
  const { GithubCopilotProvider } = require('./providers/githubCopilot');
7
7
  const { OpenAICodexProvider } = require('./providers/openaiCodex');
8
+ const { ClaudeCodeProvider } = require('./providers/claudeCode');
8
9
  const {
9
10
  AI_PROVIDER_DEFINITIONS,
10
11
  getProviderConfigs,
@@ -72,6 +73,24 @@ const STATIC_MODELS = [
72
73
  provider: 'anthropic',
73
74
  purpose: 'fast'
74
75
  },
76
+ {
77
+ id: 'claude-opus-4-7',
78
+ label: 'Claude Opus 4.7 (Claude Code / Default)',
79
+ provider: 'claude-code',
80
+ purpose: 'general'
81
+ },
82
+ {
83
+ id: 'claude-sonnet-4-6',
84
+ label: 'Claude Sonnet 4.6 (Claude Code / Fast)',
85
+ provider: 'claude-code',
86
+ purpose: 'coding'
87
+ },
88
+ {
89
+ id: 'claude-haiku-4-5-20251001',
90
+ label: 'Claude Haiku 4.5 (Claude Code / Subagents)',
91
+ provider: 'claude-code',
92
+ purpose: 'fast'
93
+ },
75
94
  {
76
95
  id: 'gemini-3.1-flash-lite-preview',
77
96
  label: 'Gemini 3.1 Flash Lite (Preview)',
@@ -338,6 +357,8 @@ function createProviderInstance(providerStr, userId = null, configOverrides = {}
338
357
  return new GithubCopilotProvider({ apiKey: runtime.apiKey, ...providerOverrides });
339
358
  } else if (providerStr === 'openai-codex') {
340
359
  return new OpenAICodexProvider({ apiKey: runtime.apiKey, ...providerOverrides });
360
+ } else if (providerStr === 'claude-code') {
361
+ return new ClaudeCodeProvider({ apiKey: runtime.apiKey, ...providerOverrides });
341
362
  }
342
363
  throw new Error(`Unknown provider: ${providerStr}`);
343
364
  }
@@ -0,0 +1,49 @@
1
+ const os = require('os');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const Anthropic = require('@anthropic-ai/sdk');
5
+ const { AnthropicProvider } = require('./anthropic');
6
+
7
+ const CLAUDE_CLI_CREDS_PATH = path.join(os.homedir(), '.claude', '.credentials.json');
8
+ const CLAUDE_CODE_BASE_URL = 'https://api.claude.ai/api';
9
+
10
+ function readClaudeCliToken() {
11
+ try {
12
+ const raw = fs.readFileSync(CLAUDE_CLI_CREDS_PATH, 'utf8');
13
+ const data = JSON.parse(raw);
14
+ const token = data?.claudeAiOauthTokens?.accessToken;
15
+ return typeof token === 'string' && token ? token : null;
16
+ } catch {
17
+ return null;
18
+ }
19
+ }
20
+
21
+ class ClaudeCodeProvider extends AnthropicProvider {
22
+ constructor(config = {}) {
23
+ super(config);
24
+ this.name = 'claude-code';
25
+ this.models = [
26
+ 'claude-opus-4-7',
27
+ 'claude-sonnet-4-6',
28
+ 'claude-haiku-4-5-20251001',
29
+ ];
30
+ this.contextWindows = {
31
+ 'claude-opus-4-7': 200000,
32
+ 'claude-sonnet-4-6': 200000,
33
+ 'claude-haiku-4-5-20251001': 200000,
34
+ };
35
+
36
+ const authToken = config.apiKey || process.env.CLAUDE_CODE_ACCESS_TOKEN || readClaudeCliToken();
37
+ if (!authToken) {
38
+ console.warn('[ClaudeCode] No access token. Run `neoagent login claude-code` to authenticate.');
39
+ }
40
+
41
+ // OAuth tokens use Authorization: Bearer — authToken option sends that header
42
+ this.client = new Anthropic({
43
+ authToken: authToken || undefined,
44
+ baseURL: config.baseUrl || CLAUDE_CODE_BASE_URL,
45
+ });
46
+ }
47
+ }
48
+
49
+ module.exports = { ClaudeCodeProvider, readClaudeCliToken };
@@ -1,6 +1,12 @@
1
+ const crypto = require('crypto');
1
2
  const OpenAI = require('openai');
2
3
  const { BaseProvider } = require('./base');
3
4
 
5
+ const DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api/codex';
6
+
7
+ // Stable per-process installation ID — Codex backend uses it for request tracking.
8
+ const INSTALLATION_ID = crypto.randomUUID();
9
+
4
10
  function decodeJwtPayload(token) {
5
11
  try {
6
12
  const parts = String(token || '').split('.');
@@ -12,8 +18,6 @@ function decodeJwtPayload(token) {
12
18
  }
13
19
  }
14
20
 
15
- const DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api/codex';
16
-
17
21
  function isCodexBackendBaseUrl(baseURL) {
18
22
  const trimmed = String(baseURL || '').trim();
19
23
  if (!trimmed) return false;
@@ -194,20 +198,27 @@ class OpenAICodexProvider extends BaseProvider {
194
198
  'gpt-5.4',
195
199
  'gpt-5.4-mini',
196
200
  ]);
201
+
197
202
  let accountId = process.env.OPENAI_CODEX_ACCOUNT_ID || '';
198
203
  if (!accountId && this.usesCodexBackend) {
199
204
  const accessToken = config.apiKey || process.env.OPENAI_CODEX_ACCESS_TOKEN || '';
200
205
  const payload = decodeJwtPayload(accessToken);
201
- accountId = payload?.chatgpt_account_id || payload?.['https://api.openai.com/auth']?.chatgpt_account_id || '';
206
+ // account_id lives in access_token directly, or nested under the OIDC namespace in id_token
207
+ accountId = payload?.chatgpt_account_id
208
+ || payload?.['https://api.openai.com/auth']?.chatgpt_account_id
209
+ || '';
202
210
  }
203
211
 
204
212
  const defaultHeaders = this.usesCodexBackend
205
213
  ? {
206
- 'Editor-Version': process.env.OPENAI_CODEX_EDITOR_VERSION || 'vscode/1.99.0',
207
- 'Editor-Plugin-Version': process.env.OPENAI_CODEX_EDITOR_PLUGIN_VERSION || 'neoagent/1.0.0',
208
- 'User-Agent': process.env.OPENAI_CODEX_USER_AGENT || 'NeoAgent/1.0.0',
209
- 'OpenAI-Beta': 'responses=experimental',
210
- ...(accountId ? { 'chatgpt-account-id': accountId } : {}),
214
+ // Required by Cloudflare bot detection on chatgpt.com
215
+ 'originator': 'Codex Desktop',
216
+ 'User-Agent': 'Codex Desktop/1.0.0 (darwin; arm64)',
217
+ // Required by the Codex responses endpoint
218
+ 'OpenAI-Beta': 'responses_websockets=2026-02-06',
219
+ 'x-codex-installation-id': INSTALLATION_ID,
220
+ 'x-openai-internal-codex-residency': process.env.OPENAI_CODEX_RESIDENCY || 'us',
221
+ ...(accountId ? { 'ChatGPT-Account-Id': accountId } : {}),
211
222
  }
212
223
  : undefined;
213
224
 
@@ -286,43 +297,60 @@ class OpenAICodexProvider extends BaseProvider {
286
297
  };
287
298
 
288
299
  if (this.usesCodexBackend) {
300
+ // instructions must always be present (even empty) — backend returns 400 if omitted
289
301
  request.instructions = instructions.join('\n\n');
290
- } else if (instructions.length > 0) {
291
- request.instructions = instructions.join('\n\n');
292
- }
293
-
294
- if (tools && tools.length > 0) {
295
- request.tools = this.formatTools(tools);
296
- request.tool_choice = options.toolChoice || 'auto';
297
- }
298
-
299
- if (!this.usesCodexBackend) {
302
+ request.store = false;
303
+ // tools fields must always be explicit
304
+ if (tools && tools.length > 0) {
305
+ request.tools = this.formatTools(tools);
306
+ request.tool_choice = options.toolChoice || 'auto';
307
+ } else {
308
+ request.tools = [];
309
+ request.tool_choice = 'auto';
310
+ }
311
+ request.parallel_tool_calls = false;
312
+ // reasoning: both effort and summary required for Codex reasoning models
313
+ if (this._isReasoningModel(model)) {
314
+ const effort = options.reasoningEffort || options.reasoning_effort || 'medium';
315
+ request.reasoning = { effort, summary: 'auto' };
316
+ request.include = ['reasoning.encrypted_content'];
317
+ }
318
+ } else {
319
+ if (instructions.length > 0) {
320
+ request.instructions = instructions.join('\n\n');
321
+ }
322
+ if (tools && tools.length > 0) {
323
+ request.tools = this.formatTools(tools);
324
+ request.tool_choice = options.toolChoice || 'auto';
325
+ }
300
326
  request.max_output_tokens = options.maxTokens || 16384;
301
-
302
327
  if (options.temperature !== undefined && options.temperature !== null) {
303
328
  request.temperature = options.temperature;
304
329
  }
305
- }
306
-
307
- const reasoningEffort = options.reasoningEffort || options.reasoning_effort;
308
- if (reasoningEffort || this._isReasoningModel(model)) {
309
- request.reasoning = {
310
- effort: reasoningEffort || 'medium',
311
- };
330
+ const reasoningEffort = options.reasoningEffort || options.reasoning_effort;
331
+ if (reasoningEffort || this._isReasoningModel(model)) {
332
+ request.reasoning = { effort: reasoningEffort || 'medium' };
333
+ }
312
334
  }
313
335
 
314
336
  return request;
315
337
  }
316
338
 
339
+ _requestHeaders() {
340
+ return this.usesCodexBackend
341
+ ? { 'x-client-request-id': crypto.randomUUID() }
342
+ : undefined;
343
+ }
344
+
317
345
  async chat(messages, tools = [], options = {}) {
318
346
  const model = options.model || this.config.model || this.getDefaultModel();
319
347
  const request = this._buildRequest(messages, tools, options, model);
320
348
  let response;
321
349
  try {
322
- response = await this.client.responses.create({
323
- model,
324
- ...request,
325
- });
350
+ response = await this.client.responses.create(
351
+ { model, ...request },
352
+ { headers: this._requestHeaders() },
353
+ );
326
354
  } catch (err) {
327
355
  throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`);
328
356
  }
@@ -347,11 +375,10 @@ class OpenAICodexProvider extends BaseProvider {
347
375
  const request = this._buildRequest(messages, tools, options, model);
348
376
  let stream;
349
377
  try {
350
- stream = await this.client.responses.create({
351
- model,
352
- ...request,
353
- stream: true,
354
- });
378
+ stream = await this.client.responses.create(
379
+ { model, ...request, stream: true },
380
+ { headers: this._requestHeaders() },
381
+ );
355
382
  } catch (err) {
356
383
  throw new Error(`OpenAI Codex request failed: ${formatOpenAIError(err)}`);
357
384
  }
@@ -79,6 +79,16 @@ const AI_PROVIDER_DEFINITIONS = Object.freeze({
79
79
  defaultEnabled: false,
80
80
  defaultBaseUrl: 'https://chatgpt.com/backend-api/codex'
81
81
  },
82
+ 'claude-code': {
83
+ id: 'claude-code',
84
+ label: 'Claude Code',
85
+ description: 'Claude models via Claude Code subscription. Login with `neoagent login claude-code`.',
86
+ envKey: 'CLAUDE_CODE_ACCESS_TOKEN',
87
+ supportsApiKey: true,
88
+ supportsBaseUrl: false,
89
+ defaultEnabled: false,
90
+ defaultBaseUrl: ''
91
+ },
82
92
  ollama: {
83
93
  id: 'ollama',
84
94
  label: 'Ollama',