neoagent 2.3.1-beta.102 → 2.3.1-beta.104

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
@@ -797,7 +797,7 @@ async function cmdLoginClaudeCode() {
797
797
  const data = JSON.parse(raw);
798
798
  const token = data?.claudeAiOauthTokens?.accessToken;
799
799
  if (token) {
800
- upsertEnvValue('CLAUDE_CODE_ACCESS_TOKEN', token);
800
+ upsertEnvValue('CLAUDE_CODE_OAUTH_TOKEN', token);
801
801
  logOk('Imported access token from Claude CLI credentials store');
802
802
  logInfo('Restarting NeoAgent to apply credentials...');
803
803
  cmdRestart();
@@ -806,13 +806,34 @@ async function cmdLoginClaudeCode() {
806
806
  } catch { }
807
807
  }
808
808
 
809
- // Fall back to browser-based PKCE OAuth flow
809
+ // Browser-based PKCE OAuth flow.
810
+ // client_id is the metadata URL per claude.ai's dynamic client registration.
811
+ // Redirect URIs registered: http://localhost/callback and http://127.0.0.1/callback (port 80).
812
+ // Per RFC 8252 §7.3, servers SHOULD allow any loopback port — we try high ports first
813
+ // and fall back to 80 if everything else is occupied.
810
814
  const http = require('http');
811
815
  const { URL: NodeURL } = require('url');
816
+ const net = require('net');
812
817
 
813
- const clientId = '9d1c250a-e61b-48f7-87a4-b8a14c8a8f5e';
814
- const redirectPort = 54321;
815
- const redirectUri = `http://localhost:${redirectPort}/callback`;
818
+ const clientId = 'https://claude.ai/oauth/claude-code-client-metadata';
819
+ const SCOPES = 'user:inference user:profile org:create_api_key user:sessions:claude_code offline_access';
820
+
821
+ // Find a free port (prefer high ports to avoid permission issues; fall back to 80)
822
+ const preferredPorts = [65432, 58080, 54321, 49152, 80];
823
+ let redirectPort = null;
824
+
825
+ for (const p of preferredPorts) {
826
+ const free = await new Promise((res) => {
827
+ const probe = net.createServer();
828
+ probe.listen(p, '127.0.0.1', () => { probe.close(() => res(true)); });
829
+ probe.on('error', () => res(false));
830
+ });
831
+ if (free) { redirectPort = p; break; }
832
+ }
833
+
834
+ if (!redirectPort) throw new Error('Could not find a free local port for the OAuth callback.');
835
+
836
+ const redirectUri = `http://127.0.0.1${redirectPort === 80 ? '' : `:${redirectPort}`}/callback`;
816
837
 
817
838
  // Generate PKCE verifier and challenge
818
839
  const codeVerifier = crypto.randomBytes(48).toString('base64url');
@@ -821,13 +842,16 @@ async function cmdLoginClaudeCode() {
821
842
  .update(codeVerifier)
822
843
  .digest('base64url');
823
844
 
845
+ const state = crypto.randomBytes(16).toString('hex');
846
+
824
847
  const authUrl = new URL('https://claude.ai/oauth/authorize');
825
848
  authUrl.searchParams.set('response_type', 'code');
826
849
  authUrl.searchParams.set('client_id', clientId);
827
850
  authUrl.searchParams.set('redirect_uri', redirectUri);
828
- authUrl.searchParams.set('scope', 'openid profile email offline_access');
851
+ authUrl.searchParams.set('scope', SCOPES);
829
852
  authUrl.searchParams.set('code_challenge', codeChallenge);
830
853
  authUrl.searchParams.set('code_challenge_method', 'S256');
854
+ authUrl.searchParams.set('state', state);
831
855
 
832
856
  console.log(`\n ${COLORS.cyan}Opening browser for Claude Code authorization...${COLORS.reset}`);
833
857
  console.log(` ${COLORS.dim}If the browser doesn't open, visit:${COLORS.reset}`);
@@ -848,8 +872,9 @@ async function cmdLoginClaudeCode() {
848
872
 
849
873
  const server = http.createServer((req, res) => {
850
874
  try {
851
- const reqUrl = new NodeURL(req.url, `http://localhost:${redirectPort}`);
875
+ const reqUrl = new NodeURL(req.url, redirectUri);
852
876
  const code = reqUrl.searchParams.get('code');
877
+ const returnedState = reqUrl.searchParams.get('state');
853
878
  const error = reqUrl.searchParams.get('error');
854
879
 
855
880
  if (error) {
@@ -861,6 +886,15 @@ async function cmdLoginClaudeCode() {
861
886
  return;
862
887
  }
863
888
 
889
+ if (returnedState && returnedState !== state) {
890
+ res.writeHead(200, { 'Content-Type': 'text/html' });
891
+ res.end('<html><body><h2>Authorization failed.</h2><p>State mismatch. You can close this tab.</p></body></html>');
892
+ clearTimeout(timeout);
893
+ server.close();
894
+ reject(new Error('OAuth state mismatch — possible CSRF attempt.'));
895
+ return;
896
+ }
897
+
864
898
  if (code) {
865
899
  res.writeHead(200, { 'Content-Type': 'text/html' });
866
900
  res.end('<html><body><h2>Authorization successful!</h2><p>You can close this tab and return to the terminal.</p></body></html>');
@@ -884,7 +918,7 @@ async function cmdLoginClaudeCode() {
884
918
  });
885
919
 
886
920
  logInfo('Exchanging authorization code for access token...');
887
- const tokenRes = await fetch('https://claude.ai/api/oauth/token', {
921
+ const tokenRes = await fetch('https://claude.ai/oauth/token', {
888
922
  method: 'POST',
889
923
  headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
890
924
  body: JSON.stringify({
@@ -907,11 +941,11 @@ async function cmdLoginClaudeCode() {
907
941
  throw new Error('Token exchange succeeded but no access_token was returned.');
908
942
  }
909
943
 
910
- upsertEnvValue('CLAUDE_CODE_ACCESS_TOKEN', accessToken);
944
+ upsertEnvValue('CLAUDE_CODE_OAUTH_TOKEN', accessToken);
911
945
  if (tokenData.refresh_token) {
912
946
  upsertEnvValue('CLAUDE_CODE_REFRESH_TOKEN', tokenData.refresh_token);
913
947
  }
914
- logOk('Saved Claude Code access token to .env');
948
+ logOk('Saved Claude Code OAuth token to .env');
915
949
  logInfo('Restarting NeoAgent to apply credentials...');
916
950
  cmdRestart();
917
951
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neoagent",
3
- "version": "2.3.1-beta.102",
3
+ "version": "2.3.1-beta.104",
4
4
  "description": "Proactive personal AI agent with no limits",
5
5
  "license": "MIT",
6
6
  "main": "server/index.js",
@@ -1 +1 @@
1
- e8fb2753d39f91b30e2cefa61e11fab9
1
+ 2f506dae7123235dcafdcda0f78d6a13
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"42d3d75a56efe1a2e9902f52dc8006099c45d9
37
37
 
38
38
  _flutter.loader.load({
39
39
  serviceWorkerSettings: {
40
- serviceWorkerVersion: "4130560553" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
40
+ serviceWorkerVersion: "2650676693" /* 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("mpabyxka-510c5db").length!==0&&r.b}if(r){r=s.d
129341
+ r=B.b.t("mpacvqjv-54ec71e").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("mpabyxka-510c5db").length===0||s.a!=null)return
134149
+ if(B.b.t("mpacvqjv-54ec71e").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,"mpabyxka-510c5db")){s=1
134167
+ if(J.bm(j)===0||J.d(j,"mpacvqjv-54ec71e")){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("mpabyxka-510c5db").length===0||n.c){s=1
134184
+ s=p}for(;;)switch(s){case 0:if(B.b.t("mpacvqjv-54ec71e").length===0||n.c){s=1
134185
134185
  break}n.c=!0
134186
134186
  n.D()
134187
134187
  p=4
@@ -33,7 +33,7 @@ class ClaudeCodeProvider extends AnthropicProvider {
33
33
  'claude-haiku-4-5-20251001': 200000,
34
34
  };
35
35
 
36
- const authToken = config.apiKey || process.env.CLAUDE_CODE_ACCESS_TOKEN || readClaudeCliToken();
36
+ const authToken = config.apiKey || process.env.CLAUDE_CODE_OAUTH_TOKEN || readClaudeCliToken();
37
37
  if (!authToken) {
38
38
  console.warn('[ClaudeCode] No access token. Run `neoagent login claude-code` to authenticate.');
39
39
  }
@@ -83,7 +83,7 @@ const AI_PROVIDER_DEFINITIONS = Object.freeze({
83
83
  id: 'claude-code',
84
84
  label: 'Claude Code',
85
85
  description: 'Claude models via Claude Code subscription. Login with `neoagent login claude-code`.',
86
- envKey: 'CLAUDE_CODE_ACCESS_TOKEN',
86
+ envKey: 'CLAUDE_CODE_OAUTH_TOKEN',
87
87
  supportsApiKey: true,
88
88
  supportsBaseUrl: false,
89
89
  defaultEnabled: false,