@lark-project/meegle 0.0.1 → 0.0.3

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 (3) hide show
  1. package/README.md +82 -57
  2. package/dist/bin/meegle.js +393 -160
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -19,7 +19,7 @@ npm install -g @lark-project/meegle
19
19
  meegle auth login
20
20
 
21
21
  # 2. 查看本周待办
22
- meegle workitem todo --action this_week --page-num 1
22
+ meegle mywork todo --action this_week --page-num 1
23
23
 
24
24
  # 3. 查看帮助
25
25
  meegle --help
@@ -31,68 +31,93 @@ meegle inspect workitem.create
31
31
 
32
32
  ## 命令一览
33
33
 
34
- ### workitem — 工作项
34
+ ### workitem — 工作项域
35
35
 
36
36
  | 命令 | 说明 |
37
37
  |------|------|
38
38
  | `workitem create` | 创建工作项 |
39
- | `workitem brief` | 查看工作项概况 |
40
- | `workitem update-field` | 修改工作项字段 |
41
- | `workitem todo` | 查看我的待办/已办 |
42
- | `workitem comments` | 查看评论列表 |
43
- | `workitem add-comment` | 添加评论 |
44
- | `workitem types` | 查看工作项类型列表 |
45
- | `workitem field-config` | 查看字段配置 |
46
- | `workitem role-config` | 查看角色配置 |
47
- | `workitem field-meta` | 查看创建元信息 |
48
- | `workitem man-hours` | 查看工时记录 |
49
- | `workitem op-record` | 查看操作记录 |
50
- | `workitem relations` | 查看关联关系 |
51
- | `workitem related` | 查看关联的工作项 |
52
- | `workitem search` | 使用 MQL 搜索工作项 |
53
-
54
- ### node 节点 / 流程
39
+ | `workitem get` | 查看工作项概况 |
40
+ | `workitem update` | 修改工作项字段 |
41
+ | `workitem query` | 使用 MQL 搜索工作项 |
42
+ | `workitem list-op-records` | 查看操作记录 |
43
+ | `workitem meta-types` | 查看工作项类型列表 |
44
+ | `workitem meta-create-fields` | 查看创建时可用字段 |
45
+ | `workitem meta-fields` | 查看字段配置 |
46
+ | `workitem meta-roles` | 查看角色配置 |
47
+
48
+ ### workflow 工作流域
49
+
50
+ | 命令 | 说明 |
51
+ |------|------|
52
+ | `workflow transition` | 流转或回滚节点 |
53
+ | `workflow get-node` | 查看节点详情 |
54
+ | `workflow update-node` | 修改节点 |
55
+ | `workflow meta-node-fields` | 查看节点字段配置 |
56
+ | `workflow list-state-transitions` | 查看可流转状态 |
57
+ | `workflow list-state-required` | 查看流转必填信息 |
58
+
59
+ ### subtask — 子任务
60
+
61
+ | 命令 | 说明 |
62
+ |------|------|
63
+ | `subtask update` | 创建/修改/完成/回滚子任务 |
64
+
65
+ ### comment — 评论域
66
+
67
+ | 命令 | 说明 |
68
+ |------|------|
69
+ | `comment add` | 添加评论 |
70
+ | `comment list` | 查看评论列表 |
71
+
72
+ ### workhour — 工时域
73
+
74
+ | 命令 | 说明 |
75
+ |------|------|
76
+ | `workhour list-records` | 查看工时登记记录 |
77
+ | `workhour list-schedule` | 查看人员排期 |
78
+
79
+ ### relation — 关系域
80
+
81
+ | 命令 | 说明 |
82
+ |------|------|
83
+ | `relation list` | 查看关联的工作项 |
84
+ | `relation meta-definitions` | 查看关联关系定义 |
85
+
86
+ ### mywork — 工作台域
55
87
 
56
88
  | 命令 | 说明 |
57
89
  |------|------|
58
- | `node detail` | 查看节点详情 |
59
- | `node transition` | 流转或回滚节点 |
60
- | `node update` | 修改节点 |
61
- | `node update-subtask` | 创建/修改/完成子任务 |
62
- | `node field-config` | 查看节点字段配置 |
63
- | `node transitable-states` | 查看可流转状态 |
64
- | `node transition-required` | 查看流转必填信息 |
90
+ | `mywork todo` | 查看我的待办/已办 |
65
91
 
66
- ### view — 视图
92
+ ### view — 视图域
67
93
 
68
94
  | 命令 | 说明 |
69
95
  |------|------|
70
- | `view detail` | 查看视图详情 |
71
- | `view create` | 创建固定视图 |
72
- | `view update` | 更新固定视图 |
96
+ | `view create-fixed` | 创建固定视图 |
97
+ | `view get` | 查看视图详情 |
98
+ | `view update-fixed` | 更新固定视图 |
73
99
  | `view search` | 按名称搜索视图 |
74
100
 
75
- ### chart — 图表
101
+ ### chart — 度量域
76
102
 
77
103
  | 命令 | 说明 |
78
104
  |------|------|
79
- | `chart detail` | 查看图表详情 |
105
+ | `chart get` | 查看图表详情 |
80
106
  | `chart list` | 查看视图下的图表列表 |
81
107
 
82
- ### team — 团队
108
+ ### team / user 人员域
83
109
 
84
110
  | 命令 | 说明 |
85
111
  |------|------|
86
112
  | `team list` | 查看空间下的团队列表 |
87
- | `team members` | 查看团队成员 |
113
+ | `team list-members` | 查看团队成员 |
114
+ | `user search` | 搜索用户信息 |
88
115
 
89
- ### 其他
116
+ ### project — 空间域
90
117
 
91
118
  | 命令 | 说明 |
92
119
  |------|------|
93
- | `schedule list` | 查看人员排期 |
94
- | `project info` | 查看空间信息 |
95
- | `user info` | 查看用户信息 |
120
+ | `project search` | 搜索空间信息 |
96
121
  | `inspect [command]` | 查看命令参数详情 |
97
122
  | `+todo` | 查看本周个人待办(快捷命令) |
98
123
 
@@ -102,23 +127,23 @@ meegle inspect workitem.create
102
127
 
103
128
  ```bash
104
129
  # 本周待办
105
- meegle workitem todo --action this_week --page-num 1
130
+ meegle mywork todo --action this_week --page-num 1
106
131
 
107
132
  # 已办事项
108
- meegle workitem todo --action done --page-num 1
133
+ meegle mywork todo --action done --page-num 1
109
134
 
110
135
  # 逾期事项
111
- meegle workitem todo --action overdue --page-num 1
136
+ meegle mywork todo --action overdue --page-num 1
112
137
  ```
113
138
 
114
139
  ### 查询工作项
115
140
 
116
141
  ```bash
117
142
  # 查看工作项概况
118
- meegle workitem brief --work-item-id 12345
143
+ meegle workitem get --work-item-id 12345
119
144
 
120
145
  # 查看工作项的节点详情
121
- meegle node detail --work-item-id 12345 --need-sub-task
146
+ meegle workflow get-node --work-item-id 12345 --need-sub-task
122
147
  ```
123
148
 
124
149
  ### 创建工作项
@@ -139,11 +164,11 @@ meegle workitem create --project-key PROJ --work-item-type story \
139
164
 
140
165
  ```bash
141
166
  # 修改工作项名称
142
- meegle workitem update-field --work-item-id 12345 \
167
+ meegle workitem update --work-item-id 12345 \
143
168
  --set name=新标题
144
169
 
145
170
  # 同时修改多个字段
146
- meegle workitem update-field --work-item-id 12345 \
171
+ meegle workitem update --work-item-id 12345 \
147
172
  --set name=新标题 \
148
173
  --set priority=P0
149
174
  ```
@@ -152,7 +177,7 @@ meegle workitem update-field --work-item-id 12345 \
152
177
 
153
178
  ```bash
154
179
  # 查询空间下的 P0 需求
155
- meegle workitem search --project-key PROJ \
180
+ meegle workitem query --project-key PROJ \
156
181
  --mql "SELECT \`name\`, \`priority\` FROM \`空间名\`.\`需求\` WHERE \`priority\` = 'P0'"
157
182
  ```
158
183
 
@@ -160,7 +185,7 @@ meegle workitem search --project-key PROJ \
160
185
 
161
186
  ```bash
162
187
  # 查看团队成员排期
163
- meegle schedule list --project-key PROJ \
188
+ meegle workhour list-schedule --project-key PROJ \
164
189
  --start-time 2026-03-01 --end-time 2026-03-31 \
165
190
  --user-keys "张三,李四,王五"
166
191
  ```
@@ -168,7 +193,7 @@ meegle schedule list --project-key PROJ \
168
193
  ### 查看用户信息
169
194
 
170
195
  ```bash
171
- meegle user info --user-keys "张三,李四" --project-key PROJ
196
+ meegle user search --user-keys "张三,李四" --project-key PROJ
172
197
  ```
173
198
 
174
199
  ## 参数传递
@@ -178,12 +203,12 @@ meegle user info --user-keys "张三,李四" --project-key PROJ
178
203
  每个命令的参数通过 `--flag-name` 传递:
179
204
 
180
205
  ```bash
181
- meegle workitem brief --work-item-id 12345 --project-key PROJ
206
+ meegle workitem get --work-item-id 12345 --project-key PROJ
182
207
  ```
183
208
 
184
209
  ### --set key=value(写入命令)
185
210
 
186
- `workitem create`、`workitem update-field`、`node update`、`node update-subtask` 支持 `--set` 简便设置字段:
211
+ `workitem create`、`workitem update`、`workflow update-node`、`subtask update` 支持 `--set` 简便设置字段:
187
212
 
188
213
  ```bash
189
214
  --set name=标题 # 字符串
@@ -225,24 +250,24 @@ meegle workitem create --project-key PROJ --work-item-type story \
225
250
  直接加 flag 即为 true,不加即为 false:
226
251
 
227
252
  ```bash
228
- meegle node detail --work-item-id 12345 --need-sub-task
253
+ meegle workflow get-node --work-item-id 12345 --need-sub-task
229
254
  ```
230
255
 
231
256
  ## 输出格式
232
257
 
233
258
  ```bash
234
259
  # JSON(默认)
235
- meegle workitem brief --work-item-id 12345
260
+ meegle workitem get --work-item-id 12345
236
261
 
237
262
  # 选取输出属性(支持 dot path)
238
- meegle workitem brief --work-item-id 12345 --select "id,name,status"
239
- meegle workitem todo --action done --page-num 1 --select "list.work_item_info.work_item_name"
263
+ meegle workitem get --work-item-id 12345 --select "id,name,status"
264
+ meegle mywork todo --action done --page-num 1 --select "list.work_item_info.work_item_name"
240
265
 
241
266
  # NDJSON(适合管道处理)
242
- meegle workitem todo --action this_week --page-num 1 --format ndjson
267
+ meegle mywork todo --action this_week --page-num 1 --format ndjson
243
268
 
244
269
  # 表格
245
- meegle workitem todo --action this_week --page-num 1 --format table
270
+ meegle mywork todo --action this_week --page-num 1 --format table
246
271
  ```
247
272
 
248
273
  ### --select dot path
@@ -341,7 +366,7 @@ meegle config profile use staging
341
366
  meegle config profile current
342
367
 
343
368
  # 临时使用其他环境(不改变默认)
344
- meegle workitem todo --action this_week --page-num 1 --profile staging
369
+ meegle mywork todo --action this_week --page-num 1 --profile staging
345
370
 
346
371
  # 删除环境
347
372
  meegle config profile delete staging
@@ -374,7 +399,7 @@ meegle auth login
374
399
 
375
400
  ```bash
376
401
  export MEEGLE_USER_ACCESS_TOKEN="your-token"
377
- meegle workitem todo --action this_week --page-num 1
402
+ meegle mywork todo --action this_week --page-num 1
378
403
  ```
379
404
 
380
405
  ## License
@@ -51,8 +51,8 @@ async function readRootConfig() {
51
51
  }
52
52
  }
53
53
  async function writeRootConfig(root) {
54
- await mkdir(CONFIG_DIR, { recursive: true });
55
- await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2));
54
+ await mkdir(CONFIG_DIR, { recursive: true, mode: 448 });
55
+ await writeFile(CONFIG_PATH, JSON.stringify(root, null, 2), { mode: 384 });
56
56
  }
57
57
  async function getCurrentProfileName() {
58
58
  const root = await readRootConfig();
@@ -115,9 +115,18 @@ async function saveConfig(config, profile) {
115
115
  const name = profile ?? await getCurrentProfileName();
116
116
  await saveProfileConfig(name, config);
117
117
  }
118
+ function sanitizeHost(raw) {
119
+ const trimmed = raw.trim();
120
+ try {
121
+ const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
122
+ return url.host;
123
+ } catch {
124
+ return trimmed;
125
+ }
126
+ }
118
127
  function getServerUrl(config) {
119
128
  const envHost = getEnv("HOST");
120
- const host = envHost ?? config.host ?? "meegle.com";
129
+ const host = sanitizeHost(envHost ?? config.host ?? "meegle.com");
121
130
  return `https://${host}/mcp_server/v1`;
122
131
  }
123
132
  var CONFIG_DIR, CONFIG_PATH;
@@ -130,6 +139,71 @@ var init_config = __esm({
130
139
  }
131
140
  });
132
141
 
142
+ // src/core/errors.ts
143
+ var MeegleError, ClientError, ServerError, HttpError;
144
+ var init_errors = __esm({
145
+ "src/core/errors.ts"() {
146
+ "use strict";
147
+ MeegleError = class extends Error {
148
+ code;
149
+ exitCode;
150
+ suggestion;
151
+ constructor(message, code, exitCode, suggestion) {
152
+ super(message);
153
+ this.name = "MeegleError";
154
+ this.code = code;
155
+ this.exitCode = exitCode;
156
+ this.suggestion = suggestion;
157
+ }
158
+ toJSON() {
159
+ return {
160
+ error: this.code,
161
+ message: this.message,
162
+ ...this.suggestion !== void 0 && { suggestion: this.suggestion }
163
+ };
164
+ }
165
+ };
166
+ ClientError = class extends MeegleError {
167
+ constructor(message, code, suggestion) {
168
+ super(message, code, 1, suggestion);
169
+ this.name = "ClientError";
170
+ }
171
+ };
172
+ ServerError = class extends MeegleError {
173
+ constructor(message, code, suggestion) {
174
+ super(message, code, 2, suggestion);
175
+ this.name = "ServerError";
176
+ }
177
+ };
178
+ HttpError = class extends ServerError {
179
+ httpStatus;
180
+ constructor(message, code, httpStatus, suggestion) {
181
+ super(message, code, suggestion);
182
+ this.name = "HttpError";
183
+ this.httpStatus = httpStatus;
184
+ }
185
+ };
186
+ }
187
+ });
188
+
189
+ // src/core/profile-validation.ts
190
+ function validateProfileName(profile) {
191
+ if (!PROFILE_NAME_RE.test(profile)) {
192
+ throw new ClientError(
193
+ `Profile \u540D\u79F0\u53EA\u80FD\u5305\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u8FDE\u5B57\u7B26\u548C\u4E0B\u5212\u7EBF\uFF0C\u6536\u5230: "${profile}"`,
194
+ "CLIENT_INVALID_PROFILE"
195
+ );
196
+ }
197
+ }
198
+ var PROFILE_NAME_RE;
199
+ var init_profile_validation = __esm({
200
+ "src/core/profile-validation.ts"() {
201
+ "use strict";
202
+ init_errors();
203
+ PROFILE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
204
+ }
205
+ });
206
+
133
207
  // src/core/auth/keychain-store.ts
134
208
  var keychain_store_exports = {};
135
209
  __export(keychain_store_exports, {
@@ -142,11 +216,13 @@ var exec, SERVICE, KeychainStore, SecretToolStore;
142
216
  var init_keychain_store = __esm({
143
217
  "src/core/auth/keychain-store.ts"() {
144
218
  "use strict";
219
+ init_profile_validation();
145
220
  exec = promisify(execFile);
146
221
  SERVICE = "meegle-cli";
147
222
  KeychainStore = class {
148
223
  account;
149
224
  constructor(profile = "default") {
225
+ validateProfileName(profile);
150
226
  this.account = profile;
151
227
  }
152
228
  async isAvailable() {
@@ -198,6 +274,7 @@ var init_keychain_store = __esm({
198
274
  SecretToolStore = class {
199
275
  account;
200
276
  constructor(profile = "default") {
277
+ validateProfileName(profile);
201
278
  this.account = profile;
202
279
  }
203
280
  async isAvailable() {
@@ -269,20 +346,34 @@ function toHex(b) {
269
346
  function fromHex(s) {
270
347
  return new Uint8Array(Buffer.from(s, "hex"));
271
348
  }
272
- function deriveKey(salt) {
273
- const material = `${hostname()}:${userInfo().username}:meegle-cli`;
349
+ async function getOrCreateMachineKey(dir) {
350
+ const keyPath = join2(dir, MACHINE_KEY_FILE);
351
+ try {
352
+ return await readFile2(keyPath, "utf-8");
353
+ } catch {
354
+ const key = randomBytes(32).toString("hex");
355
+ await mkdir2(dir, { recursive: true, mode: 448 });
356
+ await writeFile2(keyPath, key, { mode: 384 });
357
+ return key;
358
+ }
359
+ }
360
+ function deriveKey(salt, machineKey) {
361
+ const material = `${hostname()}:${userInfo().username}:${machineKey}:meegle-cli`;
274
362
  return new Uint8Array(pbkdf2Sync(material, salt, 1e5, 32, "sha256").buffer);
275
363
  }
276
- var FileStore;
364
+ var MACHINE_KEY_FILE, FileStore;
277
365
  var init_file_store = __esm({
278
366
  "src/core/auth/file-store.ts"() {
279
367
  "use strict";
280
368
  init_config();
369
+ init_profile_validation();
370
+ MACHINE_KEY_FILE = ".machine-key";
281
371
  FileStore = class {
282
372
  dir;
283
373
  filename;
284
374
  constructor(dir, profile = "default") {
285
375
  this.dir = dir ?? getConfigDir();
376
+ validateProfileName(profile);
286
377
  this.filename = profile === "default" ? "credentials.enc" : `credentials-${profile}.enc`;
287
378
  }
288
379
  get filePath() {
@@ -290,13 +381,14 @@ var init_file_store = __esm({
290
381
  }
291
382
  async load() {
292
383
  try {
384
+ const machineKey = await getOrCreateMachineKey(this.dir);
293
385
  const raw = await readFile2(this.filePath, "utf-8");
294
386
  const file = JSON.parse(raw);
295
387
  const salt = fromHex(file.salt);
296
388
  const iv = fromHex(file.iv);
297
389
  const tag = fromHex(file.tag);
298
390
  const encrypted = fromHex(file.data);
299
- const key = deriveKey(salt);
391
+ const key = deriveKey(salt, machineKey);
300
392
  const decipher = createDecipheriv("aes-256-gcm", key, iv);
301
393
  decipher.setAuthTag(tag);
302
394
  const part1 = decipher.update(encrypted);
@@ -308,9 +400,10 @@ var init_file_store = __esm({
308
400
  }
309
401
  }
310
402
  async save(data) {
403
+ const machineKey = await getOrCreateMachineKey(this.dir);
311
404
  const salt = new Uint8Array(randomBytes(16).buffer);
312
405
  const iv = new Uint8Array(randomBytes(12).buffer);
313
- const key = deriveKey(salt);
406
+ const key = deriveKey(salt, machineKey);
314
407
  const cipher = createCipheriv("aes-256-gcm", key, iv);
315
408
  const plaintext = JSON.stringify(data);
316
409
  const enc1 = cipher.update(plaintext, "utf-8");
@@ -323,8 +416,8 @@ var init_file_store = __esm({
323
416
  tag: toHex(tag),
324
417
  data: encrypted.toString("hex")
325
418
  };
326
- await mkdir2(this.dir, { recursive: true });
327
- await writeFile2(this.filePath, JSON.stringify(file, null, 2));
419
+ await mkdir2(this.dir, { recursive: true, mode: 448 });
420
+ await writeFile2(this.filePath, JSON.stringify(file, null, 2), { mode: 384 });
328
421
  }
329
422
  async clear() {
330
423
  try {
@@ -363,40 +456,26 @@ async function createTokenStore(profile = "default") {
363
456
  // src/core/auth/token-manager.ts
364
457
  init_env();
365
458
 
366
- // src/core/errors.ts
367
- var MeegleError = class extends Error {
368
- code;
369
- exitCode;
370
- suggestion;
371
- constructor(message, code, exitCode, suggestion) {
372
- super(message);
373
- this.name = "MeegleError";
374
- this.code = code;
375
- this.exitCode = exitCode;
376
- this.suggestion = suggestion;
377
- }
378
- toJSON() {
379
- return {
380
- error: this.code,
381
- message: this.message,
382
- ...this.suggestion !== void 0 && { suggestion: this.suggestion }
383
- };
384
- }
385
- };
386
- var ClientError = class extends MeegleError {
387
- constructor(message, code, suggestion) {
388
- super(message, code, 1, suggestion);
389
- this.name = "ClientError";
390
- }
391
- };
392
- var ServerError = class extends MeegleError {
393
- constructor(message, code, suggestion) {
394
- super(message, code, 2, suggestion);
395
- this.name = "ServerError";
396
- }
397
- };
398
-
399
459
  // src/core/auth/oauth-discovery.ts
460
+ init_errors();
461
+ var REQUIRED_ENDPOINTS = [
462
+ "issuer",
463
+ "authorization_endpoint",
464
+ "token_endpoint",
465
+ "registration_endpoint"
466
+ ];
467
+ function validateMetadata(metadata) {
468
+ for (const field of REQUIRED_ENDPOINTS) {
469
+ const value = metadata[field];
470
+ if (typeof value !== "string" || !value.startsWith("https://")) {
471
+ throw new ClientError(
472
+ `OAuth \u914D\u7F6E\u5B57\u6BB5\u65E0\u6548: ${field}`,
473
+ "OAUTH_DISCOVERY_INVALID"
474
+ );
475
+ }
476
+ }
477
+ return metadata;
478
+ }
400
479
  async function fetchOAuthMetadata(host, headers) {
401
480
  const url = `https://${host}/.well-known/oauth-authorization-server`;
402
481
  const res = await fetch(url, { headers });
@@ -407,7 +486,8 @@ async function fetchOAuthMetadata(host, headers) {
407
486
  `\u8BF7\u786E\u8BA4\u7AD9\u70B9 ${host} \u662F\u5426\u6B63\u786E`
408
487
  );
409
488
  }
410
- return res.json();
489
+ const data = await res.json();
490
+ return validateMetadata(data);
411
491
  }
412
492
 
413
493
  // src/core/auth/token-manager.ts
@@ -445,11 +525,14 @@ var TokenManager = class {
445
525
  });
446
526
  if (!res.ok) return null;
447
527
  const data = await res.json();
528
+ if (typeof data.access_token !== "string" || !data.access_token) {
529
+ return null;
530
+ }
448
531
  const newTokenData = {
449
532
  accessToken: data.access_token,
450
- refreshToken: data.refresh_token ?? tokenData.refreshToken,
533
+ refreshToken: (typeof data.refresh_token === "string" ? data.refresh_token : void 0) ?? tokenData.refreshToken,
451
534
  clientId: tokenData.clientId,
452
- expiresAt: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
535
+ expiresAt: typeof data.expires_in === "number" ? Date.now() + data.expires_in * 1e3 : void 0
453
536
  };
454
537
  await this.store.save(newTokenData);
455
538
  return newTokenData;
@@ -477,6 +560,7 @@ import { randomBytes as randomBytes3 } from "crypto";
477
560
  import open from "open";
478
561
 
479
562
  // src/core/auth/oauth-client-registration.ts
563
+ init_errors();
480
564
  import { hostname as hostname2, platform, arch, userInfo as userInfo2 } from "os";
481
565
  function getClientName() {
482
566
  const parts = [];
@@ -544,6 +628,7 @@ function generateCodeChallenge(verifier) {
544
628
  }
545
629
 
546
630
  // src/core/auth/callback-server.ts
631
+ init_errors();
547
632
  import http from "http";
548
633
  import { URL as URL2 } from "url";
549
634
  var SUCCESS_HTML = `<!DOCTYPE html><html><body>
@@ -631,6 +716,7 @@ async function startCallbackServer() {
631
716
  }
632
717
 
633
718
  // src/core/auth/auth-code-flow.ts
719
+ init_errors();
634
720
  function buildAuthUrl(endpoint, params) {
635
721
  const url = new URL(endpoint);
636
722
  for (const [key, value] of Object.entries(params)) {
@@ -694,6 +780,7 @@ async function startAuthCodeFlow(host, customHeaders) {
694
780
 
695
781
  // src/core/auth/device-code-flow.ts
696
782
  import qrcode from "qrcode-terminal";
783
+ init_errors();
697
784
  function generateQR(url) {
698
785
  return new Promise((resolve) => {
699
786
  qrcode.generate(url, { small: true }, (code) => {
@@ -738,45 +825,57 @@ async function startDeviceCodeInit(host, customHeaders) {
738
825
  client_id: client.client_id
739
826
  };
740
827
  }
741
- async function startDeviceCodePoll(options) {
828
+ async function pollDeviceCodeOnce(options) {
742
829
  const metadata = await fetchOAuthMetadata(options.host, options.customHeaders);
743
- let interval = options.interval * 1e3;
744
- const deadline = Date.now() + options.expires_in * 1e3;
745
- while (Date.now() < deadline) {
746
- await sleep(interval);
747
- const tokenRes = await fetch(metadata.token_endpoint, {
748
- method: "POST",
749
- headers: { "Content-Type": "application/x-www-form-urlencoded", ...options.customHeaders },
750
- body: new URLSearchParams({
751
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
752
- device_code: options.device_code,
753
- client_id: options.client_id
754
- })
755
- });
756
- const tokenRaw = await tokenRes.json();
757
- const data = tokenRaw.access_token || tokenRaw.error ? tokenRaw : tokenRaw.data ?? tokenRaw;
758
- if (data.access_token) {
759
- return {
830
+ const tokenRes = await fetch(metadata.token_endpoint, {
831
+ method: "POST",
832
+ headers: { "Content-Type": "application/x-www-form-urlencoded", ...options.customHeaders },
833
+ body: new URLSearchParams({
834
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
835
+ device_code: options.device_code,
836
+ client_id: options.client_id
837
+ })
838
+ });
839
+ const tokenRaw = await tokenRes.json();
840
+ const data = tokenRaw.access_token || tokenRaw.error ? tokenRaw : tokenRaw.data ?? tokenRaw;
841
+ if (data.access_token) {
842
+ return {
843
+ status: "ok",
844
+ token_data: {
760
845
  accessToken: data.access_token,
761
846
  refreshToken: data.refresh_token,
762
847
  clientId: options.client_id,
763
848
  expiresAt: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
764
- };
849
+ }
850
+ };
851
+ }
852
+ if (data.error === "slow_down") return { status: "slow_down" };
853
+ if (data.error === "authorization_pending") return { status: "authorization_pending" };
854
+ if (data.error === "expired_token") return { status: "expired_token" };
855
+ throw new ServerError(
856
+ `Device Code \u6388\u6743\u5931\u8D25: ${data.error ?? "\u672A\u77E5\u9519\u8BEF"}`,
857
+ "DEVICE_CODE_FAILED"
858
+ );
859
+ }
860
+ async function startDeviceCodePoll(options) {
861
+ let interval = options.interval * 1e3;
862
+ const deadline = Date.now() + options.expires_in * 1e3;
863
+ while (Date.now() < deadline) {
864
+ await sleep(interval);
865
+ const result = await pollDeviceCodeOnce(options);
866
+ if (result.status === "ok" && result.token_data) {
867
+ return result.token_data;
765
868
  }
766
- if (data.error === "slow_down") {
869
+ if (result.status === "slow_down") {
767
870
  interval += 5e3;
768
871
  continue;
769
872
  }
770
- if (data.error === "authorization_pending") {
873
+ if (result.status === "authorization_pending") {
771
874
  continue;
772
875
  }
773
- if (data.error === "expired_token") {
876
+ if (result.status === "expired_token") {
774
877
  throw new ClientError("\u6388\u6743\u5DF2\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C meegle auth login --device-code", "DEVICE_CODE_EXPIRED");
775
878
  }
776
- throw new ServerError(
777
- `Device Code \u6388\u6743\u5931\u8D25: ${data.error ?? "\u672A\u77E5\u9519\u8BEF"}`,
778
- "DEVICE_CODE_FAILED"
779
- );
780
879
  }
781
880
  throw new ClientError("\u6388\u6743\u5DF2\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C meegle auth login --device-code", "DEVICE_CODE_EXPIRED");
782
881
  }
@@ -866,6 +965,9 @@ async function checkFirstRun(profileName) {
866
965
  process.exit(0);
867
966
  }
868
967
 
968
+ // src/commands/auth/login.ts
969
+ init_errors();
970
+
869
971
  // src/output/table.ts
870
972
  import Table from "cli-table3";
871
973
  function renderTable(data) {
@@ -957,7 +1059,7 @@ async function resolveHost(profile, hostFlag) {
957
1059
  return host;
958
1060
  }
959
1061
  function registerLogin(auth) {
960
- auth.command("login").description("\u767B\u5F55\u98DE\u4E66\u9879\u76EE").option("--device-code", "\u4F7F\u7528 Device Code \u6D41\u7A0B\uFF08\u65E0\u6D4F\u89C8\u5668\u73AF\u5883\uFF09").option("--phase <phase>", "Device Code \u9636\u6BB5: init \u6216 poll").option("--host <host>", "\u6307\u5B9A\u7AD9\u70B9\u57DF\u540D\uFF08\u8DF3\u8FC7\u4EA4\u4E92\u5F0F\u9009\u62E9\uFF09").option("--device-code-value <code>", "poll \u9636\u6BB5\u7684 device_code").option("--client-id <id>", "poll \u9636\u6BB5\u7684 client_id").option("--interval <seconds>", "poll \u8F6E\u8BE2\u95F4\u9694\uFF08\u79D2\uFF09", "5").option("--expires-in <seconds>", "poll \u8D85\u65F6\u65F6\u95F4\uFF08\u79D2\uFF09", "600").option("--format <format>", "\u8F93\u51FA\u683C\u5F0F: json, table, ndjson", "table").action(async (options) => {
1062
+ auth.command("login").description("\u767B\u5F55\u98DE\u4E66\u9879\u76EE").option("--device-code", "\u4F7F\u7528 Device Code \u6D41\u7A0B\uFF08\u65E0\u6D4F\u89C8\u5668\u73AF\u5883\uFF09").option("--phase <phase>", "Device Code \u9636\u6BB5: init \u6216 poll").option("--host <host>", "\u6307\u5B9A\u7AD9\u70B9\u57DF\u540D\uFF08\u8DF3\u8FC7\u4EA4\u4E92\u5F0F\u9009\u62E9\uFF09").option("--device-code-value <code>", "poll \u9636\u6BB5\u7684 device_code").option("--client-id <id>", "poll \u9636\u6BB5\u7684 client_id").option("--interval <seconds>", "poll \u8F6E\u8BE2\u95F4\u9694\uFF08\u79D2\uFF09", "5").option("--expires-in <seconds>", "poll \u8D85\u65F6\u65F6\u95F4\uFF08\u79D2\uFF09", "600").option("--once", "poll \u9636\u6BB5\u5355\u6B21\u5C1D\u8BD5\uFF0C\u7ACB\u5373\u8FD4\u56DE\u7ED3\u679C\uFF08\u4E0D\u963B\u585E\uFF09").option("--format <format>", "\u8F93\u51FA\u683C\u5F0F: json, table, ndjson", "table").action(async (options) => {
961
1063
  const profileName = auth.parent?.opts().profile ?? await getCurrentProfileName();
962
1064
  const format = validateFormat(options.format);
963
1065
  const config = await loadConfig(profileName);
@@ -978,6 +1080,23 @@ function registerLogin(auth) {
978
1080
  throw new ClientError("--phase poll \u9700\u8981 --device-code-value \u548C --client-id", "MISSING_OPTION");
979
1081
  }
980
1082
  const host2 = await resolveHost(profileName, options.host);
1083
+ if (options.once) {
1084
+ const result = await pollDeviceCodeOnce({
1085
+ host: host2,
1086
+ device_code: options.deviceCodeValue,
1087
+ client_id: options.clientId,
1088
+ customHeaders: config.headers
1089
+ });
1090
+ if (result.status === "ok" && result.token_data) {
1091
+ const store3 = await createTokenStore(profileName);
1092
+ const tokenManager3 = new TokenManager(store3, host2);
1093
+ await tokenManager3.saveToken(result.token_data);
1094
+ console.log(formatOutput({ status: "ok", message: "\u767B\u5F55\u6210\u529F" }, format));
1095
+ } else {
1096
+ console.log(formatOutput({ status: result.status }, format));
1097
+ }
1098
+ return;
1099
+ }
981
1100
  const tokenData2 = await startDeviceCodePoll({
982
1101
  host: host2,
983
1102
  device_code: options.deviceCodeValue,
@@ -1002,9 +1121,12 @@ function registerLogin(auth) {
1002
1121
  }
1003
1122
 
1004
1123
  // src/commands/auth/logout.ts
1124
+ init_config();
1005
1125
  function registerLogout(auth) {
1006
1126
  auth.command("logout").description("\u9000\u51FA\u767B\u5F55\uFF0C\u6E05\u9664\u672C\u5730\u51ED\u636E").action(async () => {
1007
- const store = await createTokenStore();
1127
+ const profileName = auth.parent?.opts().profile;
1128
+ const profile = profileName ?? await getCurrentProfileName();
1129
+ const store = await createTokenStore(profile);
1008
1130
  await store.clear();
1009
1131
  console.log("\u2713 \u5DF2\u9000\u51FA\u767B\u5F55");
1010
1132
  });
@@ -1215,6 +1337,7 @@ function registerConfigCommands(program) {
1215
1337
  }
1216
1338
 
1217
1339
  // src/dynamic/params-parser.ts
1340
+ init_errors();
1218
1341
  function parseSetFlag(input4) {
1219
1342
  const eqIndex = input4.indexOf("=");
1220
1343
  if (eqIndex === -1) {
@@ -1227,7 +1350,8 @@ function parseSetFlag(input4) {
1227
1350
  const rawValue = input4.slice(eqIndex + 1);
1228
1351
  let value;
1229
1352
  try {
1230
- value = JSON.parse(rawValue);
1353
+ const parsed = JSON.parse(rawValue);
1354
+ value = typeof parsed === "object" && parsed !== null ? parsed : rawValue;
1231
1355
  } catch {
1232
1356
  value = rawValue;
1233
1357
  }
@@ -1303,6 +1427,9 @@ function resolveDotPath(current, segments) {
1303
1427
  return current;
1304
1428
  }
1305
1429
 
1430
+ // src/dynamic/response.ts
1431
+ init_errors();
1432
+
1306
1433
  // src/core/logger.ts
1307
1434
  import chalk from "chalk";
1308
1435
  var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
@@ -1334,6 +1461,11 @@ function unwrapResponse(raw) {
1334
1461
  const response = raw;
1335
1462
  const content = response.content;
1336
1463
  if (!Array.isArray(content)) return void 0;
1464
+ if (response.isError) {
1465
+ const allTexts = content.filter((e) => e.type === "text" && e.text).map((e) => e.text);
1466
+ const message = allTexts.join("\n") || "\u670D\u52A1\u7AEF\u8FD4\u56DE\u9519\u8BEF";
1467
+ throw new ServerError(message, "SERVER_CALL_FAILED");
1468
+ }
1337
1469
  const dataTexts = [];
1338
1470
  for (const entry of content) {
1339
1471
  if (entry.type !== "text" || !entry.text) continue;
@@ -1343,10 +1475,6 @@ function unwrapResponse(raw) {
1343
1475
  dataTexts.push(entry.text);
1344
1476
  }
1345
1477
  }
1346
- if (response.isError) {
1347
- const message = dataTexts.join("\n") || "\u670D\u52A1\u7AEF\u8FD4\u56DE\u9519\u8BEF";
1348
- throw new ServerError(message, "SERVER_CALL_FAILED");
1349
- }
1350
1478
  if (dataTexts.length === 0) return void 0;
1351
1479
  const text = dataTexts[0];
1352
1480
  try {
@@ -1357,6 +1485,21 @@ function unwrapResponse(raw) {
1357
1485
  }
1358
1486
 
1359
1487
  // src/dynamic/register.ts
1488
+ init_errors();
1489
+ var RESOURCE_DESCRIPTIONS = {
1490
+ workitem: "\u5DE5\u4F5C\u9879\u57DF \u2014 \u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7684 CRUD \u548C\u6A21\u578B\u914D\u7F6E\uFF0C\u901A\u8FC7 work-item-id \u6807\u8BC6",
1491
+ workflow: "\u5DE5\u4F5C\u6D41\u57DF \u2014 \u8282\u70B9\u6D41\u8F6C\u4E0E\u72B6\u6001\u6D41\u8F6C\uFF0C\u4F9D\u8D56 workitem \u7684 work-item-id",
1492
+ subtask: "\u5B50\u4EFB\u52A1 \u2014 \u8282\u70B9\u4E0B\u7684\u5B50\u4EFB\u52A1\u64CD\u4F5C\uFF0C\u4F9D\u8D56 workflow \u7684 node-id",
1493
+ comment: "\u8BC4\u8BBA\u57DF \u2014 \u8DE8\u5B9E\u4F53\u8BC4\u8BBA\uFF0C\u5F53\u524D\u901A\u8FC7 work-item-id \u5173\u8054",
1494
+ workhour: "\u5DE5\u65F6\u57DF \u2014 \u5DE5\u65F6\u8BB0\u5F55\u4E0E\u6392\u671F\uFF0C\u4F9D\u8D56 workitem \u7684 work-item-id",
1495
+ relation: "\u5173\u7CFB\u57DF \u2014 \u5DE5\u4F5C\u9879\u4E4B\u95F4\u7684\u5173\u8054\u5173\u7CFB\u5B9A\u4E49\u4E0E\u67E5\u8BE2",
1496
+ mywork: "\u5DE5\u4F5C\u53F0\u57DF \u2014 \u8DE8\u7A7A\u95F4\u7684\u4E2A\u4EBA\u5F85\u529E/\u5DF2\u529E",
1497
+ view: "\u89C6\u56FE\u57DF \u2014 \u89C6\u56FE\u7684\u521B\u5EFA\u3001\u67E5\u770B\u3001\u66F4\u65B0\u3001\u641C\u7D22",
1498
+ chart: "\u5EA6\u91CF\u57DF \u2014 \u56FE\u8868\u67E5\u770B\u4E0E\u5217\u8868\uFF0C\u4F9D\u8D56 view \u7684 view-id",
1499
+ team: "\u4EBA\u5458\u57DF \u2014 \u56E2\u961F\u5217\u8868\u4E0E\u6210\u5458\u67E5\u8BE2",
1500
+ user: "\u4EBA\u5458\u57DF \u2014 \u7528\u6237\u641C\u7D22\uFF0C\u652F\u6301\u540D\u79F0\u6A21\u7CCA\u5339\u914D",
1501
+ project: "\u7A7A\u95F4\u57DF \u2014 \u7A7A\u95F4\u4FE1\u606F\u641C\u7D22"
1502
+ };
1360
1503
  var registeredCommands = [];
1361
1504
  function registerDynamicCommands(program, commands, client) {
1362
1505
  registeredCommands = commands;
@@ -1367,7 +1510,7 @@ function registerDynamicCommands(program, commands, client) {
1367
1510
  groups.set(cmd.resource, list);
1368
1511
  }
1369
1512
  for (const [resource, cmds] of groups) {
1370
- const group = program.command(resource).description(`${resource} \u64CD\u4F5C`);
1513
+ const group = program.command(resource).description(RESOURCE_DESCRIPTIONS[resource] ?? `${resource} \u64CD\u4F5C`);
1371
1514
  for (const cmd of cmds) {
1372
1515
  const flagSummary = buildFlagSummary(cmd);
1373
1516
  const sub = group.command(cmd.method).description(`${cmd.description ?? ""} ${flagSummary}`);
@@ -1375,22 +1518,25 @@ function registerDynamicCommands(program, commands, client) {
1375
1518
  if (param.name === "url") continue;
1376
1519
  const kebab = param.name.replace(/_/g, "-");
1377
1520
  let desc = param.description ?? "";
1378
- if (param.type === "array") desc += "\uFF08\u591A\u4E2A\u503C\u7528\u9017\u53F7\u5206\u9694\uFF09";
1379
- if (param.type === "object") desc += "\uFF08JSON \u683C\u5F0F\uFF09";
1380
- if (param.type === "boolean") {
1381
- const flag = `--${kebab}`;
1382
- if (param.required) {
1383
- sub.requiredOption(flag, desc);
1521
+ if (param.required) desc = `[\u5FC5\u586B] ${desc}`;
1522
+ if (param.type === "array") {
1523
+ const itemType = param.items?.type;
1524
+ if (itemType === "object") {
1525
+ desc += "\uFF08\u591A\u4E2A JSON \u5BF9\u8C61\uFF0C\u91CD\u590D\u4F20\u5165\uFF09";
1526
+ } else if (itemType === "number") {
1527
+ desc += "\uFF08\u591A\u4E2A\u6570\u5B57\uFF0C\u9017\u53F7\u5206\u9694\u6216\u91CD\u590D\u4F20\u5165\uFF09";
1384
1528
  } else {
1385
- sub.option(flag, desc);
1529
+ desc += "\uFF08\u591A\u4E2A\u503C\uFF0C\u9017\u53F7\u5206\u9694\u6216\u91CD\u590D\u4F20\u5165\uFF09";
1386
1530
  }
1531
+ }
1532
+ if (param.type === "object") desc += "\uFF08JSON \u683C\u5F0F\uFF09";
1533
+ const typeLabel = formatTypeLabel(param);
1534
+ if (param.type === "boolean") {
1535
+ sub.option(`--${kebab}`, desc);
1536
+ } else if (param.type === "array") {
1537
+ sub.option(`--${kebab} <${typeLabel}>`, desc, collect, []);
1387
1538
  } else {
1388
- const flag = `--${kebab} <value>`;
1389
- if (param.required) {
1390
- sub.requiredOption(flag, desc);
1391
- } else {
1392
- sub.option(flag, desc);
1393
- }
1539
+ sub.option(`--${kebab} <${typeLabel}>`, desc);
1394
1540
  }
1395
1541
  }
1396
1542
  sub.option("--params <json>", "\u5B8C\u6574 JSON \u53C2\u6570\uFF08--set \u548C flag \u4F1A\u8986\u76D6\u540C\u540D\u5B57\u6BB5\uFF09");
@@ -1404,10 +1550,23 @@ function registerDynamicCommands(program, commands, client) {
1404
1550
  const camelKey = snakeToCamel(param.name);
1405
1551
  const value = cliFlags[camelKey];
1406
1552
  if (value === void 0) continue;
1407
- if (param.type === "array" && typeof value === "string") {
1408
- snakeFlags[param.name] = value.split(",").map((s) => s.trim());
1553
+ if (param.type === "array") {
1554
+ const arr = Array.isArray(value) ? value : [value];
1555
+ const itemType = param.items?.type;
1556
+ const expanded = itemType === "object" ? arr : arr.flatMap((s) => s.split(",").map((v) => v.trim()));
1557
+ const filtered = expanded.filter((s) => s !== "");
1558
+ if (filtered.length > 0) {
1559
+ snakeFlags[param.name] = convertArrayItems(filtered, itemType);
1560
+ }
1409
1561
  } else if (param.type === "number" && typeof value === "string") {
1410
- snakeFlags[param.name] = Number(value);
1562
+ const num = Number(value);
1563
+ if (Number.isNaN(num)) {
1564
+ throw new ClientError(
1565
+ `\u53C2\u6570 --${param.name.replace(/_/g, "-")} \u9700\u8981\u6570\u5B57\uFF0C\u6536\u5230: "${value}"`,
1566
+ "CLIENT_INVALID_TYPE"
1567
+ );
1568
+ }
1569
+ snakeFlags[param.name] = num;
1411
1570
  } else {
1412
1571
  snakeFlags[param.name] = value;
1413
1572
  }
@@ -1415,6 +1574,14 @@ function registerDynamicCommands(program, commands, client) {
1415
1574
  const setFields = (rawSet ?? []).map(parseSetFlag);
1416
1575
  const parsedParams = rawParams ? parseParams(rawParams) : void 0;
1417
1576
  const finalParams = mergeParams(setFields, parsedParams, snakeFlags);
1577
+ const missing = cmd.parameters.filter((p) => p.required && p.name !== "url").filter((p) => finalParams[p.name] === void 0 || finalParams[p.name] === null).map((p) => `--${p.name.replace(/_/g, "-")}`);
1578
+ if (missing.length > 0) {
1579
+ throw new ClientError(
1580
+ `\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${missing.join(", ")}`,
1581
+ "CLIENT_MISSING_REQUIRED",
1582
+ `\u4F7F\u7528 meegle inspect ${cmd.resource}.${cmd.method} \u67E5\u770B\u53C2\u6570\u8BE6\u60C5`
1583
+ );
1584
+ }
1418
1585
  const raw = await client.callTool(cmd.toolName, finalParams);
1419
1586
  let result = unwrapResponse(raw);
1420
1587
  if (result !== void 0) {
@@ -1434,8 +1601,41 @@ function registerDynamicCommands(program, commands, client) {
1434
1601
  function collect(value, previous) {
1435
1602
  return [...previous, value];
1436
1603
  }
1604
+ function formatTypeLabel(param) {
1605
+ if (param.type === "array" && param.items?.type) {
1606
+ return `array<${param.items.type}>`;
1607
+ }
1608
+ return param.type;
1609
+ }
1610
+ function convertArrayItems(values, itemType) {
1611
+ if (itemType === "number") {
1612
+ return values.map((v) => {
1613
+ const n = Number(v);
1614
+ if (Number.isNaN(n)) {
1615
+ throw new ClientError(
1616
+ `\u6570\u7EC4\u5143\u7D20\u9700\u8981\u6570\u5B57\uFF0C\u6536\u5230: "${v}"`,
1617
+ "CLIENT_INVALID_TYPE"
1618
+ );
1619
+ }
1620
+ return n;
1621
+ });
1622
+ }
1623
+ if (itemType === "object") {
1624
+ return values.map((v) => {
1625
+ try {
1626
+ return JSON.parse(v);
1627
+ } catch {
1628
+ throw new ClientError(
1629
+ `\u65E0\u6CD5\u89E3\u6790 JSON: ${v}`,
1630
+ "CLIENT_INVALID_JSON"
1631
+ );
1632
+ }
1633
+ });
1634
+ }
1635
+ return values;
1636
+ }
1437
1637
  function snakeToCamel(s) {
1438
- return s.replace(/[_-](\w)/g, (_, c) => c.toUpperCase());
1638
+ return s.replace(/_(\w)/g, (_, c) => c.toUpperCase());
1439
1639
  }
1440
1640
  function buildFlagSummary(cmd) {
1441
1641
  const flags = [];
@@ -1476,7 +1676,7 @@ function registerInspectCommand(program) {
1476
1676
  }
1477
1677
  const params = cmd.parameters.filter((p) => p.name !== "url").map((p) => ({
1478
1678
  flag: `--${p.name.replace(/_/g, "-")}`,
1479
- type: p.type,
1679
+ type: formatTypeLabel(p),
1480
1680
  required: p.required ?? false,
1481
1681
  description: p.description ?? ""
1482
1682
  }));
@@ -1526,62 +1726,52 @@ function registerStaticCommands(program) {
1526
1726
  registerInspectCommand(program);
1527
1727
  }
1528
1728
 
1529
- // src/helpers/todo.ts
1530
- function registerTodo(program) {
1531
- program.command("+todo").description("\u67E5\u770B\u672C\u5468\u4E2A\u4EBA\u5F85\u529E").option("--project-key <key>", "\u9879\u76EE\u6807\u8BC6").action(async (options) => {
1532
- console.log("Fetching todos...", options.projectKey ?? "(all projects)");
1533
- });
1534
- }
1535
-
1536
- // src/helpers/index.ts
1537
- function registerHelperCommands(program) {
1538
- registerTodo(program);
1539
- }
1540
-
1541
1729
  // src/dynamic/mapper.ts
1542
1730
  var FALLBACK_TABLE = {
1543
- // workitem (14)
1731
+ // WorkItem 工作项域 (9)
1544
1732
  create_workitem: { resource: "workitem", method: "create", hasFields: true },
1545
- get_workitem_brief: { resource: "workitem", method: "brief" },
1546
- get_workitem_field_meta: { resource: "workitem", method: "field-meta" },
1547
- get_workitem_man_hour_records: { resource: "workitem", method: "man-hours" },
1548
- get_workitem_op_record: { resource: "workitem", method: "op-record" },
1549
- update_field: { resource: "workitem", method: "update-field", hasFields: true },
1550
- list_todo: { resource: "workitem", method: "todo" },
1551
- list_workitem_comments: { resource: "workitem", method: "comments" },
1552
- list_workitem_field_config: { resource: "workitem", method: "field-config" },
1553
- list_workitem_relations: { resource: "workitem", method: "relations" },
1554
- list_workitem_role_config: { resource: "workitem", method: "role-config" },
1555
- list_workitem_types: { resource: "workitem", method: "types" },
1556
- list_related_workitems: { resource: "workitem", method: "related" },
1557
- add_comment: { resource: "workitem", method: "add-comment" },
1558
- // node (7)
1559
- get_node_detail: { resource: "node", method: "detail" },
1560
- transition_node: { resource: "node", method: "transition" },
1561
- update_node: { resource: "node", method: "update", hasFields: true },
1562
- update_node_subtask: { resource: "node", method: "update-subtask", hasFields: true },
1563
- list_node_field_config: { resource: "node", method: "field-config" },
1564
- get_transitable_states: { resource: "node", method: "transitable-states" },
1565
- get_transition_required: { resource: "node", method: "transition-required" },
1566
- // view (4)
1567
- get_view_detail: { resource: "view", method: "detail" },
1568
- create_fixed_view: { resource: "view", method: "create" },
1569
- update_fixed_view: { resource: "view", method: "update" },
1733
+ get_workitem_brief: { resource: "workitem", method: "get" },
1734
+ update_field: { resource: "workitem", method: "update", hasFields: true },
1735
+ search_by_mql: { resource: "workitem", method: "query" },
1736
+ get_workitem_op_record: { resource: "workitem", method: "list-op-records" },
1737
+ list_workitem_types: { resource: "workitem", method: "meta-types" },
1738
+ get_workitem_field_meta: { resource: "workitem", method: "meta-create-fields" },
1739
+ list_workitem_field_config: { resource: "workitem", method: "meta-fields" },
1740
+ list_workitem_role_config: { resource: "workitem", method: "meta-roles" },
1741
+ // WorkFlow 工作流域 (6)
1742
+ transition_node: { resource: "workflow", method: "transition" },
1743
+ get_node_detail: { resource: "workflow", method: "get-node" },
1744
+ update_node: { resource: "workflow", method: "update-node", hasFields: true },
1745
+ list_node_field_config: { resource: "workflow", method: "meta-node-fields" },
1746
+ get_transitable_states: { resource: "workflow", method: "list-state-transitions" },
1747
+ get_transition_required: { resource: "workflow", method: "list-state-required" },
1748
+ // SubTask 子任务 (1)
1749
+ update_node_subtask: { resource: "subtask", method: "update", hasFields: true },
1750
+ // Comment 评论域 (2)
1751
+ add_comment: { resource: "comment", method: "add" },
1752
+ list_workitem_comments: { resource: "comment", method: "list" },
1753
+ // WorkHour 工时域 (2)
1754
+ get_workitem_man_hour_records: { resource: "workhour", method: "list-records" },
1755
+ list_schedule: { resource: "workhour", method: "list-schedule" },
1756
+ // Relation 关系域 (2)
1757
+ list_workitem_relations: { resource: "relation", method: "meta-definitions" },
1758
+ list_related_workitems: { resource: "relation", method: "list" },
1759
+ // MyWork 工作台域 (1)
1760
+ list_todo: { resource: "mywork", method: "todo" },
1761
+ // View 视图域 (4)
1762
+ create_fixed_view: { resource: "view", method: "create-fixed" },
1763
+ get_view_detail: { resource: "view", method: "get" },
1764
+ update_fixed_view: { resource: "view", method: "update-fixed" },
1570
1765
  search_view_by_title: { resource: "view", method: "search" },
1571
- // chart (2)
1572
- get_chart_detail: { resource: "chart", method: "detail" },
1766
+ // Chart 度量域 (2)
1767
+ get_chart_detail: { resource: "chart", method: "get" },
1573
1768
  list_charts: { resource: "chart", method: "list" },
1574
- // team (2)
1769
+ // UserGroup 人员域 (3)
1575
1770
  list_project_team: { resource: "team", method: "list" },
1576
- list_team_members: { resource: "team", method: "members" },
1577
- // search → workitem
1578
- search_by_mql: { resource: "workitem", method: "search" },
1579
- // schedule (1)
1580
- list_schedule: { resource: "schedule", method: "list" },
1581
- // project (1)
1582
- search_project_info: { resource: "project", method: "info" },
1583
- // user (1)
1584
- search_user_info: { resource: "user", method: "info" }
1771
+ list_team_members: { resource: "team", method: "list-members" },
1772
+ search_user_info: { resource: "user", method: "search" },
1773
+ // Project 空间域 (1)
1774
+ search_project_info: { resource: "project", method: "search" }
1585
1775
  };
1586
1776
  function mapTool(tool) {
1587
1777
  if (tool.metadata?.resource && tool.metadata?.method) {
@@ -1604,6 +1794,7 @@ function mapTool(tool) {
1604
1794
  hasFields: fallback.hasFields
1605
1795
  };
1606
1796
  }
1797
+ logger.debug(`\u672A\u77E5 tool "${tool.name}" \u4E0D\u5728\u6620\u5C04\u8868\u4E2D\uFF0C\u4F7F\u7528 name parsing \u56DE\u9000`);
1607
1798
  const parts = tool.name.split("_");
1608
1799
  const resource = parts[0];
1609
1800
  const method = parts.slice(1).join("-");
@@ -1620,6 +1811,7 @@ function mapTools(tools) {
1620
1811
  }
1621
1812
 
1622
1813
  // src/dynamic/cache.ts
1814
+ init_profile_validation();
1623
1815
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1624
1816
  import { join as join3, dirname } from "path";
1625
1817
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -1627,6 +1819,7 @@ var ToolCache = class {
1627
1819
  filePath;
1628
1820
  ttlMs;
1629
1821
  constructor(cacheDir, profile = "default", ttlMs = DEFAULT_TTL_MS) {
1822
+ validateProfileName(profile);
1630
1823
  const filename = profile === "default" ? "tools.json" : `tools-${profile}.json`;
1631
1824
  this.filePath = join3(cacheDir, filename);
1632
1825
  this.ttlMs = ttlMs;
@@ -1635,6 +1828,9 @@ var ToolCache = class {
1635
1828
  try {
1636
1829
  const raw = await readFile3(this.filePath, "utf-8");
1637
1830
  const data = JSON.parse(raw);
1831
+ if (!Array.isArray(data.tools) || typeof data.timestamp !== "number") {
1832
+ return null;
1833
+ }
1638
1834
  const stale = Date.now() - data.timestamp > this.ttlMs;
1639
1835
  return { tools: data.tools, stale };
1640
1836
  } catch {
@@ -1659,12 +1855,16 @@ function convertTool(entry) {
1659
1855
  const props = entry.inputSchema?.properties ?? {};
1660
1856
  const required = new Set(entry.inputSchema?.required ?? []);
1661
1857
  for (const [name, schema] of Object.entries(props)) {
1662
- parameters.push({
1858
+ const param = {
1663
1859
  name,
1664
1860
  type: schema.type ?? "string",
1665
1861
  description: schema.description,
1666
1862
  required: required.has(name)
1667
- });
1863
+ };
1864
+ if (schema.type === "array" && schema.items?.type) {
1865
+ param.items = { type: schema.items.type };
1866
+ }
1867
+ parameters.push(param);
1668
1868
  }
1669
1869
  return {
1670
1870
  name: entry.name,
@@ -1675,13 +1875,15 @@ function convertTool(entry) {
1675
1875
 
1676
1876
  // src/cli.ts
1677
1877
  init_config();
1878
+ init_errors();
1678
1879
 
1679
1880
  // src/core/mcp-client.ts
1680
- var requestId = 0;
1881
+ init_errors();
1681
1882
  var McpClient = class {
1682
1883
  baseUrl;
1683
1884
  tokenManager;
1684
1885
  customHeaders;
1886
+ requestId = 0;
1685
1887
  constructor(baseUrl, tokenManager, customHeaders) {
1686
1888
  this.baseUrl = baseUrl;
1687
1889
  this.tokenManager = tokenManager;
@@ -1691,7 +1893,7 @@ var McpClient = class {
1691
1893
  try {
1692
1894
  return await this._doCall(method, params);
1693
1895
  } catch (err) {
1694
- if (err instanceof ServerError && err.message.includes("401")) {
1896
+ if (err instanceof HttpError && err.httpStatus === 401) {
1695
1897
  const store = this.tokenManager.getStore();
1696
1898
  const tokenData = await store.load();
1697
1899
  if (tokenData) {
@@ -1707,10 +1909,11 @@ var McpClient = class {
1707
1909
  }
1708
1910
  async _doCall(method, params) {
1709
1911
  const token = await this.tokenManager.getToken();
1710
- const id = ++requestId;
1912
+ const id = ++this.requestId;
1711
1913
  const body = { jsonrpc: "2.0", id, method, params };
1712
1914
  const headers = {
1713
1915
  "Content-Type": "application/json",
1916
+ "User-Agent": "meegle-cli",
1714
1917
  ...this.customHeaders
1715
1918
  };
1716
1919
  if (token) {
@@ -1722,9 +1925,10 @@ var McpClient = class {
1722
1925
  body: JSON.stringify(body)
1723
1926
  });
1724
1927
  if (!response.ok) {
1725
- throw new ServerError(
1928
+ throw new HttpError(
1726
1929
  `\u670D\u52A1\u7AEF\u8FD4\u56DE\u9519\u8BEF (${response.status})`,
1727
- "SERVER_HTTP_ERROR"
1930
+ "SERVER_HTTP_ERROR",
1931
+ response.status
1728
1932
  );
1729
1933
  }
1730
1934
  const data = await response.json();
@@ -1771,23 +1975,53 @@ function getCommandChain(cmd) {
1771
1975
  }
1772
1976
  return chain;
1773
1977
  }
1978
+ function extractProfileFromArgv(argv) {
1979
+ for (let i = 0; i < argv.length; i++) {
1980
+ if (argv[i] === "--profile" && i + 1 < argv.length) {
1981
+ return argv[i + 1];
1982
+ }
1983
+ if (argv[i]?.startsWith("--profile=")) {
1984
+ return argv[i].slice("--profile=".length);
1985
+ }
1986
+ }
1987
+ return void 0;
1988
+ }
1774
1989
  async function createProgram() {
1775
1990
  const program = new Command();
1776
- program.name("meegle").description("Agent-First CLI for Meegle (Lark Project)").version(version).option("--format <format>", "\u8F93\u51FA\u683C\u5F0F: json | table | ndjson", "json").option("--select <props>", "\u9009\u53D6\u8F93\u51FA\u5C5E\u6027\uFF0C\u9017\u53F7\u5206\u9694").option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7").option("--profile <name>", "\u4F7F\u7528\u6307\u5B9A profile\uFF08\u4E34\u65F6\uFF0C\u4E0D\u6539\u53D8\u5168\u5C40\uFF09").hook("preAction", async (thisCommand, actionCommand) => {
1991
+ program.name("meegle").description(`Agent-First CLI for Meegle (Lark Project)
1992
+
1993
+ \u9886\u57DF\u6A21\u578B:
1994
+ workitem\uFF08\u5DE5\u4F5C\u9879\uFF09\u2500 \u6838\u5FC3\u5B9E\u4F53\uFF0C\u901A\u8FC7 work-item-id \u6807\u8BC6
1995
+ \u251C\u2500 workflow\uFF08\u5DE5\u4F5C\u6D41\uFF09\u2500 \u8282\u70B9\u6D41\u8F6C\u4E0E\u72B6\u6001\u6D41\u8F6C\uFF0C\u4F9D\u8D56 work-item-id
1996
+ \u2502 \u2514\u2500 subtask\uFF08\u5B50\u4EFB\u52A1\uFF09\u2500 \u8282\u70B9\u4E0B\u7684\u5B50\u4EFB\u52A1\uFF0C\u4F9D\u8D56 node-id
1997
+ \u251C\u2500 comment\uFF08\u8BC4\u8BBA\uFF09\u2500 \u5DE5\u4F5C\u9879\u7684\u8BC4\u8BBA\uFF0C\u4F9D\u8D56 work-item-id
1998
+ \u251C\u2500 workhour\uFF08\u5DE5\u65F6\uFF09\u2500 \u5DE5\u65F6\u8BB0\u5F55\u4E0E\u6392\u671F\uFF0C\u4F9D\u8D56 work-item-id
1999
+ \u2514\u2500 relation\uFF08\u5173\u7CFB\uFF09\u2500 \u5DE5\u4F5C\u9879\u4E4B\u95F4\u7684\u5173\u8054\u5173\u7CFB
2000
+
2001
+ mywork\uFF08\u5DE5\u4F5C\u53F0\uFF09\u2500 \u8DE8\u7A7A\u95F4\u7684\u4E2A\u4EBA\u5F85\u529E/\u5DF2\u529E
2002
+ view\uFF08\u89C6\u56FE\uFF09\u2500 \u56FA\u5B9A\u89C6\u56FE\u3001\u7B5B\u9009\u89C6\u56FE
2003
+ chart\uFF08\u5EA6\u91CF\uFF09\u2500 \u89C6\u56FE\u4E0B\u7684\u56FE\u8868\uFF0C\u4F9D\u8D56 view-id
2004
+ team / user\uFF08\u4EBA\u5458\uFF09\u2500 \u56E2\u961F\u4E0E\u7528\u6237
2005
+ project\uFF08\u7A7A\u95F4\uFF09\u2500 \u7A7A\u95F4\u4FE1\u606F
2006
+
2007
+ \u5178\u578B\u64CD\u4F5C\u94FE\u8DEF:
2008
+ \u521B\u5EFA\u5DE5\u4F5C\u9879: workitem meta-types \u2192 workitem meta-create-fields \u2192 workitem create
2009
+ \u6D41\u8F6C\u8282\u70B9: workflow get-node \u2192 workflow list-state-transitions \u2192 workflow transition
2010
+ \u67E5\u770B\u5173\u8054: relation meta-definitions \u2192 relation list`).version(version).option("--format <format>", "\u8F93\u51FA\u683C\u5F0F: json | table | ndjson", "json").option("--select <props>", "\u9009\u53D6\u8F93\u51FA\u5C5E\u6027\uFF0C\u9017\u53F7\u5206\u9694").option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7").option("--profile <name>", "\u4F7F\u7528\u6307\u5B9A profile\uFF08\u4E34\u65F6\uFF0C\u4E0D\u6539\u53D8\u5168\u5C40\uFF09").hook("preAction", async (thisCommand, actionCommand) => {
1777
2011
  const opts = thisCommand.opts();
1778
2012
  if (opts.verbose) {
1779
2013
  setLogLevel("debug");
1780
2014
  }
1781
2015
  const chain = getCommandChain(actionCommand);
1782
2016
  const topCmd = chain[0] ?? "";
1783
- const profileName = thisCommand.opts().profile;
2017
+ const profileName2 = thisCommand.opts().profile;
1784
2018
  if (!SKIP_FIRST_RUN.includes(topCmd)) {
1785
- await checkFirstRun(profileName);
2019
+ await checkFirstRun(profileName2);
1786
2020
  }
1787
2021
  });
1788
2022
  registerStaticCommands(program);
2023
+ const profileName = extractProfileFromArgv(process.argv);
1789
2024
  try {
1790
- const profileName = program.opts().profile;
1791
2025
  const config = await loadConfig(profileName);
1792
2026
  const serverUrl = getServerUrl(config);
1793
2027
  const currentProfile = profileName ?? await getCurrentProfileName();
@@ -1819,7 +2053,6 @@ async function createProgram() {
1819
2053
  } catch (err) {
1820
2054
  logger.debug(`\u547D\u4EE4\u52A0\u8F7D\u8DF3\u8FC7: ${err}`);
1821
2055
  }
1822
- registerHelperCommands(program);
1823
2056
  return program;
1824
2057
  }
1825
2058
  async function run() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-project/meegle",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "Agent-First CLI for Meegle (Lark Project)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "dist"
14
14
  ],
15
15
  "publishConfig": {
16
- "tag": "dev"
16
+ "tag": "beta"
17
17
  },
18
18
  "scripts": {
19
19
  "build": "tsup",