xiaozuoassistant 0.2.46 → 0.2.48

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.
@@ -0,0 +1,15 @@
1
+ import { ReadLarkDocSkill, CreateLarkDocSkill, AppendLarkDocSkill } from './lark-doc.js';
2
+ const plugin = {
3
+ metadata: {
4
+ name: 'lark-skills',
5
+ version: '1.0.0',
6
+ description: 'Lark suite integration skills (Docs, Sheets, etc.)',
7
+ author: 'XiaoZuoClaw Team'
8
+ },
9
+ onLoad: (context) => {
10
+ context.registerSkill(new ReadLarkDocSkill());
11
+ context.registerSkill(new CreateLarkDocSkill());
12
+ context.registerSkill(new AppendLarkDocSkill());
13
+ }
14
+ };
15
+ export default plugin;
@@ -0,0 +1,251 @@
1
+ import { BaseSkill } from '../../../skills/base-skill.js';
2
+ import * as lark from '@larksuiteoapi/node-sdk';
3
+ export class ReadLarkDocSkill extends BaseSkill {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.name = 'read_lark_doc';
7
+ this.description = 'Read text content from a Lark/Feishu document using its URL or token';
8
+ this.parameters = {
9
+ type: 'object',
10
+ properties: {
11
+ document_url: {
12
+ type: 'string',
13
+ description: 'The URL or token of the Lark/Feishu document to read'
14
+ },
15
+ app_id: {
16
+ type: 'string',
17
+ description: 'The Lark App ID to use for authentication (optional, defaults to the first configured Feishu bot)'
18
+ },
19
+ app_secret: {
20
+ type: 'string',
21
+ description: 'The Lark App Secret to use for authentication (optional, defaults to the first configured Feishu bot)'
22
+ }
23
+ },
24
+ required: ['document_url']
25
+ };
26
+ }
27
+ async execute(args, ctx) {
28
+ try {
29
+ // Extract document token from URL
30
+ // Typical Lark doc URL formats:
31
+ // https://domain.feishu.cn/docx/TOKEN
32
+ // https://domain.feishu.cn/wiki/WIKITOKEN
33
+ let documentToken = args.document_url;
34
+ // Basic extraction logic
35
+ if (args.document_url.includes('/docx/')) {
36
+ documentToken = args.document_url.split('/docx/')[1].split(/[?#]/)[0];
37
+ }
38
+ else if (args.document_url.includes('/doc/')) {
39
+ documentToken = args.document_url.split('/doc/')[1].split(/[?#]/)[0];
40
+ }
41
+ // Fetch config to get app_id and app_secret if not provided
42
+ const configLoader = require('../../../config/loader.js').config;
43
+ let appId = args.app_id;
44
+ let appSecret = args.app_secret;
45
+ if (!appId || !appSecret) {
46
+ const feishuBots = configLoader.channels?.feishu;
47
+ if (feishuBots && Array.isArray(feishuBots) && feishuBots.length > 0) {
48
+ // If context has botName, try to find that specific bot
49
+ if (ctx?.metadata?.botName) {
50
+ const specificBot = feishuBots.find((b) => b.name === ctx.metadata.botName);
51
+ if (specificBot) {
52
+ appId = specificBot.appId;
53
+ appSecret = specificBot.appSecret;
54
+ }
55
+ }
56
+ // Fallback to first bot
57
+ if (!appId || !appSecret) {
58
+ appId = feishuBots[0].appId;
59
+ appSecret = feishuBots[0].appSecret;
60
+ }
61
+ }
62
+ else {
63
+ return { error: 'Lark App ID and Secret are required to read documents. Please configure a Feishu bot or provide them in the arguments.' };
64
+ }
65
+ }
66
+ const client = new lark.Client({
67
+ appId: appId,
68
+ appSecret: appSecret,
69
+ });
70
+ // We use the drive/v1/export API or docx/v1/documents API to read content
71
+ // Let's try the newer DOCX API first to get raw content
72
+ const res = await client.docx.document.rawContent({
73
+ path: {
74
+ document_id: documentToken,
75
+ },
76
+ });
77
+ if (res.code !== 0) {
78
+ return { error: `Failed to read Lark document. Code: ${res.code}, Msg: ${res.msg}. Make sure the App has the "docs:doc:readonly" permission and has been added as a collaborator to the document.` };
79
+ }
80
+ return {
81
+ success: true,
82
+ content: res.data?.content || "No content returned."
83
+ };
84
+ }
85
+ catch (error) {
86
+ return { error: `Failed to read Lark document: ${error.message}. Ensure the token is correct and the App has appropriate permissions.` };
87
+ }
88
+ }
89
+ }
90
+ export class CreateLarkDocSkill extends BaseSkill {
91
+ constructor() {
92
+ super(...arguments);
93
+ this.name = 'create_lark_doc';
94
+ this.description = 'Create a new Lark/Feishu document (docx)';
95
+ this.parameters = {
96
+ type: 'object',
97
+ properties: {
98
+ title: {
99
+ type: 'string',
100
+ description: 'Title of the new document'
101
+ },
102
+ folder_token: {
103
+ type: 'string',
104
+ description: 'Optional folder token to create the document in'
105
+ },
106
+ app_id: {
107
+ type: 'string',
108
+ description: 'The Lark App ID (optional, defaults to configured bot)'
109
+ },
110
+ app_secret: {
111
+ type: 'string',
112
+ description: 'The Lark App Secret (optional)'
113
+ }
114
+ },
115
+ required: ['title']
116
+ };
117
+ }
118
+ async execute(args, ctx) {
119
+ try {
120
+ const configLoader = require('../../../config/loader.js').config;
121
+ let appId = args.app_id;
122
+ let appSecret = args.app_secret;
123
+ if (!appId || !appSecret) {
124
+ const feishuBots = configLoader.channels?.feishu;
125
+ if (feishuBots && Array.isArray(feishuBots) && feishuBots.length > 0) {
126
+ if (ctx?.metadata?.botName) {
127
+ const specificBot = feishuBots.find((b) => b.name === ctx.metadata.botName);
128
+ if (specificBot) {
129
+ appId = specificBot.appId;
130
+ appSecret = specificBot.appSecret;
131
+ }
132
+ }
133
+ if (!appId || !appSecret) {
134
+ appId = feishuBots[0].appId;
135
+ appSecret = feishuBots[0].appSecret;
136
+ }
137
+ }
138
+ else {
139
+ return { error: 'Lark App ID and Secret are required.' };
140
+ }
141
+ }
142
+ const client = new lark.Client({ appId: appId, appSecret: appSecret });
143
+ const res = await client.docx.document.create({
144
+ data: {
145
+ title: args.title,
146
+ folder_token: args.folder_token || ''
147
+ }
148
+ });
149
+ if (res.code !== 0) {
150
+ return { error: `Failed to create document. Code: ${res.code}, Msg: ${res.msg}` };
151
+ }
152
+ const docId = res.data?.document?.document_id;
153
+ return {
154
+ success: true,
155
+ document_id: docId,
156
+ url: `https://${client.domain || 'feishu.cn'}/docx/${docId}`
157
+ };
158
+ }
159
+ catch (error) {
160
+ return { error: `Failed to create document: ${error.message}` };
161
+ }
162
+ }
163
+ }
164
+ export class AppendLarkDocSkill extends BaseSkill {
165
+ constructor() {
166
+ super(...arguments);
167
+ this.name = 'append_lark_doc';
168
+ this.description = 'Append text content to an existing Lark/Feishu document (docx)';
169
+ this.parameters = {
170
+ type: 'object',
171
+ properties: {
172
+ document_url: {
173
+ type: 'string',
174
+ description: 'The URL or token of the document to append to'
175
+ },
176
+ content: {
177
+ type: 'string',
178
+ description: 'The text content to append'
179
+ },
180
+ app_id: { type: 'string' },
181
+ app_secret: { type: 'string' }
182
+ },
183
+ required: ['document_url', 'content']
184
+ };
185
+ }
186
+ async execute(args, ctx) {
187
+ try {
188
+ let documentToken = args.document_url;
189
+ if (args.document_url.includes('/docx/')) {
190
+ documentToken = args.document_url.split('/docx/')[1].split(/[?#]/)[0];
191
+ }
192
+ else if (args.document_url.includes('/doc/')) {
193
+ documentToken = args.document_url.split('/doc/')[1].split(/[?#]/)[0];
194
+ }
195
+ const configLoader = require('../../../config/loader.js').config;
196
+ let appId = args.app_id;
197
+ let appSecret = args.app_secret;
198
+ if (!appId || !appSecret) {
199
+ const feishuBots = configLoader.channels?.feishu;
200
+ if (feishuBots && Array.isArray(feishuBots) && feishuBots.length > 0) {
201
+ if (ctx?.metadata?.botName) {
202
+ const specificBot = feishuBots.find((b) => b.name === ctx.metadata.botName);
203
+ if (specificBot) {
204
+ appId = specificBot.appId;
205
+ appSecret = specificBot.appSecret;
206
+ }
207
+ }
208
+ if (!appId || !appSecret) {
209
+ appId = feishuBots[0].appId;
210
+ appSecret = feishuBots[0].appSecret;
211
+ }
212
+ }
213
+ else {
214
+ return { error: 'Lark App ID and Secret are required.' };
215
+ }
216
+ }
217
+ const client = new lark.Client({ appId: appId, appSecret: appSecret });
218
+ // Create blocks from content
219
+ const lines = args.content.split('\n').filter(l => l.trim() !== '');
220
+ const children = lines.map(line => ({
221
+ block_type: 2, // Text block
222
+ text: {
223
+ elements: [
224
+ {
225
+ text_run: { content: line }
226
+ }
227
+ ]
228
+ }
229
+ }));
230
+ if (children.length === 0)
231
+ return { success: true, message: 'Nothing to append' };
232
+ const res = await client.docx.documentBlockChildren.create({
233
+ path: {
234
+ document_id: documentToken,
235
+ block_id: documentToken
236
+ },
237
+ data: {
238
+ children: children,
239
+ index: -1
240
+ }
241
+ });
242
+ if (res.code !== 0) {
243
+ return { error: `Failed to append to document. Code: ${res.code}, Msg: ${res.msg}` };
244
+ }
245
+ return { success: true, message: 'Content appended successfully' };
246
+ }
247
+ catch (error) {
248
+ return { error: `Failed to append to document: ${error.message}` };
249
+ }
250
+ }
251
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xiaozuoassistant",
3
- "version": "0.2.46",
3
+ "version": "0.2.48",
4
4
  "description": "A local-first personal AI assistant with multi-channel support and enhanced memory.",
5
5
  "author": "mantle.lau",
6
6
  "license": "MIT",