@redonvn/cli 0.1.0

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 (141) hide show
  1. package/README.md +99 -0
  2. package/bin/redai.js +22 -0
  3. package/dist/auth/client-id.d.ts +8 -0
  4. package/dist/auth/client-id.js +41 -0
  5. package/dist/auth/client-id.js.map +1 -0
  6. package/dist/auth/store.d.ts +16 -0
  7. package/dist/auth/store.js +39 -0
  8. package/dist/auth/store.js.map +1 -0
  9. package/dist/cli/commands/login.d.ts +6 -0
  10. package/dist/cli/commands/login.js +82 -0
  11. package/dist/cli/commands/login.js.map +1 -0
  12. package/dist/cli/commands/logout.d.ts +1 -0
  13. package/dist/cli/commands/logout.js +18 -0
  14. package/dist/cli/commands/logout.js.map +1 -0
  15. package/dist/cli/commands/oauth.d.ts +11 -0
  16. package/dist/cli/commands/oauth.js +311 -0
  17. package/dist/cli/commands/oauth.js.map +1 -0
  18. package/dist/cli/commands/serve.d.ts +7 -0
  19. package/dist/cli/commands/serve.js +24 -0
  20. package/dist/cli/commands/serve.js.map +1 -0
  21. package/dist/cli/commands/start.d.ts +1 -0
  22. package/dist/cli/commands/start.js +26 -0
  23. package/dist/cli/commands/start.js.map +1 -0
  24. package/dist/cli/commands/status.d.ts +1 -0
  25. package/dist/cli/commands/status.js +51 -0
  26. package/dist/cli/commands/status.js.map +1 -0
  27. package/dist/cli/index.d.ts +2 -0
  28. package/dist/cli/index.js +127 -0
  29. package/dist/cli/index.js.map +1 -0
  30. package/dist/cli-router/detect.d.ts +13 -0
  31. package/dist/cli-router/detect.js +58 -0
  32. package/dist/cli-router/detect.js.map +1 -0
  33. package/dist/config.d.ts +12 -0
  34. package/dist/config.js +24 -0
  35. package/dist/config.js.map +1 -0
  36. package/dist/daemon/router.d.ts +17 -0
  37. package/dist/daemon/router.js +39 -0
  38. package/dist/daemon/router.js.map +1 -0
  39. package/dist/daemon/tunnel.d.ts +10 -0
  40. package/dist/daemon/tunnel.js +156 -0
  41. package/dist/daemon/tunnel.js.map +1 -0
  42. package/dist/llm/llm-request.d.ts +9 -0
  43. package/dist/llm/llm-request.js +98 -0
  44. package/dist/llm/llm-request.js.map +1 -0
  45. package/dist/llm/oauth/antigravity-oauth.d.ts +22 -0
  46. package/dist/llm/oauth/antigravity-oauth.js +128 -0
  47. package/dist/llm/oauth/antigravity-oauth.js.map +1 -0
  48. package/dist/llm/oauth/callback-server.d.ts +18 -0
  49. package/dist/llm/oauth/callback-server.js +78 -0
  50. package/dist/llm/oauth/callback-server.js.map +1 -0
  51. package/dist/llm/oauth/claude-oauth.d.ts +29 -0
  52. package/dist/llm/oauth/claude-oauth.js +115 -0
  53. package/dist/llm/oauth/claude-oauth.js.map +1 -0
  54. package/dist/llm/oauth/codex-oauth.d.ts +21 -0
  55. package/dist/llm/oauth/codex-oauth.js +137 -0
  56. package/dist/llm/oauth/codex-oauth.js.map +1 -0
  57. package/dist/llm/oauth/gemini-oauth.d.ts +26 -0
  58. package/dist/llm/oauth/gemini-oauth.js +132 -0
  59. package/dist/llm/oauth/gemini-oauth.js.map +1 -0
  60. package/dist/llm/oauth/iflow-oauth.d.ts +26 -0
  61. package/dist/llm/oauth/iflow-oauth.js +151 -0
  62. package/dist/llm/oauth/iflow-oauth.js.map +1 -0
  63. package/dist/llm/oauth/kimi-oauth.d.ts +16 -0
  64. package/dist/llm/oauth/kimi-oauth.js +126 -0
  65. package/dist/llm/oauth/kimi-oauth.js.map +1 -0
  66. package/dist/llm/oauth/open-browser.d.ts +5 -0
  67. package/dist/llm/oauth/open-browser.js +32 -0
  68. package/dist/llm/oauth/open-browser.js.map +1 -0
  69. package/dist/llm/oauth/pkce.d.ts +12 -0
  70. package/dist/llm/oauth/pkce.js +27 -0
  71. package/dist/llm/oauth/pkce.js.map +1 -0
  72. package/dist/llm/oauth/qwen-oauth.d.ts +27 -0
  73. package/dist/llm/oauth/qwen-oauth.js +138 -0
  74. package/dist/llm/oauth/qwen-oauth.js.map +1 -0
  75. package/dist/llm/oauth/store.d.ts +34 -0
  76. package/dist/llm/oauth/store.js +72 -0
  77. package/dist/llm/oauth/store.js.map +1 -0
  78. package/dist/llm/oauth/xai-oauth.d.ts +28 -0
  79. package/dist/llm/oauth/xai-oauth.js +132 -0
  80. package/dist/llm/oauth/xai-oauth.js.map +1 -0
  81. package/dist/llm/providers/antigravity.d.ts +23 -0
  82. package/dist/llm/providers/antigravity.js +103 -0
  83. package/dist/llm/providers/antigravity.js.map +1 -0
  84. package/dist/llm/providers/claude.d.ts +36 -0
  85. package/dist/llm/providers/claude.js +148 -0
  86. package/dist/llm/providers/claude.js.map +1 -0
  87. package/dist/llm/providers/codex.d.ts +23 -0
  88. package/dist/llm/providers/codex.js +122 -0
  89. package/dist/llm/providers/codex.js.map +1 -0
  90. package/dist/llm/providers/gemini.d.ts +23 -0
  91. package/dist/llm/providers/gemini.js +112 -0
  92. package/dist/llm/providers/gemini.js.map +1 -0
  93. package/dist/llm/providers/generic-client.d.ts +45 -0
  94. package/dist/llm/providers/generic-client.js +98 -0
  95. package/dist/llm/providers/generic-client.js.map +1 -0
  96. package/dist/llm/providers/iflow.d.ts +8 -0
  97. package/dist/llm/providers/iflow.js +15 -0
  98. package/dist/llm/providers/iflow.js.map +1 -0
  99. package/dist/llm/providers/kimi.d.ts +8 -0
  100. package/dist/llm/providers/kimi.js +16 -0
  101. package/dist/llm/providers/kimi.js.map +1 -0
  102. package/dist/llm/providers/qwen.d.ts +8 -0
  103. package/dist/llm/providers/qwen.js +15 -0
  104. package/dist/llm/providers/qwen.js.map +1 -0
  105. package/dist/llm/providers/xai.d.ts +8 -0
  106. package/dist/llm/providers/xai.js +15 -0
  107. package/dist/llm/providers/xai.js.map +1 -0
  108. package/dist/llm/selector.d.ts +14 -0
  109. package/dist/llm/selector.js +81 -0
  110. package/dist/llm/selector.js.map +1 -0
  111. package/dist/server/http-server.d.ts +22 -0
  112. package/dist/server/http-server.js +194 -0
  113. package/dist/server/http-server.js.map +1 -0
  114. package/dist/tools/bash.d.ts +7 -0
  115. package/dist/tools/bash.js +61 -0
  116. package/dist/tools/bash.js.map +1 -0
  117. package/dist/tools/edit.d.ts +4 -0
  118. package/dist/tools/edit.js +35 -0
  119. package/dist/tools/edit.js.map +1 -0
  120. package/dist/tools/glob.d.ts +5 -0
  121. package/dist/tools/glob.js +27 -0
  122. package/dist/tools/glob.js.map +1 -0
  123. package/dist/tools/grep.d.ts +5 -0
  124. package/dist/tools/grep.js +63 -0
  125. package/dist/tools/grep.js.map +1 -0
  126. package/dist/tools/read.d.ts +6 -0
  127. package/dist/tools/read.js +25 -0
  128. package/dist/tools/read.js.map +1 -0
  129. package/dist/tools/registry.d.ts +8 -0
  130. package/dist/tools/registry.js +43 -0
  131. package/dist/tools/registry.js.map +1 -0
  132. package/dist/tools/run-cli.d.ts +7 -0
  133. package/dist/tools/run-cli.js +83 -0
  134. package/dist/tools/run-cli.js.map +1 -0
  135. package/dist/tools/webfetch.d.ts +7 -0
  136. package/dist/tools/webfetch.js +26 -0
  137. package/dist/tools/webfetch.js.map +1 -0
  138. package/dist/tools/write.d.ts +5 -0
  139. package/dist/tools/write.js +29 -0
  140. package/dist/tools/write.js.map +1 -0
  141. package/package.json +67 -0
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IFLOW_DEFAULT_API_BASE = exports.IFLOW_DEFAULT_CALLBACK_PORT = exports.IFLOW_CLIENT_ID = exports.IFLOW_API_KEY_URL = exports.IFLOW_TOKEN_URL = exports.IFLOW_AUTH_URL = void 0;
4
+ exports.startIFlowAuth = startIFlowAuth;
5
+ exports.exchangeIFlowCodeForTokens = exchangeIFlowCodeForTokens;
6
+ exports.refreshIFlowToken = refreshIFlowToken;
7
+ exports.exchangeIFlowCookieForApiKey = exchangeIFlowCookieForApiKey;
8
+ const undici_1 = require("undici");
9
+ const pkce_1 = require("./pkce");
10
+ /**
11
+ * iFlow OAuth — port từ `internal/auth/iflow/iflow_auth.go`.
12
+ * Có 2 path: OAuth PKCE thường + Cookie (BXAuth) flow.
13
+ */
14
+ exports.IFLOW_AUTH_URL = 'https://iflow.cn/oauth';
15
+ exports.IFLOW_TOKEN_URL = 'https://iflow.cn/oauth/token';
16
+ exports.IFLOW_API_KEY_URL = 'https://platform.iflow.cn/api/openapi/apikey';
17
+ exports.IFLOW_CLIENT_ID = '10009311001';
18
+ exports.IFLOW_DEFAULT_CALLBACK_PORT = 11451;
19
+ exports.IFLOW_DEFAULT_API_BASE = 'https://apis.iflow.cn/v1';
20
+ function startIFlowAuth(callbackPort = exports.IFLOW_DEFAULT_CALLBACK_PORT) {
21
+ const pkce = (0, pkce_1.generatePKCECodes)();
22
+ const state = (0, pkce_1.generateRandomState)();
23
+ const redirectUri = `http://localhost:${callbackPort}/callback`;
24
+ const params = new URLSearchParams({
25
+ client_id: exports.IFLOW_CLIENT_ID,
26
+ redirect_uri: redirectUri,
27
+ response_type: 'code',
28
+ state,
29
+ code_challenge: pkce.codeChallenge,
30
+ code_challenge_method: 'S256',
31
+ scope: 'openid profile email',
32
+ });
33
+ return {
34
+ authUrl: `${exports.IFLOW_AUTH_URL}?${params.toString()}`,
35
+ state,
36
+ pkce,
37
+ redirectUri,
38
+ };
39
+ }
40
+ async function exchangeIFlowCodeForTokens(code, pkce, redirectUri, account = 'default') {
41
+ const data = new URLSearchParams({
42
+ grant_type: 'authorization_code',
43
+ client_id: exports.IFLOW_CLIENT_ID,
44
+ code,
45
+ redirect_uri: redirectUri,
46
+ code_verifier: pkce.codeVerifier,
47
+ });
48
+ const res = await (0, undici_1.request)(exports.IFLOW_TOKEN_URL, {
49
+ method: 'POST',
50
+ headers: {
51
+ 'Content-Type': 'application/x-www-form-urlencoded',
52
+ Accept: 'application/json',
53
+ },
54
+ body: data.toString(),
55
+ headersTimeout: 15000,
56
+ bodyTimeout: 30000,
57
+ });
58
+ const text = await res.body.text();
59
+ if (res.statusCode !== 200) {
60
+ throw new Error(`iFlow token exchange failed (${res.statusCode}): ${text}`);
61
+ }
62
+ const parsed = JSON.parse(text);
63
+ const now = Date.now();
64
+ return {
65
+ type: 'iflow',
66
+ account,
67
+ accessToken: parsed.access_token,
68
+ refreshToken: parsed.refresh_token ?? '',
69
+ expiresAt: now + parsed.expires_in * 1000,
70
+ lastRefresh: now,
71
+ };
72
+ }
73
+ async function refreshIFlowToken(current) {
74
+ if (!current.refreshToken)
75
+ throw new Error('iFlow refresh token missing');
76
+ const data = new URLSearchParams({
77
+ grant_type: 'refresh_token',
78
+ client_id: exports.IFLOW_CLIENT_ID,
79
+ refresh_token: current.refreshToken,
80
+ });
81
+ const res = await (0, undici_1.request)(exports.IFLOW_TOKEN_URL, {
82
+ method: 'POST',
83
+ headers: {
84
+ 'Content-Type': 'application/x-www-form-urlencoded',
85
+ Accept: 'application/json',
86
+ },
87
+ body: data.toString(),
88
+ headersTimeout: 15000,
89
+ bodyTimeout: 30000,
90
+ });
91
+ const text = await res.body.text();
92
+ if (res.statusCode !== 200) {
93
+ throw new Error(`iFlow refresh failed (${res.statusCode}): ${text}`);
94
+ }
95
+ const parsed = JSON.parse(text);
96
+ const now = Date.now();
97
+ return {
98
+ ...current,
99
+ accessToken: parsed.access_token,
100
+ refreshToken: parsed.refresh_token ?? current.refreshToken,
101
+ expiresAt: now + parsed.expires_in * 1000,
102
+ lastRefresh: now,
103
+ };
104
+ }
105
+ /**
106
+ * Cookie flow: user paste cookie BXAuth từ iflow.cn (đã đăng nhập browser).
107
+ * Gọi platform.iflow.cn/api/openapi/apikey để đổi cookie lấy API key.
108
+ */
109
+ async function exchangeIFlowCookieForApiKey(cookie, account = 'default') {
110
+ if (!cookie.includes('BXAuth')) {
111
+ throw new Error('iFlow cookie phải chứa field BXAuth');
112
+ }
113
+ const res = await (0, undici_1.request)(exports.IFLOW_API_KEY_URL, {
114
+ method: 'GET',
115
+ headers: {
116
+ Cookie: cookie.trim(),
117
+ Accept: 'application/json',
118
+ Origin: 'https://platform.iflow.cn',
119
+ Referer: 'https://platform.iflow.cn/',
120
+ },
121
+ headersTimeout: 15000,
122
+ bodyTimeout: 30000,
123
+ });
124
+ const text = await res.body.text();
125
+ if (res.statusCode !== 200) {
126
+ throw new Error(`iFlow cookie exchange failed (${res.statusCode}): ${text}`);
127
+ }
128
+ // Response chứa apiKey trong field `data.apiKey` (xem source CLIProxyAPI)
129
+ let apiKey = '';
130
+ try {
131
+ const parsed = JSON.parse(text);
132
+ apiKey = parsed.data?.apiKey ?? parsed.apiKey ?? '';
133
+ }
134
+ catch {
135
+ // ignore
136
+ }
137
+ if (!apiKey) {
138
+ throw new Error('iFlow cookie response không có apiKey');
139
+ }
140
+ const now = Date.now();
141
+ return {
142
+ type: 'iflow',
143
+ account,
144
+ accessToken: apiKey,
145
+ refreshToken: '', // cookie flow không có refresh — sẽ phải re-paste khi expire
146
+ expiresAt: now + 30 * 24 * 3600 * 1000, // giả sử 30 ngày
147
+ lastRefresh: now,
148
+ metadata: { authMode: 'cookie' },
149
+ };
150
+ }
151
+ //# sourceMappingURL=iflow-oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iflow-oauth.js","sourceRoot":"","sources":["../../../src/llm/oauth/iflow-oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { OAuthTokenStorage } from './store';
2
+ export declare const KIMI_DEVICE_CODE_URL = "https://auth.kimi.com/api/oauth/device_authorization";
3
+ export declare const KIMI_TOKEN_URL = "https://auth.kimi.com/api/oauth/token";
4
+ export declare const KIMI_CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098";
5
+ export declare const KIMI_API_BASE_URL = "https://api.kimi.com/coding";
6
+ export interface KimiDeviceAuth {
7
+ deviceCode: string;
8
+ userCode: string;
9
+ verificationUri: string;
10
+ verificationUriComplete?: string;
11
+ expiresIn: number;
12
+ intervalSec: number;
13
+ }
14
+ export declare function requestKimiDeviceCode(): Promise<KimiDeviceAuth>;
15
+ export declare function pollKimiToken(device: KimiDeviceAuth, account?: string): Promise<OAuthTokenStorage>;
16
+ export declare function refreshKimiToken(current: OAuthTokenStorage): Promise<OAuthTokenStorage>;
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KIMI_API_BASE_URL = exports.KIMI_CLIENT_ID = exports.KIMI_TOKEN_URL = exports.KIMI_DEVICE_CODE_URL = void 0;
4
+ exports.requestKimiDeviceCode = requestKimiDeviceCode;
5
+ exports.pollKimiToken = pollKimiToken;
6
+ exports.refreshKimiToken = refreshKimiToken;
7
+ const undici_1 = require("undici");
8
+ /**
9
+ * Kimi Device Flow OAuth — port từ `internal/auth/kimi/kimi.go`.
10
+ * Constants: kimiClientID + kimiOAuthHost = auth.kimi.com
11
+ */
12
+ const KIMI_OAUTH_HOST = 'https://auth.kimi.com';
13
+ exports.KIMI_DEVICE_CODE_URL = `${KIMI_OAUTH_HOST}/api/oauth/device_authorization`;
14
+ exports.KIMI_TOKEN_URL = `${KIMI_OAUTH_HOST}/api/oauth/token`;
15
+ exports.KIMI_CLIENT_ID = '17e5f671-d194-4dfb-9706-5516cb48c098';
16
+ exports.KIMI_API_BASE_URL = 'https://api.kimi.com/coding';
17
+ async function requestKimiDeviceCode() {
18
+ const data = new URLSearchParams({
19
+ client_id: exports.KIMI_CLIENT_ID,
20
+ scope: 'openid profile email',
21
+ });
22
+ const res = await (0, undici_1.request)(exports.KIMI_DEVICE_CODE_URL, {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
25
+ body: data.toString(),
26
+ headersTimeout: 15000,
27
+ bodyTimeout: 30000,
28
+ });
29
+ const text = await res.body.text();
30
+ if (res.statusCode !== 200) {
31
+ throw new Error(`Kimi device code failed (${res.statusCode}): ${text}`);
32
+ }
33
+ const parsed = JSON.parse(text);
34
+ return {
35
+ deviceCode: parsed.device_code,
36
+ userCode: parsed.user_code,
37
+ verificationUri: parsed.verification_uri,
38
+ verificationUriComplete: parsed.verification_uri_complete,
39
+ expiresIn: parsed.expires_in,
40
+ intervalSec: parsed.interval ?? 5,
41
+ };
42
+ }
43
+ async function pollKimiToken(device, account = 'default') {
44
+ const deadline = Date.now() + device.expiresIn * 1000;
45
+ let interval = Math.max(device.intervalSec * 1000, 3000);
46
+ while (Date.now() < deadline) {
47
+ await new Promise((r) => setTimeout(r, interval));
48
+ const data = new URLSearchParams({
49
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
50
+ client_id: exports.KIMI_CLIENT_ID,
51
+ device_code: device.deviceCode,
52
+ });
53
+ const res = await (0, undici_1.request)(exports.KIMI_TOKEN_URL, {
54
+ method: 'POST',
55
+ headers: {
56
+ 'Content-Type': 'application/x-www-form-urlencoded',
57
+ Accept: 'application/json',
58
+ },
59
+ body: data.toString(),
60
+ headersTimeout: 15000,
61
+ bodyTimeout: 30000,
62
+ });
63
+ const text = await res.body.text();
64
+ if (res.statusCode === 200) {
65
+ const parsed = JSON.parse(text);
66
+ const now = Date.now();
67
+ return {
68
+ type: 'kimi',
69
+ account,
70
+ accessToken: parsed.access_token,
71
+ refreshToken: parsed.refresh_token ?? '',
72
+ expiresAt: now + parsed.expires_in * 1000,
73
+ lastRefresh: now,
74
+ };
75
+ }
76
+ try {
77
+ const err = JSON.parse(text);
78
+ if (err.error === 'authorization_pending')
79
+ continue;
80
+ if (err.error === 'slow_down') {
81
+ interval += 5000;
82
+ continue;
83
+ }
84
+ throw new Error(`Kimi device flow error: ${err.error ?? text}`);
85
+ }
86
+ catch (e) {
87
+ if (e instanceof SyntaxError)
88
+ throw new Error(`Kimi polling failed: ${text}`);
89
+ throw e;
90
+ }
91
+ }
92
+ throw new Error('Kimi device flow expired');
93
+ }
94
+ async function refreshKimiToken(current) {
95
+ if (!current.refreshToken)
96
+ throw new Error('Kimi refresh token missing');
97
+ const data = new URLSearchParams({
98
+ grant_type: 'refresh_token',
99
+ client_id: exports.KIMI_CLIENT_ID,
100
+ refresh_token: current.refreshToken,
101
+ });
102
+ const res = await (0, undici_1.request)(exports.KIMI_TOKEN_URL, {
103
+ method: 'POST',
104
+ headers: {
105
+ 'Content-Type': 'application/x-www-form-urlencoded',
106
+ Accept: 'application/json',
107
+ },
108
+ body: data.toString(),
109
+ headersTimeout: 15000,
110
+ bodyTimeout: 30000,
111
+ });
112
+ const text = await res.body.text();
113
+ if (res.statusCode !== 200) {
114
+ throw new Error(`Kimi refresh failed (${res.statusCode}): ${text}`);
115
+ }
116
+ const parsed = JSON.parse(text);
117
+ const now = Date.now();
118
+ return {
119
+ ...current,
120
+ accessToken: parsed.access_token,
121
+ refreshToken: parsed.refresh_token ?? current.refreshToken,
122
+ expiresAt: now + parsed.expires_in * 1000,
123
+ lastRefresh: now,
124
+ };
125
+ }
126
+ //# sourceMappingURL=kimi-oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kimi-oauth.js","sourceRoot":"","sources":["../../../src/llm/oauth/kimi-oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Mở URL trong browser mặc định của OS.
3
+ * Không depend `open` package để giữ deps gọn.
4
+ */
5
+ export declare function openBrowser(url: string): void;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.openBrowser = openBrowser;
4
+ const child_process_1 = require("child_process");
5
+ /**
6
+ * Mở URL trong browser mặc định của OS.
7
+ * Không depend `open` package để giữ deps gọn.
8
+ */
9
+ function openBrowser(url) {
10
+ const platform = process.platform;
11
+ let cmd;
12
+ let args;
13
+ if (platform === 'win32') {
14
+ cmd = 'cmd';
15
+ args = ['/c', 'start', '""', url.replace(/&/g, '^&')];
16
+ }
17
+ else if (platform === 'darwin') {
18
+ cmd = 'open';
19
+ args = [url];
20
+ }
21
+ else {
22
+ cmd = 'xdg-open';
23
+ args = [url];
24
+ }
25
+ const child = (0, child_process_1.spawn)(cmd, args, {
26
+ detached: true,
27
+ stdio: 'ignore',
28
+ windowsHide: true,
29
+ });
30
+ child.unref();
31
+ }
32
+ //# sourceMappingURL=open-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../../../src/llm/oauth/open-browser.ts"],"names":[],"mappings":";;AAMA,kCAsBC;AA5BD,iDAAsC;AAEtC;;;GAGG;AACH,SAAgB,WAAW,CAAC,GAAW;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,IAAI,GAAW,CAAC;IAChB,IAAI,IAAc,CAAC;IAEnB,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,GAAG,GAAG,KAAK,CAAC;QACZ,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACxD,CAAC;SAAM,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACjC,GAAG,GAAG,MAAM,CAAC;QACb,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,UAAU,CAAC;QACjB,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IAED,MAAM,KAAK,GAAG,IAAA,qBAAK,EAAC,GAAG,EAAE,IAAI,EAAE;QAC7B,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IACH,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC"}
@@ -0,0 +1,12 @@
1
+ export interface PKCECodes {
2
+ codeVerifier: string;
3
+ codeChallenge: string;
4
+ }
5
+ /**
6
+ * Generate PKCE codes theo RFC 7636 cho OAuth 2.0.
7
+ * Port từ CLIProxyAPI `internal/auth/claude/pkce.go`:
8
+ * - 96 random bytes → 128 ký tự base64url (no padding) làm verifier
9
+ * - SHA256 verifier → base64url (no padding) làm challenge
10
+ */
11
+ export declare function generatePKCECodes(): PKCECodes;
12
+ export declare function generateRandomState(): string;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generatePKCECodes = generatePKCECodes;
4
+ exports.generateRandomState = generateRandomState;
5
+ const crypto_1 = require("crypto");
6
+ /**
7
+ * Generate PKCE codes theo RFC 7636 cho OAuth 2.0.
8
+ * Port từ CLIProxyAPI `internal/auth/claude/pkce.go`:
9
+ * - 96 random bytes → 128 ký tự base64url (no padding) làm verifier
10
+ * - SHA256 verifier → base64url (no padding) làm challenge
11
+ */
12
+ function generatePKCECodes() {
13
+ const codeVerifier = base64UrlEncode((0, crypto_1.randomBytes)(96));
14
+ const codeChallenge = base64UrlEncode((0, crypto_1.createHash)('sha256').update(codeVerifier).digest());
15
+ return { codeVerifier, codeChallenge };
16
+ }
17
+ function base64UrlEncode(buf) {
18
+ return buf
19
+ .toString('base64')
20
+ .replace(/\+/g, '-')
21
+ .replace(/\//g, '_')
22
+ .replace(/=+$/, '');
23
+ }
24
+ function generateRandomState() {
25
+ return base64UrlEncode((0, crypto_1.randomBytes)(24));
26
+ }
27
+ //# sourceMappingURL=pkce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/llm/oauth/pkce.ts"],"names":[],"mappings":";;AAaA,8CAIC;AAUD,kDAEC;AA7BD,mCAAiD;AAOjD;;;;;GAKG;AACH,SAAgB,iBAAiB;IAC/B,MAAM,YAAY,GAAG,eAAe,CAAC,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,eAAe,CAAC,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1F,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;AACzC,CAAC;AAED,SAAS,eAAe,CAAC,GAAW;IAClC,OAAO,GAAG;SACP,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACxB,CAAC;AAED,SAAgB,mBAAmB;IACjC,OAAO,eAAe,CAAC,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { PKCECodes } from './pkce';
2
+ import { OAuthTokenStorage } from './store';
3
+ /**
4
+ * Qwen Device Flow OAuth — port từ `internal/auth/qwen/qwen_auth.go`.
5
+ *
6
+ * RFC 8628 Device Authorization Grant: app yêu cầu device_code + user_code,
7
+ * hiển thị user_code để user nhập trên browser, poll token định kỳ.
8
+ */
9
+ export declare const QWEN_DEVICE_CODE_URL = "https://chat.qwen.ai/api/v1/oauth2/device/code";
10
+ export declare const QWEN_TOKEN_URL = "https://chat.qwen.ai/api/v1/oauth2/token";
11
+ export declare const QWEN_CLIENT_ID = "f0304373b74a44d2b584a3fb70ca9e56";
12
+ export interface QwenDeviceAuth {
13
+ deviceCode: string;
14
+ userCode: string;
15
+ verificationUri: string;
16
+ verificationUriComplete?: string;
17
+ expiresIn: number;
18
+ intervalSec: number;
19
+ pkce: PKCECodes;
20
+ }
21
+ export declare function requestQwenDeviceCode(): Promise<QwenDeviceAuth>;
22
+ /**
23
+ * Poll token endpoint cho đến khi user đã authorize hoặc hết hạn / lỗi.
24
+ * Throw Error nếu user huỷ / expired.
25
+ */
26
+ export declare function pollQwenToken(device: QwenDeviceAuth, account?: string): Promise<OAuthTokenStorage>;
27
+ export declare function refreshQwenToken(current: OAuthTokenStorage): Promise<OAuthTokenStorage>;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.QWEN_CLIENT_ID = exports.QWEN_TOKEN_URL = exports.QWEN_DEVICE_CODE_URL = void 0;
4
+ exports.requestQwenDeviceCode = requestQwenDeviceCode;
5
+ exports.pollQwenToken = pollQwenToken;
6
+ exports.refreshQwenToken = refreshQwenToken;
7
+ const undici_1 = require("undici");
8
+ const pkce_1 = require("./pkce");
9
+ /**
10
+ * Qwen Device Flow OAuth — port từ `internal/auth/qwen/qwen_auth.go`.
11
+ *
12
+ * RFC 8628 Device Authorization Grant: app yêu cầu device_code + user_code,
13
+ * hiển thị user_code để user nhập trên browser, poll token định kỳ.
14
+ */
15
+ exports.QWEN_DEVICE_CODE_URL = 'https://chat.qwen.ai/api/v1/oauth2/device/code';
16
+ exports.QWEN_TOKEN_URL = 'https://chat.qwen.ai/api/v1/oauth2/token';
17
+ exports.QWEN_CLIENT_ID = 'f0304373b74a44d2b584a3fb70ca9e56';
18
+ async function requestQwenDeviceCode() {
19
+ const pkce = (0, pkce_1.generatePKCECodes)();
20
+ const data = new URLSearchParams({
21
+ client_id: exports.QWEN_CLIENT_ID,
22
+ scope: 'openid profile email model.completion',
23
+ code_challenge: pkce.codeChallenge,
24
+ code_challenge_method: 'S256',
25
+ });
26
+ const res = await (0, undici_1.request)(exports.QWEN_DEVICE_CODE_URL, {
27
+ method: 'POST',
28
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
29
+ body: data.toString(),
30
+ headersTimeout: 15000,
31
+ bodyTimeout: 30000,
32
+ });
33
+ const text = await res.body.text();
34
+ if (res.statusCode !== 200) {
35
+ throw new Error(`Qwen device code request failed (${res.statusCode}): ${text}`);
36
+ }
37
+ const parsed = JSON.parse(text);
38
+ return {
39
+ deviceCode: parsed.device_code,
40
+ userCode: parsed.user_code,
41
+ verificationUri: parsed.verification_uri,
42
+ verificationUriComplete: parsed.verification_uri_complete,
43
+ expiresIn: parsed.expires_in,
44
+ intervalSec: parsed.interval ?? 5,
45
+ pkce,
46
+ };
47
+ }
48
+ /**
49
+ * Poll token endpoint cho đến khi user đã authorize hoặc hết hạn / lỗi.
50
+ * Throw Error nếu user huỷ / expired.
51
+ */
52
+ async function pollQwenToken(device, account = 'default') {
53
+ const deadline = Date.now() + device.expiresIn * 1000;
54
+ let interval = Math.max(device.intervalSec * 1000, 3000);
55
+ while (Date.now() < deadline) {
56
+ await new Promise((r) => setTimeout(r, interval));
57
+ const data = new URLSearchParams({
58
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
59
+ client_id: exports.QWEN_CLIENT_ID,
60
+ device_code: device.deviceCode,
61
+ code_verifier: device.pkce.codeVerifier,
62
+ });
63
+ const res = await (0, undici_1.request)(exports.QWEN_TOKEN_URL, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/x-www-form-urlencoded',
67
+ Accept: 'application/json',
68
+ },
69
+ body: data.toString(),
70
+ headersTimeout: 15000,
71
+ bodyTimeout: 30000,
72
+ });
73
+ const text = await res.body.text();
74
+ if (res.statusCode === 200) {
75
+ const parsed = JSON.parse(text);
76
+ const now = Date.now();
77
+ return {
78
+ type: 'qwen',
79
+ account,
80
+ accessToken: parsed.access_token,
81
+ refreshToken: parsed.refresh_token ?? '',
82
+ expiresAt: now + parsed.expires_in * 1000,
83
+ lastRefresh: now,
84
+ };
85
+ }
86
+ // RFC 8628 errors: authorization_pending → tiếp tục, slow_down → +5s,
87
+ // còn lại = abort
88
+ try {
89
+ const err = JSON.parse(text);
90
+ if (err.error === 'authorization_pending')
91
+ continue;
92
+ if (err.error === 'slow_down') {
93
+ interval += 5000;
94
+ continue;
95
+ }
96
+ throw new Error(`Qwen device flow error: ${err.error ?? text}`);
97
+ }
98
+ catch (e) {
99
+ if (e instanceof SyntaxError)
100
+ throw new Error(`Qwen polling failed: ${text}`);
101
+ throw e;
102
+ }
103
+ }
104
+ throw new Error('Qwen device flow expired');
105
+ }
106
+ async function refreshQwenToken(current) {
107
+ if (!current.refreshToken)
108
+ throw new Error('Qwen refresh token missing');
109
+ const data = new URLSearchParams({
110
+ grant_type: 'refresh_token',
111
+ client_id: exports.QWEN_CLIENT_ID,
112
+ refresh_token: current.refreshToken,
113
+ });
114
+ const res = await (0, undici_1.request)(exports.QWEN_TOKEN_URL, {
115
+ method: 'POST',
116
+ headers: {
117
+ 'Content-Type': 'application/x-www-form-urlencoded',
118
+ Accept: 'application/json',
119
+ },
120
+ body: data.toString(),
121
+ headersTimeout: 15000,
122
+ bodyTimeout: 30000,
123
+ });
124
+ const text = await res.body.text();
125
+ if (res.statusCode !== 200) {
126
+ throw new Error(`Qwen refresh failed (${res.statusCode}): ${text}`);
127
+ }
128
+ const parsed = JSON.parse(text);
129
+ const now = Date.now();
130
+ return {
131
+ ...current,
132
+ accessToken: parsed.access_token,
133
+ refreshToken: parsed.refresh_token ?? current.refreshToken,
134
+ expiresAt: now + parsed.expires_in * 1000,
135
+ lastRefresh: now,
136
+ };
137
+ }
138
+ //# sourceMappingURL=qwen-oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qwen-oauth.js","sourceRoot":"","sources":["../../../src/llm/oauth/qwen-oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Token storage cho 1 OAuth account.
3
+ *
4
+ * Lưu vào `~/.redai/oauth/<provider>/<account>.json` (chmod 600).
5
+ * Đa số provider (Claude/Codex/Gemini) chỉ có 1 account → file `default.json`.
6
+ * Multi-account (M4) thêm folder con + tên account.
7
+ */
8
+ export interface OAuthTokenStorage {
9
+ /** Provider type: claude | codex | gemini | ... */
10
+ type: string;
11
+ /** Account identifier mặc định 'default'. */
12
+ account: string;
13
+ /** OAuth access_token. */
14
+ accessToken: string;
15
+ /** OAuth refresh_token (long-lived). */
16
+ refreshToken: string;
17
+ /** Optional JWT id_token (Anthropic không trả nhưng Google CLI có). */
18
+ idToken?: string;
19
+ /** Email account (informational, hiển thị trong status). */
20
+ email?: string;
21
+ /** Unix ms khi access_token hết hạn. */
22
+ expiresAt: number;
23
+ /** Unix ms lần cuối refresh. */
24
+ lastRefresh: number;
25
+ /** Free-form metadata (provider-specific). */
26
+ metadata?: Record<string, unknown>;
27
+ }
28
+ export declare function oauthDir(provider: string): string;
29
+ export declare function oauthFilePath(provider: string, account?: string): string;
30
+ export declare function saveToken(provider: string, token: OAuthTokenStorage): void;
31
+ export declare function loadToken(provider: string, account?: string): OAuthTokenStorage | null;
32
+ export declare function listAccounts(provider: string): string[];
33
+ export declare function deleteToken(provider: string, account?: string): void;
34
+ export declare function listProvidersWithLogin(): string[];