@vibe-lark/larkpal 0.1.10 → 0.1.12

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.
Files changed (2) hide show
  1. package/dist/main.mjs +163 -3
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -237,7 +237,29 @@ const DEFAULT_SETTINGS = {
237
237
  "WebFetch(*)",
238
238
  "WebSearch(*)"
239
239
  ],
240
- deny: []
240
+ deny: [
241
+ "Read(//.lark-cli/**)",
242
+ "Read(//.config/lark/**)",
243
+ "Read(//.larkpal/credentials.json)",
244
+ "Read(//.env)",
245
+ "Read(//.env.*)",
246
+ "Bash(cat ~/.lark-cli:*)",
247
+ "Bash(cat ~/.config/lark:*)",
248
+ "Bash(cat ~/.larkpal/credentials:*)",
249
+ "Bash(head ~/.lark-cli:*)",
250
+ "Bash(head ~/.config/lark:*)",
251
+ "Bash(tail ~/.lark-cli:*)",
252
+ "Bash(tail ~/.config/lark:*)",
253
+ "Bash(less ~/.lark-cli:*)",
254
+ "Bash(less ~/.config/lark:*)",
255
+ "Bash(more ~/.lark-cli:*)",
256
+ "Bash(more ~/.config/lark:*)",
257
+ "Bash(env:*)",
258
+ "Bash(printenv:*)",
259
+ "Bash(export -p:*)",
260
+ "Bash(echo $LARK_APP_SECRET:*)",
261
+ "Bash(echo $ANTHROPIC_API_KEY:*)"
262
+ ]
241
263
  },
242
264
  hooks: {
243
265
  SessionStart: [{ hooks: [{
@@ -2848,7 +2870,8 @@ async function fetchFromApplicationApi(token) {
2848
2870
  return {
2849
2871
  appName: zhInfo?.name || app.app_name || "",
2850
2872
  avatarUrl: app.avatar_url,
2851
- description: zhInfo?.description || app.description
2873
+ description: zhInfo?.description || app.description,
2874
+ helpDocUrl: zhInfo?.help_use
2852
2875
  };
2853
2876
  } catch (err) {
2854
2877
  log$22.warn("application/v6 API 请求异常", { error: err instanceof Error ? err.message : String(err) });
@@ -2904,9 +2927,100 @@ async function getTenantAccessToken(appId, appSecret) {
2904
2927
  return null;
2905
2928
  }
2906
2929
  }
2930
+ /**
2931
+ * 从飞书云文档 URL 中解析文档 token
2932
+ *
2933
+ * 支持的 URL 格式:
2934
+ * - https://xxx.feishu.cn/docx/{token}
2935
+ * - https://xxx.feishu.cn/wiki/{token}
2936
+ * - https://xxx.larksuite.com/docx/{token}
2937
+ */
2938
+ function parseDocTokenFromUrl(url) {
2939
+ try {
2940
+ const parts = new URL(url).pathname.split("/").filter(Boolean);
2941
+ for (let i = 0; i < parts.length; i++) {
2942
+ if (parts[i] === "docx" && parts[i + 1]) return {
2943
+ token: parts[i + 1],
2944
+ type: "docx"
2945
+ };
2946
+ if (parts[i] === "wiki" && parts[i + 1]) return {
2947
+ token: parts[i + 1],
2948
+ type: "wiki"
2949
+ };
2950
+ }
2951
+ return null;
2952
+ } catch {
2953
+ return null;
2954
+ }
2955
+ }
2956
+ /**
2957
+ * 从飞书云文档读取纯文本内容(作为人设文档)
2958
+ *
2959
+ * 对于 wiki 类型,先通过 wiki API 获取实际的 doc token,再读取内容。
2960
+ */
2961
+ async function fetchDocContent(docUrl, accessToken) {
2962
+ const parsed = parseDocTokenFromUrl(docUrl);
2963
+ if (!parsed) {
2964
+ log$22.warn("无法从 URL 中解析文档 token", { url: docUrl });
2965
+ return null;
2966
+ }
2967
+ log$22.info("开始读取人设文档", {
2968
+ url: docUrl,
2969
+ type: parsed.type,
2970
+ token: parsed.token
2971
+ });
2972
+ let docToken = parsed.token;
2973
+ if (parsed.type === "wiki") {
2974
+ const realToken = await resolveWikiNodeToDocToken(parsed.token, accessToken);
2975
+ if (!realToken) log$22.warn("wiki 节点解析失败,尝试直接使用 token 读取");
2976
+ else docToken = realToken;
2977
+ }
2978
+ try {
2979
+ const data = await (await fetch(`https://open.feishu.cn/open-apis/docx/v1/documents/${docToken}/raw_content`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
2980
+ log$22.info("文档 raw_content API 响应", {
2981
+ code: data.code,
2982
+ msg: data.msg,
2983
+ contentLength: data.data?.content?.length
2984
+ });
2985
+ if (data.code !== 0 || !data.data?.content) {
2986
+ log$22.warn("读取文档内容失败", {
2987
+ code: data.code,
2988
+ msg: data.msg,
2989
+ docToken
2990
+ });
2991
+ return null;
2992
+ }
2993
+ return data.data.content.trim();
2994
+ } catch (err) {
2995
+ log$22.error("读取文档内容异常", {
2996
+ error: err instanceof Error ? err.message : String(err),
2997
+ docToken
2998
+ });
2999
+ return null;
3000
+ }
3001
+ }
3002
+ /** 将 wiki 节点 token 解析为实际的文档 token */
3003
+ async function resolveWikiNodeToDocToken(wikiToken, accessToken) {
3004
+ try {
3005
+ const data = await (await fetch(`https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token=${wikiToken}`, { headers: { Authorization: `Bearer ${accessToken}` } })).json();
3006
+ log$22.info("wiki get_node API 响应", {
3007
+ code: data.code,
3008
+ msg: data.msg,
3009
+ objType: data.data?.node?.obj_type
3010
+ });
3011
+ if (data.code !== 0 || !data.data?.node?.obj_token) return null;
3012
+ return data.data.node.obj_token;
3013
+ } catch (err) {
3014
+ log$22.warn("wiki get_node 请求异常", { error: err instanceof Error ? err.message : String(err) });
3015
+ return null;
3016
+ }
3017
+ }
2907
3018
  /** CLAUDE.md 中的应用信息区块标记 */
2908
3019
  const APP_INFO_START = "<!-- APP_INFO_START -->";
2909
3020
  const APP_INFO_END = "<!-- APP_INFO_END -->";
3021
+ /** CLAUDE.md 中的人设文档区块标记 */
3022
+ const PERSONA_DOC_START = "<!-- PERSONA_DOC_START -->";
3023
+ const PERSONA_DOC_END = "<!-- PERSONA_DOC_END -->";
2910
3024
  /**
2911
3025
  * 将应用信息同步到 ~/.claude/CLAUDE.md
2912
3026
  *
@@ -2937,6 +3051,38 @@ async function syncAppInfoToClaudeMd(appInfo) {
2937
3051
  hasAvatar: !!appInfo.avatarUrl
2938
3052
  });
2939
3053
  }
3054
+ /**
3055
+ * 将人设文档内容同步到 ~/.claude/CLAUDE.md
3056
+ *
3057
+ * 在文件中维护一个 PERSONA_DOC 标记区块,内容来源于飞书云文档。
3058
+ * 如果文件中已有标记区块则替换,否则追加到文件末尾。
3059
+ */
3060
+ async function syncPersonaDocToClaudeMd(personaContent) {
3061
+ const claudeMdPath = join$1(homedir$1(), ".claude", "CLAUDE.md");
3062
+ if (!existsSync$1(claudeMdPath)) {
3063
+ log$22.warn("CLAUDE.md 不存在,无法同步人设文档(需先同步应用信息)");
3064
+ return;
3065
+ }
3066
+ let content = await readFile$1(claudeMdPath, "utf-8");
3067
+ const personaBlock = [
3068
+ PERSONA_DOC_START,
3069
+ "## 人设与行为规范",
3070
+ "",
3071
+ "> 以下内容来自飞书人设文档,是你的核心身份定义和行为准则。",
3072
+ "",
3073
+ personaContent,
3074
+ PERSONA_DOC_END
3075
+ ].join("\n");
3076
+ const startIdx = content.indexOf(PERSONA_DOC_START);
3077
+ const endIdx = content.indexOf(PERSONA_DOC_END);
3078
+ if (startIdx !== -1 && endIdx !== -1) {
3079
+ const before = content.substring(0, startIdx);
3080
+ const after = content.substring(endIdx + 24);
3081
+ content = before + personaBlock + after;
3082
+ } else content = content.trimEnd() + "\n\n" + personaBlock + "\n";
3083
+ await writeFile$1(claudeMdPath, content, "utf-8");
3084
+ log$22.info("CLAUDE.md 人设文档已同步", { contentLength: personaContent.length });
3085
+ }
2940
3086
  /** 构建应用信息标记区块 */
2941
3087
  function buildAppInfoBlock(appInfo) {
2942
3088
  const lines = [
@@ -2980,10 +3126,20 @@ async function syncAppInfo(credentials) {
2980
3126
  return null;
2981
3127
  }
2982
3128
  await syncAppInfoToClaudeMd(appInfo);
3129
+ if (appInfo.helpDocUrl) {
3130
+ log$22.info("检测到帮助文档 URL,尝试同步人设文档", { helpDocUrl: appInfo.helpDocUrl });
3131
+ const token = await getTenantAccessToken(credentials.appId, credentials.appSecret);
3132
+ if (token) {
3133
+ const personaContent = await fetchDocContent(appInfo.helpDocUrl, token);
3134
+ if (personaContent) await syncPersonaDocToClaudeMd(personaContent);
3135
+ else log$22.warn("人设文档内容为空或读取失败,跳过同步");
3136
+ }
3137
+ }
2983
3138
  await installSyncSkill();
2984
3139
  log$22.info("应用信息同步完成", {
2985
3140
  appName: appInfo.appName,
2986
- description: appInfo.description?.substring(0, 50)
3141
+ description: appInfo.description?.substring(0, 50),
3142
+ hasPersonaDoc: !!appInfo.helpDocUrl
2987
3143
  });
2988
3144
  return appInfo;
2989
3145
  }
@@ -12074,6 +12230,10 @@ async function main() {
12074
12230
  const credentialProvider = new LarkCliCredentialProvider();
12075
12231
  const appId = credentialProvider.getAppId();
12076
12232
  logger.info("凭证加载完成", { appId });
12233
+ if (process.env.LARK_APP_SECRET) {
12234
+ delete process.env.LARK_APP_SECRET;
12235
+ logger.info("已从 process.env 清除 LARK_APP_SECRET(CC 子进程不可继承)");
12236
+ }
12077
12237
  await ensureDefaults();
12078
12238
  logger.info("默认配置检查完成");
12079
12239
  const workspaceRoot = process.env.LARKPAL_WORKSPACE ?? join(homedir(), ".larkpal", "workspace");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",