@lark-project/openclaw-lark-project 2026.3.167 → 2026.3.168
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/src/core/config-schema.js +6 -1
- package/dist/src/core/config-schema.js.map +2 -2
- package/dist/src/core/device-flow.js +2 -2
- package/dist/src/core/device-flow.js.map +2 -2
- package/dist/src/tools/mcp/project/endpoint.js +15 -8
- package/dist/src/tools/mcp/project/endpoint.js.map +2 -2
- package/package.json +1 -1
- package/skills/feishu-project/SKILL.md +14 -0
|
@@ -13,6 +13,10 @@ const ReplyModeSchema = z.union([
|
|
|
13
13
|
]).optional();
|
|
14
14
|
const ChunkModeEnum = z.enum(["newline", "paragraph", "none"]);
|
|
15
15
|
const DomainSchema = z.union([z.literal("feishu"), z.literal("lark"), z.string().regex(/^https:\/\//)]).optional();
|
|
16
|
+
const ProjectDomainSchema = z.union([z.literal("feishu"), z.literal("meegle"), z.string().regex(/^https:\/\//)]).optional();
|
|
17
|
+
const FeishuProjectConfigSchema = z.object({
|
|
18
|
+
domain: ProjectDomainSchema
|
|
19
|
+
}).optional();
|
|
16
20
|
const AllowFromSchema = z.union([z.string(), z.array(z.string())]).optional().transform((v) => {
|
|
17
21
|
if (v === void 0 || v === null) return void 0;
|
|
18
22
|
return Array.isArray(v) ? v : [v];
|
|
@@ -117,7 +121,8 @@ const FeishuAccountConfigSchema = z.object({
|
|
|
117
121
|
dedup: DedupSchema,
|
|
118
122
|
reactionNotifications: ReactionNotificationModeSchema,
|
|
119
123
|
threadSession: z.boolean().optional(),
|
|
120
|
-
uat: UATConfigSchema
|
|
124
|
+
uat: UATConfigSchema,
|
|
125
|
+
project: FeishuProjectConfigSchema
|
|
121
126
|
});
|
|
122
127
|
const FeishuConfigSchema = FeishuAccountConfigSchema.extend({
|
|
123
128
|
accounts: z.record(z.string(), FeishuAccountConfigSchema).optional()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/config-schema.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Zod-based configuration schema for the OpenClaw Lark/Feishu channel plugin.\n *\n * Provides runtime validation, sensible defaults, and cross-field refinements\n * so that every consuming module can rely on well-typed configuration objects.\n */\n\nimport { z, toJSONSchema } from 'zod';\n\nexport { z };\n\n// ---------------------------------------------------------------------------\n// Shared micro-schemas\n// ---------------------------------------------------------------------------\n\nconst DmPolicyEnum = z.enum(['open', 'pairing', 'allowlist', 'disabled']);\nconst GroupPolicyEnum = z.enum(['open', 'allowlist', 'disabled']);\nconst ConnectionModeEnum = z.enum(['websocket', 'webhook']);\nconst ReplyModeValue = z.enum(['auto', 'static', 'streaming']);\nconst ReplyModeSchema = z\n .union([\n ReplyModeValue,\n z.object({\n default: ReplyModeValue.optional(),\n group: ReplyModeValue.optional(),\n direct: ReplyModeValue.optional(),\n }),\n ])\n .optional();\nconst ChunkModeEnum = z.enum(['newline', 'paragraph', 'none']);\n\nconst DomainSchema = z.union([z.literal('feishu'), z.literal('lark'), z.string().regex(/^https:\\/\\//)]).optional();\n\nconst AllowFromSchema = z\n .union([z.string(), z.array(z.string())])\n .optional()\n .transform((v) => {\n if (v === undefined || v === null) return undefined;\n return Array.isArray(v) ? v : [v];\n });\n\nconst ToolPolicySchema = z\n .object({\n allow: z.array(z.string()).optional(),\n deny: z.array(z.string()).optional(),\n })\n .optional();\n\nconst FeishuToolsFlagSchema = z\n .object({\n doc: z.boolean().optional(),\n wiki: z.boolean().optional(),\n drive: z.boolean().optional(),\n perm: z.boolean().optional(),\n scopes: z.boolean().optional(),\n })\n .optional();\n\nconst FeishuFooterSchema = z\n .object({\n status: z.boolean().optional(),\n elapsed: z.boolean().optional(),\n })\n .optional();\n\nconst BlockStreamingCoalesceSchema = z\n .object({\n minChars: z.number().optional(),\n maxChars: z.number().optional(),\n idleMs: z.number().optional(),\n })\n .optional();\n\nconst MarkdownConfigSchema = z\n .object({\n tables: z.enum(['off', 'bullets', 'code']).optional(),\n })\n .optional();\n\nconst HeartbeatSchema = z\n .object({\n every: z.string().optional(),\n activeHours: z\n .object({\n start: z.string().optional(),\n end: z.string().optional(),\n timezone: z.string().optional(),\n })\n .optional(),\n target: z.string().optional(),\n to: z.string().optional(),\n prompt: z.string().optional(),\n accountId: z.string().optional(),\n })\n .optional();\n\nconst CapabilitiesSchema = z\n .object({\n image: z.boolean().optional(),\n audio: z.boolean().optional(),\n video: z.boolean().optional(),\n })\n .optional();\n\nconst DedupSchema = z\n .object({\n ttlMs: z.number().optional(), // default 43200000 (12h)\n maxEntries: z.number().optional(), // default 5000\n })\n .optional();\n\nconst ReactionNotificationModeSchema = z.enum(['off', 'own', 'all']).optional();\n\nexport const UATConfigSchema = z\n .object({\n enabled: z.boolean().optional(),\n allowedScopes: z.array(z.string()).optional(),\n blockedScopes: z.array(z.string()).optional(),\n })\n .optional();\n\nconst DmConfigSchema = z\n .object({\n historyLimit: z.number().optional(),\n })\n .optional();\n\n// ---------------------------------------------------------------------------\n// Group schema\n// ---------------------------------------------------------------------------\n\nexport const FeishuGroupSchema = z.object({\n groupPolicy: GroupPolicyEnum.optional(),\n requireMention: z.boolean().optional(),\n tools: ToolPolicySchema,\n skills: z.array(z.string()).optional(),\n enabled: z.boolean().optional(),\n allowFrom: AllowFromSchema,\n systemPrompt: z.string().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Account config schema (same shape as top-level minus `accounts`)\n// ---------------------------------------------------------------------------\n\nexport const FeishuAccountConfigSchema = z.object({\n appId: z.string().optional(),\n appSecret: z.string().optional(),\n encryptKey: z.string().optional(),\n verificationToken: z.string().optional(),\n name: z.string().optional(),\n enabled: z.boolean().optional(),\n domain: DomainSchema,\n connectionMode: ConnectionModeEnum.optional(),\n webhookPath: z.string().optional(),\n webhookPort: z.number().optional(),\n dmPolicy: DmPolicyEnum.optional(),\n allowFrom: AllowFromSchema,\n groupPolicy: GroupPolicyEnum.optional(),\n groupAllowFrom: AllowFromSchema,\n requireMention: z.boolean().optional(),\n groups: z.record(z.string(), FeishuGroupSchema).optional(),\n historyLimit: z.number().optional(),\n dmHistoryLimit: z.number().optional(),\n dms: DmConfigSchema,\n textChunkLimit: z.number().optional(),\n chunkMode: ChunkModeEnum.optional(),\n blockStreamingCoalesce: BlockStreamingCoalesceSchema,\n mediaMaxMb: z.number().optional(),\n heartbeat: HeartbeatSchema,\n replyMode: ReplyModeSchema,\n streaming: z.boolean().optional(),\n blockStreaming: z.boolean().optional(),\n tools: FeishuToolsFlagSchema,\n footer: FeishuFooterSchema,\n markdown: MarkdownConfigSchema,\n configWrites: z.boolean().optional(),\n capabilities: CapabilitiesSchema,\n dedup: DedupSchema,\n reactionNotifications: ReactionNotificationModeSchema,\n threadSession: z.boolean().optional(),\n uat: UATConfigSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Top-level Feishu config schema\n// ---------------------------------------------------------------------------\n\nexport const FeishuConfigSchema = FeishuAccountConfigSchema.extend({\n accounts: z.record(z.string(), FeishuAccountConfigSchema).optional(),\n}).superRefine((data, ctx) => {\n // When dmPolicy is \"open\", allowFrom must contain the wildcard \"*\".\n if (data.dmPolicy === 'open') {\n const list = data.allowFrom;\n const hasWildcard = Array.isArray(list) && list.includes('*');\n\n if (!hasWildcard) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['allowFrom'],\n message: 'When dmPolicy is \"open\", allowFrom must include \"*\" to permit all senders.',\n });\n }\n }\n});\n\n// ---------------------------------------------------------------------------\n// Auto-generated JSON Schema (single source of truth)\n// ---------------------------------------------------------------------------\n\n/**\n * JSON Schema derived from FeishuConfigSchema.\n *\n * - `io: \"input\"` exposes the input type for `.transform()` schemas (e.g. AllowFromSchema).\n * - `unrepresentable: \"any\"` degrades `.superRefine()` constraints to `{}`.\n * - `target: \"draft-07\"` matches the plugin system's expected JSON Schema version.\n */\nexport const FEISHU_CONFIG_JSON_SCHEMA: Record<string, unknown> = toJSONSchema(FeishuConfigSchema, {\n target: 'draft-07',\n io: 'input',\n unrepresentable: 'any',\n});\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * Zod-based configuration schema for the OpenClaw Lark/Feishu channel plugin.\n *\n * Provides runtime validation, sensible defaults, and cross-field refinements\n * so that every consuming module can rely on well-typed configuration objects.\n */\n\nimport { z, toJSONSchema } from 'zod';\n\nexport { z };\n\n// ---------------------------------------------------------------------------\n// Shared micro-schemas\n// ---------------------------------------------------------------------------\n\nconst DmPolicyEnum = z.enum(['open', 'pairing', 'allowlist', 'disabled']);\nconst GroupPolicyEnum = z.enum(['open', 'allowlist', 'disabled']);\nconst ConnectionModeEnum = z.enum(['websocket', 'webhook']);\nconst ReplyModeValue = z.enum(['auto', 'static', 'streaming']);\nconst ReplyModeSchema = z\n .union([\n ReplyModeValue,\n z.object({\n default: ReplyModeValue.optional(),\n group: ReplyModeValue.optional(),\n direct: ReplyModeValue.optional(),\n }),\n ])\n .optional();\nconst ChunkModeEnum = z.enum(['newline', 'paragraph', 'none']);\n\nconst DomainSchema = z.union([z.literal('feishu'), z.literal('lark'), z.string().regex(/^https:\\/\\//)]).optional();\n\nconst ProjectDomainSchema = z\n .union([z.literal('feishu'), z.literal('meegle'), z.string().regex(/^https:\\/\\//)])\n .optional();\n\nconst FeishuProjectConfigSchema = z\n .object({\n domain: ProjectDomainSchema,\n })\n .optional();\n\nconst AllowFromSchema = z\n .union([z.string(), z.array(z.string())])\n .optional()\n .transform((v) => {\n if (v === undefined || v === null) return undefined;\n return Array.isArray(v) ? v : [v];\n });\n\nconst ToolPolicySchema = z\n .object({\n allow: z.array(z.string()).optional(),\n deny: z.array(z.string()).optional(),\n })\n .optional();\n\nconst FeishuToolsFlagSchema = z\n .object({\n doc: z.boolean().optional(),\n wiki: z.boolean().optional(),\n drive: z.boolean().optional(),\n perm: z.boolean().optional(),\n scopes: z.boolean().optional(),\n })\n .optional();\n\nconst FeishuFooterSchema = z\n .object({\n status: z.boolean().optional(),\n elapsed: z.boolean().optional(),\n })\n .optional();\n\nconst BlockStreamingCoalesceSchema = z\n .object({\n minChars: z.number().optional(),\n maxChars: z.number().optional(),\n idleMs: z.number().optional(),\n })\n .optional();\n\nconst MarkdownConfigSchema = z\n .object({\n tables: z.enum(['off', 'bullets', 'code']).optional(),\n })\n .optional();\n\nconst HeartbeatSchema = z\n .object({\n every: z.string().optional(),\n activeHours: z\n .object({\n start: z.string().optional(),\n end: z.string().optional(),\n timezone: z.string().optional(),\n })\n .optional(),\n target: z.string().optional(),\n to: z.string().optional(),\n prompt: z.string().optional(),\n accountId: z.string().optional(),\n })\n .optional();\n\nconst CapabilitiesSchema = z\n .object({\n image: z.boolean().optional(),\n audio: z.boolean().optional(),\n video: z.boolean().optional(),\n })\n .optional();\n\nconst DedupSchema = z\n .object({\n ttlMs: z.number().optional(), // default 43200000 (12h)\n maxEntries: z.number().optional(), // default 5000\n })\n .optional();\n\nconst ReactionNotificationModeSchema = z.enum(['off', 'own', 'all']).optional();\n\nexport const UATConfigSchema = z\n .object({\n enabled: z.boolean().optional(),\n allowedScopes: z.array(z.string()).optional(),\n blockedScopes: z.array(z.string()).optional(),\n })\n .optional();\n\nconst DmConfigSchema = z\n .object({\n historyLimit: z.number().optional(),\n })\n .optional();\n\n// ---------------------------------------------------------------------------\n// Group schema\n// ---------------------------------------------------------------------------\n\nexport const FeishuGroupSchema = z.object({\n groupPolicy: GroupPolicyEnum.optional(),\n requireMention: z.boolean().optional(),\n tools: ToolPolicySchema,\n skills: z.array(z.string()).optional(),\n enabled: z.boolean().optional(),\n allowFrom: AllowFromSchema,\n systemPrompt: z.string().optional(),\n});\n\n// ---------------------------------------------------------------------------\n// Account config schema (same shape as top-level minus `accounts`)\n// ---------------------------------------------------------------------------\n\nexport const FeishuAccountConfigSchema = z.object({\n appId: z.string().optional(),\n appSecret: z.string().optional(),\n encryptKey: z.string().optional(),\n verificationToken: z.string().optional(),\n name: z.string().optional(),\n enabled: z.boolean().optional(),\n domain: DomainSchema,\n connectionMode: ConnectionModeEnum.optional(),\n webhookPath: z.string().optional(),\n webhookPort: z.number().optional(),\n dmPolicy: DmPolicyEnum.optional(),\n allowFrom: AllowFromSchema,\n groupPolicy: GroupPolicyEnum.optional(),\n groupAllowFrom: AllowFromSchema,\n requireMention: z.boolean().optional(),\n groups: z.record(z.string(), FeishuGroupSchema).optional(),\n historyLimit: z.number().optional(),\n dmHistoryLimit: z.number().optional(),\n dms: DmConfigSchema,\n textChunkLimit: z.number().optional(),\n chunkMode: ChunkModeEnum.optional(),\n blockStreamingCoalesce: BlockStreamingCoalesceSchema,\n mediaMaxMb: z.number().optional(),\n heartbeat: HeartbeatSchema,\n replyMode: ReplyModeSchema,\n streaming: z.boolean().optional(),\n blockStreaming: z.boolean().optional(),\n tools: FeishuToolsFlagSchema,\n footer: FeishuFooterSchema,\n markdown: MarkdownConfigSchema,\n configWrites: z.boolean().optional(),\n capabilities: CapabilitiesSchema,\n dedup: DedupSchema,\n reactionNotifications: ReactionNotificationModeSchema,\n threadSession: z.boolean().optional(),\n uat: UATConfigSchema,\n project: FeishuProjectConfigSchema,\n});\n\n// ---------------------------------------------------------------------------\n// Top-level Feishu config schema\n// ---------------------------------------------------------------------------\n\nexport const FeishuConfigSchema = FeishuAccountConfigSchema.extend({\n accounts: z.record(z.string(), FeishuAccountConfigSchema).optional(),\n}).superRefine((data, ctx) => {\n // When dmPolicy is \"open\", allowFrom must contain the wildcard \"*\".\n if (data.dmPolicy === 'open') {\n const list = data.allowFrom;\n const hasWildcard = Array.isArray(list) && list.includes('*');\n\n if (!hasWildcard) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['allowFrom'],\n message: 'When dmPolicy is \"open\", allowFrom must include \"*\" to permit all senders.',\n });\n }\n }\n});\n\n// ---------------------------------------------------------------------------\n// Auto-generated JSON Schema (single source of truth)\n// ---------------------------------------------------------------------------\n\n/**\n * JSON Schema derived from FeishuConfigSchema.\n *\n * - `io: \"input\"` exposes the input type for `.transform()` schemas (e.g. AllowFromSchema).\n * - `unrepresentable: \"any\"` degrades `.superRefine()` constraints to `{}`.\n * - `target: \"draft-07\"` matches the plugin system's expected JSON Schema version.\n */\nexport const FEISHU_CONFIG_JSON_SCHEMA: Record<string, unknown> = toJSONSchema(FeishuConfigSchema, {\n target: 'draft-07',\n io: 'input',\n unrepresentable: 'any',\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -30,7 +30,7 @@ function resolveOAuthEndpoints(brand) {
|
|
|
30
30
|
}
|
|
31
31
|
async function requestDeviceAuthorization(params) {
|
|
32
32
|
const { appId, appSecret, brand } = params;
|
|
33
|
-
const endpoints =
|
|
33
|
+
const endpoints = resolveOAuthEndpoints(brand);
|
|
34
34
|
let scope = params.scope ?? "";
|
|
35
35
|
if (!scope.includes("offline_access")) {
|
|
36
36
|
scope = scope ? `${scope} offline_access` : "offline_access";
|
|
@@ -92,7 +92,7 @@ async function pollDeviceToken(params) {
|
|
|
92
92
|
const MAX_POLL_ATTEMPTS = 200;
|
|
93
93
|
const { appId, appSecret, brand, deviceCode, expiresIn, signal } = params;
|
|
94
94
|
let interval = params.interval;
|
|
95
|
-
const endpoints =
|
|
95
|
+
const endpoints = resolveOAuthEndpoints(brand);
|
|
96
96
|
const deadline = Date.now() + expiresIn * 1e3;
|
|
97
97
|
let attempts = 0;
|
|
98
98
|
while (Date.now() < deadline && attempts < MAX_POLL_ATTEMPTS) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/core/device-flow.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * OAuth 2.0 Device Authorization Grant (RFC 8628) for Lark/Feishu.\n *\n * Two-step flow:\n * 1. `requestDeviceAuthorization` \u2013 obtains device_code + user_code.\n * 2. `pollDeviceToken` \u2013 polls the token endpoint until the user authorises,\n * rejects, or the code expires.\n *\n * All HTTP calls use the built-in `fetch` (Node 18+). The Lark SDK is not\n * used here because these OAuth endpoints are outside the SDK's scope.\n */\n\nimport type { LarkBrand } from './types';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/device-flow');\nimport { feishuFetch } from './feishu-fetch';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DeviceAuthResponse {\n deviceCode: string;\n userCode: string;\n verificationUri: string;\n verificationUriComplete: string;\n expiresIn: number; // seconds\n interval: number; // recommended polling interval (seconds)\n}\n\nexport interface DeviceFlowTokenData {\n accessToken: string;\n refreshToken: string;\n expiresIn: number; // seconds\n refreshExpiresIn: number; // seconds\n scope: string;\n}\n\nexport type DeviceFlowResult =\n | { ok: true; token: DeviceFlowTokenData }\n | { ok: false; error: DeviceFlowError; message: string };\n\nexport type DeviceFlowError = 'authorization_pending' | 'slow_down' | 'access_denied' | 'expired_token';\n\n// ---------------------------------------------------------------------------\n// Endpoint resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the two OAuth endpoint URLs based on the configured brand.\n */\nexport function resolveOAuthEndpoints(brand: LarkBrand): {\n deviceAuthorization: string;\n token: string;\n} {\n if (!brand || brand === 'feishu') {\n return {\n deviceAuthorization: 'https://accounts.feishu.cn/oauth/v1/device_authorization',\n token: 'https://open.feishu.cn/open-apis/authen/v2/oauth/token',\n };\n }\n if (brand === 'lark') {\n return {\n deviceAuthorization: 'https://accounts.larksuite.com/oauth/v1/device_authorization',\n token: 'https://open.larksuite.com/open-apis/authen/v2/oauth/token',\n };\n }\n // Custom domain \u2013 derive paths by convention.\n // Smart derivation: open.X \u2192 accounts.X for the device authorization endpoint.\n const base = brand.replace(/\\/+$/, '');\n let accountsBase = base;\n try {\n const parsed = new URL(base);\n if (parsed.hostname.startsWith('open.')) {\n accountsBase = `${parsed.protocol}//${parsed.hostname.replace(/^open\\./, 'accounts.')}`;\n }\n } catch {\n /* fallback to base */\n }\n\n return {\n deviceAuthorization: `${accountsBase}/oauth/v1/device_authorization`,\n token: `${base}/open-apis/authen/v2/oauth/token`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Step 1 \u2013 Device Authorization Request\n// ---------------------------------------------------------------------------\n\n/**\n * Request a device authorisation code from the Feishu OAuth server.\n *\n * Uses Confidential Client authentication (HTTP Basic with appId:appSecret).\n * The `offline_access` scope is automatically appended so that the token\n * response includes a refresh_token.\n */\nexport async function requestDeviceAuthorization(params: {\n appId: string;\n appSecret: string;\n brand: LarkBrand;\n scope?: string;\n endpoints?: { deviceAuthorization: string; token: string };\n}): Promise<DeviceAuthResponse> {\n const { appId, appSecret, brand } = params;\n const endpoints = params.endpoints || resolveOAuthEndpoints(brand);\n\n // Ensure offline_access is always requested.\n let scope = params.scope ?? '';\n if (!scope.includes('offline_access')) {\n scope = scope ? `${scope} offline_access` : 'offline_access';\n }\n\n const basicAuth = Buffer.from(`${appId}:${appSecret}`).toString('base64');\n\n const body = new URLSearchParams();\n body.set('client_id', appId);\n body.set('scope', scope);\n\n log.info(\n `requesting device authorization (scope=\"${scope}\") url=${endpoints.deviceAuthorization} token_url=${endpoints.token}`,\n );\n\n const resp = await feishuFetch(endpoints.deviceAuthorization, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Authorization: `Basic ${basicAuth}`,\n },\n body: body.toString(),\n });\n\n const text = await resp.text();\n log.info(`response status=${resp.status} body=${text.slice(0, 500)}`);\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n throw new Error(`Device authorization failed: HTTP ${resp.status} \u2013 ${text.slice(0, 200)}`);\n }\n\n if (!resp.ok || data.error) {\n const msg = (data.error_description as string) ?? (data.error as string) ?? 'Unknown error';\n throw new Error(`Device authorization failed: ${msg}`);\n }\n\n const expiresIn = (data.expires_in as number) ?? 240;\n const interval = (data.interval as number) ?? 5;\n log.info(`device_code obtained, expires_in=${expiresIn}s (${Math.round(expiresIn / 60)}min), interval=${interval}s`);\n\n return {\n deviceCode: data.device_code as string,\n userCode: data.user_code as string,\n verificationUri: data.verification_uri as string,\n verificationUriComplete: (data.verification_uri_complete as string) ?? (data.verification_uri as string),\n expiresIn,\n interval,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Step 2 \u2013 Poll Token Endpoint\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(new DOMException('Aborted', 'AbortError'));\n },\n { once: true },\n );\n });\n}\n\n/**\n * Poll the token endpoint until the user authorises, rejects, or the code\n * expires.\n *\n * Handles `authorization_pending` (keep polling), `slow_down` (back off by\n * +5 s), `access_denied` and `expired_token` (terminal errors).\n *\n * Pass an `AbortSignal` to cancel polling from the outside.\n */\nexport async function pollDeviceToken(params: {\n appId: string;\n appSecret: string;\n brand: LarkBrand;\n deviceCode: string;\n interval: number;\n expiresIn: number;\n signal?: AbortSignal;\n endpoints?: { deviceAuthorization: string; token: string };\n}): Promise<DeviceFlowResult> {\n const MAX_POLL_INTERVAL = 60; // slow_down \u6700\u5927\u95F4\u9694 60 \u79D2\n const MAX_POLL_ATTEMPTS = 200; // \u5B89\u5168\u4E0A\u9650\uFF08\u8FDC\u8D85\u8BBE\u5907\u7801\u6709\u6548\u671F\uFF09\n\n const { appId, appSecret, brand, deviceCode, expiresIn, signal } = params;\n let interval = params.interval;\n const endpoints = params.endpoints || resolveOAuthEndpoints(brand);\n const deadline = Date.now() + expiresIn * 1000;\n let attempts = 0;\n\n while (Date.now() < deadline && attempts < MAX_POLL_ATTEMPTS) {\n attempts++;\n if (signal?.aborted) {\n return { ok: false, error: 'expired_token', message: 'Polling was cancelled' };\n }\n\n await sleep(interval * 1000, signal);\n\n let data: Record<string, unknown>;\n try {\n const resp = await feishuFetch(endpoints.token, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: appId,\n client_secret: appSecret,\n }).toString(),\n });\n data = (await resp.json()) as Record<string, unknown>;\n } catch (err) {\n log.warn(`poll network error: ${err}`);\n interval = Math.min(interval + 1, MAX_POLL_INTERVAL);\n continue;\n }\n\n const error = data.error as string | undefined;\n\n if (!error && data.access_token) {\n log.info('token obtained successfully');\n const refreshToken = (data.refresh_token as string) ?? '';\n const expiresIn = (data.expires_in as number) ?? 7200;\n let refreshExpiresIn = (data.refresh_token_expires_in as number) ?? 604800;\n if (!refreshToken) {\n log.warn('no refresh_token in response, token will not be refreshable');\n refreshExpiresIn = expiresIn;\n }\n return {\n ok: true,\n token: {\n accessToken: data.access_token as string,\n refreshToken,\n expiresIn,\n refreshExpiresIn,\n scope: (data.scope as string) ?? '',\n },\n };\n }\n\n if (error === 'authorization_pending') {\n log.debug('authorization_pending, retrying...');\n continue;\n }\n\n if (error === 'slow_down') {\n interval = Math.min(interval + 5, MAX_POLL_INTERVAL);\n log.info(`slow_down, interval increased to ${interval}s`);\n continue;\n }\n\n if (error === 'access_denied') {\n log.info('user denied authorization');\n return { ok: false, error: 'access_denied', message: '\u7528\u6237\u62D2\u7EDD\u4E86\u6388\u6743' };\n }\n\n if (error === 'expired_token' || error === 'invalid_grant') {\n log.info(`device code expired/invalid (error=${error})`);\n return { ok: false, error: 'expired_token', message: '\u6388\u6743\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77' };\n }\n\n // Unknown error \u2013 treat as terminal.\n const desc = (data.error_description as string) ?? error ?? 'Unknown error';\n log.warn(`unexpected error: error=${error}, desc=${desc}`);\n return { ok: false, error: 'expired_token', message: desc };\n }\n\n if (attempts >= MAX_POLL_ATTEMPTS) {\n log.warn(`max poll attempts (${MAX_POLL_ATTEMPTS}) reached`);\n }\n return { ok: false, error: 'expired_token', message: '\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77' };\n}\n"],
|
|
5
|
-
"mappings": "AAgBA,SAAS,kBAAkB;AAE3B,MAAM,MAAM,WAAW,kBAAkB;AACzC,SAAS,mBAAmB;AAoCrB,SAAS,sBAAsB,OAGpC;AACA,MAAI,CAAC,SAAS,UAAU,UAAU;AAChC,WAAO;AAAA,MACL,qBAAqB;AAAA,MACrB,OAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,UAAU,QAAQ;AACpB,WAAO;AAAA,MACL,qBAAqB;AAAA,MACrB,OAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,OAAO,MAAM,QAAQ,QAAQ,EAAE;AACrC,MAAI,eAAe;AACnB,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,IAAI;AAC3B,QAAI,OAAO,SAAS,WAAW,OAAO,GAAG;AACvC,qBAAe,GAAG,OAAO,QAAQ,KAAK,OAAO,SAAS,QAAQ,WAAW,WAAW,CAAC;AAAA,IACvF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AAAA,IACL,qBAAqB,GAAG,YAAY;AAAA,IACpC,OAAO,GAAG,IAAI;AAAA,EAChB;AACF;AAaA,eAAsB,2BAA2B,
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * OAuth 2.0 Device Authorization Grant (RFC 8628) for Lark/Feishu.\n *\n * Two-step flow:\n * 1. `requestDeviceAuthorization` \u2013 obtains device_code + user_code.\n * 2. `pollDeviceToken` \u2013 polls the token endpoint until the user authorises,\n * rejects, or the code expires.\n *\n * All HTTP calls use the built-in `fetch` (Node 18+). The Lark SDK is not\n * used here because these OAuth endpoints are outside the SDK's scope.\n */\n\nimport type { LarkBrand } from './types';\nimport { larkLogger } from './lark-logger';\n\nconst log = larkLogger('core/device-flow');\nimport { feishuFetch } from './feishu-fetch';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DeviceAuthResponse {\n deviceCode: string;\n userCode: string;\n verificationUri: string;\n verificationUriComplete: string;\n expiresIn: number; // seconds\n interval: number; // recommended polling interval (seconds)\n}\n\nexport interface DeviceFlowTokenData {\n accessToken: string;\n refreshToken: string;\n expiresIn: number; // seconds\n refreshExpiresIn: number; // seconds\n scope: string;\n}\n\nexport type DeviceFlowResult =\n | { ok: true; token: DeviceFlowTokenData }\n | { ok: false; error: DeviceFlowError; message: string };\n\nexport type DeviceFlowError = 'authorization_pending' | 'slow_down' | 'access_denied' | 'expired_token';\n\n// ---------------------------------------------------------------------------\n// Endpoint resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve the two OAuth endpoint URLs based on the configured brand.\n */\nexport function resolveOAuthEndpoints(brand: LarkBrand): {\n deviceAuthorization: string;\n token: string;\n} {\n if (!brand || brand === 'feishu') {\n return {\n deviceAuthorization: 'https://accounts.feishu.cn/oauth/v1/device_authorization',\n token: 'https://open.feishu.cn/open-apis/authen/v2/oauth/token',\n };\n }\n if (brand === 'lark') {\n return {\n deviceAuthorization: 'https://accounts.larksuite.com/oauth/v1/device_authorization',\n token: 'https://open.larksuite.com/open-apis/authen/v2/oauth/token',\n };\n }\n // Custom domain \u2013 derive paths by convention.\n // Smart derivation: open.X \u2192 accounts.X for the device authorization endpoint.\n const base = brand.replace(/\\/+$/, '');\n let accountsBase = base;\n try {\n const parsed = new URL(base);\n if (parsed.hostname.startsWith('open.')) {\n accountsBase = `${parsed.protocol}//${parsed.hostname.replace(/^open\\./, 'accounts.')}`;\n }\n } catch {\n /* fallback to base */\n }\n\n return {\n deviceAuthorization: `${accountsBase}/oauth/v1/device_authorization`,\n token: `${base}/open-apis/authen/v2/oauth/token`,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Step 1 \u2013 Device Authorization Request\n// ---------------------------------------------------------------------------\n\n/**\n * Request a device authorisation code from the Feishu OAuth server.\n *\n * Uses Confidential Client authentication (HTTP Basic with appId:appSecret).\n * The `offline_access` scope is automatically appended so that the token\n * response includes a refresh_token.\n */\nexport async function requestDeviceAuthorization(params: {\n appId: string;\n appSecret: string;\n brand: LarkBrand;\n scope?: string;\n}): Promise<DeviceAuthResponse> {\n const { appId, appSecret, brand } = params;\n const endpoints = resolveOAuthEndpoints(brand);\n\n // Ensure offline_access is always requested.\n let scope = params.scope ?? '';\n if (!scope.includes('offline_access')) {\n scope = scope ? `${scope} offline_access` : 'offline_access';\n }\n\n const basicAuth = Buffer.from(`${appId}:${appSecret}`).toString('base64');\n\n const body = new URLSearchParams();\n body.set('client_id', appId);\n body.set('scope', scope);\n\n log.info(\n `requesting device authorization (scope=\"${scope}\") url=${endpoints.deviceAuthorization} token_url=${endpoints.token}`,\n );\n\n const resp = await feishuFetch(endpoints.deviceAuthorization, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Authorization: `Basic ${basicAuth}`,\n },\n body: body.toString(),\n });\n\n const text = await resp.text();\n log.info(`response status=${resp.status} body=${text.slice(0, 500)}`);\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(text) as Record<string, unknown>;\n } catch {\n throw new Error(`Device authorization failed: HTTP ${resp.status} \u2013 ${text.slice(0, 200)}`);\n }\n\n if (!resp.ok || data.error) {\n const msg = (data.error_description as string) ?? (data.error as string) ?? 'Unknown error';\n throw new Error(`Device authorization failed: ${msg}`);\n }\n\n const expiresIn = (data.expires_in as number) ?? 240;\n const interval = (data.interval as number) ?? 5;\n log.info(`device_code obtained, expires_in=${expiresIn}s (${Math.round(expiresIn / 60)}min), interval=${interval}s`);\n\n return {\n deviceCode: data.device_code as string,\n userCode: data.user_code as string,\n verificationUri: data.verification_uri as string,\n verificationUriComplete: (data.verification_uri_complete as string) ?? (data.verification_uri as string),\n expiresIn,\n interval,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Step 2 \u2013 Poll Token Endpoint\n// ---------------------------------------------------------------------------\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\n 'abort',\n () => {\n clearTimeout(timer);\n reject(new DOMException('Aborted', 'AbortError'));\n },\n { once: true },\n );\n });\n}\n\n/**\n * Poll the token endpoint until the user authorises, rejects, or the code\n * expires.\n *\n * Handles `authorization_pending` (keep polling), `slow_down` (back off by\n * +5 s), `access_denied` and `expired_token` (terminal errors).\n *\n * Pass an `AbortSignal` to cancel polling from the outside.\n */\nexport async function pollDeviceToken(params: {\n appId: string;\n appSecret: string;\n brand: LarkBrand;\n deviceCode: string;\n interval: number;\n expiresIn: number;\n signal?: AbortSignal;\n}): Promise<DeviceFlowResult> {\n const MAX_POLL_INTERVAL = 60; // slow_down \u6700\u5927\u95F4\u9694 60 \u79D2\n const MAX_POLL_ATTEMPTS = 200; // \u5B89\u5168\u4E0A\u9650\uFF08\u8FDC\u8D85\u8BBE\u5907\u7801\u6709\u6548\u671F\uFF09\n\n const { appId, appSecret, brand, deviceCode, expiresIn, signal } = params;\n let interval = params.interval;\n const endpoints = resolveOAuthEndpoints(brand);\n const deadline = Date.now() + expiresIn * 1000;\n let attempts = 0;\n\n while (Date.now() < deadline && attempts < MAX_POLL_ATTEMPTS) {\n attempts++;\n if (signal?.aborted) {\n return { ok: false, error: 'expired_token', message: 'Polling was cancelled' };\n }\n\n await sleep(interval * 1000, signal);\n\n let data: Record<string, unknown>;\n try {\n const resp = await feishuFetch(endpoints.token, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'urn:ietf:params:oauth:grant-type:device_code',\n device_code: deviceCode,\n client_id: appId,\n client_secret: appSecret,\n }).toString(),\n });\n data = (await resp.json()) as Record<string, unknown>;\n } catch (err) {\n log.warn(`poll network error: ${err}`);\n interval = Math.min(interval + 1, MAX_POLL_INTERVAL);\n continue;\n }\n\n const error = data.error as string | undefined;\n\n if (!error && data.access_token) {\n log.info('token obtained successfully');\n const refreshToken = (data.refresh_token as string) ?? '';\n const expiresIn = (data.expires_in as number) ?? 7200;\n let refreshExpiresIn = (data.refresh_token_expires_in as number) ?? 604800;\n if (!refreshToken) {\n log.warn('no refresh_token in response, token will not be refreshable');\n refreshExpiresIn = expiresIn;\n }\n return {\n ok: true,\n token: {\n accessToken: data.access_token as string,\n refreshToken,\n expiresIn,\n refreshExpiresIn,\n scope: (data.scope as string) ?? '',\n },\n };\n }\n\n if (error === 'authorization_pending') {\n log.debug('authorization_pending, retrying...');\n continue;\n }\n\n if (error === 'slow_down') {\n interval = Math.min(interval + 5, MAX_POLL_INTERVAL);\n log.info(`slow_down, interval increased to ${interval}s`);\n continue;\n }\n\n if (error === 'access_denied') {\n log.info('user denied authorization');\n return { ok: false, error: 'access_denied', message: '\u7528\u6237\u62D2\u7EDD\u4E86\u6388\u6743' };\n }\n\n if (error === 'expired_token' || error === 'invalid_grant') {\n log.info(`device code expired/invalid (error=${error})`);\n return { ok: false, error: 'expired_token', message: '\u6388\u6743\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77' };\n }\n\n // Unknown error \u2013 treat as terminal.\n const desc = (data.error_description as string) ?? error ?? 'Unknown error';\n log.warn(`unexpected error: error=${error}, desc=${desc}`);\n return { ok: false, error: 'expired_token', message: desc };\n }\n\n if (attempts >= MAX_POLL_ATTEMPTS) {\n log.warn(`max poll attempts (${MAX_POLL_ATTEMPTS}) reached`);\n }\n return { ok: false, error: 'expired_token', message: '\u6388\u6743\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u53D1\u8D77' };\n}\n"],
|
|
5
|
+
"mappings": "AAgBA,SAAS,kBAAkB;AAE3B,MAAM,MAAM,WAAW,kBAAkB;AACzC,SAAS,mBAAmB;AAoCrB,SAAS,sBAAsB,OAGpC;AACA,MAAI,CAAC,SAAS,UAAU,UAAU;AAChC,WAAO;AAAA,MACL,qBAAqB;AAAA,MACrB,OAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,UAAU,QAAQ;AACpB,WAAO;AAAA,MACL,qBAAqB;AAAA,MACrB,OAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,OAAO,MAAM,QAAQ,QAAQ,EAAE;AACrC,MAAI,eAAe;AACnB,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,IAAI;AAC3B,QAAI,OAAO,SAAS,WAAW,OAAO,GAAG;AACvC,qBAAe,GAAG,OAAO,QAAQ,KAAK,OAAO,SAAS,QAAQ,WAAW,WAAW,CAAC;AAAA,IACvF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO;AAAA,IACL,qBAAqB,GAAG,YAAY;AAAA,IACpC,OAAO,GAAG,IAAI;AAAA,EAChB;AACF;AAaA,eAAsB,2BAA2B,QAKjB;AAC9B,QAAM,EAAE,OAAO,WAAW,MAAM,IAAI;AACpC,QAAM,YAAY,sBAAsB,KAAK;AAG7C,MAAI,QAAQ,OAAO,SAAS;AAC5B,MAAI,CAAC,MAAM,SAAS,gBAAgB,GAAG;AACrC,YAAQ,QAAQ,GAAG,KAAK,oBAAoB;AAAA,EAC9C;AAEA,QAAM,YAAY,OAAO,KAAK,GAAG,KAAK,IAAI,SAAS,EAAE,EAAE,SAAS,QAAQ;AAExE,QAAM,OAAO,IAAI,gBAAgB;AACjC,OAAK,IAAI,aAAa,KAAK;AAC3B,OAAK,IAAI,SAAS,KAAK;AAEvB,MAAI;AAAA,IACF,2CAA2C,KAAK,UAAU,UAAU,mBAAmB,cAAc,UAAU,KAAK;AAAA,EACtH;AAEA,QAAM,OAAO,MAAM,YAAY,UAAU,qBAAqB;AAAA,IAC5D,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,eAAe,SAAS,SAAS;AAAA,IACnC;AAAA,IACA,MAAM,KAAK,SAAS;AAAA,EACtB,CAAC;AAED,QAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,MAAI,KAAK,mBAAmB,KAAK,MAAM,SAAS,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAEpE,MAAI;AACJ,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,UAAM,IAAI,MAAM,qCAAqC,KAAK,MAAM,WAAM,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EAC5F;AAEA,MAAI,CAAC,KAAK,MAAM,KAAK,OAAO;AAC1B,UAAM,MAAO,KAAK,qBAAiC,KAAK,SAAoB;AAC5E,UAAM,IAAI,MAAM,gCAAgC,GAAG,EAAE;AAAA,EACvD;AAEA,QAAM,YAAa,KAAK,cAAyB;AACjD,QAAM,WAAY,KAAK,YAAuB;AAC9C,MAAI,KAAK,oCAAoC,SAAS,MAAM,KAAK,MAAM,YAAY,EAAE,CAAC,kBAAkB,QAAQ,GAAG;AAEnH,SAAO;AAAA,IACL,YAAY,KAAK;AAAA,IACjB,UAAU,KAAK;AAAA,IACf,iBAAiB,KAAK;AAAA,IACtB,yBAA0B,KAAK,6BAAyC,KAAK;AAAA,IAC7E;AAAA,IACA;AAAA,EACF;AACF;AAMA,SAAS,MAAM,IAAY,QAAqC;AAC9D,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,UAAM,QAAQ,WAAW,SAAS,EAAE;AACpC,YAAQ;AAAA,MACN;AAAA,MACA,MAAM;AACJ,qBAAa,KAAK;AAClB,eAAO,IAAI,aAAa,WAAW,YAAY,CAAC;AAAA,MAClD;AAAA,MACA,EAAE,MAAM,KAAK;AAAA,IACf;AAAA,EACF,CAAC;AACH;AAWA,eAAsB,gBAAgB,QAQR;AAC5B,QAAM,oBAAoB;AAC1B,QAAM,oBAAoB;AAE1B,QAAM,EAAE,OAAO,WAAW,OAAO,YAAY,WAAW,OAAO,IAAI;AACnE,MAAI,WAAW,OAAO;AACtB,QAAM,YAAY,sBAAsB,KAAK;AAC7C,QAAM,WAAW,KAAK,IAAI,IAAI,YAAY;AAC1C,MAAI,WAAW;AAEf,SAAO,KAAK,IAAI,IAAI,YAAY,WAAW,mBAAmB;AAC5D;AACA,QAAI,QAAQ,SAAS;AACnB,aAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,SAAS,wBAAwB;AAAA,IAC/E;AAEA,UAAM,MAAM,WAAW,KAAM,MAAM;AAEnC,QAAI;AACJ,QAAI;AACF,YAAM,OAAO,MAAM,YAAY,UAAU,OAAO;AAAA,QAC9C,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,QAC/D,MAAM,IAAI,gBAAgB;AAAA,UACxB,YAAY;AAAA,UACZ,aAAa;AAAA,UACb,WAAW;AAAA,UACX,eAAe;AAAA,QACjB,CAAC,EAAE,SAAS;AAAA,MACd,CAAC;AACD,aAAQ,MAAM,KAAK,KAAK;AAAA,IAC1B,SAAS,KAAK;AACZ,UAAI,KAAK,uBAAuB,GAAG,EAAE;AACrC,iBAAW,KAAK,IAAI,WAAW,GAAG,iBAAiB;AACnD;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK;AAEnB,QAAI,CAAC,SAAS,KAAK,cAAc;AAC/B,UAAI,KAAK,6BAA6B;AACtC,YAAM,eAAgB,KAAK,iBAA4B;AACvD,YAAMA,aAAa,KAAK,cAAyB;AACjD,UAAI,mBAAoB,KAAK,4BAAuC;AACpE,UAAI,CAAC,cAAc;AACjB,YAAI,KAAK,6DAA6D;AACtE,2BAAmBA;AAAA,MACrB;AACA,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO;AAAA,UACL,aAAa,KAAK;AAAA,UAClB;AAAA,UACA,WAAAA;AAAA,UACA;AAAA,UACA,OAAQ,KAAK,SAAoB;AAAA,QACnC;AAAA,MACF;AAAA,IACF;AAEA,QAAI,UAAU,yBAAyB;AACrC,UAAI,MAAM,oCAAoC;AAC9C;AAAA,IACF;AAEA,QAAI,UAAU,aAAa;AACzB,iBAAW,KAAK,IAAI,WAAW,GAAG,iBAAiB;AACnD,UAAI,KAAK,oCAAoC,QAAQ,GAAG;AACxD;AAAA,IACF;AAEA,QAAI,UAAU,iBAAiB;AAC7B,UAAI,KAAK,2BAA2B;AACpC,aAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,SAAS,6CAAU;AAAA,IACjE;AAEA,QAAI,UAAU,mBAAmB,UAAU,iBAAiB;AAC1D,UAAI,KAAK,sCAAsC,KAAK,GAAG;AACvD,aAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,SAAS,2EAAe;AAAA,IACtE;AAGA,UAAM,OAAQ,KAAK,qBAAgC,SAAS;AAC5D,QAAI,KAAK,2BAA2B,KAAK,UAAU,IAAI,EAAE;AACzD,WAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,SAAS,KAAK;AAAA,EAC5D;AAEA,MAAI,YAAY,mBAAmB;AACjC,QAAI,KAAK,sBAAsB,iBAAiB,WAAW;AAAA,EAC7D;AACA,SAAO,EAAE,IAAI,OAAO,OAAO,iBAAiB,SAAS,+DAAa;AACpE;",
|
|
6
6
|
"names": ["expiresIn"]
|
|
7
7
|
}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
const
|
|
1
|
+
const PROJECT_DOMAIN_MAP = {
|
|
2
|
+
feishu: "https://project.feishu.cn",
|
|
3
|
+
meegle: "https://meegle.com"
|
|
4
|
+
};
|
|
5
|
+
const MCP_PATH = "/mcp_server/v1";
|
|
6
|
+
function resolveProjectBaseUrl(domain) {
|
|
7
|
+
return PROJECT_DOMAIN_MAP[domain] ?? domain.replace(/\/+$/, "");
|
|
8
|
+
}
|
|
2
9
|
function getProjectMcpEndpoint(cfg) {
|
|
3
|
-
|
|
4
|
-
if (envEndpoint) return envEndpoint;
|
|
10
|
+
let domain = "feishu";
|
|
5
11
|
if (cfg && typeof cfg === "object") {
|
|
6
12
|
const channels = cfg.channels;
|
|
7
13
|
if (channels && typeof channels === "object") {
|
|
@@ -9,17 +15,18 @@ function getProjectMcpEndpoint(cfg) {
|
|
|
9
15
|
if (feishu && typeof feishu === "object") {
|
|
10
16
|
const project = feishu.project;
|
|
11
17
|
if (project && typeof project === "object") {
|
|
12
|
-
const
|
|
13
|
-
if (typeof
|
|
14
|
-
|
|
18
|
+
const d = project.domain;
|
|
19
|
+
if (typeof d === "string" && d.trim()) {
|
|
20
|
+
domain = d.trim();
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
}
|
|
19
25
|
}
|
|
20
|
-
return
|
|
26
|
+
return `${resolveProjectBaseUrl(domain)}${MCP_PATH}`;
|
|
21
27
|
}
|
|
22
28
|
export {
|
|
23
|
-
getProjectMcpEndpoint
|
|
29
|
+
getProjectMcpEndpoint,
|
|
30
|
+
resolveProjectBaseUrl
|
|
24
31
|
};
|
|
25
32
|
//# sourceMappingURL=endpoint.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/tools/mcp/project/endpoint.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE MCP Server \u7AEF\u70B9\u914D\u7F6E\n */\n\nconst
|
|
5
|
-
"mappings": "AAOA,MAAM
|
|
4
|
+
"sourcesContent": ["/**\n * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates\n * SPDX-License-Identifier: MIT\n *\n * \u98DE\u4E66\u9879\u76EE MCP Server \u7AEF\u70B9\u914D\u7F6E\n */\n\nconst PROJECT_DOMAIN_MAP: Record<string, string> = {\n feishu: 'https://project.feishu.cn',\n meegle: 'https://meegle.com',\n};\n\nconst MCP_PATH = '/mcp_server/v1';\n\n/**\n * \u5C06 domain \u7B80\u5199\u6216\u81EA\u5B9A\u4E49 URL \u89E3\u6790\u4E3A\u98DE\u4E66\u9879\u76EE base URL\u3002\n *\n * - `'feishu'` \u2192 `https://project.feishu.cn`\n * - `'meegle'` \u2192 `https://meegle.com`\n * - `'https://custom.example.com/'` \u2192 `https://custom.example.com`\n */\nexport function resolveProjectBaseUrl(domain: string): string {\n return PROJECT_DOMAIN_MAP[domain] ?? domain.replace(/\\/+$/, '');\n}\n\n/**\n * \u83B7\u53D6\u98DE\u4E66\u9879\u76EE MCP \u7AEF\u70B9\u3002\n *\n * \u4F18\u5148\u7EA7\uFF1A\n * 1. \u914D\u7F6E channels.feishu.project.domain \u2192 \u6620\u5C04\u4E3A MCP endpoint\n * 2. \u9ED8\u8BA4\u503C\uFF08\u7B49\u540C domain: 'feishu'\uFF09\u2192 https://project.feishu.cn/mcp_server/v1\n */\nexport function getProjectMcpEndpoint(cfg?: unknown): string {\n let domain = 'feishu';\n\n if (cfg && typeof cfg === 'object') {\n const channels = (cfg as Record<string, unknown>).channels;\n if (channels && typeof channels === 'object') {\n const feishu = (channels as Record<string, unknown>).feishu;\n if (feishu && typeof feishu === 'object') {\n const project = (feishu as Record<string, unknown>).project;\n if (project && typeof project === 'object') {\n const d = (project as Record<string, unknown>).domain;\n if (typeof d === 'string' && d.trim()) {\n domain = d.trim();\n }\n }\n }\n }\n }\n\n return `${resolveProjectBaseUrl(domain)}${MCP_PATH}`;\n}\n"],
|
|
5
|
+
"mappings": "AAOA,MAAM,qBAA6C;AAAA,EACjD,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,MAAM,WAAW;AASV,SAAS,sBAAsB,QAAwB;AAC5D,SAAO,mBAAmB,MAAM,KAAK,OAAO,QAAQ,QAAQ,EAAE;AAChE;AASO,SAAS,sBAAsB,KAAuB;AAC3D,MAAI,SAAS;AAEb,MAAI,OAAO,OAAO,QAAQ,UAAU;AAClC,UAAM,WAAY,IAAgC;AAClD,QAAI,YAAY,OAAO,aAAa,UAAU;AAC5C,YAAM,SAAU,SAAqC;AACrD,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,cAAM,UAAW,OAAmC;AACpD,YAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,gBAAM,IAAK,QAAoC;AAC/C,cAAI,OAAO,MAAM,YAAY,EAAE,KAAK,GAAG;AACrC,qBAAS,EAAE,KAAK;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO,GAAG,sBAAsB,MAAM,CAAC,GAAG,QAAQ;AACpD;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -15,6 +15,20 @@ description: |
|
|
|
15
15
|
|
|
16
16
|
# 飞书项目(Meego)工具使用指南
|
|
17
17
|
|
|
18
|
+
## 环境确认
|
|
19
|
+
|
|
20
|
+
首次使用飞书项目能力、发起授权之前,检查配置中 `channels.feishu.project.domain` 是否已设置。
|
|
21
|
+
|
|
22
|
+
**如果已配置**:直接使用配置值,跳过询问。
|
|
23
|
+
|
|
24
|
+
**如果未配置(值为空)**:询问用户所使用的飞书项目环境,提供以下选项:
|
|
25
|
+
|
|
26
|
+
1. **飞书项目**(国内 SaaS,project.feishu.cn)— 选此项无需额外配置,直接继续
|
|
27
|
+
2. **Meegle**(meegle.com)— 需要在配置中设置 `channels.feishu.project.domain: "meegle"`
|
|
28
|
+
3. **自定义域名** — 需要设置为完整 URL,例如 `channels.feishu.project.domain: "https://custom.example.com"`
|
|
29
|
+
|
|
30
|
+
用户确认后,如果选择了非默认环境,提示用户将 `project.domain` 写入配置文件以避免每次询问。
|
|
31
|
+
|
|
18
32
|
## 授权前置检查
|
|
19
33
|
|
|
20
34
|
飞书项目的 OAuth 授权与飞书 IM/文档的授权**完全独立**,不共享 token。首次使用飞书项目功能时,必须先完成独立授权。
|