@xopcai/xopc 0.0.15 → 0.0.16
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/extensions/feishu/src/adapters/onboard-cli.d.ts +7 -0
- package/dist/extensions/feishu/src/adapters/onboard-cli.js +432 -0
- package/dist/extensions/feishu/src/adapters/onboard-cli.js.map +1 -0
- package/dist/extensions/feishu/src/auth/pairing.d.ts +7 -0
- package/dist/extensions/feishu/src/auth/pairing.js +45 -0
- package/dist/extensions/feishu/src/auth/pairing.js.map +1 -0
- package/dist/extensions/feishu/src/auth/paths.d.ts +2 -0
- package/dist/extensions/feishu/src/auth/paths.js +18 -0
- package/dist/extensions/feishu/src/auth/paths.js.map +1 -0
- package/dist/extensions/feishu/src/directory/directory-adapter.d.ts +2 -0
- package/dist/extensions/feishu/src/directory/directory-adapter.js +27 -0
- package/dist/extensions/feishu/src/directory/directory-adapter.js.map +1 -0
- package/dist/extensions/feishu/src/format.d.ts +21 -0
- package/dist/extensions/feishu/src/format.js +99 -0
- package/dist/extensions/feishu/src/format.js.map +1 -0
- package/dist/extensions/feishu/src/index.d.ts +5 -0
- package/dist/extensions/feishu/src/index.js +3 -0
- package/dist/extensions/feishu/src/outbound/actions.d.ts +51 -0
- package/dist/extensions/feishu/src/outbound/actions.js +62 -0
- package/dist/extensions/feishu/src/outbound/actions.js.map +1 -0
- package/dist/extensions/feishu/src/outbound/media-load.d.ts +12 -0
- package/dist/extensions/feishu/src/outbound/media-load.js +125 -0
- package/dist/extensions/feishu/src/outbound/media-load.js.map +1 -0
- package/dist/extensions/feishu/src/outbound/outbound-adapter.d.ts +2 -0
- package/dist/extensions/feishu/src/outbound/outbound-adapter.js +201 -0
- package/dist/extensions/feishu/src/outbound/outbound-adapter.js.map +1 -0
- package/dist/extensions/feishu/src/plugin.d.ts +70 -0
- package/dist/extensions/feishu/src/plugin.js +313 -0
- package/dist/extensions/feishu/src/plugin.js.map +1 -0
- package/dist/extensions/feishu/src/schema/config-schema.d.ts +215 -0
- package/dist/extensions/feishu/src/schema/config-schema.js +198 -0
- package/dist/extensions/feishu/src/schema/config-schema.js.map +1 -0
- package/dist/extensions/feishu/src/state/accounts.d.ts +38 -0
- package/dist/extensions/feishu/src/state/accounts.js +96 -0
- package/dist/extensions/feishu/src/state/accounts.js.map +1 -0
- package/dist/extensions/feishu/src/state/message-bindings.d.ts +11 -0
- package/dist/extensions/feishu/src/state/message-bindings.js +41 -0
- package/dist/extensions/feishu/src/state/message-bindings.js.map +1 -0
- package/dist/extensions/feishu/src/state/thread-bindings.js +46 -0
- package/dist/extensions/feishu/src/state/thread-bindings.js.map +1 -0
- package/dist/extensions/feishu/src/status/doctor.d.ts +2 -0
- package/dist/extensions/feishu/src/status/doctor.js +38 -0
- package/dist/extensions/feishu/src/status/doctor.js.map +1 -0
- package/dist/extensions/feishu/src/status/status-adapter.d.ts +3 -0
- package/dist/extensions/feishu/src/status/status-adapter.js +45 -0
- package/dist/extensions/feishu/src/status/status-adapter.js.map +1 -0
- package/dist/extensions/feishu/src/streaming/streaming-adapter.d.ts +3 -0
- package/dist/extensions/feishu/src/streaming/streaming-adapter.js +242 -0
- package/dist/extensions/feishu/src/streaming/streaming-adapter.js.map +1 -0
- package/dist/extensions/feishu/src/subagent-hooks.js +52 -0
- package/dist/extensions/feishu/src/subagent-hooks.js.map +1 -0
- package/dist/extensions/feishu/src/tools/docx/docx-batch-insert.js +95 -0
- package/dist/extensions/feishu/src/tools/docx/docx-batch-insert.js.map +1 -0
- package/dist/extensions/feishu/src/tools/docx/docx-color-text.js +75 -0
- package/dist/extensions/feishu/src/tools/docx/docx-color-text.js.map +1 -0
- package/dist/extensions/feishu/src/tools/docx/docx-table-ops.js +173 -0
- package/dist/extensions/feishu/src/tools/docx/docx-table-ops.js.map +1 -0
- package/dist/extensions/feishu/src/tools/docx/docx-types.js +1 -0
- package/dist/extensions/feishu/src/tools/tools.d.ts +5 -0
- package/dist/extensions/feishu/src/tools/tools.js +46 -0
- package/dist/extensions/feishu/src/tools/tools.js.map +1 -0
- package/dist/extensions/feishu/src/transport/client/client.d.ts +6 -0
- package/dist/extensions/feishu/src/transport/client/client.js +41 -0
- package/dist/extensions/feishu/src/transport/client/client.js.map +1 -0
- package/dist/extensions/feishu/src/transport/client/lark-sdk-logger.d.ts +13 -0
- package/dist/extensions/feishu/src/transport/client/lark-sdk-logger.js +104 -0
- package/dist/extensions/feishu/src/transport/client/lark-sdk-logger.js.map +1 -0
- package/dist/extensions/feishu/src/transport/reliability/dedupe.d.ts +7 -0
- package/dist/extensions/feishu/src/transport/reliability/dedupe.js +30 -0
- package/dist/extensions/feishu/src/transport/reliability/dedupe.js.map +1 -0
- package/dist/extensions/feishu/src/transport/socket-mode/monitor.d.ts +19 -0
- package/dist/extensions/feishu/src/transport/socket-mode/monitor.js +326 -0
- package/dist/extensions/feishu/src/transport/socket-mode/monitor.js.map +1 -0
- package/dist/extensions/feishu/src/transport/socket-mode/retry.d.ts +1 -0
- package/dist/extensions/feishu/src/transport/socket-mode/retry.js +10 -0
- package/dist/extensions/feishu/src/transport/socket-mode/retry.js.map +1 -0
- package/dist/extensions/feishu/src/transport/text/mentions.d.ts +1 -0
- package/dist/extensions/feishu/src/transport/text/mentions.js +9 -0
- package/dist/extensions/feishu/src/transport/text/mentions.js.map +1 -0
- package/dist/extensions/feishu/src/transport/webhook/monitor.d.ts +19 -0
- package/dist/extensions/feishu/src/transport/webhook/monitor.js +271 -0
- package/dist/extensions/feishu/src/transport/webhook/monitor.js.map +1 -0
- package/dist/extensions/feishu/src/ui/config-surface.d.ts +2 -0
- package/dist/extensions/feishu/src/ui/config-surface.js +6 -0
- package/dist/extensions/feishu/src/ui/config-surface.js.map +1 -0
- package/dist/extensions/feishu/xopc.extension.json +18 -0
- package/dist/extensions/telegram/xopc.extension.json +20 -0
- package/dist/extensions/weixin/xopc.extension.json +17 -0
- package/dist/gateway/static/root/assets/{agents-Be8iYqc2.js → agents-Dy5cGVVQ.js} +2 -2
- package/dist/gateway/static/root/assets/{agents-Be8iYqc2.js.map → agents-Dy5cGVVQ.js.map} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-BLNTewgA.js → apps-page-BOpDR0Lz.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-BLNTewgA.js.map → apps-page-BOpDR0Lz.js.map} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-CrCesccB.js +9 -0
- package/dist/gateway/static/root/assets/channels-settings-CrCesccB.js.map +1 -0
- package/dist/gateway/static/root/assets/{cron-page-HoSnHbPX.js → cron-page-B_XY0gPt.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-page-HoSnHbPX.js.map → cron-page-B_XY0gPt.js.map} +1 -1
- package/dist/gateway/static/root/assets/{cron-utils-D1mrWEee.js → cron-utils-BYdnLwhl.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-utils-D1mrWEee.js.map → cron-utils-BYdnLwhl.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-CowQhLuH.js → dist-DvaA5uNp.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-CowQhLuH.js.map → dist-DvaA5uNp.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-bjoNo6JH.js → extension-debug-page-CPSk7gFW.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-bjoNo6JH.js.map → extension-debug-page-CPSk7gFW.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-NU-MTrtq.js → extension-page-COdbk9I6.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-NU-MTrtq.js.map → extension-page-COdbk9I6.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-_kAL2Nt-.js → extension-settings-page-BlEz2Ily.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-_kAL2Nt-.js.map → extension-settings-page-BlEz2Ily.js.map} +1 -1
- package/dist/gateway/static/root/assets/index-BQNdJlkw.css +1 -0
- package/dist/gateway/static/root/assets/index-tm9ZY35l.js +144 -0
- package/dist/gateway/static/root/assets/{index-DoudkP0H.js.map → index-tm9ZY35l.js.map} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-BYPAmUPJ.js → logs-page-LSa0jmLO.js} +2 -2
- package/dist/gateway/static/root/assets/{logs-page-BYPAmUPJ.js.map → logs-page-LSa0jmLO.js.map} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-cn2fi_V3.js +2 -0
- package/dist/gateway/static/root/assets/sessions-page-cn2fi_V3.js.map +1 -0
- package/dist/gateway/static/root/assets/{settings-page-BWE5R4rm.js → settings-page-CyHd5szQ.js} +2 -2
- package/dist/gateway/static/root/assets/{settings-page-BWE5R4rm.js.map → settings-page-CyHd5szQ.js.map} +1 -1
- package/dist/gateway/static/root/assets/{skills-page-CA2NcCUa.js → skills-page-irjxwW9u.js} +2 -2
- package/dist/gateway/static/root/assets/{skills-page-CA2NcCUa.js.map → skills-page-irjxwW9u.js.map} +1 -1
- package/dist/gateway/static/root/channel-icons/feishu.svg +12 -0
- package/dist/gateway/static/root/channel-icons/lark.svg +12 -0
- package/dist/gateway/static/root/channel-icons/telegram.svg +1 -0
- package/dist/gateway/static/root/channel-icons/wechat.svg +1 -0
- package/dist/gateway/static/root/channel-icons/weixin.svg +1 -0
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.d.ts +1 -0
- package/dist/src/agent/agent-manager.js +1 -0
- package/dist/src/agent/agent-manager.js.map +1 -1
- package/dist/src/agent/service.js +1 -0
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/tools/delegate-tool.d.ts +8 -0
- package/dist/src/agent/tools/delegate-tool.js +60 -2
- package/dist/src/agent/tools/delegate-tool.js.map +1 -1
- package/dist/src/agent/tools/factory.d.ts +1 -0
- package/dist/src/agent/tools/factory.js +2 -0
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/channels/envelope-timestamp.d.ts +5 -0
- package/dist/src/channels/envelope-timestamp.js +10 -1
- package/dist/src/channels/envelope-timestamp.js.map +1 -1
- package/dist/src/channels/feishu/index.d.ts +5 -0
- package/dist/src/channels/feishu/index.js +4 -0
- package/dist/src/chat-commands/types.d.ts +1 -1
- package/dist/src/extensions/types/hooks.d.ts +46 -1
- package/dist/src/extensions/types/hooks.js +3 -0
- package/dist/src/extensions/types/hooks.js.map +1 -1
- package/dist/src/gateway/service.js +1 -1
- package/dist/src/generated/bundled-channel-plugins.d.ts +2 -1
- package/dist/src/generated/bundled-channel-plugins.js +8 -2
- package/dist/src/generated/bundled-channel-plugins.js.map +1 -1
- package/dist/src/session/session-title.js +2 -1
- package/dist/src/session/session-title.js.map +1 -1
- package/package.json +2 -2
- package/dist/gateway/static/root/assets/channels-settings-p-9taPc_.js +0 -9
- package/dist/gateway/static/root/assets/channels-settings-p-9taPc_.js.map +0 -1
- package/dist/gateway/static/root/assets/index-CbNEU6bw.css +0 -1
- package/dist/gateway/static/root/assets/index-DoudkP0H.js +0 -144
- package/dist/gateway/static/root/assets/sessions-page-cNZK0pkU.js +0 -2
- package/dist/gateway/static/root/assets/sessions-page-cNZK0pkU.js.map +0 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feishu interactive onboarding (CLI onboard) — {@link ChannelOnboardAdapter}.
|
|
3
|
+
*
|
|
4
|
+
* This configures the single-account layout under `channels.feishu.*`.
|
|
5
|
+
*/
|
|
6
|
+
import type { ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';
|
|
7
|
+
export declare const feishuOnboardAdapter: ChannelOnboardAdapter;
|
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
2
|
+
//#region extensions/feishu/src/adapters/onboard-cli.ts
|
|
3
|
+
/**
|
|
4
|
+
* Feishu interactive onboarding (CLI onboard) — {@link ChannelOnboardAdapter}.
|
|
5
|
+
*
|
|
6
|
+
* This configures the single-account layout under `channels.feishu.*`.
|
|
7
|
+
*/
|
|
8
|
+
const FEISHU_ACCOUNTS_URL = "https://accounts.feishu.cn";
|
|
9
|
+
const LARK_ACCOUNTS_URL = "https://accounts.larksuite.com";
|
|
10
|
+
const REGISTRATION_PATH = "/oauth/v1/app/registration";
|
|
11
|
+
const REQUEST_TIMEOUT_MS = 1e4;
|
|
12
|
+
function isFeishuConfigured(config) {
|
|
13
|
+
const feishu = config.channels?.feishu;
|
|
14
|
+
if (!feishu) return false;
|
|
15
|
+
const appId = typeof feishu.appId === "string" ? feishu.appId.trim() : "";
|
|
16
|
+
const appSecret = typeof feishu.appSecret === "string" ? feishu.appSecret.trim() : "";
|
|
17
|
+
return feishu.enabled === true && Boolean(appId && appSecret);
|
|
18
|
+
}
|
|
19
|
+
function parseAllowlistRaw(raw) {
|
|
20
|
+
if (!raw.trim()) return [];
|
|
21
|
+
return raw.split(/[,\s\n]+/).map((s) => s.trim()).filter(Boolean).map((e) => {
|
|
22
|
+
const num = parseInt(e, 10);
|
|
23
|
+
return !isNaN(num) && String(num) === e ? num : e;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function accountsBaseUrl(domain) {
|
|
27
|
+
return domain === "lark" ? LARK_ACCOUNTS_URL : FEISHU_ACCOUNTS_URL;
|
|
28
|
+
}
|
|
29
|
+
async function postRegistration(baseUrl, body) {
|
|
30
|
+
return await (await fetch(`${baseUrl}${REGISTRATION_PATH}`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
33
|
+
body: new URLSearchParams(body).toString(),
|
|
34
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
35
|
+
})).json();
|
|
36
|
+
}
|
|
37
|
+
async function initAppRegistration(domain) {
|
|
38
|
+
try {
|
|
39
|
+
const res = await postRegistration(accountsBaseUrl(domain), { action: "init" });
|
|
40
|
+
return Boolean(res.supported_auth_methods?.includes("client_secret"));
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function beginAppRegistration(domain) {
|
|
46
|
+
const res = await postRegistration(accountsBaseUrl(domain), {
|
|
47
|
+
action: "begin",
|
|
48
|
+
archetype: "PersonalAgent",
|
|
49
|
+
auth_method: "client_secret",
|
|
50
|
+
request_user_info: "open_id"
|
|
51
|
+
});
|
|
52
|
+
const qrUrl = new URL(res.verification_uri_complete);
|
|
53
|
+
qrUrl.searchParams.set("from", "xopc_onboard");
|
|
54
|
+
qrUrl.searchParams.set("tp", "ob_cli_app");
|
|
55
|
+
return {
|
|
56
|
+
deviceCode: res.device_code,
|
|
57
|
+
qrUrl: qrUrl.toString(),
|
|
58
|
+
intervalSec: res.interval || 5,
|
|
59
|
+
expireInSec: res.expire_in || 600
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function sleep(ms) {
|
|
63
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
64
|
+
}
|
|
65
|
+
async function pollAppRegistration(params) {
|
|
66
|
+
let domain = params.initialDomain;
|
|
67
|
+
let intervalSec = params.intervalSec;
|
|
68
|
+
let domainSwitched = false;
|
|
69
|
+
const deadline = Date.now() + params.expireInSec * 1e3;
|
|
70
|
+
while (Date.now() < deadline) {
|
|
71
|
+
let pollRes;
|
|
72
|
+
try {
|
|
73
|
+
pollRes = await postRegistration(accountsBaseUrl(domain), {
|
|
74
|
+
action: "poll",
|
|
75
|
+
device_code: params.deviceCode,
|
|
76
|
+
tp: "ob_cli_app"
|
|
77
|
+
});
|
|
78
|
+
} catch {
|
|
79
|
+
await sleep(intervalSec * 1e3);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (pollRes.user_info?.tenant_brand) {
|
|
83
|
+
const isLark = pollRes.user_info.tenant_brand === "lark";
|
|
84
|
+
if (!domainSwitched && isLark) {
|
|
85
|
+
domain = "lark";
|
|
86
|
+
domainSwitched = true;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (pollRes.client_id && pollRes.client_secret) return {
|
|
91
|
+
status: "success",
|
|
92
|
+
result: {
|
|
93
|
+
appId: pollRes.client_id,
|
|
94
|
+
appSecret: pollRes.client_secret,
|
|
95
|
+
domain,
|
|
96
|
+
openId: pollRes.user_info?.open_id
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
if (pollRes.error) if (pollRes.error === "authorization_pending") {} else if (pollRes.error === "slow_down") intervalSec += 5;
|
|
100
|
+
else if (pollRes.error === "access_denied") return { status: "access_denied" };
|
|
101
|
+
else if (pollRes.error === "expired_token") return { status: "expired" };
|
|
102
|
+
else return {
|
|
103
|
+
status: "error",
|
|
104
|
+
message: `${pollRes.error}: ${pollRes.error_description ?? "unknown"}`
|
|
105
|
+
};
|
|
106
|
+
await sleep(intervalSec * 1e3);
|
|
107
|
+
}
|
|
108
|
+
return { status: "timeout" };
|
|
109
|
+
}
|
|
110
|
+
async function printQrCode(url) {
|
|
111
|
+
try {
|
|
112
|
+
const qrcodeTerminal = await import(
|
|
113
|
+
/* @vite-ignore */
|
|
114
|
+
"qrcode-terminal"
|
|
115
|
+
);
|
|
116
|
+
await new Promise((resolve) => {
|
|
117
|
+
qrcodeTerminal.default.generate(url, { small: true }, (qr) => {
|
|
118
|
+
process.stdout.write(qr.endsWith("\n") ? qr : `${qr}\n`);
|
|
119
|
+
resolve();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
} catch {
|
|
123
|
+
console.log("Open this URL in a browser to scan:\n");
|
|
124
|
+
console.log(url);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function configureFeishu(config) {
|
|
128
|
+
console.log(`\n${"=".repeat(50)}`);
|
|
129
|
+
console.log("📱 Feishu / Lark setup");
|
|
130
|
+
console.log(`${"=".repeat(50)}\n`);
|
|
131
|
+
const existing = config.channels?.feishu;
|
|
132
|
+
const existingAppId = typeof existing?.appId === "string" ? existing.appId : "";
|
|
133
|
+
const existingDomain = typeof existing?.domain === "string" ? existing.domain : "";
|
|
134
|
+
if (existing?.enabled === true && existingAppId) {
|
|
135
|
+
if (!await confirm({
|
|
136
|
+
message: "A Feishu config already exists. Reconfigure it?",
|
|
137
|
+
default: false
|
|
138
|
+
})) return config;
|
|
139
|
+
}
|
|
140
|
+
const initialDomain = await select({
|
|
141
|
+
message: "Feishu/Lark domain:",
|
|
142
|
+
choices: [{
|
|
143
|
+
value: "feishu",
|
|
144
|
+
name: "feishu (open.feishu.cn)",
|
|
145
|
+
description: "China / Feishu"
|
|
146
|
+
}, {
|
|
147
|
+
value: "lark",
|
|
148
|
+
name: "lark (open.larksuite.com)",
|
|
149
|
+
description: "International / Lark"
|
|
150
|
+
}],
|
|
151
|
+
default: existingDomain === "lark" ? "lark" : "feishu"
|
|
152
|
+
});
|
|
153
|
+
const useScanToCreate = await initAppRegistration(initialDomain) ? await confirm({
|
|
154
|
+
message: "Create an app by scanning a QR code (recommended)?",
|
|
155
|
+
default: true
|
|
156
|
+
}) : false;
|
|
157
|
+
let appId = "";
|
|
158
|
+
let appSecret = "";
|
|
159
|
+
let domain = initialDomain;
|
|
160
|
+
let ownerOpenId;
|
|
161
|
+
if (useScanToCreate) {
|
|
162
|
+
console.log("\nScan this QR code with Feishu/Lark to create an app:\n");
|
|
163
|
+
const begin = await beginAppRegistration(initialDomain);
|
|
164
|
+
await printQrCode(begin.qrUrl);
|
|
165
|
+
console.log("\nWaiting for confirmation...\n");
|
|
166
|
+
const outcome = await pollAppRegistration({
|
|
167
|
+
deviceCode: begin.deviceCode,
|
|
168
|
+
intervalSec: begin.intervalSec,
|
|
169
|
+
expireInSec: begin.expireInSec,
|
|
170
|
+
initialDomain
|
|
171
|
+
});
|
|
172
|
+
if (outcome.status !== "success") console.log("Scan-to-create did not complete. Falling back to manual input.\n");
|
|
173
|
+
else {
|
|
174
|
+
appId = outcome.result.appId;
|
|
175
|
+
appSecret = outcome.result.appSecret;
|
|
176
|
+
domain = outcome.result.domain;
|
|
177
|
+
ownerOpenId = outcome.result.openId;
|
|
178
|
+
console.log(`✅ App created. Domain detected as "${domain}".\n`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!appId || !appSecret) {
|
|
182
|
+
console.log("📝 Feishu app credentials (from Feishu Open Platform developer console):\n");
|
|
183
|
+
appId = (await input({
|
|
184
|
+
message: "App ID (cli_xxx):",
|
|
185
|
+
default: existingAppId || void 0,
|
|
186
|
+
validate: (v) => v.trim().length > 0 || "App ID cannot be empty"
|
|
187
|
+
})).trim();
|
|
188
|
+
appSecret = (await input({
|
|
189
|
+
message: "App Secret:",
|
|
190
|
+
validate: (v) => v.trim().length > 0 || "App Secret cannot be empty"
|
|
191
|
+
})).trim();
|
|
192
|
+
domain = initialDomain;
|
|
193
|
+
}
|
|
194
|
+
const connectionMode = await select({
|
|
195
|
+
message: "Connection mode:",
|
|
196
|
+
choices: [{
|
|
197
|
+
value: "websocket",
|
|
198
|
+
name: "websocket [recommended]",
|
|
199
|
+
description: "Socket Mode (persistent connection)"
|
|
200
|
+
}, {
|
|
201
|
+
value: "webhook",
|
|
202
|
+
name: "webhook",
|
|
203
|
+
description: "Local HTTP server receives events"
|
|
204
|
+
}],
|
|
205
|
+
default: "websocket"
|
|
206
|
+
});
|
|
207
|
+
let verificationToken;
|
|
208
|
+
let encryptKey;
|
|
209
|
+
let webhookHost;
|
|
210
|
+
let webhookPort;
|
|
211
|
+
let webhookPath;
|
|
212
|
+
if (connectionMode === "webhook") {
|
|
213
|
+
console.log("\n🪝 Webhook secrets (from Feishu event subscription settings):\n");
|
|
214
|
+
verificationToken = (await input({
|
|
215
|
+
message: "Verification Token:",
|
|
216
|
+
validate: (v) => v.trim().length > 0 || "Verification Token cannot be empty"
|
|
217
|
+
})).trim();
|
|
218
|
+
encryptKey = (await input({
|
|
219
|
+
message: "Encrypt Key:",
|
|
220
|
+
validate: (v) => v.trim().length > 0 || "Encrypt Key cannot be empty"
|
|
221
|
+
})).trim();
|
|
222
|
+
webhookHost = (await input({
|
|
223
|
+
message: "Webhook host:",
|
|
224
|
+
default: typeof existing?.webhookHost === "string" ? existing.webhookHost : "127.0.0.1"
|
|
225
|
+
})).trim();
|
|
226
|
+
webhookPort = Number((await input({
|
|
227
|
+
message: "Webhook port:",
|
|
228
|
+
default: typeof existing?.webhookPort === "number" ? String(existing.webhookPort) : "3000"
|
|
229
|
+
})).trim());
|
|
230
|
+
webhookPath = (await input({
|
|
231
|
+
message: "Webhook path:",
|
|
232
|
+
default: typeof existing?.webhookPath === "string" ? existing.webhookPath : "/feishu/events"
|
|
233
|
+
})).trim();
|
|
234
|
+
}
|
|
235
|
+
const dmPolicy = await select({
|
|
236
|
+
message: "DM (private chat) policy:",
|
|
237
|
+
choices: [
|
|
238
|
+
{
|
|
239
|
+
value: "pairing",
|
|
240
|
+
name: "pairing [recommended]",
|
|
241
|
+
description: "New users must /pair before chatting"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
value: "allowlist",
|
|
245
|
+
name: "allowlist",
|
|
246
|
+
description: "Only allowlisted users can DM"
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
value: "open",
|
|
250
|
+
name: "open",
|
|
251
|
+
description: "Anyone can DM (not recommended)"
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
value: "disabled",
|
|
255
|
+
name: "disabled",
|
|
256
|
+
description: "Disable DMs"
|
|
257
|
+
}
|
|
258
|
+
],
|
|
259
|
+
default: "pairing"
|
|
260
|
+
});
|
|
261
|
+
let allowFrom;
|
|
262
|
+
if (dmPolicy === "allowlist") {
|
|
263
|
+
const defaultAllow = ownerOpenId && (existing?.allowFrom == null || Array.isArray(existing.allowFrom) && existing.allowFrom.length === 0) ? ownerOpenId : "";
|
|
264
|
+
allowFrom = parseAllowlistRaw(await input({
|
|
265
|
+
message: "Allowed user open_id / union_id / numeric ids (comma-separated):",
|
|
266
|
+
default: defaultAllow
|
|
267
|
+
}) || defaultAllow);
|
|
268
|
+
}
|
|
269
|
+
const groupPolicy = await select({
|
|
270
|
+
message: "Group chat policy:",
|
|
271
|
+
choices: [
|
|
272
|
+
{
|
|
273
|
+
value: "allowlist",
|
|
274
|
+
name: "allowlist [recommended]",
|
|
275
|
+
description: "Only allowlisted groups can use the bot"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
value: "open",
|
|
279
|
+
name: "open",
|
|
280
|
+
description: "All groups allowed"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
value: "disabled",
|
|
284
|
+
name: "disabled",
|
|
285
|
+
description: "Disable groups"
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
default: "allowlist"
|
|
289
|
+
});
|
|
290
|
+
let groupAllowFrom;
|
|
291
|
+
if (groupPolicy === "allowlist") groupAllowFrom = parseAllowlistRaw(await input({
|
|
292
|
+
message: "Allowed group chat IDs (comma-separated, e.g. oc_xxx):",
|
|
293
|
+
default: ""
|
|
294
|
+
}));
|
|
295
|
+
const requireMention = await confirm({
|
|
296
|
+
message: "Require @mention in groups?",
|
|
297
|
+
default: true
|
|
298
|
+
});
|
|
299
|
+
const renderMode = await select({
|
|
300
|
+
message: "Default render mode:",
|
|
301
|
+
choices: [
|
|
302
|
+
{
|
|
303
|
+
value: "auto",
|
|
304
|
+
name: "auto",
|
|
305
|
+
description: "Let xopc decide (default)"
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
value: "raw",
|
|
309
|
+
name: "raw",
|
|
310
|
+
description: "Send plain text only"
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
value: "card",
|
|
314
|
+
name: "card",
|
|
315
|
+
description: "Prefer interactive cards (CardKit streaming)"
|
|
316
|
+
}
|
|
317
|
+
],
|
|
318
|
+
default: "auto"
|
|
319
|
+
});
|
|
320
|
+
const streaming = await confirm({
|
|
321
|
+
message: "Enable streaming updates (Thinking… + incremental output)?",
|
|
322
|
+
default: false
|
|
323
|
+
});
|
|
324
|
+
const reactionNotifications = await select({
|
|
325
|
+
message: "Reaction notifications:",
|
|
326
|
+
choices: [
|
|
327
|
+
{
|
|
328
|
+
value: "off",
|
|
329
|
+
name: "off",
|
|
330
|
+
description: "Disable reaction notifications"
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
value: "own",
|
|
334
|
+
name: "own [recommended]",
|
|
335
|
+
description: "Only notify reactions to bot messages"
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
value: "all",
|
|
339
|
+
name: "all",
|
|
340
|
+
description: "Notify all reactions (noisy)"
|
|
341
|
+
}
|
|
342
|
+
],
|
|
343
|
+
default: "own"
|
|
344
|
+
});
|
|
345
|
+
const enableFeishuTools = await select({
|
|
346
|
+
message: "Enable Feishu tools (docs/wiki/drive/etc.)?",
|
|
347
|
+
choices: [
|
|
348
|
+
{
|
|
349
|
+
value: "minimal",
|
|
350
|
+
name: "minimal",
|
|
351
|
+
description: "No extra tools (chat only)"
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
value: "docs",
|
|
355
|
+
name: "docs",
|
|
356
|
+
description: "Enable doc/wiki/drive/scopes (common)"
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
value: "full",
|
|
360
|
+
name: "full",
|
|
361
|
+
description: "Enable doc/wiki/drive/bitable/perm/scopes"
|
|
362
|
+
}
|
|
363
|
+
],
|
|
364
|
+
default: "docs"
|
|
365
|
+
});
|
|
366
|
+
const tools = enableFeishuTools === "minimal" ? {
|
|
367
|
+
doc: false,
|
|
368
|
+
wiki: false,
|
|
369
|
+
drive: false,
|
|
370
|
+
perm: false,
|
|
371
|
+
bitable: false,
|
|
372
|
+
scopes: true
|
|
373
|
+
} : enableFeishuTools === "docs" ? {
|
|
374
|
+
doc: true,
|
|
375
|
+
wiki: true,
|
|
376
|
+
drive: true,
|
|
377
|
+
perm: false,
|
|
378
|
+
bitable: true,
|
|
379
|
+
scopes: true
|
|
380
|
+
} : {
|
|
381
|
+
doc: true,
|
|
382
|
+
wiki: true,
|
|
383
|
+
drive: true,
|
|
384
|
+
perm: true,
|
|
385
|
+
bitable: true,
|
|
386
|
+
scopes: true
|
|
387
|
+
};
|
|
388
|
+
const nextFeishu = {
|
|
389
|
+
...existing ?? {},
|
|
390
|
+
enabled: true,
|
|
391
|
+
appId,
|
|
392
|
+
appSecret,
|
|
393
|
+
domain,
|
|
394
|
+
connectionMode,
|
|
395
|
+
...connectionMode === "webhook" ? {
|
|
396
|
+
verificationToken,
|
|
397
|
+
encryptKey,
|
|
398
|
+
webhookHost,
|
|
399
|
+
webhookPort,
|
|
400
|
+
webhookPath
|
|
401
|
+
} : {},
|
|
402
|
+
dmPolicy,
|
|
403
|
+
groupPolicy,
|
|
404
|
+
allowFrom: allowFrom ?? existing?.allowFrom ?? [],
|
|
405
|
+
groupAllowFrom: groupAllowFrom ?? existing?.groupAllowFrom ?? [],
|
|
406
|
+
requireMention,
|
|
407
|
+
renderMode,
|
|
408
|
+
streaming,
|
|
409
|
+
reactionNotifications,
|
|
410
|
+
actions: { reactions: true },
|
|
411
|
+
tools,
|
|
412
|
+
historyLimit: typeof existing?.historyLimit === "number" ? existing.historyLimit : 50,
|
|
413
|
+
textChunkLimit: typeof existing?.textChunkLimit === "number" ? existing.textChunkLimit : 4e3
|
|
414
|
+
};
|
|
415
|
+
const newConfig = {
|
|
416
|
+
...config,
|
|
417
|
+
channels: {
|
|
418
|
+
...config.channels,
|
|
419
|
+
feishu: nextFeishu
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
console.log("\n✅ Feishu configuration complete\n");
|
|
423
|
+
return newConfig;
|
|
424
|
+
}
|
|
425
|
+
const feishuOnboardAdapter = {
|
|
426
|
+
isConfigured: isFeishuConfigured,
|
|
427
|
+
configure: configureFeishu
|
|
428
|
+
};
|
|
429
|
+
//#endregion
|
|
430
|
+
export { feishuOnboardAdapter };
|
|
431
|
+
|
|
432
|
+
//# sourceMappingURL=onboard-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onboard-cli.js","names":[],"sources":["../../../../../extensions/feishu/src/adapters/onboard-cli.ts"],"sourcesContent":["/**\n * Feishu interactive onboarding (CLI onboard) — {@link ChannelOnboardAdapter}.\n *\n * This configures the single-account layout under `channels.feishu.*`.\n */\n\nimport { confirm, input, select } from '@inquirer/prompts';\n\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { ChannelOnboardAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\n\ntype DmPolicy = 'pairing' | 'allowlist' | 'open' | 'disabled';\ntype GroupPolicy = 'open' | 'disabled' | 'allowlist';\ntype Domain = 'feishu' | 'lark';\ntype RenderMode = 'auto' | 'raw' | 'card';\ntype ConnectionMode = 'websocket' | 'webhook';\ntype ReactionNotifications = 'off' | 'own' | 'all';\n\ntype AppRegistrationResult = {\n appId: string;\n appSecret: string;\n domain: Domain;\n openId?: string;\n};\n\nconst FEISHU_ACCOUNTS_URL = 'https://accounts.feishu.cn';\nconst LARK_ACCOUNTS_URL = 'https://accounts.larksuite.com';\nconst REGISTRATION_PATH = '/oauth/v1/app/registration';\nconst REQUEST_TIMEOUT_MS = 10_000;\n\nfunction isFeishuConfigured(config: Config): boolean {\n const feishu = config.channels?.feishu as Record<string, unknown> | undefined;\n if (!feishu) return false;\n const appId = typeof feishu.appId === 'string' ? feishu.appId.trim() : '';\n const appSecret = typeof feishu.appSecret === 'string' ? feishu.appSecret.trim() : '';\n const enabled = feishu.enabled === true;\n return enabled && Boolean(appId && appSecret);\n}\n\nfunction parseAllowlistRaw(raw: string): Array<string | number> {\n if (!raw.trim()) return [];\n const entries = raw\n .split(/[,\\s\\n]+/)\n .map((s) => s.trim())\n .filter(Boolean);\n return entries.map((e) => {\n const num = parseInt(e, 10);\n return !isNaN(num) && String(num) === e ? num : e;\n });\n}\n\nfunction accountsBaseUrl(domain: Domain): string {\n return domain === 'lark' ? LARK_ACCOUNTS_URL : FEISHU_ACCOUNTS_URL;\n}\n\nasync function postRegistration<T>(baseUrl: string, body: Record<string, string>): Promise<T> {\n const res = await fetch(`${baseUrl}${REGISTRATION_PATH}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams(body).toString(),\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n // Registration poll returns 4xx for pending/error states with a JSON body.\n return (await res.json()) as T;\n}\n\nasync function initAppRegistration(domain: Domain): Promise<boolean> {\n type InitResponse = { supported_auth_methods?: string[] };\n try {\n const res = await postRegistration<InitResponse>(accountsBaseUrl(domain), { action: 'init' });\n return Boolean(res.supported_auth_methods?.includes('client_secret'));\n } catch {\n return false;\n }\n}\n\nasync function beginAppRegistration(domain: Domain): Promise<{\n deviceCode: string;\n qrUrl: string;\n intervalSec: number;\n expireInSec: number;\n}> {\n type RawBeginResponse = {\n device_code: string;\n verification_uri_complete: string;\n interval?: number;\n expire_in?: number;\n };\n const res = await postRegistration<RawBeginResponse>(accountsBaseUrl(domain), {\n action: 'begin',\n archetype: 'PersonalAgent',\n auth_method: 'client_secret',\n request_user_info: 'open_id',\n });\n const qrUrl = new URL(res.verification_uri_complete);\n qrUrl.searchParams.set('from', 'xopc_onboard');\n qrUrl.searchParams.set('tp', 'ob_cli_app');\n return {\n deviceCode: res.device_code,\n qrUrl: qrUrl.toString(),\n intervalSec: res.interval || 5,\n expireInSec: res.expire_in || 600,\n };\n}\n\nasync function sleep(ms: number): Promise<void> {\n await new Promise<void>((r) => setTimeout(r, ms));\n}\n\nasync function pollAppRegistration(params: {\n deviceCode: string;\n intervalSec: number;\n expireInSec: number;\n initialDomain: Domain;\n}): Promise<\n | { status: 'success'; result: AppRegistrationResult }\n | { status: 'access_denied' | 'expired' | 'timeout'; message?: string }\n | { status: 'error'; message: string }\n> {\n type PollResponse = {\n client_id?: string;\n client_secret?: string;\n user_info?: { open_id?: string; tenant_brand?: 'feishu' | 'lark' };\n error?: string;\n error_description?: string;\n };\n\n let domain: Domain = params.initialDomain;\n let intervalSec = params.intervalSec;\n let domainSwitched = false;\n const deadline = Date.now() + params.expireInSec * 1000;\n\n while (Date.now() < deadline) {\n let pollRes: PollResponse;\n try {\n pollRes = await postRegistration<PollResponse>(accountsBaseUrl(domain), {\n action: 'poll',\n device_code: params.deviceCode,\n tp: 'ob_cli_app',\n });\n } catch {\n await sleep(intervalSec * 1000);\n continue;\n }\n\n // Auto-detect domain based on tenant.\n if (pollRes.user_info?.tenant_brand) {\n const isLark = pollRes.user_info.tenant_brand === 'lark';\n if (!domainSwitched && isLark) {\n domain = 'lark';\n domainSwitched = true;\n continue;\n }\n }\n\n if (pollRes.client_id && pollRes.client_secret) {\n return {\n status: 'success',\n result: {\n appId: pollRes.client_id,\n appSecret: pollRes.client_secret,\n domain,\n openId: pollRes.user_info?.open_id,\n },\n };\n }\n\n if (pollRes.error) {\n if (pollRes.error === 'authorization_pending') {\n // keep waiting\n } else if (pollRes.error === 'slow_down') {\n intervalSec += 5;\n } else if (pollRes.error === 'access_denied') {\n return { status: 'access_denied' };\n } else if (pollRes.error === 'expired_token') {\n return { status: 'expired' };\n } else {\n return {\n status: 'error',\n message: `${pollRes.error}: ${pollRes.error_description ?? 'unknown'}`,\n };\n }\n }\n\n await sleep(intervalSec * 1000);\n }\n\n return { status: 'timeout' };\n}\n\nasync function printQrCode(url: string): Promise<void> {\n try {\n const qrcodeTerminal = await import(/* @vite-ignore */ 'qrcode-terminal');\n await new Promise<void>((resolve) => {\n qrcodeTerminal.default.generate(url, { small: true }, (qr: string) => {\n process.stdout.write(qr.endsWith('\\n') ? qr : `${qr}\\n`);\n resolve();\n });\n });\n } catch {\n console.log('Open this URL in a browser to scan:\\n');\n console.log(url);\n }\n}\n\nasync function configureFeishu(config: Config): Promise<Config> {\n console.log(`\\n${'='.repeat(50)}`);\n console.log('📱 Feishu / Lark setup');\n console.log(`${'='.repeat(50)}\\n`);\n\n const existing = config.channels?.feishu as Record<string, unknown> | undefined;\n const existingAppId = typeof existing?.appId === 'string' ? existing.appId : '';\n const existingDomain = typeof existing?.domain === 'string' ? existing.domain : '';\n\n if (existing?.enabled === true && existingAppId) {\n const keep = await confirm({\n message: 'A Feishu config already exists. Reconfigure it?',\n default: false,\n });\n if (!keep) return config;\n }\n\n const initialDomain = await select<Domain>({\n message: 'Feishu/Lark domain:',\n choices: [\n { value: 'feishu', name: 'feishu (open.feishu.cn)', description: 'China / Feishu' },\n { value: 'lark', name: 'lark (open.larksuite.com)', description: 'International / Lark' },\n ],\n default: existingDomain === 'lark' ? 'lark' : 'feishu',\n });\n\n const canScanToCreate = await initAppRegistration(initialDomain);\n const useScanToCreate = canScanToCreate\n ? await confirm({\n message: 'Create an app by scanning a QR code (recommended)?',\n default: true,\n })\n : false;\n\n let appId = '';\n let appSecret = '';\n let domain: Domain = initialDomain;\n let ownerOpenId: string | undefined;\n\n if (useScanToCreate) {\n console.log('\\nScan this QR code with Feishu/Lark to create an app:\\n');\n const begin = await beginAppRegistration(initialDomain);\n await printQrCode(begin.qrUrl);\n console.log('\\nWaiting for confirmation...\\n');\n const outcome = await pollAppRegistration({\n deviceCode: begin.deviceCode,\n intervalSec: begin.intervalSec,\n expireInSec: begin.expireInSec,\n initialDomain,\n });\n if (outcome.status !== 'success') {\n console.log('Scan-to-create did not complete. Falling back to manual input.\\n');\n } else {\n appId = outcome.result.appId;\n appSecret = outcome.result.appSecret;\n domain = outcome.result.domain;\n ownerOpenId = outcome.result.openId;\n console.log(`✅ App created. Domain detected as \"${domain}\".\\n`);\n }\n }\n\n if (!appId || !appSecret) {\n console.log('📝 Feishu app credentials (from Feishu Open Platform developer console):\\n');\n\n appId = (await input({\n message: 'App ID (cli_xxx):',\n default: existingAppId || undefined,\n validate: (v) => v.trim().length > 0 || 'App ID cannot be empty',\n })).trim();\n\n appSecret = (await input({\n message: 'App Secret:',\n validate: (v) => v.trim().length > 0 || 'App Secret cannot be empty',\n })).trim();\n\n domain = initialDomain;\n }\n\n const connectionMode = await select<ConnectionMode>({\n message: 'Connection mode:',\n choices: [\n { value: 'websocket', name: 'websocket [recommended]', description: 'Socket Mode (persistent connection)' },\n { value: 'webhook', name: 'webhook', description: 'Local HTTP server receives events' },\n ],\n default: 'websocket',\n });\n\n let verificationToken: string | undefined;\n let encryptKey: string | undefined;\n let webhookHost: string | undefined;\n let webhookPort: number | undefined;\n let webhookPath: string | undefined;\n if (connectionMode === 'webhook') {\n console.log('\\n🪝 Webhook secrets (from Feishu event subscription settings):\\n');\n verificationToken = (await input({\n message: 'Verification Token:',\n validate: (v) => v.trim().length > 0 || 'Verification Token cannot be empty',\n })).trim();\n encryptKey = (await input({\n message: 'Encrypt Key:',\n validate: (v) => v.trim().length > 0 || 'Encrypt Key cannot be empty',\n })).trim();\n webhookHost = (await input({\n message: 'Webhook host:',\n default: typeof existing?.webhookHost === 'string' ? existing.webhookHost : '127.0.0.1',\n })).trim();\n webhookPort = Number(\n (await input({\n message: 'Webhook port:',\n default: typeof existing?.webhookPort === 'number' ? String(existing.webhookPort) : '3000',\n })).trim(),\n );\n webhookPath = (await input({\n message: 'Webhook path:',\n default: typeof existing?.webhookPath === 'string' ? existing.webhookPath : '/feishu/events',\n })).trim();\n }\n\n const dmPolicy = await select<DmPolicy>({\n message: 'DM (private chat) policy:',\n choices: [\n { value: 'pairing', name: 'pairing [recommended]', description: 'New users must /pair before chatting' },\n { value: 'allowlist', name: 'allowlist', description: 'Only allowlisted users can DM' },\n { value: 'open', name: 'open', description: 'Anyone can DM (not recommended)' },\n { value: 'disabled', name: 'disabled', description: 'Disable DMs' },\n ],\n default: 'pairing',\n });\n\n let allowFrom: Array<string | number> | undefined;\n if (dmPolicy === 'allowlist') {\n const defaultAllow =\n ownerOpenId && (existing?.allowFrom == null || (Array.isArray(existing.allowFrom) && existing.allowFrom.length === 0))\n ? ownerOpenId\n : '';\n const raw = await input({\n message: 'Allowed user open_id / union_id / numeric ids (comma-separated):',\n default: defaultAllow,\n });\n allowFrom = parseAllowlistRaw(raw || defaultAllow);\n }\n\n const groupPolicy = await select<GroupPolicy>({\n message: 'Group chat policy:',\n choices: [\n { value: 'allowlist', name: 'allowlist [recommended]', description: 'Only allowlisted groups can use the bot' },\n { value: 'open', name: 'open', description: 'All groups allowed' },\n { value: 'disabled', name: 'disabled', description: 'Disable groups' },\n ],\n default: 'allowlist',\n });\n\n let groupAllowFrom: Array<string | number> | undefined;\n if (groupPolicy === 'allowlist') {\n const raw = await input({\n message: 'Allowed group chat IDs (comma-separated, e.g. oc_xxx):',\n default: '',\n });\n groupAllowFrom = parseAllowlistRaw(raw);\n }\n\n const requireMention = await confirm({\n message: 'Require @mention in groups?',\n default: true,\n });\n\n const renderMode = await select<RenderMode>({\n message: 'Default render mode:',\n choices: [\n { value: 'auto', name: 'auto', description: 'Let xopc decide (default)' },\n { value: 'raw', name: 'raw', description: 'Send plain text only' },\n { value: 'card', name: 'card', description: 'Prefer interactive cards (CardKit streaming)' },\n ],\n default: 'auto',\n });\n\n const streaming = await confirm({\n message: 'Enable streaming updates (Thinking… + incremental output)?',\n default: false,\n });\n\n const reactionNotifications = await select<ReactionNotifications>({\n message: 'Reaction notifications:',\n choices: [\n { value: 'off', name: 'off', description: 'Disable reaction notifications' },\n { value: 'own', name: 'own [recommended]', description: 'Only notify reactions to bot messages' },\n { value: 'all', name: 'all', description: 'Notify all reactions (noisy)' },\n ],\n default: 'own',\n });\n\n const enableFeishuTools = await select<'minimal' | 'docs' | 'full'>({\n message: 'Enable Feishu tools (docs/wiki/drive/etc.)?',\n choices: [\n { value: 'minimal', name: 'minimal', description: 'No extra tools (chat only)' },\n { value: 'docs', name: 'docs', description: 'Enable doc/wiki/drive/scopes (common)' },\n { value: 'full', name: 'full', description: 'Enable doc/wiki/drive/bitable/perm/scopes' },\n ],\n default: 'docs',\n });\n\n const tools =\n enableFeishuTools === 'minimal'\n ? { doc: false, wiki: false, drive: false, perm: false, bitable: false, scopes: true }\n : enableFeishuTools === 'docs'\n ? { doc: true, wiki: true, drive: true, perm: false, bitable: true, scopes: true }\n : { doc: true, wiki: true, drive: true, perm: true, bitable: true, scopes: true };\n\n const nextFeishu: Record<string, unknown> = {\n ...(existing ?? {}),\n enabled: true,\n appId,\n appSecret,\n domain,\n connectionMode,\n ...(connectionMode === 'webhook'\n ? {\n verificationToken,\n encryptKey,\n webhookHost,\n webhookPort,\n webhookPath,\n }\n : {}),\n dmPolicy,\n groupPolicy,\n allowFrom: allowFrom ?? (existing?.allowFrom as any) ?? [],\n groupAllowFrom: groupAllowFrom ?? (existing?.groupAllowFrom as any) ?? [],\n requireMention,\n renderMode,\n streaming,\n reactionNotifications,\n actions: { reactions: true },\n tools,\n historyLimit: typeof existing?.historyLimit === 'number' ? existing.historyLimit : 50,\n textChunkLimit: typeof existing?.textChunkLimit === 'number' ? existing.textChunkLimit : 4000,\n };\n\n const newConfig: Config = {\n ...config,\n channels: {\n ...config.channels,\n feishu: nextFeishu,\n },\n };\n\n console.log('\\n✅ Feishu configuration complete\\n');\n return newConfig;\n}\n\nexport const feishuOnboardAdapter: ChannelOnboardAdapter = {\n isConfigured: isFeishuConfigured,\n configure: configureFeishu,\n};\n\n"],"mappings":";;;;;;;AAyBA,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB;AAE3B,SAAS,mBAAmB,QAAyB;CACnD,MAAM,SAAS,OAAO,UAAU;AAChC,KAAI,CAAC,OAAQ,QAAO;CACpB,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,MAAM,MAAM,GAAG;CACvE,MAAM,YAAY,OAAO,OAAO,cAAc,WAAW,OAAO,UAAU,MAAM,GAAG;AAEnF,QADgB,OAAO,YAAY,QACjB,QAAQ,SAAS,UAAU;;AAG/C,SAAS,kBAAkB,KAAqC;AAC9D,KAAI,CAAC,IAAI,MAAM,CAAE,QAAO,EAAE;AAK1B,QAJgB,IACb,MAAM,WAAW,CACjB,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QACI,CAAC,KAAK,MAAM;EACxB,MAAM,MAAM,SAAS,GAAG,GAAG;AAC3B,SAAO,CAAC,MAAM,IAAI,IAAI,OAAO,IAAI,KAAK,IAAI,MAAM;GAChD;;AAGJ,SAAS,gBAAgB,QAAwB;AAC/C,QAAO,WAAW,SAAS,oBAAoB;;AAGjD,eAAe,iBAAoB,SAAiB,MAA0C;AAQ5F,QAAQ,OAAM,MAPI,MAAM,GAAG,UAAU,qBAAqB;EACxD,QAAQ;EACR,SAAS,EAAE,gBAAgB,qCAAqC;EAChE,MAAM,IAAI,gBAAgB,KAAK,CAAC,UAAU;EAC1C,QAAQ,YAAY,QAAQ,mBAAmB;EAChD,CAAC,EAEgB,MAAM;;AAG1B,eAAe,oBAAoB,QAAkC;AAEnE,KAAI;EACF,MAAM,MAAM,MAAM,iBAA+B,gBAAgB,OAAO,EAAE,EAAE,QAAQ,QAAQ,CAAC;AAC7F,SAAO,QAAQ,IAAI,wBAAwB,SAAS,gBAAgB,CAAC;SAC/D;AACN,SAAO;;;AAIX,eAAe,qBAAqB,QAKjC;CAOD,MAAM,MAAM,MAAM,iBAAmC,gBAAgB,OAAO,EAAE;EAC5E,QAAQ;EACR,WAAW;EACX,aAAa;EACb,mBAAmB;EACpB,CAAC;CACF,MAAM,QAAQ,IAAI,IAAI,IAAI,0BAA0B;AACpD,OAAM,aAAa,IAAI,QAAQ,eAAe;AAC9C,OAAM,aAAa,IAAI,MAAM,aAAa;AAC1C,QAAO;EACL,YAAY,IAAI;EAChB,OAAO,MAAM,UAAU;EACvB,aAAa,IAAI,YAAY;EAC7B,aAAa,IAAI,aAAa;EAC/B;;AAGH,eAAe,MAAM,IAA2B;AAC9C,OAAM,IAAI,SAAe,MAAM,WAAW,GAAG,GAAG,CAAC;;AAGnD,eAAe,oBAAoB,QASjC;CASA,IAAI,SAAiB,OAAO;CAC5B,IAAI,cAAc,OAAO;CACzB,IAAI,iBAAiB;CACrB,MAAM,WAAW,KAAK,KAAK,GAAG,OAAO,cAAc;AAEnD,QAAO,KAAK,KAAK,GAAG,UAAU;EAC5B,IAAI;AACJ,MAAI;AACF,aAAU,MAAM,iBAA+B,gBAAgB,OAAO,EAAE;IACtE,QAAQ;IACR,aAAa,OAAO;IACpB,IAAI;IACL,CAAC;UACI;AACN,SAAM,MAAM,cAAc,IAAK;AAC/B;;AAIF,MAAI,QAAQ,WAAW,cAAc;GACnC,MAAM,SAAS,QAAQ,UAAU,iBAAiB;AAClD,OAAI,CAAC,kBAAkB,QAAQ;AAC7B,aAAS;AACT,qBAAiB;AACjB;;;AAIJ,MAAI,QAAQ,aAAa,QAAQ,cAC/B,QAAO;GACL,QAAQ;GACR,QAAQ;IACN,OAAO,QAAQ;IACf,WAAW,QAAQ;IACnB;IACA,QAAQ,QAAQ,WAAW;IAC5B;GACF;AAGH,MAAI,QAAQ,MACV,KAAI,QAAQ,UAAU,yBAAyB,YAEpC,QAAQ,UAAU,YAC3B,gBAAe;WACN,QAAQ,UAAU,gBAC3B,QAAO,EAAE,QAAQ,iBAAiB;WACzB,QAAQ,UAAU,gBAC3B,QAAO,EAAE,QAAQ,WAAW;MAE5B,QAAO;GACL,QAAQ;GACR,SAAS,GAAG,QAAQ,MAAM,IAAI,QAAQ,qBAAqB;GAC5D;AAIL,QAAM,MAAM,cAAc,IAAK;;AAGjC,QAAO,EAAE,QAAQ,WAAW;;AAG9B,eAAe,YAAY,KAA4B;AACrD,KAAI;EACF,MAAM,iBAAiB,MAAM;;GAA0B;;AACvD,QAAM,IAAI,SAAe,YAAY;AACnC,kBAAe,QAAQ,SAAS,KAAK,EAAE,OAAO,MAAM,GAAG,OAAe;AACpE,YAAQ,OAAO,MAAM,GAAG,SAAS,KAAK,GAAG,KAAK,GAAG,GAAG,IAAI;AACxD,aAAS;KACT;IACF;SACI;AACN,UAAQ,IAAI,wCAAwC;AACpD,UAAQ,IAAI,IAAI;;;AAIpB,eAAe,gBAAgB,QAAiC;AAC9D,SAAQ,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG;AAClC,SAAQ,IAAI,yBAAyB;AACrC,SAAQ,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI;CAElC,MAAM,WAAW,OAAO,UAAU;CAClC,MAAM,gBAAgB,OAAO,UAAU,UAAU,WAAW,SAAS,QAAQ;CAC7E,MAAM,iBAAiB,OAAO,UAAU,WAAW,WAAW,SAAS,SAAS;AAEhF,KAAI,UAAU,YAAY,QAAQ;MAK5B,CAAC,MAJc,QAAQ;GACzB,SAAS;GACT,SAAS;GACV,CAAC,CACS,QAAO;;CAGpB,MAAM,gBAAgB,MAAM,OAAe;EACzC,SAAS;EACT,SAAS,CACP;GAAE,OAAO;GAAU,MAAM;GAA2B,aAAa;GAAkB,EACnF;GAAE,OAAO;GAAQ,MAAM;GAA6B,aAAa;GAAwB,CAC1F;EACD,SAAS,mBAAmB,SAAS,SAAS;EAC/C,CAAC;CAGF,MAAM,kBAAkB,MADM,oBAAoB,cAAc,GAE5D,MAAM,QAAQ;EACZ,SAAS;EACT,SAAS;EACV,CAAC,GACF;CAEJ,IAAI,QAAQ;CACZ,IAAI,YAAY;CAChB,IAAI,SAAiB;CACrB,IAAI;AAEJ,KAAI,iBAAiB;AACnB,UAAQ,IAAI,2DAA2D;EACvE,MAAM,QAAQ,MAAM,qBAAqB,cAAc;AACvD,QAAM,YAAY,MAAM,MAAM;AAC9B,UAAQ,IAAI,kCAAkC;EAC9C,MAAM,UAAU,MAAM,oBAAoB;GACxC,YAAY,MAAM;GAClB,aAAa,MAAM;GACnB,aAAa,MAAM;GACnB;GACD,CAAC;AACF,MAAI,QAAQ,WAAW,UACrB,SAAQ,IAAI,mEAAmE;OAC1E;AACL,WAAQ,QAAQ,OAAO;AACvB,eAAY,QAAQ,OAAO;AAC3B,YAAS,QAAQ,OAAO;AACxB,iBAAc,QAAQ,OAAO;AAC7B,WAAQ,IAAI,sCAAsC,OAAO,MAAM;;;AAInE,KAAI,CAAC,SAAS,CAAC,WAAW;AACxB,UAAQ,IAAI,6EAA6E;AAEzF,WAAS,MAAM,MAAM;GACnB,SAAS;GACT,SAAS,iBAAiB,KAAA;GAC1B,WAAW,MAAM,EAAE,MAAM,CAAC,SAAS,KAAK;GACzC,CAAC,EAAE,MAAM;AAEV,eAAa,MAAM,MAAM;GACvB,SAAS;GACT,WAAW,MAAM,EAAE,MAAM,CAAC,SAAS,KAAK;GACzC,CAAC,EAAE,MAAM;AAEV,WAAS;;CAGX,MAAM,iBAAiB,MAAM,OAAuB;EAClD,SAAS;EACT,SAAS,CACP;GAAE,OAAO;GAAa,MAAM;GAA4B,aAAa;GAAuC,EAC5G;GAAE,OAAO;GAAW,MAAM;GAAW,aAAa;GAAqC,CACxF;EACD,SAAS;EACV,CAAC;CAEF,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACJ,KAAI,mBAAmB,WAAW;AAChC,UAAQ,IAAI,oEAAoE;AAChF,uBAAqB,MAAM,MAAM;GAC/B,SAAS;GACT,WAAW,MAAM,EAAE,MAAM,CAAC,SAAS,KAAK;GACzC,CAAC,EAAE,MAAM;AACV,gBAAc,MAAM,MAAM;GACxB,SAAS;GACT,WAAW,MAAM,EAAE,MAAM,CAAC,SAAS,KAAK;GACzC,CAAC,EAAE,MAAM;AACV,iBAAe,MAAM,MAAM;GACzB,SAAS;GACT,SAAS,OAAO,UAAU,gBAAgB,WAAW,SAAS,cAAc;GAC7E,CAAC,EAAE,MAAM;AACV,gBAAc,QACX,MAAM,MAAM;GACX,SAAS;GACT,SAAS,OAAO,UAAU,gBAAgB,WAAW,OAAO,SAAS,YAAY,GAAG;GACrF,CAAC,EAAE,MAAM,CACX;AACD,iBAAe,MAAM,MAAM;GACzB,SAAS;GACT,SAAS,OAAO,UAAU,gBAAgB,WAAW,SAAS,cAAc;GAC7E,CAAC,EAAE,MAAM;;CAGZ,MAAM,WAAW,MAAM,OAAiB;EACtC,SAAS;EACT,SAAS;GACP;IAAE,OAAO;IAAW,MAAM;IAA0B,aAAa;IAAwC;GACzG;IAAE,OAAO;IAAa,MAAM;IAAa,aAAa;IAAiC;GACvF;IAAE,OAAO;IAAQ,MAAM;IAAQ,aAAa;IAAmC;GAC/E;IAAE,OAAO;IAAY,MAAM;IAAY,aAAa;IAAe;GACpE;EACD,SAAS;EACV,CAAC;CAEF,IAAI;AACJ,KAAI,aAAa,aAAa;EAC5B,MAAM,eACJ,gBAAgB,UAAU,aAAa,QAAS,MAAM,QAAQ,SAAS,UAAU,IAAI,SAAS,UAAU,WAAW,KAC/G,cACA;AAKN,cAAY,kBAAkB,MAJZ,MAAM;GACtB,SAAS;GACT,SAAS;GACV,CAAC,IACmC,aAAa;;CAGpD,MAAM,cAAc,MAAM,OAAoB;EAC5C,SAAS;EACT,SAAS;GACP;IAAE,OAAO;IAAa,MAAM;IAA4B,aAAa;IAA2C;GAChH;IAAE,OAAO;IAAQ,MAAM;IAAQ,aAAa;IAAsB;GAClE;IAAE,OAAO;IAAY,MAAM;IAAY,aAAa;IAAkB;GACvE;EACD,SAAS;EACV,CAAC;CAEF,IAAI;AACJ,KAAI,gBAAgB,YAKlB,kBAAiB,kBAAkB,MAJjB,MAAM;EACtB,SAAS;EACT,SAAS;EACV,CAAC,CACqC;CAGzC,MAAM,iBAAiB,MAAM,QAAQ;EACnC,SAAS;EACT,SAAS;EACV,CAAC;CAEF,MAAM,aAAa,MAAM,OAAmB;EAC1C,SAAS;EACT,SAAS;GACP;IAAE,OAAO;IAAQ,MAAM;IAAQ,aAAa;IAA6B;GACzE;IAAE,OAAO;IAAO,MAAM;IAAO,aAAa;IAAwB;GAClE;IAAE,OAAO;IAAQ,MAAM;IAAQ,aAAa;IAAgD;GAC7F;EACD,SAAS;EACV,CAAC;CAEF,MAAM,YAAY,MAAM,QAAQ;EAC9B,SAAS;EACT,SAAS;EACV,CAAC;CAEF,MAAM,wBAAwB,MAAM,OAA8B;EAChE,SAAS;EACT,SAAS;GACP;IAAE,OAAO;IAAO,MAAM;IAAO,aAAa;IAAkC;GAC5E;IAAE,OAAO;IAAO,MAAM;IAAsB,aAAa;IAAyC;GAClG;IAAE,OAAO;IAAO,MAAM;IAAO,aAAa;IAAgC;GAC3E;EACD,SAAS;EACV,CAAC;CAEF,MAAM,oBAAoB,MAAM,OAAoC;EAClE,SAAS;EACT,SAAS;GACP;IAAE,OAAO;IAAW,MAAM;IAAW,aAAa;IAA8B;GAChF;IAAE,OAAO;IAAQ,MAAM;IAAQ,aAAa;IAAyC;GACrF;IAAE,OAAO;IAAQ,MAAM;IAAQ,aAAa;IAA6C;GAC1F;EACD,SAAS;EACV,CAAC;CAEF,MAAM,QACJ,sBAAsB,YAClB;EAAE,KAAK;EAAO,MAAM;EAAO,OAAO;EAAO,MAAM;EAAO,SAAS;EAAO,QAAQ;EAAM,GACpF,sBAAsB,SACpB;EAAE,KAAK;EAAM,MAAM;EAAM,OAAO;EAAM,MAAM;EAAO,SAAS;EAAM,QAAQ;EAAM,GAChF;EAAE,KAAK;EAAM,MAAM;EAAM,OAAO;EAAM,MAAM;EAAM,SAAS;EAAM,QAAQ;EAAM;CAEvF,MAAM,aAAsC;EAC1C,GAAI,YAAY,EAAE;EAClB,SAAS;EACT;EACA;EACA;EACA;EACA,GAAI,mBAAmB,YACnB;GACE;GACA;GACA;GACA;GACA;GACD,GACD,EAAE;EACN;EACA;EACA,WAAW,aAAc,UAAU,aAAqB,EAAE;EAC1D,gBAAgB,kBAAmB,UAAU,kBAA0B,EAAE;EACzE;EACA;EACA;EACA;EACA,SAAS,EAAE,WAAW,MAAM;EAC5B;EACA,cAAc,OAAO,UAAU,iBAAiB,WAAW,SAAS,eAAe;EACnF,gBAAgB,OAAO,UAAU,mBAAmB,WAAW,SAAS,iBAAiB;EAC1F;CAED,MAAM,YAAoB;EACxB,GAAG;EACH,UAAU;GACR,GAAG,OAAO;GACV,QAAQ;GACT;EACF;AAED,SAAQ,IAAI,sCAAsC;AAClD,QAAO;;AAGT,MAAa,uBAA8C;CACzD,cAAc;CACd,WAAW;CACZ"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { resolveFeishuFrameworkAllowFromPath } from "./paths.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fsSync from "node:fs";
|
|
4
|
+
//#region extensions/feishu/src/auth/pairing.ts
|
|
5
|
+
function readFrameworkAllowFromList(accountId) {
|
|
6
|
+
const filePath = resolveFeishuFrameworkAllowFromPath(accountId);
|
|
7
|
+
try {
|
|
8
|
+
if (!fsSync.existsSync(filePath)) return [];
|
|
9
|
+
const raw = fsSync.readFileSync(filePath, "utf-8");
|
|
10
|
+
const parsed = JSON.parse(raw);
|
|
11
|
+
if (Array.isArray(parsed.allowFrom)) return parsed.allowFrom.filter((id) => typeof id === "string" && id.trim() !== "" || typeof id === "number" && Number.isFinite(id));
|
|
12
|
+
} catch {}
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
async function registerUserInFrameworkStore(params) {
|
|
16
|
+
const { accountId, userId } = params;
|
|
17
|
+
const normalized = typeof userId === "number" ? userId : userId.trim();
|
|
18
|
+
if (typeof normalized === "string" && !normalized) return { changed: false };
|
|
19
|
+
const filePath = resolveFeishuFrameworkAllowFromPath(accountId);
|
|
20
|
+
fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
21
|
+
if (!fsSync.existsSync(filePath)) fsSync.writeFileSync(filePath, JSON.stringify({
|
|
22
|
+
version: 1,
|
|
23
|
+
allowFrom: []
|
|
24
|
+
}, null, 2), "utf-8");
|
|
25
|
+
let content = {
|
|
26
|
+
version: 1,
|
|
27
|
+
allowFrom: []
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
const raw = fsSync.readFileSync(filePath, "utf-8");
|
|
31
|
+
const parsed = JSON.parse(raw);
|
|
32
|
+
if (Array.isArray(parsed.allowFrom)) content = parsed;
|
|
33
|
+
} catch {}
|
|
34
|
+
if (content.allowFrom.some((x) => String(x) === String(normalized))) return { changed: false };
|
|
35
|
+
content.allowFrom.push(normalized);
|
|
36
|
+
fsSync.writeFileSync(filePath, JSON.stringify(content, null, 2), "utf-8");
|
|
37
|
+
try {
|
|
38
|
+
fsSync.chmodSync(filePath, 384);
|
|
39
|
+
} catch {}
|
|
40
|
+
return { changed: true };
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { readFrameworkAllowFromList, registerUserInFrameworkStore };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=pairing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing.js","names":["fs"],"sources":["../../../../../extensions/feishu/src/auth/pairing.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { resolveFeishuFrameworkAllowFromPath } from './paths.js';\n\ntype AllowFromFileContent = {\n version: number;\n allowFrom: Array<string | number>;\n};\n\nexport function readFrameworkAllowFromList(accountId: string): Array<string | number> {\n const filePath = resolveFeishuFrameworkAllowFromPath(accountId);\n try {\n if (!fs.existsSync(filePath)) return [];\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw) as AllowFromFileContent;\n if (Array.isArray(parsed.allowFrom)) {\n return parsed.allowFrom.filter(\n (id): id is string | number =>\n (typeof id === 'string' && id.trim() !== '') || (typeof id === 'number' && Number.isFinite(id)),\n );\n }\n } catch {\n // best-effort\n }\n return [];\n}\n\nexport async function registerUserInFrameworkStore(params: {\n accountId: string;\n userId: string | number;\n}): Promise<{ changed: boolean }> {\n const { accountId, userId } = params;\n const normalized = typeof userId === 'number' ? userId : userId.trim();\n if (typeof normalized === 'string' && !normalized) return { changed: false };\n\n const filePath = resolveFeishuFrameworkAllowFromPath(accountId);\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n\n if (!fs.existsSync(filePath)) {\n const initial: AllowFromFileContent = { version: 1, allowFrom: [] };\n fs.writeFileSync(filePath, JSON.stringify(initial, null, 2), 'utf-8');\n }\n\n let content: AllowFromFileContent = { version: 1, allowFrom: [] };\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw) as AllowFromFileContent;\n if (Array.isArray(parsed.allowFrom)) {\n content = parsed;\n }\n } catch {\n // start fresh\n }\n\n if (content.allowFrom.some((x) => String(x) === String(normalized))) {\n return { changed: false };\n }\n\n content.allowFrom.push(normalized);\n fs.writeFileSync(filePath, JSON.stringify(content, null, 2), 'utf-8');\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n // best-effort\n }\n return { changed: true };\n}\n\n"],"mappings":";;;;AAUA,SAAgB,2BAA2B,WAA2C;CACpF,MAAM,WAAW,oCAAoC,UAAU;AAC/D,KAAI;AACF,MAAI,CAACA,OAAG,WAAW,SAAS,CAAE,QAAO,EAAE;EACvC,MAAM,MAAMA,OAAG,aAAa,UAAU,QAAQ;EAC9C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,MAAM,QAAQ,OAAO,UAAU,CACjC,QAAO,OAAO,UAAU,QACrB,OACE,OAAO,OAAO,YAAY,GAAG,MAAM,KAAK,MAAQ,OAAO,OAAO,YAAY,OAAO,SAAS,GAAG,CACjG;SAEG;AAGR,QAAO,EAAE;;AAGX,eAAsB,6BAA6B,QAGjB;CAChC,MAAM,EAAE,WAAW,WAAW;CAC9B,MAAM,aAAa,OAAO,WAAW,WAAW,SAAS,OAAO,MAAM;AACtE,KAAI,OAAO,eAAe,YAAY,CAAC,WAAY,QAAO,EAAE,SAAS,OAAO;CAE5E,MAAM,WAAW,oCAAoC,UAAU;AAC/D,QAAG,UAAU,KAAK,QAAQ,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AAEzD,KAAI,CAACA,OAAG,WAAW,SAAS,CAE1B,QAAG,cAAc,UAAU,KAAK,UAAU;EADF,SAAS;EAAG,WAAW,EAAE;EAChB,EAAE,MAAM,EAAE,EAAE,QAAQ;CAGvE,IAAI,UAAgC;EAAE,SAAS;EAAG,WAAW,EAAE;EAAE;AACjE,KAAI;EACF,MAAM,MAAMA,OAAG,aAAa,UAAU,QAAQ;EAC9C,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,MAAM,QAAQ,OAAO,UAAU,CACjC,WAAU;SAEN;AAIR,KAAI,QAAQ,UAAU,MAAM,MAAM,OAAO,EAAE,KAAK,OAAO,WAAW,CAAC,CACjE,QAAO,EAAE,SAAS,OAAO;AAG3B,SAAQ,UAAU,KAAK,WAAW;AAClC,QAAG,cAAc,UAAU,KAAK,UAAU,SAAS,MAAM,EAAE,EAAE,QAAQ;AACrE,KAAI;AACF,SAAG,UAAU,UAAU,IAAM;SACvB;AAGR,QAAO,EAAE,SAAS,MAAM"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ENV_VARS, init_paths_state, resolveStateDir } from "../../../../src/config/paths-state.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region extensions/feishu/src/auth/paths.ts
|
|
4
|
+
init_paths_state();
|
|
5
|
+
function resolveFeishuCredentialsDir() {
|
|
6
|
+
const explicit = process.env[ENV_VARS.CREDENTIALS_DIR]?.trim();
|
|
7
|
+
if (explicit) return explicit;
|
|
8
|
+
return path.join(resolveStateDir(), "credentials");
|
|
9
|
+
}
|
|
10
|
+
function resolveFeishuFrameworkAllowFromPath(accountId) {
|
|
11
|
+
const base = "xopc-feishu".replace(/[\\/:*?"<>|]/g, "_");
|
|
12
|
+
const safeAccount = accountId.trim().toLowerCase().replace(/[\\/:*?"<>|]/g, "_").replace(/\.\./g, "_");
|
|
13
|
+
return path.join(resolveFeishuCredentialsDir(), `${base}-${safeAccount}-allowFrom.json`);
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { resolveFeishuCredentialsDir, resolveFeishuFrameworkAllowFromPath };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","names":[],"sources":["../../../../../extensions/feishu/src/auth/paths.ts"],"sourcesContent":["import path from 'node:path';\n\nimport { resolveStateDir, ENV_VARS } from '@xopcai/xopc/config/paths-state.js';\n\nexport function resolveFeishuCredentialsDir(): string {\n const explicit = process.env[ENV_VARS.CREDENTIALS_DIR]?.trim();\n if (explicit) return explicit;\n return path.join(resolveStateDir(), 'credentials');\n}\n\nexport function resolveFeishuFrameworkAllowFromPath(accountId: string): string {\n const base = 'xopc-feishu'.replace(/[\\\\/:*?\"<>|]/g, '_');\n const safeAccount = accountId\n .trim()\n .toLowerCase()\n .replace(/[\\\\/:*?\"<>|]/g, '_')\n .replace(/\\.\\./g, '_');\n return path.join(resolveFeishuCredentialsDir(), `${base}-${safeAccount}-allowFrom.json`);\n}\n\n"],"mappings":";;;kBAE+E;AAE/E,SAAgB,8BAAsC;CACpD,MAAM,WAAW,QAAQ,IAAI,SAAS,kBAAkB,MAAM;AAC9D,KAAI,SAAU,QAAO;AACrB,QAAO,KAAK,KAAK,iBAAiB,EAAE,cAAc;;AAGpD,SAAgB,oCAAoC,WAA2B;CAC7E,MAAM,OAAO,cAAc,QAAQ,iBAAiB,IAAI;CACxD,MAAM,cAAc,UACjB,MAAM,CACN,aAAa,CACb,QAAQ,iBAAiB,IAAI,CAC7B,QAAQ,SAAS,IAAI;AACxB,QAAO,KAAK,KAAK,6BAA6B,EAAE,GAAG,KAAK,GAAG,YAAY,iBAAiB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { resolveFeishuAccount } from "../state/accounts.js";
|
|
2
|
+
import { createFeishuClient } from "../transport/client/client.js";
|
|
3
|
+
//#region extensions/feishu/src/directory/directory-adapter.ts
|
|
4
|
+
function createFeishuDirectoryAdapter() {
|
|
5
|
+
return { resolveDisplayName: async (params) => {
|
|
6
|
+
const id = params.id.trim();
|
|
7
|
+
if (!id) return void 0;
|
|
8
|
+
if (!id.startsWith("ou_") && !id.startsWith("on_")) return;
|
|
9
|
+
const account = resolveFeishuAccount(params.cfg, "default");
|
|
10
|
+
if (!account.configured) return void 0;
|
|
11
|
+
const { api } = createFeishuClient(account);
|
|
12
|
+
try {
|
|
13
|
+
const res = await api.contact.user.get({
|
|
14
|
+
path: { user_id: id },
|
|
15
|
+
params: { user_id_type: "open_id" }
|
|
16
|
+
});
|
|
17
|
+
const name = res?.data?.user?.name ?? res?.data?.name;
|
|
18
|
+
return typeof name === "string" && name.trim() ? name.trim() : void 0;
|
|
19
|
+
} catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
} };
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { createFeishuDirectoryAdapter };
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=directory-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directory-adapter.js","names":[],"sources":["../../../../../extensions/feishu/src/directory/directory-adapter.ts"],"sourcesContent":["import type { ChannelDirectoryAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\n\nimport { resolveFeishuAccount } from '../state/accounts.js';\nimport { createFeishuClient } from '../transport/client/client.js';\n\nexport function createFeishuDirectoryAdapter(): ChannelDirectoryAdapter {\n return {\n resolveDisplayName: async (params: { cfg: Config; id: string }) => {\n const id = params.id.trim();\n if (!id) return undefined;\n\n // Best-effort: if this looks like an open_id, try resolve user name.\n if (!id.startsWith('ou_') && !id.startsWith('on_')) {\n return undefined;\n }\n\n // Use default account for directory lookups for now.\n const account = resolveFeishuAccount(params.cfg, 'default');\n if (!account.configured) return undefined;\n\n const { api } = createFeishuClient(account);\n try {\n const res = await (api as any).contact.user.get({\n path: { user_id: id },\n params: { user_id_type: 'open_id' },\n });\n const name = res?.data?.user?.name ?? res?.data?.name;\n return typeof name === 'string' && name.trim() ? name.trim() : undefined;\n } catch {\n return undefined;\n }\n },\n };\n}\n\n"],"mappings":";;;AAMA,SAAgB,+BAAwD;AACtE,QAAO,EACL,oBAAoB,OAAO,WAAwC;EACjE,MAAM,KAAK,OAAO,GAAG,MAAM;AAC3B,MAAI,CAAC,GAAI,QAAO,KAAA;AAGhB,MAAI,CAAC,GAAG,WAAW,MAAM,IAAI,CAAC,GAAG,WAAW,MAAM,CAChD;EAIF,MAAM,UAAU,qBAAqB,OAAO,KAAK,UAAU;AAC3D,MAAI,CAAC,QAAQ,WAAY,QAAO,KAAA;EAEhC,MAAM,EAAE,QAAQ,mBAAmB,QAAQ;AAC3C,MAAI;GACF,MAAM,MAAM,MAAO,IAAY,QAAQ,KAAK,IAAI;IAC9C,MAAM,EAAE,SAAS,IAAI;IACrB,QAAQ,EAAE,cAAc,WAAW;IACpC,CAAC;GACF,MAAM,OAAO,KAAK,MAAM,MAAM,QAAQ,KAAK,MAAM;AACjD,UAAO,OAAO,SAAS,YAAY,KAAK,MAAM,GAAG,KAAK,MAAM,GAAG,KAAA;UACzD;AACN;;IAGL"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feishu / Lark outbound text formatting.
|
|
3
|
+
*
|
|
4
|
+
* Agent output is often Markdown. Feishu card markdown (`tag: markdown`) expects
|
|
5
|
+
* Lark-flavored Markdown (bold/italic/strikethrough, links, code fences, etc.), not
|
|
6
|
+
* CommonMark-only constructs like `**bold**` left uninterpreted in plain `text`
|
|
7
|
+
* messages — and plain text messages do not render Markdown at all.
|
|
8
|
+
*
|
|
9
|
+
* Pipeline mirrors Telegram: Markdown → shared IR → channel-specific rendering.
|
|
10
|
+
*/
|
|
11
|
+
import type { MarkdownTableMode } from '@xopcai/xopc/config/types.base.js';
|
|
12
|
+
export declare function renderFeishuCardMarkdown(markdown: string, options?: {
|
|
13
|
+
tableMode?: MarkdownTableMode;
|
|
14
|
+
}): string;
|
|
15
|
+
export declare function markdownToFeishuPlainText(markdown: string): string;
|
|
16
|
+
export declare function formatFeishuOutboundText(input: {
|
|
17
|
+
text: string;
|
|
18
|
+
renderMode?: 'auto' | 'raw' | 'card';
|
|
19
|
+
/** When true, target is Feishu interactive markdown (card / card element). */
|
|
20
|
+
forCardMarkdown: boolean;
|
|
21
|
+
}): string;
|