@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 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 回调地址(本地 stdio 自收) */
8
+ /** OAuth 回调地址(本地 8199) */
7
9
  redirectUri?: string;
8
- /** token 落盘文件名(~/.recolx/ 内;默认 tokens-mcp.json 与 CLI 分离) */
10
+ /** token 落盘文件名;缺省 tokens-mcp-{env}.json(每环境独立登录态) */
9
11
  tokenFile?: string;
10
- /** token 落盘目录(测试注入用),默认 ~/.recolx */
12
+ /** token 落盘目录;缺省 ~/.recolx */
11
13
  tokenDir?: string;
12
- /** API base,默认 http://127.0.0.1:8005(本地 dev) */
14
+ /** 环境:local | staging | prod;缺省按 配置文件 > RECOLX_ENV > prod */
15
+ env?: RecolxEnv;
16
+ /** 后端地址覆盖(自定义部署逃生舱);缺省按环境预设 */
13
17
  apiBase?: string;
14
- /** 授权页地址;缺省 = `${apiBase}/open/oauth/authorize` */
18
+ /** 授权页地址(developer 模式);缺省 = `${apiBase}/open/oauth/authorize` */
15
19
  authorizationUrl?: string;
16
- /** token/refresh 端点;缺省 = `${apiBase}/open/oauth/token`(grant_type 区分) */
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 DEFAULT_API_BASE: string;
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 function resolveConfig(config: RecolxConfig): Required<Pick<RecolxConfig, "clientId" | "redirectUri" | "tokenFile" | "apiBase" | "authorizationUrl" | "tokenUrl">> & Pick<RecolxConfig, "extraHeaders" | "fetch" | "tokenDir">;
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
- /** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(env=local 才接受)。 */
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
- /** 授权码换 token 并落盘;失败抛 Error(含状态码与响应体)。 */
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(后端滚动轮换),失败返回 null。 */
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
- readonly oauth: OAuth;
78
- readonly apiBase: string;
79
- readonly extraHeaders: Record<string, string>;
122
+ oauth: OAuth;
123
+ apiBase: string;
124
+ env: RecolxEnv;
125
+ extraHeaders: Record<string, string>;
80
126
  /** 测试/联调用:设置后不再走 OAuth token 获取 */
81
127
  staticToken?: string;
82
- private readonly fetchImpl;
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
- var DEFAULT_API_BASE = process.env.RECOLX_API_BASE ?? "http://127.0.0.1:8005";
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 apiBase = (config.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, "");
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 ?? "tokens-mcp.json",
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 = join(homedir(), ".recolx")) {
83
+ constructor(filename = "tokens-mcp.json", dir = join2(homedir2(), ".recolx")) {
28
84
  this.dir = dir;
29
- this.path = join(dir, filename);
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 = join(homedir(), ".recolx", "tokens-mcp.json");
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
- /** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(env=local 才接受)。 */
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: generateState(),
185
+ state,
95
186
  ...extraParams
96
187
  });
97
- return { url: `${this.authorizationUrl}?${params.toString()}`, codeVerifier, state: params.get("state") };
188
+ return { url: `${this.authorizationUrl}?${params.toString()}`, codeVerifier, state };
98
189
  }
99
- /** 授权码换 token 并落盘;失败抛 Error(含状态码与响应体)。 */
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(后端滚动轮换),失败返回 null。 */
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
- runOAuthCallback
654
+ resolveEnv,
655
+ runOAuthCallback,
656
+ writeEnvConfig
493
657
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uzqw/shared",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Shared types, zod schemas, token store and API client for recolx MCP/CLI",
5
5
  "publishConfig": {
6
6
  "access": "public"