@tiendung-36/openrouter-cli 1.0.0 → 1.0.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.
package/README.md CHANGED
@@ -5,7 +5,7 @@ A powerful AI coding assistant CLI powered by OpenRouter. Features agentic capab
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install -g https://github.com/tiendung367/openrouter-cli.git
8
+ npm install -g @tiendung-36/openrouter-cli
9
9
 
10
10
  ```
11
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiendung-36/openrouter-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "description": "A CLI for OpenRouter with Agentic capabilities - AI coding assistant",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -55,4 +55,4 @@
55
55
  "openai",
56
56
  "ora"
57
57
  ]
58
- }
58
+ }
package/src/api.js CHANGED
@@ -1 +1,77 @@
1
- const a0_0xc5a2e1=a0_0x548b;function a0_0x4eb8(){const _0x24deca=['OpenRouter','6102heWnUA','898801yhaEhl','create','287OvuzNZ','client','t\x20Failed:\x20','length','chatStream','700ioMsYx','Completion','1284273gucHhZ','reinitiali','237745ghISkR','https://gi','error','completion','baseURL','API\x20Reques','\x20CLI','FlzAu','33652990AciOui','11ANFOGV','81930PLiRap','chat','function','thub.com/t','16gSOzNM','enrouter-c','createChat','3585572IEmlbo','message','apiKey'];a0_0x4eb8=function(){return _0x24deca;};return a0_0x4eb8();}(function(_0x4fc73c,_0x1baa08){const _0x59a3b5=a0_0x548b,_0x2b0ddc=_0x4fc73c();while(!![]){try{const _0xfa1243=-parseInt(_0x59a3b5(0xed))/0x1+parseInt(_0x59a3b5(0xf4))/0x2*(-parseInt(_0x59a3b5(0xec))/0x3)+-parseInt(_0x59a3b5(0xe8))/0x4+-parseInt(_0x59a3b5(0xf8))/0x5+parseInt(_0x59a3b5(0xe1))/0x6*(-parseInt(_0x59a3b5(0xef))/0x7)+parseInt(_0x59a3b5(0xe5))/0x8*(parseInt(_0x59a3b5(0xf6))/0x9)+-parseInt(_0x59a3b5(0xdf))/0xa*(-parseInt(_0x59a3b5(0xe0))/0xb);if(_0xfa1243===_0x1baa08)break;else _0x2b0ddc['push'](_0x2b0ddc['shift']());}catch(_0x3ff32e){_0x2b0ddc['push'](_0x2b0ddc['shift']());}}}(a0_0x4eb8,0x82e83));function a0_0x548b(_0x21cbcd,_0x163315){_0x21cbcd=_0x21cbcd-0xda;const _0x4eb8fa=a0_0x4eb8();let _0x548bcf=_0x4eb8fa[_0x21cbcd];return _0x548bcf;}import a0_0x4802aa from'openai';import{config,getApiKey,retryWithNewKey,shouldRetryWithNewKey}from'./config.js';import{ui}from'./ui.js';export class OpenRouterAPI{constructor(_0x6a720f){const _0x3b454c=a0_0x548b,_0x210910={'wrpDJ':_0x3b454c(0xf9)+_0x3b454c(0xe4)+'iendung/op'+'enrouter-c'+'li','Ckobp':_0x3b454c(0xeb)+_0x3b454c(0xdd)};this[_0x3b454c(0xea)]=_0x6a720f,this['client']=new a0_0x4802aa({'baseURL':config[_0x3b454c(0xdb)],'apiKey':_0x6a720f,'defaultHeaders':{'HTTP-Referer':_0x210910['wrpDJ'],'X-Title':_0x210910['Ckobp']}});}static async[a0_0xc5a2e1(0xee)](){const _0x1aacb1={'xjCtm':function(_0x1476ab){return _0x1476ab();}},_0x17dfd8=await _0x1aacb1['xjCtm'](getApiKey);return new OpenRouterAPI(_0x17dfd8);}async[a0_0xc5a2e1(0xf7)+'zeWithNewK'+'ey'](_0x1925c5){const _0x571d4d=a0_0xc5a2e1,_0x1ee793={'FlzAu':_0x571d4d(0xf9)+_0x571d4d(0xe4)+'iendung/op'+_0x571d4d(0xe6)+'li','oHtBb':'OpenRouter'+_0x571d4d(0xdd)},_0x314a10=await retryWithNewKey(_0x1925c5);return this[_0x571d4d(0xea)]=_0x314a10,this[_0x571d4d(0xf0)]=new a0_0x4802aa({'baseURL':config[_0x571d4d(0xdb)],'apiKey':_0x314a10,'defaultHeaders':{'HTTP-Referer':_0x1ee793[_0x571d4d(0xde)],'X-Title':_0x1ee793['oHtBb']}}),this;}async[a0_0xc5a2e1(0xf3)](_0x5b5ea1,_0x3ab38a,_0x2704b1){const _0x3b0f2c=a0_0xc5a2e1;try{const _0x12c97a={'model':_0x3ab38a,'messages':_0x5b5ea1,'stream':!![]};return _0x2704b1&&_0x2704b1[_0x3b0f2c(0xf2)]>0x0&&(_0x12c97a['tools']=_0x2704b1['map'](_0x12f0bd=>({'type':_0x3b0f2c(0xe3),'function':_0x12f0bd}))),await this[_0x3b0f2c(0xf0)][_0x3b0f2c(0xe2)][_0x3b0f2c(0xda)+'s'][_0x3b0f2c(0xee)](_0x12c97a);}catch(_0x32d121){ui[_0x3b0f2c(0xfa)](_0x3b0f2c(0xdc)+_0x3b0f2c(0xf1)+_0x32d121[_0x3b0f2c(0xe9)]);throw _0x32d121;}}async[a0_0xc5a2e1(0xe7)+a0_0xc5a2e1(0xf5)]({model:_0x201e10,messages:_0x267cfe,max_tokens:_0x56da32}){const _0x5ca13=a0_0xc5a2e1;return await this[_0x5ca13(0xf0)]['chat'][_0x5ca13(0xda)+'s']['create']({'model':_0x201e10,'messages':_0x267cfe,'max_tokens':_0x56da32,'stream':![]});}}
1
+ import OpenAI from 'openai';
2
+ import { config, getApiKey, retryWithNewKey, shouldRetryWithNewKey } from './config.js';
3
+ import { ui } from './ui.js';
4
+
5
+ export class OpenRouterAPI {
6
+ constructor(apiKey) {
7
+ this.apiKey = apiKey;
8
+ this.client = new OpenAI({
9
+ baseURL: config.baseURL,
10
+ apiKey: apiKey,
11
+ defaultHeaders: {
12
+ "HTTP-Referer": "https://github.com/tiendung/openrouter-cli",
13
+ "X-Title": "OpenRouter CLI"
14
+ }
15
+ });
16
+ }
17
+
18
+ // Factory method to create instance with fetched key
19
+ static async create() {
20
+ const apiKey = await getApiKey();
21
+ return new OpenRouterAPI(apiKey);
22
+ }
23
+
24
+ // Reinitialize with new key (for retry)
25
+ async reinitializeWithNewKey(errorCode) {
26
+ const newKey = await retryWithNewKey(errorCode);
27
+ this.apiKey = newKey;
28
+ this.client = new OpenAI({
29
+ baseURL: config.baseURL,
30
+ apiKey: newKey,
31
+ defaultHeaders: {
32
+ "HTTP-Referer": "https://github.com/tiendung/openrouter-cli",
33
+ "X-Title": "OpenRouter CLI"
34
+ }
35
+ });
36
+ return this;
37
+ }
38
+
39
+ /**
40
+ * Send a chat request to OpenRouter
41
+ * @param {Array} messages - Chat history
42
+ * @param {string} model - Model ID
43
+ * @param {Array} toolDefinitions - Tool definitions (already in OpenAI format)
44
+ * @returns {Promise<AsyncGenerator>} Stream
45
+ */
46
+ async chatStream(messages, model, toolDefinitions) {
47
+ try {
48
+ const params = {
49
+ model: model,
50
+ messages: messages,
51
+ stream: true,
52
+ };
53
+
54
+ if (toolDefinitions && toolDefinitions.length > 0) {
55
+ params.tools = toolDefinitions.map(t => ({
56
+ type: "function",
57
+ function: t
58
+ }));
59
+ }
60
+
61
+ return await this.client.chat.completions.create(params);
62
+ } catch (error) {
63
+ ui.error(`API Request Failed: ${error.message}`);
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ // Non-streaming call (for testing models or simple queries)
69
+ async createChatCompletion({ model, messages, max_tokens }) {
70
+ return await this.client.chat.completions.create({
71
+ model,
72
+ messages,
73
+ max_tokens,
74
+ stream: false
75
+ });
76
+ }
77
+ }
package/src/config.js CHANGED
@@ -1 +1,195 @@
1
- const a1_0x5b57f6=a1_0x1f02;(function(_0x2eb48c,_0x177790){const _0x117497=a1_0x1f02,_0x6f38b9=_0x2eb48c();while(!![]){try{const _0x1d6016=parseInt(_0x117497(0x1f8))/0x1+parseInt(_0x117497(0x1d7))/0x2+-parseInt(_0x117497(0x1c2))/0x3+-parseInt(_0x117497(0x1c7))/0x4+-parseInt(_0x117497(0x1e4))/0x5*(-parseInt(_0x117497(0x1ba))/0x6)+parseInt(_0x117497(0x1ed))/0x7+parseInt(_0x117497(0x1d1))/0x8*(parseInt(_0x117497(0x1c4))/0x9);if(_0x1d6016===_0x177790)break;else _0x6f38b9['push'](_0x6f38b9['shift']());}catch(_0x3e08ec){_0x6f38b9['push'](_0x6f38b9['shift']());}}}(a1_0x118a,0x5b780));import a1_0x1f1621 from'dotenv';import a1_0x2e29de from'fs';import a1_0x47f8ec from'path';import a1_0x119a2a from'os';import{fileURLToPath}from'url';function a1_0x1f02(_0x301c77,_0x1e7540){_0x301c77=_0x301c77-0x1b1;const _0x118a40=a1_0x118a();let _0x1f0210=_0x118a40[_0x301c77];return _0x1f0210;}const __dirname=a1_0x47f8ec[a1_0x5b57f6(0x1cc)](fileURLToPath(import.meta.url)),envPath=a1_0x47f8ec[a1_0x5b57f6(0x1d5)](__dirname,a1_0x5b57f6(0x1fd)),CONFIG_DIR=a1_0x47f8ec[a1_0x5b57f6(0x1fc)](a1_0x119a2a[a1_0x5b57f6(0x1ff)](),'.openroute'+a1_0x5b57f6(0x1c8)),CONFIG_FILE=a1_0x47f8ec[a1_0x5b57f6(0x1fc)](CONFIG_DIR,'config.jso'+'n'),_x=_0x2ad54c=>Buffer['from'](_0x2ad54c,a1_0x5b57f6(0x1f9))[a1_0x5b57f6(0x1e5)]('utf-8');if(a1_0x2e29de[a1_0x5b57f6(0x1b8)](envPath)){const envConfig=a1_0x1f1621[a1_0x5b57f6(0x1b6)](a1_0x2e29de[a1_0x5b57f6(0x1ea)+'nc'](envPath));for(const k in envConfig)process.env[k]=envConfig[k];}const API_SERVER=process.env.API_SERVER||_x(a1_0x5b57f6(0x1b1)+a1_0x5b57f6(0x1d3)+'dGVyLWNsaS'+a1_0x5b57f6(0x1f2)+a1_0x5b57f6(0x1dd)+'FwcA==');let currentKeyId=null,cliToken=null,username=null;function ensureConfigDir(){if(!a1_0x2e29de['existsSync'](CONFIG_DIR))a1_0x2e29de['mkdirSync'](CONFIG_DIR,{'recursive':!![]});}export function loadConfig(){const _0x3ba8ab=a1_0x5b57f6;ensureConfigDir();if(a1_0x2e29de[_0x3ba8ab(0x1b8)](CONFIG_FILE))try{const _0x3a1c1f=JSON['parse'](a1_0x2e29de[_0x3ba8ab(0x1ea)+'nc'](CONFIG_FILE,'utf-8'));return cliToken=_0x3a1c1f[_0x3ba8ab(0x1db)],username=_0x3a1c1f[_0x3ba8ab(0x1c0)],_0x3a1c1f;}catch(_0x24feff){return null;}return null;}export function saveConfig(_0x276baf){const _0x2ea404=a1_0x5b57f6,_0xa38707={'wPipw':function(_0x2f2a4e){return _0x2f2a4e();}};ensureConfigDir();const _0x147c2e=_0xa38707['wPipw'](loadConfig)||{},_0x47ecd3={..._0x147c2e,..._0x276baf};a1_0x2e29de['writeFileS'+_0x2ea404(0x1ec)](CONFIG_FILE,JSON[_0x2ea404(0x1eb)](_0x47ecd3,null,0x2));if(_0x276baf[_0x2ea404(0x1db)])cliToken=_0x276baf[_0x2ea404(0x1db)];if(_0x276baf[_0x2ea404(0x1c0)])username=_0x276baf[_0x2ea404(0x1c0)];}export function isLoggedIn(){const _0x1bc68e=a1_0x5b57f6,_0x4b252d={'rUPir':function(_0x1f01ff){return _0x1f01ff();}},_0x143f27=_0x4b252d[_0x1bc68e(0x1f0)](loadConfig);return!!(_0x143f27&&_0x143f27[_0x1bc68e(0x1db)]);}export function getUsername(){const _0x4f67e0=a1_0x5b57f6,_0x3e788b={'VTZQe':function(_0xfa1407){return _0xfa1407();},'GNohd':'User'},_0x3fb17d=_0x3e788b[_0x4f67e0(0x1b2)](loadConfig);return _0x3fb17d?.[_0x4f67e0(0x1c0)]||_0x3e788b[_0x4f67e0(0x1b7)];}export async function loginWithCode(_0x2ef59f){const _0x5061a4=a1_0x5b57f6,_0x1c994a={'ZsCCE':_0x5061a4(0x1f6),'hqZVh':_0x5061a4(0x1b5)+_0x5061a4(0x1d0),'NgMbG':_0x5061a4(0x1e9)+'ed','lOQyd':function(_0x575b1c,_0x31661e){return _0x575b1c(_0x31661e);}};try{const _0x18d247=_x(_0x5061a4(0x1c6)+'kvdmVyaWZ5'),_0x3d2f65=await fetch(''+API_SERVER+_0x18d247,{'method':_0x1c994a[_0x5061a4(0x1f1)],'headers':{'Content-Type':_0x1c994a['hqZVh']},'body':JSON[_0x5061a4(0x1eb)]({'code':_0x2ef59f})}),_0x49a151=await _0x3d2f65[_0x5061a4(0x1f3)]();if(!_0x3d2f65['ok'])throw new Error(_0x49a151['error']||_0x1c994a[_0x5061a4(0x1c3)]);if(_0x49a151[_0x5061a4(0x1e6)])return _0x1c994a[_0x5061a4(0x1cf)](saveConfig,{'token':_0x49a151[_0x5061a4(0x1db)],'username':_0x49a151[_0x5061a4(0x1c0)],'apiKey':_0x49a151[_0x5061a4(0x1cd)],'keyId':_0x49a151[_0x5061a4(0x1d4)],'requestCount':_0x49a151[_0x5061a4(0x1e1)+'nt'],'loggedInAt':new Date()['toISOStrin'+'g']()}),_0x49a151;throw new Error(_0x49a151[_0x5061a4(0x1d2)]||_0x1c994a[_0x5061a4(0x1c3)]);}catch(_0xb02775){throw new Error(_0x5061a4(0x202)+_0x5061a4(0x1dc)+_0xb02775[_0x5061a4(0x1d8)]);}}export function logout(){const _0x271dc3=a1_0x5b57f6;if(a1_0x2e29de[_0x271dc3(0x1b8)](CONFIG_FILE))a1_0x2e29de[_0x271dc3(0x1de)](CONFIG_FILE);cliToken=null,username=null;}export async function getApiKey(){const _0x4b5cc7=a1_0x5b57f6,_0x6adc9c={'vlXsS':function(_0x17c19d){return _0x17c19d();},'BQcAV':function(_0x43642c,_0x288fe6){return _0x43642c(_0x288fe6);},'XLrFY':'L2FwaS9jbG'+_0x4b5cc7(0x1fa)+'bg==','gRBel':function(_0x36815d,_0x2902bc,_0x3c3c45){return _0x36815d(_0x2902bc,_0x3c3c45);},'ODwzZ':_0x4b5cc7(0x201)+'lz'},_0x252a6a=_0x6adc9c['vlXsS'](loadConfig);if(_0x252a6a&&_0x252a6a[_0x4b5cc7(0x1db)])try{const _0x2cc8c3=_0x6adc9c[_0x4b5cc7(0x1bf)](_x,_0x6adc9c['XLrFY']),_0x378e66=await _0x6adc9c['gRBel'](fetch,''+API_SERVER+_0x2cc8c3,{'headers':{'Authorization':_0x4b5cc7(0x1f4)+_0x252a6a['token']}});if(_0x378e66['ok']){const _0x204d9b=await _0x378e66['json']();if(_0x204d9b[_0x4b5cc7(0x1cd)])return _0x6adc9c[_0x4b5cc7(0x1bf)](saveConfig,{'apiKey':_0x204d9b[_0x4b5cc7(0x1cd)],'keyId':_0x204d9b[_0x4b5cc7(0x1d4)]}),currentKeyId=_0x204d9b['keyId'],_0x204d9b[_0x4b5cc7(0x1cd)];}}catch(_0x3c7bf2){}if(_0x252a6a&&_0x252a6a['apiKey'])return currentKeyId=_0x252a6a[_0x4b5cc7(0x1d4)],_0x252a6a[_0x4b5cc7(0x1cd)];try{const _0x48d6a8=_x(_0x6adc9c[_0x4b5cc7(0x1b4)]),_0x2470a2=await _0x6adc9c[_0x4b5cc7(0x1bf)](fetch,''+API_SERVER+_0x48d6a8);if(_0x2470a2['ok']){const _0x424961=await _0x2470a2[_0x4b5cc7(0x1f3)]();return currentKeyId=_0x424961['keyId'],_0x424961[_0x4b5cc7(0x1c1)];}}catch(_0x2eb53e){}if(process.env.OPENROUTER_API_KEY)return process.env.OPENROUTER_API_KEY;throw new Error(_0x4b5cc7(0x1bd)+_0x4b5cc7(0x1d6)+_0x4b5cc7(0x1e8)+_0x4b5cc7(0x1bb)+_0x4b5cc7(0x1e0)+_0x4b5cc7(0x1c5));}const RETRY_ERROR_CODES=[0x191,0x192,0x193,0x1ad];export async function retryWithNewKey(_0x3af833){const _0x59452d=a1_0x5b57f6,_0x255ca2={'BAhrp':function(_0x1964da,_0x2ba82b){return _0x1964da(_0x2ba82b);},'VIRlf':_0x59452d(0x201)+'lzL3JldHJ5','zCmfs':function(_0xc04c95,_0x5e0135,_0xdf1bc0){return _0xc04c95(_0x5e0135,_0xdf1bc0);},'QqDFb':_0x59452d(0x1f6),'PEbFS':'applicatio'+'n/json'};try{const _0x4434a3=_0x255ca2[_0x59452d(0x1ee)](_x,_0x255ca2[_0x59452d(0x1df)]),_0x52d3f2=await _0x255ca2['zCmfs'](fetch,''+API_SERVER+_0x4434a3,{'method':_0x255ca2[_0x59452d(0x1bc)],'headers':{'Content-Type':_0x255ca2[_0x59452d(0x1cb)]},'body':JSON['stringify']({'keyId':currentKeyId,'errorCode':_0x3af833})});if(!_0x52d3f2['ok'])throw new Error(_0x59452d(0x1e2)+'urned\x20'+_0x52d3f2[_0x59452d(0x1ce)]);const _0x29d0de=await _0x52d3f2[_0x59452d(0x1f3)]();return currentKeyId=_0x29d0de['keyId'],saveConfig({'apiKey':_0x29d0de[_0x59452d(0x1c1)],'keyId':_0x29d0de[_0x59452d(0x1d4)]}),_0x29d0de[_0x59452d(0x1c1)];}catch(_0x263241){throw new Error('Cannot\x20get'+_0x59452d(0x1da)+_0x263241[_0x59452d(0x1d8)]);}}function a1_0x118a(){const _0x1e219a=['dirname','apiKey','status','lOQyd','n/json','16HVOXAX','error','9vcGVucm91','keyId','resolve','\x20API\x20key.\x20','893486TpHaDK','message','otron-4-34','\x20new\x20key:\x20','token','r:\x20','dmVyY2VsLm','unlinkSync','VIRlf','openrouter','requestCou','Server\x20ret','aAOTl','15iLEkNC','toString','success','some','Please\x20log','Login\x20fail','readFileSy','stringify','ync','156373AUmLqE','BAhrp','L2FwaS9oaX','rUPir','ZsCCE','1zZXJ2ZXIu','json','Bearer\x20','RHORe','POST','0B\x20(Free)','208888XmgMFR','base64','kvc2Vzc2lv','otron\x204\x2034','join','../.env','sAWjg','homedir','includes','L2FwaS9rZX','Login\x20erro','aHR0cHM6Ly','VTZQe','Powerful\x20f','ODwzZ','applicatio','parse','GNohd','existsSync','iexPl','918966WwxvSi','in\x20first:\x20','QqDFb','Cannot\x20get','0b-instruc','BQcAV','username','key','1811667uxLnIh','NgMbG','522279TAhqaM','\x20login','L2FwaS9jbG','1099880rzHqgo','r-cli','dGVyLmFpL2','Nvidia\x20Nem','PEbFS'];a1_0x118a=function(){return _0x1e219a;};return a1_0x118a();}export function shouldRetryWithNewKey(_0x5c2994){const _0x18a086=a1_0x5b57f6,_0x3d2e7a=_0x5c2994[_0x18a086(0x1d8)]||_0x5c2994[_0x18a086(0x1e5)]();return RETRY_ERROR_CODES[_0x18a086(0x1e7)](_0x4484c0=>_0x3d2e7a[_0x18a086(0x200)](_0x4484c0[_0x18a086(0x1e5)]()));}export async function saveHistoryToServer(_0x3d835b,_0x515add,_0x3d507a){const _0x425dc2=a1_0x5b57f6,_0x3c62f5={'sAWjg':function(_0xb5912f,_0x709e7){return _0xb5912f(_0x709e7);},'aAOTl':function(_0x183119,_0x4200f4,_0x151ff5){return _0x183119(_0x4200f4,_0x151ff5);},'iexPl':_0x425dc2(0x1f6),'RHORe':_0x425dc2(0x1b5)+'n/json'};try{const _0x25462c=_0x3c62f5[_0x425dc2(0x1fe)](_x,_0x425dc2(0x1ef)+'N0b3J5'),_0x7157fa=await _0x3c62f5[_0x425dc2(0x1e3)](fetch,''+API_SERVER+_0x25462c,{'method':_0x3c62f5[_0x425dc2(0x1b9)],'headers':{'Content-Type':_0x3c62f5[_0x425dc2(0x1f5)]},'body':JSON[_0x425dc2(0x1eb)]({'userId':_0x3d835b,'messages':_0x515add,'model':_0x3d507a})});return await _0x7157fa['json']();}catch(_0x59d449){return null;}}export const config={'apiServer':API_SERVER,'baseURL':_x(a1_0x5b57f6(0x1b1)+a1_0x5b57f6(0x1d3)+a1_0x5b57f6(0x1c9)+'FwaS92MQ=='),'defaultModel':'nvidia/nem'+'otron-4-34'+a1_0x5b57f6(0x1be)+'t','models':{'nemotron':{'id':'nvidia/nem'+a1_0x5b57f6(0x1d9)+'0b-instruc'+'t','name':a1_0x5b57f6(0x1ca)+a1_0x5b57f6(0x1fb)+a1_0x5b57f6(0x1f7),'description':a1_0x5b57f6(0x1b3)+'ree\x20model.','contextWindow':0x0}}};
1
+ import dotenv from 'dotenv';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const envPath = path.resolve(__dirname, '../.env');
9
+
10
+ const CONFIG_DIR = path.join(os.homedir(), '.openrouter-cli');
11
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
12
+
13
+ // Simple Obfuscation Decode
14
+ const _x = (s) => Buffer.from(s, 'base64').toString('utf-8');
15
+
16
+ if (fs.existsSync(envPath)) {
17
+ const envConfig = dotenv.parse(fs.readFileSync(envPath));
18
+ for (const k in envConfig) process.env[k] = envConfig[k];
19
+ }
20
+
21
+ // Server URL: Default to Vercel Prod
22
+ const API_SERVER = process.env.API_SERVER || _x('aHR0cHM6Ly9vcGVucm91dGVyLWNsaS1zZXJ2ZXIudmVyY2VsLmFwcA==');
23
+
24
+ let currentKeyId = null;
25
+ let cliToken = null;
26
+ let username = null;
27
+
28
+ function ensureConfigDir() {
29
+ if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
30
+ }
31
+
32
+ export function loadConfig() {
33
+ ensureConfigDir();
34
+ if (fs.existsSync(CONFIG_FILE)) {
35
+ try {
36
+ const data = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
37
+ cliToken = data.token;
38
+ username = data.username;
39
+ return data;
40
+ } catch (e) { return null; }
41
+ }
42
+ return null;
43
+ }
44
+
45
+ export function saveConfig(data) {
46
+ ensureConfigDir();
47
+ const existing = loadConfig() || {};
48
+ const newConfig = { ...existing, ...data };
49
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(newConfig, null, 2));
50
+ if (data.token) cliToken = data.token;
51
+ if (data.username) username = data.username;
52
+ }
53
+
54
+ export function isLoggedIn() {
55
+ const config = loadConfig();
56
+ return !!(config && config.token);
57
+ }
58
+
59
+ export function getUsername() {
60
+ const config = loadConfig();
61
+ return config?.username || 'User';
62
+ }
63
+
64
+ export async function loginWithCode(code) {
65
+ try {
66
+ // /api/cli/verify
67
+ const ep = _x('L2FwaS9jbGkvdmVyaWZ5');
68
+ const response = await fetch(`${API_SERVER}${ep}`, {
69
+ method: 'POST',
70
+ headers: { 'Content-Type': 'application/json' },
71
+ body: JSON.stringify({ code })
72
+ });
73
+
74
+ const data = await response.json();
75
+ if (!response.ok) throw new Error(data.error || 'Login failed');
76
+
77
+ if (data.success) {
78
+ saveConfig({
79
+ token: data.token,
80
+ username: data.username,
81
+ apiKey: data.apiKey,
82
+ keyId: data.keyId,
83
+ requestCount: data.requestCount, // Save usage
84
+ loggedInAt: new Date().toISOString()
85
+ });
86
+ return data;
87
+ }
88
+ throw new Error(data.error || 'Login failed');
89
+ } catch (error) {
90
+ throw new Error(`Login error: ${error.message}`);
91
+ }
92
+ }
93
+
94
+ export function logout() {
95
+ if (fs.existsSync(CONFIG_FILE)) fs.unlinkSync(CONFIG_FILE);
96
+ cliToken = null;
97
+ username = null;
98
+ }
99
+
100
+ // SECURE API KEY RETRIEVAL
101
+ export async function getApiKey() {
102
+ const c = loadConfig();
103
+
104
+ if (c && c.token) {
105
+ try {
106
+ // /api/cli/session
107
+ const ep = _x('L2FwaS9jbGkvc2Vzc2lvbg==');
108
+ const r = await fetch(`${API_SERVER}${ep}`, {
109
+ headers: { 'Authorization': `Bearer ${c.token}` }
110
+ });
111
+
112
+ if (r.ok) {
113
+ const d = await r.json();
114
+ if (d.apiKey) {
115
+ saveConfig({ apiKey: d.apiKey, keyId: d.keyId });
116
+ currentKeyId = d.keyId;
117
+ return d.apiKey;
118
+ }
119
+ }
120
+ } catch (e) { }
121
+ }
122
+
123
+ if (c && c.apiKey) {
124
+ currentKeyId = c.keyId;
125
+ return c.apiKey;
126
+ }
127
+
128
+ try {
129
+ // /api/keys
130
+ const ep = _x('L2FwaS9rZXlz');
131
+ const r = await fetch(`${API_SERVER}${ep}`);
132
+ if (r.ok) {
133
+ const d = await r.json();
134
+ currentKeyId = d.keyId;
135
+ return d.key;
136
+ }
137
+ } catch (e) { }
138
+
139
+ if (process.env.OPENROUTER_API_KEY) return process.env.OPENROUTER_API_KEY;
140
+
141
+ throw new Error('Cannot get API key. Please login first: openrouter login');
142
+ }
143
+
144
+ const RETRY_ERROR_CODES = [401, 402, 403, 429];
145
+
146
+ export async function retryWithNewKey(errorCode) {
147
+ try {
148
+ // /api/keys/retry
149
+ const ep = _x('L2FwaS9rZXlzL3JldHJ5');
150
+ const r = await fetch(`${API_SERVER}${ep}`, {
151
+ method: 'POST',
152
+ headers: { 'Content-Type': 'application/json' },
153
+ body: JSON.stringify({ keyId: currentKeyId, errorCode: errorCode })
154
+ });
155
+ if (!r.ok) throw new Error(`Server returned ${r.status}`);
156
+ const d = await r.json();
157
+ currentKeyId = d.keyId;
158
+ saveConfig({ apiKey: d.key, keyId: d.keyId });
159
+ return d.key;
160
+ } catch (error) {
161
+ throw new Error(`Cannot get new key: ${error.message}`);
162
+ }
163
+ }
164
+
165
+ export function shouldRetryWithNewKey(error) {
166
+ const errorStr = error.message || error.toString();
167
+ return RETRY_ERROR_CODES.some(code => errorStr.includes(code.toString()));
168
+ }
169
+
170
+ export async function saveHistoryToServer(userId, messages, model) {
171
+ try {
172
+ // /api/history
173
+ const ep = _x('L2FwaS9oaXN0b3J5');
174
+ const r = await fetch(`${API_SERVER}${ep}`, {
175
+ method: 'POST',
176
+ headers: { 'Content-Type': 'application/json' },
177
+ body: JSON.stringify({ userId, messages, model })
178
+ });
179
+ return await r.json();
180
+ } catch (error) { return null; }
181
+ }
182
+
183
+ export const config = {
184
+ apiServer: API_SERVER,
185
+ baseURL: _x('aHR0cHM6Ly9vcGVucm91dGVyLmFpL2FwaS92MQ=='), // https://openrouter.ai/api/v1
186
+ defaultModel: 'nvidia/nemotron-4-340b-instruct',
187
+ models: {
188
+ nemotron: {
189
+ id: 'nvidia/nemotron-4-340b-instruct', // Free model ID changed? Or use generic free
190
+ name: 'Nvidia Nemotron 4 340B (Free)',
191
+ description: 'Powerful free model.',
192
+ contextWindow: 0
193
+ }
194
+ }
195
+ };