@uzqw/shared 0.1.0 → 0.1.2
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/dist/index.d.ts +75 -16
- package/dist/index.js +178 -14
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,29 +1,60 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
/** 环境:local 本地测试 / staging 线上测试 / prod 线上正式 */
|
|
4
|
+
type RecolxEnv = "local" | "staging" | "prod";
|
|
3
5
|
interface RecolxConfig {
|
|
4
6
|
/** OAuth client id(developer 客户端注册名) */
|
|
5
7
|
clientId?: string;
|
|
6
|
-
/** OAuth 回调地址(本地
|
|
8
|
+
/** OAuth 回调地址(本地 8199) */
|
|
7
9
|
redirectUri?: string;
|
|
8
|
-
/** token
|
|
10
|
+
/** token 落盘文件名;缺省 tokens-mcp-{env}.json(每环境独立登录态) */
|
|
9
11
|
tokenFile?: string;
|
|
10
|
-
/** token
|
|
12
|
+
/** token 落盘目录;缺省 ~/.recolx */
|
|
11
13
|
tokenDir?: string;
|
|
12
|
-
/**
|
|
14
|
+
/** 环境:local | staging | prod;缺省按 配置文件 > RECOLX_ENV > prod */
|
|
15
|
+
env?: RecolxEnv;
|
|
16
|
+
/** 后端地址覆盖(自定义部署逃生舱);缺省按环境预设 */
|
|
13
17
|
apiBase?: string;
|
|
14
|
-
/**
|
|
18
|
+
/** 授权页地址(developer 模式);缺省 = `${apiBase}/open/oauth/authorize` */
|
|
15
19
|
authorizationUrl?: string;
|
|
16
|
-
/** token/refresh
|
|
20
|
+
/** token/refresh 端点(developer 模式);缺省 = `${apiBase}/open/oauth/token` */
|
|
17
21
|
tokenUrl?: string;
|
|
22
|
+
/** Logto 端点(logto 模式),默认 https://test-auth.recolx.ai */
|
|
23
|
+
logtoEndpoint?: string;
|
|
24
|
+
/** Logto application id(logto 模式;需在 Logto 控制台注册 redirect_uri=http://localhost:8199/auth/callback) */
|
|
25
|
+
logtoClientId?: string;
|
|
18
26
|
/** 附加请求头 */
|
|
19
27
|
extraHeaders?: Record<string, string>;
|
|
20
28
|
/** fetch 实现(测试注入用),默认全局 fetch */
|
|
21
29
|
fetch?: typeof fetch;
|
|
22
30
|
}
|
|
23
|
-
declare const
|
|
31
|
+
declare const DEFAULT_ENV: RecolxEnv;
|
|
24
32
|
declare const DEFAULT_CLIENT_ID: string;
|
|
25
33
|
declare const DEFAULT_REDIRECT_URI = "http://localhost:8199/auth/callback";
|
|
26
|
-
declare
|
|
34
|
+
declare const DEFAULT_LOGTO_ENDPOINT: string;
|
|
35
|
+
declare const DEFAULT_LOGTO_CLIENT_ID: string;
|
|
36
|
+
/** 默认后端地址(prod 预设;RECOLX_API_BASE 可覆盖) */
|
|
37
|
+
declare const DEFAULT_API_BASE = "https://api.recolx.ai/api";
|
|
38
|
+
/** 环境预设:后端地址 + 鉴权模式 + Logto 配置(logto 模式用)。authMode 由环境推导,不再猜 IP。
|
|
39
|
+
* prod 的 Logto 为独立实例,地址/应用 id 未知——留空,logto 模式登录时明确报错,绝不静默连到测试实例。 */
|
|
40
|
+
declare const ENV_PRESETS: Record<RecolxEnv, {
|
|
41
|
+
apiBase: string;
|
|
42
|
+
authMode: "developer" | "logto";
|
|
43
|
+
logtoEndpoint: string;
|
|
44
|
+
logtoClientId: string;
|
|
45
|
+
}>;
|
|
46
|
+
/** 环境配置文件路径(AI set_env / install --env 共用);RECOLX_ENV_CONFIG 为测试钩子。 */
|
|
47
|
+
declare function envConfigPath(): string;
|
|
48
|
+
/** 读取环境配置文件中的 env;无文件/非法值返回 null */
|
|
49
|
+
declare function readEnvConfig(): RecolxEnv | null;
|
|
50
|
+
/** 写入环境配置文件(AI set_env / install --env 共用) */
|
|
51
|
+
declare function writeEnvConfig(env: RecolxEnv): void;
|
|
52
|
+
/** 解析最终环境:实例配置 > 配置文件 > RECOLX_ENV > prod */
|
|
53
|
+
declare function resolveEnv(env?: RecolxEnv): RecolxEnv;
|
|
54
|
+
declare function resolveConfig(config: RecolxConfig): Required<Pick<RecolxConfig, "clientId" | "redirectUri" | "tokenFile" | "apiBase" | "authorizationUrl" | "tokenUrl" | "logtoEndpoint" | "logtoClientId">> & {
|
|
55
|
+
env: RecolxEnv;
|
|
56
|
+
authMode: "developer" | "logto";
|
|
57
|
+
} & Pick<RecolxConfig, "extraHeaders" | "fetch" | "tokenDir">;
|
|
27
58
|
|
|
28
59
|
interface TokenSet {
|
|
29
60
|
access_token: string;
|
|
@@ -57,13 +88,21 @@ declare class OAuth {
|
|
|
57
88
|
readonly tokenStore: TokenStore;
|
|
58
89
|
readonly authorizationUrl: string;
|
|
59
90
|
readonly tokenUrl: string;
|
|
91
|
+
/** developer | logto(resolveConfig 已解析) */
|
|
92
|
+
readonly mode: "developer" | "logto";
|
|
60
93
|
private readonly fetchImpl;
|
|
61
94
|
constructor(config: RecolxConfig);
|
|
62
|
-
/**
|
|
95
|
+
/** logto 模式 client id 校验:缺省抛错(authorize / exchangeCode 共用) */
|
|
96
|
+
private logtoClientId;
|
|
97
|
+
/** logto 模式端点校验:缺省抛错(prod 独立 Logto 未配置时明确报错,不静默连测试实例) */
|
|
98
|
+
private logtoEndpoint;
|
|
99
|
+
/** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(developer 模式,env=local 才接受)。 */
|
|
63
100
|
createAuthorizationRequest(extraParams?: Record<string, string>): AuthorizationRequest;
|
|
64
|
-
/**
|
|
101
|
+
/** 授权码换凭证并落盘;失败抛 Error(含状态码与响应体)。
|
|
102
|
+
* developer 模式:直接换 developerOAuth JWT;
|
|
103
|
+
* logto 模式:先换 Logto access token,再 POST /open/oauth/apikey 换 sk- 长期 key(落盘的是 sk- key)。 */
|
|
65
104
|
exchangeCode(code: string, codeVerifier: string): Promise<TokenSet>;
|
|
66
|
-
/** 用 refresh_token 换新 token
|
|
105
|
+
/** 用 refresh_token 换新 token(后端滚动轮换);logto 模式无 refresh(sk- key 长期有效),恒返回 null。 */
|
|
67
106
|
refresh(refreshToken: string): Promise<TokenSet | null>;
|
|
68
107
|
/** 取可用 access token:未过期直接用;过期则滚动刷新;失败返回 null。 */
|
|
69
108
|
getAccessToken(): Promise<string | null>;
|
|
@@ -73,15 +112,27 @@ declare class OAuth {
|
|
|
73
112
|
logout(): Promise<void>;
|
|
74
113
|
}
|
|
75
114
|
|
|
115
|
+
interface EnvState {
|
|
116
|
+
env: RecolxEnv;
|
|
117
|
+
api_base: string;
|
|
118
|
+
auth_mode: "developer" | "logto";
|
|
119
|
+
logged_in: boolean;
|
|
120
|
+
}
|
|
76
121
|
declare class RecolxClient {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
122
|
+
oauth: OAuth;
|
|
123
|
+
apiBase: string;
|
|
124
|
+
env: RecolxEnv;
|
|
125
|
+
extraHeaders: Record<string, string>;
|
|
80
126
|
/** 测试/联调用:设置后不再走 OAuth token 获取 */
|
|
81
127
|
staticToken?: string;
|
|
82
|
-
private
|
|
128
|
+
private fetchImpl;
|
|
83
129
|
constructor(config: RecolxConfig);
|
|
130
|
+
private applyConfig;
|
|
84
131
|
get auth(): OAuth;
|
|
132
|
+
/** 切换环境:写 ~/.recolx/config.json 并即时重建 OAuth/API base;返回新状态 */
|
|
133
|
+
setEnv(env: RecolxEnv): Promise<EnvState>;
|
|
134
|
+
/** 当前环境状态(含登录态) */
|
|
135
|
+
getEnvState(): Promise<EnvState>;
|
|
85
136
|
request(path: string, init?: RequestInit & {
|
|
86
137
|
token?: string;
|
|
87
138
|
}): Promise<unknown>;
|
|
@@ -617,6 +668,14 @@ type RecolxFileDetail = z.infer<typeof FileDetailSchema>;
|
|
|
617
668
|
declare const LoginInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
|
|
618
669
|
declare const LogoutInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
|
|
619
670
|
declare const GetCurrentUserInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
|
|
671
|
+
declare const SetEnvInput: z.ZodObject<{
|
|
672
|
+
env: z.ZodEnum<["local", "staging", "prod"]>;
|
|
673
|
+
}, "strip", z.ZodTypeAny, {
|
|
674
|
+
env: "local" | "staging" | "prod";
|
|
675
|
+
}, {
|
|
676
|
+
env: "local" | "staging" | "prod";
|
|
677
|
+
}>;
|
|
678
|
+
declare const GetEnvInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
|
|
620
679
|
declare const ListFilesInput: z.ZodObject<{
|
|
621
680
|
page: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
622
681
|
page_size: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
@@ -705,4 +764,4 @@ interface RunOAuthCallbackOpts {
|
|
|
705
764
|
}
|
|
706
765
|
declare function runOAuthCallback(opts: RunOAuthCallbackOpts): Promise<OAuthCallbackResult>;
|
|
707
766
|
|
|
708
|
-
export { type AuthorizationRequest, CurrentUserSchema, DEFAULT_API_BASE, DEFAULT_CLIENT_ID, DEFAULT_REDIRECT_URI, DeviceSchema, FileDetailSchema, FileListSchema, FileSchema, GetCurrentUserInput, GetFileInput, GetNoteInput, GetTranscriptInput, ListFilesInput, LoginInput, LogoutInput, OAuth, type OAuthCallbackResult, RecolxClient, type RecolxConfig, type RecolxCurrentUser, type RecolxDevice, type RecolxErrorType, type RecolxFile, type RecolxFileDetail, type RecolxFileList, type RecolxSegment, type RecolxSubscription, type RecolxSummary, type RecolxTranscript, type RunOAuthCallbackOpts, SegmentSchema, SubscriptionSchema, SummarySchema, TOKENS_MCP_PATH, type TokenSet, TokenStore, TranscriptSchema, classifyError, errorTypeFromStatus, generateCodeChallenge, generateCodeVerifier, generateState, localDayEnd, localDayStart, parseApiTimestamp, resolveConfig, runOAuthCallback };
|
|
767
|
+
export { type AuthorizationRequest, CurrentUserSchema, DEFAULT_API_BASE, DEFAULT_CLIENT_ID, DEFAULT_ENV, DEFAULT_LOGTO_CLIENT_ID, DEFAULT_LOGTO_ENDPOINT, DEFAULT_REDIRECT_URI, DeviceSchema, ENV_PRESETS, type EnvState, FileDetailSchema, FileListSchema, FileSchema, GetCurrentUserInput, GetEnvInput, GetFileInput, GetNoteInput, GetTranscriptInput, ListFilesInput, LoginInput, LogoutInput, OAuth, type OAuthCallbackResult, RecolxClient, type RecolxConfig, type RecolxCurrentUser, type RecolxDevice, type RecolxEnv, type RecolxErrorType, type RecolxFile, type RecolxFileDetail, type RecolxFileList, type RecolxSegment, type RecolxSubscription, type RecolxSummary, type RecolxTranscript, type RunOAuthCallbackOpts, SegmentSchema, SetEnvInput, SubscriptionSchema, SummarySchema, TOKENS_MCP_PATH, type TokenSet, TokenStore, TranscriptSchema, classifyError, envConfigPath, errorTypeFromStatus, generateCodeChallenge, generateCodeVerifier, generateState, localDayEnd, localDayStart, parseApiTimestamp, readEnvConfig, resolveConfig, resolveEnv, runOAuthCallback, writeEnvConfig };
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,72 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
|
-
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
var DEFAULT_ENV = "prod";
|
|
3
6
|
var DEFAULT_CLIENT_ID = process.env.RECOLX_MCP_CLIENT_ID ?? "recolx-mcp";
|
|
4
7
|
var DEFAULT_REDIRECT_URI = "http://localhost:8199/auth/callback";
|
|
8
|
+
var DEFAULT_LOGTO_ENDPOINT = process.env.RECOLX_LOGTO_ENDPOINT ?? "https://test-auth.recolx.ai";
|
|
9
|
+
var DEFAULT_LOGTO_CLIENT_ID = process.env.RECOLX_LOGTO_CLIENT_ID ?? "7gxhhlqavsei898azq1e9";
|
|
10
|
+
var DEFAULT_API_BASE = "https://api.recolx.ai/api";
|
|
11
|
+
var ENV_PRESETS = {
|
|
12
|
+
// local 与 staging 共用同一套 Logto(test-auth.recolx.ai):本地测试环境不再走 developer OAuth 单独体系。
|
|
13
|
+
// 前提:本地后端(192.168.3.194:8000)已接真实 Logto,且用户先用 App/Web 登录过(本地库有该 Logto sub)。
|
|
14
|
+
local: {
|
|
15
|
+
apiBase: "http://192.168.3.194:8000",
|
|
16
|
+
authMode: "logto",
|
|
17
|
+
logtoEndpoint: DEFAULT_LOGTO_ENDPOINT,
|
|
18
|
+
logtoClientId: DEFAULT_LOGTO_CLIENT_ID
|
|
19
|
+
},
|
|
20
|
+
staging: {
|
|
21
|
+
apiBase: "https://test-202412-api.recolx.cn/api",
|
|
22
|
+
authMode: "logto",
|
|
23
|
+
logtoEndpoint: DEFAULT_LOGTO_ENDPOINT,
|
|
24
|
+
logtoClientId: DEFAULT_LOGTO_CLIENT_ID
|
|
25
|
+
},
|
|
26
|
+
prod: { apiBase: DEFAULT_API_BASE, authMode: "logto", logtoEndpoint: "", logtoClientId: "" }
|
|
27
|
+
};
|
|
28
|
+
function envConfigPath() {
|
|
29
|
+
return process.env.RECOLX_ENV_CONFIG ?? join(homedir(), ".recolx", "config.json");
|
|
30
|
+
}
|
|
31
|
+
function isEnv(v) {
|
|
32
|
+
return v === "local" || v === "staging" || v === "prod";
|
|
33
|
+
}
|
|
34
|
+
function readEnvConfig() {
|
|
35
|
+
try {
|
|
36
|
+
const raw = JSON.parse(readFileSync(envConfigPath(), "utf8"));
|
|
37
|
+
return isEnv(raw.env) ? raw.env : null;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function writeEnvConfig(env) {
|
|
43
|
+
mkdirSync(join(homedir(), ".recolx"), { recursive: true });
|
|
44
|
+
writeFileSync(envConfigPath(), JSON.stringify({ env }, null, 2) + "\n", "utf8");
|
|
45
|
+
}
|
|
46
|
+
function resolveEnv(env) {
|
|
47
|
+
if (isEnv(env)) return env;
|
|
48
|
+
const fromFile = readEnvConfig();
|
|
49
|
+
if (fromFile) return fromFile;
|
|
50
|
+
const fromVar = process.env.RECOLX_ENV;
|
|
51
|
+
if (isEnv(fromVar)) return fromVar;
|
|
52
|
+
return DEFAULT_ENV;
|
|
53
|
+
}
|
|
5
54
|
function resolveConfig(config) {
|
|
6
|
-
const
|
|
55
|
+
const env = resolveEnv(config.env);
|
|
56
|
+
const preset = ENV_PRESETS[env];
|
|
57
|
+
const apiBase = (config.apiBase ?? process.env.RECOLX_API_BASE ?? preset.apiBase).replace(/\/+$/, "");
|
|
58
|
+
const authMode = process.env.RECOLX_AUTH_MODE === "developer" || process.env.RECOLX_AUTH_MODE === "logto" ? process.env.RECOLX_AUTH_MODE : preset.authMode;
|
|
7
59
|
return {
|
|
8
60
|
clientId: config.clientId ?? DEFAULT_CLIENT_ID,
|
|
9
61
|
redirectUri: config.redirectUri ?? DEFAULT_REDIRECT_URI,
|
|
10
|
-
tokenFile: config.tokenFile ??
|
|
62
|
+
tokenFile: config.tokenFile ?? `tokens-mcp-${env}.json`,
|
|
11
63
|
apiBase,
|
|
12
64
|
authorizationUrl: config.authorizationUrl ?? `${apiBase}/open/oauth/authorize`,
|
|
13
65
|
tokenUrl: config.tokenUrl ?? `${apiBase}/open/oauth/token`,
|
|
66
|
+
env,
|
|
67
|
+
authMode,
|
|
68
|
+
logtoEndpoint: (config.logtoEndpoint ?? process.env.RECOLX_LOGTO_ENDPOINT ?? preset.logtoEndpoint).replace(/\/+$/, ""),
|
|
69
|
+
logtoClientId: config.logtoClientId ?? process.env.RECOLX_LOGTO_CLIENT_ID ?? preset.logtoClientId,
|
|
14
70
|
extraHeaders: config.extraHeaders ?? {},
|
|
15
71
|
fetch: config.fetch,
|
|
16
72
|
tokenDir: config.tokenDir
|
|
@@ -19,14 +75,14 @@ function resolveConfig(config) {
|
|
|
19
75
|
|
|
20
76
|
// src/token-store.ts
|
|
21
77
|
import { readFile, writeFile, mkdir, rm } from "fs/promises";
|
|
22
|
-
import { join } from "path";
|
|
23
|
-
import { homedir } from "os";
|
|
78
|
+
import { join as join2 } from "path";
|
|
79
|
+
import { homedir as homedir2 } from "os";
|
|
24
80
|
var TokenStore = class {
|
|
25
81
|
dir;
|
|
26
82
|
path;
|
|
27
|
-
constructor(filename = "tokens-mcp.json", dir =
|
|
83
|
+
constructor(filename = "tokens-mcp.json", dir = join2(homedir2(), ".recolx")) {
|
|
28
84
|
this.dir = dir;
|
|
29
|
-
this.path =
|
|
85
|
+
this.path = join2(dir, filename);
|
|
30
86
|
}
|
|
31
87
|
async save(tokenSet) {
|
|
32
88
|
await mkdir(this.dir, { recursive: true });
|
|
@@ -46,7 +102,7 @@ var TokenStore = class {
|
|
|
46
102
|
}
|
|
47
103
|
}
|
|
48
104
|
};
|
|
49
|
-
var TOKENS_MCP_PATH =
|
|
105
|
+
var TOKENS_MCP_PATH = join2(homedir2(), ".recolx", "tokens-mcp.json");
|
|
50
106
|
|
|
51
107
|
// src/oauth.ts
|
|
52
108
|
import { createHash, randomBytes } from "crypto";
|
|
@@ -73,17 +129,52 @@ var OAuth = class {
|
|
|
73
129
|
tokenStore;
|
|
74
130
|
authorizationUrl;
|
|
75
131
|
tokenUrl;
|
|
132
|
+
/** developer | logto(resolveConfig 已解析) */
|
|
133
|
+
mode;
|
|
76
134
|
fetchImpl;
|
|
77
135
|
constructor(config) {
|
|
78
136
|
this.config = resolveConfig(config);
|
|
79
137
|
this.tokenStore = new TokenStore(this.config.tokenFile, this.config.tokenDir);
|
|
80
138
|
this.authorizationUrl = this.config.authorizationUrl;
|
|
81
139
|
this.tokenUrl = this.config.tokenUrl;
|
|
140
|
+
this.mode = this.config.authMode;
|
|
82
141
|
this.fetchImpl = this.config.fetch ?? fetch;
|
|
83
142
|
}
|
|
84
|
-
/**
|
|
143
|
+
/** logto 模式 client id 校验:缺省抛错(authorize / exchangeCode 共用) */
|
|
144
|
+
logtoClientId() {
|
|
145
|
+
const id = this.config.logtoClientId;
|
|
146
|
+
if (!id) {
|
|
147
|
+
throw new Error("logto \u6A21\u5F0F\u7F3A\u5C11 client id\uFF1A\u8BF7\u8BBE\u7F6E RECOLX_LOGTO_CLIENT_ID\uFF08Logto \u63A7\u5236\u53F0\u6CE8\u518C\u7684 application id\uFF09");
|
|
148
|
+
}
|
|
149
|
+
return id;
|
|
150
|
+
}
|
|
151
|
+
/** logto 模式端点校验:缺省抛错(prod 独立 Logto 未配置时明确报错,不静默连测试实例) */
|
|
152
|
+
logtoEndpoint() {
|
|
153
|
+
const ep = this.config.logtoEndpoint;
|
|
154
|
+
if (!ep) {
|
|
155
|
+
throw new Error("logto \u6A21\u5F0F\u7F3A\u5C11 Logto \u7AEF\u70B9\uFF1A\u5F53\u524D\u73AF\u5883\u7684 Logto \u5C1A\u672A\u914D\u7F6E\uFF08prod \u9700\u8BBE\u7F6E RECOLX_LOGTO_ENDPOINT\uFF09");
|
|
156
|
+
}
|
|
157
|
+
return ep;
|
|
158
|
+
}
|
|
159
|
+
/** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(developer 模式,env=local 才接受)。 */
|
|
85
160
|
createAuthorizationRequest(extraParams = {}) {
|
|
86
161
|
const codeVerifier = generateCodeVerifier();
|
|
162
|
+
const state = generateState();
|
|
163
|
+
if (this.mode === "logto") {
|
|
164
|
+
const { redirectUri } = this.config;
|
|
165
|
+
const logtoClientId = this.logtoClientId();
|
|
166
|
+
const logtoEndpoint = this.logtoEndpoint();
|
|
167
|
+
const params2 = new URLSearchParams({
|
|
168
|
+
client_id: logtoClientId,
|
|
169
|
+
redirect_uri: redirectUri,
|
|
170
|
+
response_type: "code",
|
|
171
|
+
scope: "openid profile email",
|
|
172
|
+
code_challenge: generateCodeChallenge(codeVerifier),
|
|
173
|
+
code_challenge_method: "S256",
|
|
174
|
+
state
|
|
175
|
+
});
|
|
176
|
+
return { url: `${logtoEndpoint}/oidc/auth?${params2.toString()}`, codeVerifier, state };
|
|
177
|
+
}
|
|
87
178
|
const params = new URLSearchParams({
|
|
88
179
|
client_id: this.config.clientId,
|
|
89
180
|
redirect_uri: this.config.redirectUri,
|
|
@@ -91,13 +182,55 @@ var OAuth = class {
|
|
|
91
182
|
scope: "read",
|
|
92
183
|
code_challenge: generateCodeChallenge(codeVerifier),
|
|
93
184
|
code_challenge_method: "S256",
|
|
94
|
-
state
|
|
185
|
+
state,
|
|
95
186
|
...extraParams
|
|
96
187
|
});
|
|
97
|
-
return { url: `${this.authorizationUrl}?${params.toString()}`, codeVerifier, state
|
|
188
|
+
return { url: `${this.authorizationUrl}?${params.toString()}`, codeVerifier, state };
|
|
98
189
|
}
|
|
99
|
-
/**
|
|
190
|
+
/** 授权码换凭证并落盘;失败抛 Error(含状态码与响应体)。
|
|
191
|
+
* developer 模式:直接换 developerOAuth JWT;
|
|
192
|
+
* logto 模式:先换 Logto access token,再 POST /open/oauth/apikey 换 sk- 长期 key(落盘的是 sk- key)。 */
|
|
100
193
|
async exchangeCode(code, codeVerifier) {
|
|
194
|
+
if (this.mode === "logto") {
|
|
195
|
+
const { redirectUri } = this.config;
|
|
196
|
+
const logtoClientId = this.logtoClientId();
|
|
197
|
+
const logtoEndpoint = this.logtoEndpoint();
|
|
198
|
+
const tokenRes = await this.fetchImpl(`${logtoEndpoint}/oidc/token`, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
201
|
+
body: new URLSearchParams({
|
|
202
|
+
grant_type: "authorization_code",
|
|
203
|
+
code,
|
|
204
|
+
code_verifier: codeVerifier,
|
|
205
|
+
client_id: logtoClientId,
|
|
206
|
+
redirect_uri: redirectUri
|
|
207
|
+
})
|
|
208
|
+
});
|
|
209
|
+
if (!tokenRes.ok) {
|
|
210
|
+
throw new Error(`Logto token exchange failed: ${tokenRes.status} ${await tokenRes.text()}`);
|
|
211
|
+
}
|
|
212
|
+
const tokenBody = await tokenRes.json();
|
|
213
|
+
const logtoToken = tokenBody.access_token;
|
|
214
|
+
if (typeof logtoToken !== "string" || logtoToken.length === 0) {
|
|
215
|
+
throw new Error("Logto token exchange: missing access_token");
|
|
216
|
+
}
|
|
217
|
+
const keyRes = await this.fetchImpl(`${this.config.apiBase}/open/oauth/apikey`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
220
|
+
body: new URLSearchParams({ logto_token: logtoToken })
|
|
221
|
+
});
|
|
222
|
+
if (!keyRes.ok) {
|
|
223
|
+
throw new Error(`Issue API key failed: ${keyRes.status} ${await keyRes.text()}`);
|
|
224
|
+
}
|
|
225
|
+
const body = await keyRes.json();
|
|
226
|
+
const apiKey = typeof body.api_key === "string" ? body.api_key : "";
|
|
227
|
+
if (!apiKey) {
|
|
228
|
+
throw new Error("Issue API key: missing api_key in response");
|
|
229
|
+
}
|
|
230
|
+
const tokenSet2 = { access_token: apiKey, token_type: "Bearer" };
|
|
231
|
+
await this.tokenStore.save(tokenSet2);
|
|
232
|
+
return tokenSet2;
|
|
233
|
+
}
|
|
101
234
|
const res = await this.fetchImpl(this.tokenUrl, {
|
|
102
235
|
method: "POST",
|
|
103
236
|
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
@@ -110,8 +243,9 @@ var OAuth = class {
|
|
|
110
243
|
await this.tokenStore.save(tokenSet);
|
|
111
244
|
return tokenSet;
|
|
112
245
|
}
|
|
113
|
-
/** 用 refresh_token 换新 token
|
|
246
|
+
/** 用 refresh_token 换新 token(后端滚动轮换);logto 模式无 refresh(sk- key 长期有效),恒返回 null。 */
|
|
114
247
|
async refresh(refreshToken) {
|
|
248
|
+
if (this.mode === "logto") return null;
|
|
115
249
|
const res = await this.fetchImpl(this.tokenUrl, {
|
|
116
250
|
method: "POST",
|
|
117
251
|
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
@@ -159,20 +293,36 @@ var OAuth = class {
|
|
|
159
293
|
var RecolxClient = class {
|
|
160
294
|
oauth;
|
|
161
295
|
apiBase;
|
|
296
|
+
env;
|
|
162
297
|
extraHeaders;
|
|
163
298
|
/** 测试/联调用:设置后不再走 OAuth token 获取 */
|
|
164
299
|
staticToken;
|
|
165
300
|
fetchImpl;
|
|
166
301
|
constructor(config) {
|
|
302
|
+
this.applyConfig(config);
|
|
303
|
+
}
|
|
304
|
+
applyConfig(config) {
|
|
167
305
|
const resolved = resolveConfig(config);
|
|
168
306
|
this.oauth = new OAuth(resolved);
|
|
169
307
|
this.apiBase = resolved.apiBase;
|
|
308
|
+
this.env = resolved.env;
|
|
170
309
|
this.extraHeaders = resolved.extraHeaders ?? {};
|
|
171
310
|
this.fetchImpl = resolved.fetch ?? fetch;
|
|
172
311
|
}
|
|
173
312
|
get auth() {
|
|
174
313
|
return this.oauth;
|
|
175
314
|
}
|
|
315
|
+
/** 切换环境:写 ~/.recolx/config.json 并即时重建 OAuth/API base;返回新状态 */
|
|
316
|
+
async setEnv(env) {
|
|
317
|
+
writeEnvConfig(env);
|
|
318
|
+
this.applyConfig({});
|
|
319
|
+
return this.getEnvState();
|
|
320
|
+
}
|
|
321
|
+
/** 当前环境状态(含登录态) */
|
|
322
|
+
async getEnvState() {
|
|
323
|
+
const token = await this.oauth.getAccessToken();
|
|
324
|
+
return { env: this.env, api_base: this.apiBase, auth_mode: this.oauth.mode, logged_in: token !== null };
|
|
325
|
+
}
|
|
176
326
|
async request(path, init) {
|
|
177
327
|
const token = init?.token ?? this.staticToken ?? await this.oauth.getAccessToken();
|
|
178
328
|
if (!token) {
|
|
@@ -308,6 +458,10 @@ var FileDetailSchema = FileSchema.extend({
|
|
|
308
458
|
var LoginInput = z.object({}).optional();
|
|
309
459
|
var LogoutInput = z.object({}).optional();
|
|
310
460
|
var GetCurrentUserInput = z.object({}).optional();
|
|
461
|
+
var SetEnvInput = z.object({
|
|
462
|
+
env: z.enum(["local", "staging", "prod"]).describe("\u76EE\u6807\u73AF\u5883\uFF1Alocal \u672C\u5730\u6D4B\u8BD5 / staging \u7EBF\u4E0A\u6D4B\u8BD5 / prod \u7EBF\u4E0A\u6B63\u5F0F")
|
|
463
|
+
});
|
|
464
|
+
var GetEnvInput = z.object({}).optional();
|
|
311
465
|
var ListFilesInput = z.object({
|
|
312
466
|
page: z.number().int().min(1).optional().default(1),
|
|
313
467
|
page_size: z.number().int().min(1).max(100).optional().default(20),
|
|
@@ -460,12 +614,17 @@ export {
|
|
|
460
614
|
CurrentUserSchema,
|
|
461
615
|
DEFAULT_API_BASE,
|
|
462
616
|
DEFAULT_CLIENT_ID,
|
|
617
|
+
DEFAULT_ENV,
|
|
618
|
+
DEFAULT_LOGTO_CLIENT_ID,
|
|
619
|
+
DEFAULT_LOGTO_ENDPOINT,
|
|
463
620
|
DEFAULT_REDIRECT_URI,
|
|
464
621
|
DeviceSchema,
|
|
622
|
+
ENV_PRESETS,
|
|
465
623
|
FileDetailSchema,
|
|
466
624
|
FileListSchema,
|
|
467
625
|
FileSchema,
|
|
468
626
|
GetCurrentUserInput,
|
|
627
|
+
GetEnvInput,
|
|
469
628
|
GetFileInput,
|
|
470
629
|
GetNoteInput,
|
|
471
630
|
GetTranscriptInput,
|
|
@@ -475,12 +634,14 @@ export {
|
|
|
475
634
|
OAuth,
|
|
476
635
|
RecolxClient,
|
|
477
636
|
SegmentSchema,
|
|
637
|
+
SetEnvInput,
|
|
478
638
|
SubscriptionSchema,
|
|
479
639
|
SummarySchema,
|
|
480
640
|
TOKENS_MCP_PATH,
|
|
481
641
|
TokenStore,
|
|
482
642
|
TranscriptSchema,
|
|
483
643
|
classifyError,
|
|
644
|
+
envConfigPath,
|
|
484
645
|
errorTypeFromStatus,
|
|
485
646
|
generateCodeChallenge,
|
|
486
647
|
generateCodeVerifier,
|
|
@@ -488,6 +649,9 @@ export {
|
|
|
488
649
|
localDayEnd,
|
|
489
650
|
localDayStart,
|
|
490
651
|
parseApiTimestamp,
|
|
652
|
+
readEnvConfig,
|
|
491
653
|
resolveConfig,
|
|
492
|
-
|
|
654
|
+
resolveEnv,
|
|
655
|
+
runOAuthCallback,
|
|
656
|
+
writeEnvConfig
|
|
493
657
|
};
|