sophhub 0.2.3 → 0.2.4

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 (107) hide show
  1. package/package.json +1 -1
  2. package/skills/consensus/skill.json +20 -0
  3. package/skills/consensus/src/SKILL.md +93 -0
  4. package/skills/deepwiki/skill.json +20 -0
  5. package/skills/deepwiki/src/SKILL.md +45 -0
  6. package/skills/deepwiki/src/_meta.json +6 -0
  7. package/skills/deepwiki/src/scripts/deepwiki.js +135 -0
  8. package/skills/feishu-bitable/skill.json +20 -0
  9. package/skills/feishu-bitable/src/CHECKLIST.md +150 -0
  10. package/skills/feishu-bitable/src/README.md +178 -0
  11. package/skills/feishu-bitable/src/SKILL.md +113 -0
  12. package/skills/feishu-bitable/src/_meta.json +6 -0
  13. package/skills/feishu-bitable/src/api.js +381 -0
  14. package/skills/feishu-bitable/src/bin/cli.js +284 -0
  15. package/skills/feishu-bitable/src/description.md +143 -0
  16. package/skills/feishu-bitable/src/examples/create-records.json +52 -0
  17. package/skills/feishu-bitable/src/examples/create-table.json +64 -0
  18. package/skills/feishu-bitable/src/package-lock.json +324 -0
  19. package/skills/feishu-bitable/src/package.json +33 -0
  20. package/skills/feishu-bitable/src/publish-config.json +14 -0
  21. package/skills/feishu-bitable/src/test-simple.js +61 -0
  22. package/skills/feishu-bitable/src/utils.js +261 -0
  23. package/skills/google-maps/skill.json +20 -0
  24. package/skills/google-maps/src/SKILL.md +237 -0
  25. package/skills/google-maps/src/_meta.json +6 -0
  26. package/skills/google-maps/src/lib/map_helper.py +912 -0
  27. package/skills/large-task-router/skill.json +20 -0
  28. package/skills/large-task-router/src/SKILL.md +79 -0
  29. package/skills/large-task-router/src/templates/plan.md +74 -0
  30. package/skills/skillhub/skill.json +11 -4
  31. package/skills/skillhub/src/SKILL.md +11 -1
  32. package/skills/sophnet-dailynews/skill.json +20 -0
  33. package/skills/sophnet-dailynews/src/SKILL.md +179 -0
  34. package/skills/sophnet-dailynews/src/cache.json +151 -0
  35. package/skills/sophnet-dailynews/src/sources.json +230 -0
  36. package/skills/sophnet-schedule/skill.json +20 -0
  37. package/skills/sophnet-schedule/src/ARCHITECTURE.md +321 -0
  38. package/skills/sophnet-schedule/src/IMPROVEMENTS.md +145 -0
  39. package/skills/sophnet-schedule/src/SKILL.md +1050 -0
  40. package/skills/sophnet-schedule/src/_meta.json +6 -0
  41. package/skills/sophnet-schedule/src/api/__init__.py +0 -0
  42. package/skills/sophnet-schedule/src/api/models.py +245 -0
  43. package/skills/sophnet-schedule/src/apps/add_event.py +237 -0
  44. package/skills/sophnet-schedule/src/apps/check_reminders.py +112 -0
  45. package/skills/sophnet-schedule/src/apps/check_roc.py +246 -0
  46. package/skills/sophnet-schedule/src/apps/generate_daily_plan.py +342 -0
  47. package/skills/sophnet-schedule/src/apps/import_events.py +216 -0
  48. package/skills/sophnet-schedule/src/apps/monitor_calendar_changes.py +140 -0
  49. package/skills/sophnet-schedule/src/apps/register_tasks.py +169 -0
  50. package/skills/sophnet-schedule/src/apps/sync_roc_to_gcal.py +174 -0
  51. package/skills/sophnet-schedule/src/compat.py +66 -0
  52. package/skills/sophnet-schedule/src/config/__init__.py +0 -0
  53. package/skills/sophnet-schedule/src/config/reminder_rules.yaml +96 -0
  54. package/skills/sophnet-schedule/src/config/roc_events.yaml +44 -0
  55. package/skills/sophnet-schedule/src/config/settings.py +133 -0
  56. package/skills/sophnet-schedule/src/config/task_registry.yaml +92 -0
  57. package/skills/sophnet-schedule/src/docs/FRONTEND_INTEGRATION_GUIDE.md +437 -0
  58. package/skills/sophnet-schedule/src/gcal/__init__.py +0 -0
  59. package/skills/sophnet-schedule/src/gcal/client.py +374 -0
  60. package/skills/sophnet-schedule/src/gcal/models.py +91 -0
  61. package/skills/sophnet-schedule/src/requirements.txt +6 -0
  62. package/skills/sophnet-schedule/src/scripts/setup_gcal_token.py +85 -0
  63. package/skills/sophnet-schedule/src/server.py +669 -0
  64. package/skills/sophnet-schedule/src/services/__init__.py +0 -0
  65. package/skills/sophnet-schedule/src/services/calendar_backend.py +139 -0
  66. package/skills/sophnet-schedule/src/services/conflict_detector.py +96 -0
  67. package/skills/sophnet-schedule/src/services/datetime_utils.py +117 -0
  68. package/skills/sophnet-schedule/src/services/event_classifier.py +100 -0
  69. package/skills/sophnet-schedule/src/services/event_diff.py +160 -0
  70. package/skills/sophnet-schedule/src/services/google_integration.py +500 -0
  71. package/skills/sophnet-schedule/src/services/job_store.py +100 -0
  72. package/skills/sophnet-schedule/src/services/local_event_store.py +266 -0
  73. package/skills/sophnet-schedule/src/services/reminder_planner.py +116 -0
  74. package/skills/sophnet-schedule/src/services/runtime_utils.py +31 -0
  75. package/skills/sophnet-schedule/src/services/table_parser.py +286 -0
  76. package/skills/sophnet-schedule/src/services/task_builder.py +167 -0
  77. package/skills/sophnet-schedule/src/services/time_window.py +72 -0
  78. package/skills/sophnet-stock/skill.json +20 -0
  79. package/skills/sophnet-stock/src/App-Plan.md +442 -0
  80. package/skills/sophnet-stock/src/README.md +214 -0
  81. package/skills/sophnet-stock/src/SKILL.md +236 -0
  82. package/skills/sophnet-stock/src/TODO.md +394 -0
  83. package/skills/sophnet-stock/src/_meta.json +6 -0
  84. package/skills/sophnet-stock/src/docs/ARCHITECTURE.md +408 -0
  85. package/skills/sophnet-stock/src/docs/CONCEPT.md +233 -0
  86. package/skills/sophnet-stock/src/docs/HOT_SCANNER.md +288 -0
  87. package/skills/sophnet-stock/src/docs/README.md +95 -0
  88. package/skills/sophnet-stock/src/docs/USAGE.md +465 -0
  89. package/skills/sophnet-stock/src/scripts/analyze_stock.py +2565 -0
  90. package/skills/sophnet-stock/src/scripts/dividends.py +365 -0
  91. package/skills/sophnet-stock/src/scripts/hot_scanner.py +582 -0
  92. package/skills/sophnet-stock/src/scripts/portfolio.py +548 -0
  93. package/skills/sophnet-stock/src/scripts/rumor_scanner.py +342 -0
  94. package/skills/sophnet-stock/src/scripts/test_stock_analysis.py +409 -0
  95. package/skills/sophnet-stock/src/scripts/watchlist.py +336 -0
  96. package/skills/xiaohongshu/skill.json +20 -0
  97. package/skills/xiaohongshu/src/SKILL.md +91 -0
  98. package/skills/xiaohongshu/src/_meta.json +6 -0
  99. package/skills/xiaohongshu/src/assets/card.html +216 -0
  100. package/skills/xiaohongshu/src/assets/cover.html +82 -0
  101. package/skills/xiaohongshu/src/assets/example.md +84 -0
  102. package/skills/xiaohongshu/src/assets/styles.css +318 -0
  103. package/skills/xiaohongshu/src/scripts/render_xhs_v2.py +737 -0
  104. package/skills/xiaohongshu/src/scripts/sign_server.py +158 -0
  105. package/skills/xiaohongshu/src/scripts/stealth.min.js +7 -0
  106. package/skills/xiaohongshu/src/scripts/xhs_tool.py +186 -0
  107. package/skills/xiaohongshu/src/workflow.py +185 -0
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: feishu-bitable
3
+ description: CRUD operations for Feishu Bitable (multidimensional tables). Use when the user needs to manage tables, records, fields, or views in Feishu Bitable, or when dealing with bitable-related URLs.
4
+ metadata: {"openclaw":{"emoji":"📊","requires":{"env":["FEISHU_APP_ID","FEISHU_APP_SECRET"]},"primaryEnv":"FEISHU_APP_ID"}}
5
+ ---
6
+
7
+ # Feishu Bitable Skill
8
+
9
+ CRUD operations for Feishu Bitable (多维表格) — tables, records, fields, and views.
10
+
11
+ ## Environment Variables
12
+
13
+ `FEISHU_APP_ID` and `FEISHU_APP_SECRET` are already available from the Feishu channel configuration. No extra setup is needed. The CLI reads them automatically via `dotenv` or shell environment.
14
+
15
+ ## Extracting app-token from URLs
16
+
17
+ Feishu Bitable URLs come in three formats. Extract the `app-token` as follows:
18
+
19
+ | URL Format | Example | app-token |
20
+ |-----------|---------|-----------|
21
+ | **Wiki /wiki/** | `feishu.cn/wiki/AbCdEf?table=tblXXX` | Wiki token `AbCdEf` can be used directly as app-token (Feishu API accepts it) |
22
+ | **Standalone /base/** | `feishu.cn/base/basXXXXXX` | `basXXXXXX` |
23
+ | **Advanced /app/** | `feishu.cn/app/AbCdEf?pageId=pgeXXX` | `AbCdEf` |
24
+
25
+ The `table=tblXXX` query parameter is the `table-id`. The `view=vewXXX` parameter is the view-id.
26
+
27
+ **Note on /wiki/ URLs:** Feishu Bitable API accepts wiki tokens directly. If user provides a /wiki/ URL with `table=` parameter, you can safely pass the wiki token as `--app-token`. If unsure whether the URL is a bitable, use `feishu_wiki` to check `obj_type` first.
28
+
29
+ ## CLI Reference
30
+
31
+ All commands are run from the skill directory:
32
+
33
+ ```bash
34
+ cd {baseDir}
35
+ node bin/cli.js <command> [options]
36
+ ```
37
+
38
+ ### Commands
39
+
40
+ | Command | Required Options | Optional | Description |
41
+ |---------|-----------------|----------|-------------|
42
+ | `test` | — | — | Test connection and token validity |
43
+ | `get-app` | `--app-token` | — | Get bitable app info |
44
+ | `list-tables` | `--app-token` | `--page-size`, `--page-token` | List all tables |
45
+ | `list-fields` | `--app-token --table-id` | `--page-size` | List fields in a table |
46
+ | `list-records` | `--app-token --table-id` | `--page-size`, `--page-token`, `--filter`, `--sort` | List records |
47
+ | `list-views` | `--app-token --table-id` | `--page-size` | List views |
48
+ | `create-table` | `--app-token --name` | `--fields <json>` | Create a new table |
49
+ | `create-record` | `--app-token --table-id --data <json>` | — | Create one record |
50
+ | `batch-create` | `--app-token --table-id --data <json-array>` | — | Batch create records (max 100) |
51
+ | `update-record` | `--app-token --table-id --record-id --data <json>` | — | Update a record |
52
+ | `delete-record` | `--app-token --table-id --record-id` | — | Delete a record |
53
+
54
+ ### Data format
55
+
56
+ The `--data` option accepts inline JSON or a file reference with `@` prefix:
57
+
58
+ ```bash
59
+ # Inline JSON
60
+ --data '{"任务名称": "新任务", "状态": "待办"}'
61
+
62
+ # From file
63
+ --data @examples/create-records.json
64
+ ```
65
+
66
+ For `batch-create`, `--data` must be a JSON array of record objects.
67
+
68
+ ### Typical workflow
69
+
70
+ 1. Parse the user's Feishu URL to extract `app-token` and `table-id`
71
+ 2. `list-tables` → confirm the target table exists
72
+ 3. `list-fields` → understand the table schema (field names and types)
73
+ 4. Perform the requested operation (list/create/update/delete records)
74
+ 5. Verify the result by re-reading if needed
75
+
76
+ ## Field Types
77
+
78
+ | Type | API Value | Example Data |
79
+ |------|-----------|-------------|
80
+ | Text | 1 | `"Hello"` |
81
+ | Number | 2 | `123.45` |
82
+ | SingleSelect | 3 | `"选项名"` |
83
+ | MultiSelect | 4 | `["A", "B"]` |
84
+ | DateTime | 5 | Unix timestamp in ms: `1700000000000` |
85
+ | Checkbox | 7 | `true` / `false` |
86
+ | User | 11 | `[{"id": "ou_xxx"}]` |
87
+ | URL | 15 | `"https://..."` |
88
+ | Formula | 20 | Read-only, computed |
89
+
90
+ ## Error Codes
91
+
92
+ | Code | Meaning | Action |
93
+ |------|---------|--------|
94
+ | 1254003 | WrongBaseToken | Check app-token format |
95
+ | 1254004 | WrongTableId | Verify table-id exists via `list-tables` |
96
+ | 1254041 | TableIdNotFound | Table does not exist |
97
+ | 1254043 | RecordIdNotFound | Record does not exist |
98
+ | 403 | Forbidden | App needs to be added as bitable collaborator with edit permission |
99
+
100
+ ## Collaboration with Other Skills
101
+
102
+ - **File operations** (move/delete bitable): Use `feishu_drive` instead
103
+ - **Sharing/permissions**: Use `feishu_perm` instead
104
+ - **Wiki navigation**: Use `feishu_wiki` to discover file type from /wiki/ URLs when ambiguous
105
+ - **Spreadsheets**: If user mentions 单元格/cell/行列/row/column/A1, use `feishu-sheets` instead
106
+ - **Documents**: If user wants to edit text/paragraphs, use `feishu_doc` instead
107
+
108
+ ## Notes
109
+
110
+ - Max 100 records per batch request
111
+ - Field names must exactly match the column names in the bitable
112
+ - DateTime fields use Unix timestamps in milliseconds
113
+ - Formula fields are read-only
@@ -0,0 +1,6 @@
1
+ {
2
+ "ownerId": "kn79529g0cetfnymbs3ev6dtkx80n1ax",
3
+ "slug": "feishu-api-bitable",
4
+ "version": "1.0.0",
5
+ "publishedAt": 1770346670898
6
+ }
@@ -0,0 +1,381 @@
1
+ /**
2
+ * 飞书多维表格API客户端
3
+ */
4
+
5
+ const axios = require('axios');
6
+ const { getEnvVar, getAccessToken, validateRequired, parseJsonInput, delay } = require('./utils');
7
+
8
+ class FeishuBitableAPI {
9
+ constructor(options = {}) {
10
+ this.appId = options.appId || getEnvVar('FEISHU_APP_ID');
11
+ this.appSecret = options.appSecret || getEnvVar('FEISHU_APP_SECRET') || getEnvVar('FEISHU_APP_SECRET_PATH');
12
+ this.baseURL = options.baseURL || 'https://open.feishu.cn/open-apis/bitable/v1';
13
+ this.accessToken = options.accessToken;
14
+ this.autoRefreshToken = options.autoRefreshToken !== false;
15
+
16
+ if (!this.appId || !this.appSecret) {
17
+ throw new Error('缺少FEISHU_APP_ID或FEISHU_APP_SECRET环境变量');
18
+ }
19
+
20
+ this.client = axios.create({
21
+ baseURL: this.baseURL,
22
+ timeout: 30000,
23
+ headers: {
24
+ 'Content-Type': 'application/json; charset=utf-8'
25
+ }
26
+ });
27
+
28
+ // 请求拦截器:添加认证头
29
+ this.client.interceptors.request.use(async (config) => {
30
+ if (!this.accessToken) {
31
+ await this.refreshAccessToken();
32
+ }
33
+
34
+ config.headers.Authorization = `Bearer ${this.accessToken}`;
35
+ return config;
36
+ });
37
+
38
+ // 响应拦截器:处理token过期
39
+ this.client.interceptors.response.use(
40
+ (response) => response,
41
+ async (error) => {
42
+ const originalRequest = error.config;
43
+
44
+ // 如果是token过期错误且未重试过
45
+ if (error.response && error.response.status === 99991663 && !originalRequest._retry && this.autoRefreshToken) {
46
+ originalRequest._retry = true;
47
+
48
+ try {
49
+ await this.refreshAccessToken();
50
+ originalRequest.headers.Authorization = `Bearer ${this.accessToken}`;
51
+ return this.client(originalRequest);
52
+ } catch (refreshError) {
53
+ return Promise.reject(refreshError);
54
+ }
55
+ }
56
+
57
+ return Promise.reject(error);
58
+ }
59
+ );
60
+ }
61
+
62
+ /**
63
+ * 刷新访问令牌
64
+ */
65
+ async refreshAccessToken() {
66
+ try {
67
+ this.accessToken = await getAccessToken(this.appId, this.appSecret);
68
+ return this.accessToken;
69
+ } catch (error) {
70
+ throw new Error(`刷新访问令牌失败: ${error.message}`);
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 获取应用信息
76
+ */
77
+ async getApp(appToken) {
78
+ validateRequired({ appToken }, ['appToken']);
79
+
80
+ try {
81
+ const response = await this.client.get(`/apps/${appToken}`);
82
+ return response.data;
83
+ } catch (error) {
84
+ throw this.handleError(error, '获取应用信息');
85
+ }
86
+ }
87
+
88
+ /**
89
+ * 获取数据表列表
90
+ */
91
+ async listTables(appToken, params = {}) {
92
+ validateRequired({ appToken }, ['appToken']);
93
+
94
+ try {
95
+ const response = await this.client.get(`/apps/${appToken}/tables`, { params });
96
+ return response.data;
97
+ } catch (error) {
98
+ throw this.handleError(error, '获取数据表列表');
99
+ }
100
+ }
101
+
102
+ /**
103
+ * 创建数据表
104
+ */
105
+ async createTable(appToken, tableData) {
106
+ validateRequired({ appToken }, ['appToken']);
107
+
108
+ try {
109
+ const response = await this.client.post(`/apps/${appToken}/tables`, tableData);
110
+ return response.data;
111
+ } catch (error) {
112
+ throw this.handleError(error, '创建数据表');
113
+ }
114
+ }
115
+
116
+ /**
117
+ * 获取数据表详情
118
+ */
119
+ async getTable(appToken, tableId) {
120
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
121
+
122
+ try {
123
+ const response = await this.client.get(`/apps/${appToken}/tables/${tableId}`);
124
+ return response.data;
125
+ } catch (error) {
126
+ throw this.handleError(error, '获取数据表详情');
127
+ }
128
+ }
129
+
130
+ /**
131
+ * 删除数据表
132
+ */
133
+ async deleteTable(appToken, tableId) {
134
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
135
+
136
+ try {
137
+ const response = await this.client.delete(`/apps/${appToken}/tables/${tableId}`);
138
+ return response.data;
139
+ } catch (error) {
140
+ throw this.handleError(error, '删除数据表');
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 获取字段列表
146
+ */
147
+ async listFields(appToken, tableId, params = {}) {
148
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
149
+
150
+ try {
151
+ const response = await this.client.get(`/apps/${appToken}/tables/${tableId}/fields`, { params });
152
+ return response.data;
153
+ } catch (error) {
154
+ throw this.handleError(error, '获取字段列表');
155
+ }
156
+ }
157
+
158
+ /**
159
+ * 创建字段
160
+ */
161
+ async createField(appToken, tableId, fieldData) {
162
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
163
+
164
+ try {
165
+ const response = await this.client.post(`/apps/${appToken}/tables/${tableId}/fields`, fieldData);
166
+ return response.data;
167
+ } catch (error) {
168
+ throw this.handleError(error, '创建字段');
169
+ }
170
+ }
171
+
172
+ /**
173
+ * 更新字段
174
+ */
175
+ async updateField(appToken, tableId, fieldId, fieldData) {
176
+ validateRequired({ appToken, tableId, fieldId }, ['appToken', 'tableId', 'fieldId']);
177
+
178
+ try {
179
+ const response = await this.client.put(`/apps/${appToken}/tables/${tableId}/fields/${fieldId}`, fieldData);
180
+ return response.data;
181
+ } catch (error) {
182
+ throw this.handleError(error, '更新字段');
183
+ }
184
+ }
185
+
186
+ /**
187
+ * 获取记录列表
188
+ */
189
+ async listRecords(appToken, tableId, params = {}) {
190
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
191
+
192
+ try {
193
+ const response = await this.client.get(`/apps/${appToken}/tables/${tableId}/records`, { params });
194
+ return response.data;
195
+ } catch (error) {
196
+ throw this.handleError(error, '获取记录列表');
197
+ }
198
+ }
199
+
200
+ /**
201
+ * 创建记录
202
+ */
203
+ async createRecord(appToken, tableId, recordData) {
204
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
205
+
206
+ try {
207
+ const response = await this.client.post(`/apps/${appToken}/tables/${tableId}/records`, {
208
+ fields: recordData
209
+ });
210
+ return response.data;
211
+ } catch (error) {
212
+ throw this.handleError(error, '创建记录');
213
+ }
214
+ }
215
+
216
+ /**
217
+ * 批量创建记录
218
+ */
219
+ async batchCreateRecords(appToken, tableId, records) {
220
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
221
+
222
+ try {
223
+ const response = await this.client.post(`/apps/${appToken}/tables/${tableId}/records/batch_create`, {
224
+ records: records.map(record => ({ fields: record }))
225
+ });
226
+ return response.data;
227
+ } catch (error) {
228
+ throw this.handleError(error, '批量创建记录');
229
+ }
230
+ }
231
+
232
+ /**
233
+ * 获取记录详情
234
+ */
235
+ async getRecord(appToken, tableId, recordId) {
236
+ validateRequired({ appToken, tableId, recordId }, ['appToken', 'tableId', 'recordId']);
237
+
238
+ try {
239
+ const response = await this.client.get(`/apps/${appToken}/tables/${tableId}/records/${recordId}`);
240
+ return response.data;
241
+ } catch (error) {
242
+ throw this.handleError(error, '获取记录详情');
243
+ }
244
+ }
245
+
246
+ /**
247
+ * 更新记录
248
+ */
249
+ async updateRecord(appToken, tableId, recordId, recordData) {
250
+ validateRequired({ appToken, tableId, recordId }, ['appToken', 'tableId', 'recordId']);
251
+
252
+ try {
253
+ const response = await this.client.put(`/apps/${appToken}/tables/${tableId}/records/${recordId}`, {
254
+ fields: recordData
255
+ });
256
+ return response.data;
257
+ } catch (error) {
258
+ throw this.handleError(error, '更新记录');
259
+ }
260
+ }
261
+
262
+ /**
263
+ * 批量更新记录
264
+ */
265
+ async batchUpdateRecords(appToken, tableId, records) {
266
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
267
+
268
+ try {
269
+ const response = await this.client.post(`/apps/${appToken}/tables/${tableId}/records/batch_update`, {
270
+ records: records.map(record => ({
271
+ record_id: record.record_id,
272
+ fields: record.fields
273
+ }))
274
+ });
275
+ return response.data;
276
+ } catch (error) {
277
+ throw this.handleError(error, '批量更新记录');
278
+ }
279
+ }
280
+
281
+ /**
282
+ * 删除记录
283
+ */
284
+ async deleteRecord(appToken, tableId, recordId) {
285
+ validateRequired({ appToken, tableId, recordId }, ['appToken', 'tableId', 'recordId']);
286
+
287
+ try {
288
+ const response = await this.client.delete(`/apps/${appToken}/tables/${tableId}/records/${recordId}`);
289
+ return response.data;
290
+ } catch (error) {
291
+ throw this.handleError(error, '删除记录');
292
+ }
293
+ }
294
+
295
+ /**
296
+ * 批量删除记录
297
+ */
298
+ async batchDeleteRecords(appToken, tableId, recordIds) {
299
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
300
+
301
+ try {
302
+ const response = await this.client.post(`/apps/${appToken}/tables/${tableId}/records/batch_delete`, {
303
+ records: recordIds.map(recordId => ({ record_id: recordId }))
304
+ });
305
+ return response.data;
306
+ } catch (error) {
307
+ throw this.handleError(error, '批量删除记录');
308
+ }
309
+ }
310
+
311
+ /**
312
+ * 获取视图列表
313
+ */
314
+ async listViews(appToken, tableId, params = {}) {
315
+ validateRequired({ appToken, tableId }, ['appToken', 'tableId']);
316
+
317
+ try {
318
+ const response = await this.client.get(`/apps/${appToken}/tables/${tableId}/views`, { params });
319
+ return response.data;
320
+ } catch (error) {
321
+ throw this.handleError(error, '获取视图列表');
322
+ }
323
+ }
324
+
325
+ /**
326
+ * 获取视图详情
327
+ */
328
+ async getView(appToken, tableId, viewId) {
329
+ validateRequired({ appToken, tableId, viewId }, ['appToken', 'tableId', 'viewId']);
330
+
331
+ try {
332
+ const response = await this.client.get(`/apps/${appToken}/tables/${tableId}/views/${viewId}`);
333
+ return response.data;
334
+ } catch (error) {
335
+ throw this.handleError(error, '获取视图详情');
336
+ }
337
+ }
338
+
339
+ /**
340
+ * 错误处理
341
+ */
342
+ handleError(error, operation) {
343
+ if (error.response) {
344
+ const { status, data } = error.response;
345
+ const errorMsg = data.msg || data.message || error.message;
346
+
347
+ const fullError = new Error(`${operation}失败: ${errorMsg} (状态码: ${status})`);
348
+ fullError.status = status;
349
+ fullError.data = data;
350
+
351
+ return fullError;
352
+ } else if (error.request) {
353
+ return new Error(`${operation}失败: 网络错误,无法连接到飞书服务器`);
354
+ } else {
355
+ return new Error(`${operation}失败: ${error.message}`);
356
+ }
357
+ }
358
+
359
+ /**
360
+ * 测试连接
361
+ */
362
+ async testConnection() {
363
+ try {
364
+ await this.refreshAccessToken();
365
+ return {
366
+ success: true,
367
+ message: '连接测试成功',
368
+ appId: this.appId,
369
+ tokenValid: !!this.accessToken
370
+ };
371
+ } catch (error) {
372
+ return {
373
+ success: false,
374
+ message: `连接测试失败: ${error.message}`,
375
+ appId: this.appId
376
+ };
377
+ }
378
+ }
379
+ }
380
+
381
+ module.exports = FeishuBitableAPI;