@uzqw/shared 0.1.1 → 0.1.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/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://192.168.3.194:8000(局域网 gateway);可经环境变量/实例配置覆盖 */
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,75 @@
1
1
  // src/config.ts
2
- var DEFAULT_API_BASE = process.env.RECOLX_API_BASE ?? "http://192.168.3.194:8000";
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 LEGACY_BAKED_API_BASE = "http://192.168.3.194:8000";
12
+ var ENV_PRESETS = {
13
+ // local 与 staging 共用同一套 Logto(test-auth.recolx.ai):本地测试环境不再走 developer OAuth 单独体系。
14
+ // 前提:本地后端(192.168.3.194:8000)已接真实 Logto,且用户先用 App/Web 登录过(本地库有该 Logto sub)。
15
+ local: {
16
+ apiBase: "http://192.168.3.194:8000",
17
+ authMode: "logto",
18
+ logtoEndpoint: DEFAULT_LOGTO_ENDPOINT,
19
+ logtoClientId: DEFAULT_LOGTO_CLIENT_ID
20
+ },
21
+ staging: {
22
+ apiBase: "https://test-202412-api.recolx.cn/api",
23
+ authMode: "logto",
24
+ logtoEndpoint: DEFAULT_LOGTO_ENDPOINT,
25
+ logtoClientId: DEFAULT_LOGTO_CLIENT_ID
26
+ },
27
+ prod: { apiBase: DEFAULT_API_BASE, authMode: "logto", logtoEndpoint: "", logtoClientId: "" }
28
+ };
29
+ function envConfigPath() {
30
+ return process.env.RECOLX_ENV_CONFIG ?? join(homedir(), ".recolx", "config.json");
31
+ }
32
+ function isEnv(v) {
33
+ return v === "local" || v === "staging" || v === "prod";
34
+ }
35
+ function readEnvConfig() {
36
+ try {
37
+ const raw = JSON.parse(readFileSync(envConfigPath(), "utf8"));
38
+ return isEnv(raw.env) ? raw.env : null;
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+ function writeEnvConfig(env) {
44
+ mkdirSync(join(homedir(), ".recolx"), { recursive: true });
45
+ writeFileSync(envConfigPath(), JSON.stringify({ env }, null, 2) + "\n", "utf8");
46
+ }
47
+ function resolveEnv(env) {
48
+ if (isEnv(env)) return env;
49
+ const fromFile = readEnvConfig();
50
+ if (fromFile) return fromFile;
51
+ const fromVar = process.env.RECOLX_ENV;
52
+ if (isEnv(fromVar)) return fromVar;
53
+ return DEFAULT_ENV;
54
+ }
5
55
  function resolveConfig(config) {
6
- const apiBase = (config.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, "");
56
+ const env = resolveEnv(config.env);
57
+ const preset = ENV_PRESETS[env];
58
+ const envApiBase = process.env.RECOLX_API_BASE?.replace(/\/+$/, "");
59
+ const legacyOverride = env !== "local" && envApiBase === LEGACY_BAKED_API_BASE;
60
+ const apiBase = (config.apiBase ?? (legacyOverride ? preset.apiBase : envApiBase ?? preset.apiBase)).replace(/\/+$/, "");
61
+ const authMode = process.env.RECOLX_AUTH_MODE === "developer" || process.env.RECOLX_AUTH_MODE === "logto" ? process.env.RECOLX_AUTH_MODE : preset.authMode;
7
62
  return {
8
63
  clientId: config.clientId ?? DEFAULT_CLIENT_ID,
9
64
  redirectUri: config.redirectUri ?? DEFAULT_REDIRECT_URI,
10
- tokenFile: config.tokenFile ?? "tokens-mcp.json",
65
+ tokenFile: config.tokenFile ?? `tokens-mcp-${env}.json`,
11
66
  apiBase,
12
67
  authorizationUrl: config.authorizationUrl ?? `${apiBase}/open/oauth/authorize`,
13
68
  tokenUrl: config.tokenUrl ?? `${apiBase}/open/oauth/token`,
69
+ env,
70
+ authMode,
71
+ logtoEndpoint: (config.logtoEndpoint ?? process.env.RECOLX_LOGTO_ENDPOINT ?? preset.logtoEndpoint).replace(/\/+$/, ""),
72
+ logtoClientId: config.logtoClientId ?? process.env.RECOLX_LOGTO_CLIENT_ID ?? preset.logtoClientId,
14
73
  extraHeaders: config.extraHeaders ?? {},
15
74
  fetch: config.fetch,
16
75
  tokenDir: config.tokenDir
@@ -19,14 +78,14 @@ function resolveConfig(config) {
19
78
 
20
79
  // src/token-store.ts
21
80
  import { readFile, writeFile, mkdir, rm } from "fs/promises";
22
- import { join } from "path";
23
- import { homedir } from "os";
81
+ import { join as join2 } from "path";
82
+ import { homedir as homedir2 } from "os";
24
83
  var TokenStore = class {
25
84
  dir;
26
85
  path;
27
- constructor(filename = "tokens-mcp.json", dir = join(homedir(), ".recolx")) {
86
+ constructor(filename = "tokens-mcp.json", dir = join2(homedir2(), ".recolx")) {
28
87
  this.dir = dir;
29
- this.path = join(dir, filename);
88
+ this.path = join2(dir, filename);
30
89
  }
31
90
  async save(tokenSet) {
32
91
  await mkdir(this.dir, { recursive: true });
@@ -46,7 +105,7 @@ var TokenStore = class {
46
105
  }
47
106
  }
48
107
  };
49
- var TOKENS_MCP_PATH = join(homedir(), ".recolx", "tokens-mcp.json");
108
+ var TOKENS_MCP_PATH = join2(homedir2(), ".recolx", "tokens-mcp.json");
50
109
 
51
110
  // src/oauth.ts
52
111
  import { createHash, randomBytes } from "crypto";
@@ -73,17 +132,52 @@ var OAuth = class {
73
132
  tokenStore;
74
133
  authorizationUrl;
75
134
  tokenUrl;
135
+ /** developer | logto(resolveConfig 已解析) */
136
+ mode;
76
137
  fetchImpl;
77
138
  constructor(config) {
78
139
  this.config = resolveConfig(config);
79
140
  this.tokenStore = new TokenStore(this.config.tokenFile, this.config.tokenDir);
80
141
  this.authorizationUrl = this.config.authorizationUrl;
81
142
  this.tokenUrl = this.config.tokenUrl;
143
+ this.mode = this.config.authMode;
82
144
  this.fetchImpl = this.config.fetch ?? fetch;
83
145
  }
84
- /** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(env=local 才接受)。 */
146
+ /** logto 模式 client id 校验:缺省抛错(authorize / exchangeCode 共用) */
147
+ logtoClientId() {
148
+ const id = this.config.logtoClientId;
149
+ if (!id) {
150
+ 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");
151
+ }
152
+ return id;
153
+ }
154
+ /** logto 模式端点校验:缺省抛错(prod 独立 Logto 未配置时明确报错,不静默连测试实例) */
155
+ logtoEndpoint() {
156
+ const ep = this.config.logtoEndpoint;
157
+ if (!ep) {
158
+ 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");
159
+ }
160
+ return ep;
161
+ }
162
+ /** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(developer 模式,env=local 才接受)。 */
85
163
  createAuthorizationRequest(extraParams = {}) {
86
164
  const codeVerifier = generateCodeVerifier();
165
+ const state = generateState();
166
+ if (this.mode === "logto") {
167
+ const { redirectUri } = this.config;
168
+ const logtoClientId = this.logtoClientId();
169
+ const logtoEndpoint = this.logtoEndpoint();
170
+ const params2 = new URLSearchParams({
171
+ client_id: logtoClientId,
172
+ redirect_uri: redirectUri,
173
+ response_type: "code",
174
+ scope: "openid profile email",
175
+ code_challenge: generateCodeChallenge(codeVerifier),
176
+ code_challenge_method: "S256",
177
+ state
178
+ });
179
+ return { url: `${logtoEndpoint}/oidc/auth?${params2.toString()}`, codeVerifier, state };
180
+ }
87
181
  const params = new URLSearchParams({
88
182
  client_id: this.config.clientId,
89
183
  redirect_uri: this.config.redirectUri,
@@ -91,13 +185,55 @@ var OAuth = class {
91
185
  scope: "read",
92
186
  code_challenge: generateCodeChallenge(codeVerifier),
93
187
  code_challenge_method: "S256",
94
- state: generateState(),
188
+ state,
95
189
  ...extraParams
96
190
  });
97
- return { url: `${this.authorizationUrl}?${params.toString()}`, codeVerifier, state: params.get("state") };
191
+ return { url: `${this.authorizationUrl}?${params.toString()}`, codeVerifier, state };
98
192
  }
99
- /** 授权码换 token 并落盘;失败抛 Error(含状态码与响应体)。 */
193
+ /** 授权码换凭证并落盘;失败抛 Error(含状态码与响应体)。
194
+ * developer 模式:直接换 developerOAuth JWT;
195
+ * logto 模式:先换 Logto access token,再 POST /open/oauth/apikey 换 sk- 长期 key(落盘的是 sk- key)。 */
100
196
  async exchangeCode(code, codeVerifier) {
197
+ if (this.mode === "logto") {
198
+ const { redirectUri } = this.config;
199
+ const logtoClientId = this.logtoClientId();
200
+ const logtoEndpoint = this.logtoEndpoint();
201
+ const tokenRes = await this.fetchImpl(`${logtoEndpoint}/oidc/token`, {
202
+ method: "POST",
203
+ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
204
+ body: new URLSearchParams({
205
+ grant_type: "authorization_code",
206
+ code,
207
+ code_verifier: codeVerifier,
208
+ client_id: logtoClientId,
209
+ redirect_uri: redirectUri
210
+ })
211
+ });
212
+ if (!tokenRes.ok) {
213
+ throw new Error(`Logto token exchange failed: ${tokenRes.status} ${await tokenRes.text()}`);
214
+ }
215
+ const tokenBody = await tokenRes.json();
216
+ const logtoToken = tokenBody.access_token;
217
+ if (typeof logtoToken !== "string" || logtoToken.length === 0) {
218
+ throw new Error("Logto token exchange: missing access_token");
219
+ }
220
+ const keyRes = await this.fetchImpl(`${this.config.apiBase}/open/oauth/apikey`, {
221
+ method: "POST",
222
+ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
223
+ body: new URLSearchParams({ logto_token: logtoToken })
224
+ });
225
+ if (!keyRes.ok) {
226
+ throw new Error(`Issue API key failed: ${keyRes.status} ${await keyRes.text()}`);
227
+ }
228
+ const body = await keyRes.json();
229
+ const apiKey = typeof body.api_key === "string" ? body.api_key : "";
230
+ if (!apiKey) {
231
+ throw new Error("Issue API key: missing api_key in response");
232
+ }
233
+ const tokenSet2 = { access_token: apiKey, token_type: "Bearer" };
234
+ await this.tokenStore.save(tokenSet2);
235
+ return tokenSet2;
236
+ }
101
237
  const res = await this.fetchImpl(this.tokenUrl, {
102
238
  method: "POST",
103
239
  headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
@@ -110,8 +246,9 @@ var OAuth = class {
110
246
  await this.tokenStore.save(tokenSet);
111
247
  return tokenSet;
112
248
  }
113
- /** 用 refresh_token 换新 token(后端滚动轮换),失败返回 null。 */
249
+ /** 用 refresh_token 换新 token(后端滚动轮换);logto 模式无 refresh(sk- key 长期有效),恒返回 null。 */
114
250
  async refresh(refreshToken) {
251
+ if (this.mode === "logto") return null;
115
252
  const res = await this.fetchImpl(this.tokenUrl, {
116
253
  method: "POST",
117
254
  headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
@@ -159,20 +296,36 @@ var OAuth = class {
159
296
  var RecolxClient = class {
160
297
  oauth;
161
298
  apiBase;
299
+ env;
162
300
  extraHeaders;
163
301
  /** 测试/联调用:设置后不再走 OAuth token 获取 */
164
302
  staticToken;
165
303
  fetchImpl;
166
304
  constructor(config) {
305
+ this.applyConfig(config);
306
+ }
307
+ applyConfig(config) {
167
308
  const resolved = resolveConfig(config);
168
309
  this.oauth = new OAuth(resolved);
169
310
  this.apiBase = resolved.apiBase;
311
+ this.env = resolved.env;
170
312
  this.extraHeaders = resolved.extraHeaders ?? {};
171
313
  this.fetchImpl = resolved.fetch ?? fetch;
172
314
  }
173
315
  get auth() {
174
316
  return this.oauth;
175
317
  }
318
+ /** 切换环境:写 ~/.recolx/config.json 并即时重建 OAuth/API base;返回新状态 */
319
+ async setEnv(env) {
320
+ writeEnvConfig(env);
321
+ this.applyConfig({});
322
+ return this.getEnvState();
323
+ }
324
+ /** 当前环境状态(含登录态) */
325
+ async getEnvState() {
326
+ const token = await this.oauth.getAccessToken();
327
+ return { env: this.env, api_base: this.apiBase, auth_mode: this.oauth.mode, logged_in: token !== null };
328
+ }
176
329
  async request(path, init) {
177
330
  const token = init?.token ?? this.staticToken ?? await this.oauth.getAccessToken();
178
331
  if (!token) {
@@ -308,6 +461,10 @@ var FileDetailSchema = FileSchema.extend({
308
461
  var LoginInput = z.object({}).optional();
309
462
  var LogoutInput = z.object({}).optional();
310
463
  var GetCurrentUserInput = z.object({}).optional();
464
+ var SetEnvInput = z.object({
465
+ 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")
466
+ });
467
+ var GetEnvInput = z.object({}).optional();
311
468
  var ListFilesInput = z.object({
312
469
  page: z.number().int().min(1).optional().default(1),
313
470
  page_size: z.number().int().min(1).max(100).optional().default(20),
@@ -460,12 +617,17 @@ export {
460
617
  CurrentUserSchema,
461
618
  DEFAULT_API_BASE,
462
619
  DEFAULT_CLIENT_ID,
620
+ DEFAULT_ENV,
621
+ DEFAULT_LOGTO_CLIENT_ID,
622
+ DEFAULT_LOGTO_ENDPOINT,
463
623
  DEFAULT_REDIRECT_URI,
464
624
  DeviceSchema,
625
+ ENV_PRESETS,
465
626
  FileDetailSchema,
466
627
  FileListSchema,
467
628
  FileSchema,
468
629
  GetCurrentUserInput,
630
+ GetEnvInput,
469
631
  GetFileInput,
470
632
  GetNoteInput,
471
633
  GetTranscriptInput,
@@ -475,12 +637,14 @@ export {
475
637
  OAuth,
476
638
  RecolxClient,
477
639
  SegmentSchema,
640
+ SetEnvInput,
478
641
  SubscriptionSchema,
479
642
  SummarySchema,
480
643
  TOKENS_MCP_PATH,
481
644
  TokenStore,
482
645
  TranscriptSchema,
483
646
  classifyError,
647
+ envConfigPath,
484
648
  errorTypeFromStatus,
485
649
  generateCodeChallenge,
486
650
  generateCodeVerifier,
@@ -488,6 +652,9 @@ export {
488
652
  localDayEnd,
489
653
  localDayStart,
490
654
  parseApiTimestamp,
655
+ readEnvConfig,
491
656
  resolveConfig,
492
- runOAuthCallback
657
+ resolveEnv,
658
+ runOAuthCallback,
659
+ writeEnvConfig
493
660
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uzqw/shared",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Shared types, zod schemas, token store and API client for recolx MCP/CLI",
5
5
  "publishConfig": {
6
6
  "access": "public"