@lark-project/meegle 0.0.2 → 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 +337 -133
  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) => {
@@ -878,6 +965,9 @@ async function checkFirstRun(profileName) {
878
965
  process.exit(0);
879
966
  }
880
967
 
968
+ // src/commands/auth/login.ts
969
+ init_errors();
970
+
881
971
  // src/output/table.ts
882
972
  import Table from "cli-table3";
883
973
  function renderTable(data) {
@@ -1031,9 +1121,12 @@ function registerLogin(auth) {
1031
1121
  }
1032
1122
 
1033
1123
  // src/commands/auth/logout.ts
1124
+ init_config();
1034
1125
  function registerLogout(auth) {
1035
1126
  auth.command("logout").description("\u9000\u51FA\u767B\u5F55\uFF0C\u6E05\u9664\u672C\u5730\u51ED\u636E").action(async () => {
1036
- const store = await createTokenStore();
1127
+ const profileName = auth.parent?.opts().profile;
1128
+ const profile = profileName ?? await getCurrentProfileName();
1129
+ const store = await createTokenStore(profile);
1037
1130
  await store.clear();
1038
1131
  console.log("\u2713 \u5DF2\u9000\u51FA\u767B\u5F55");
1039
1132
  });
@@ -1244,6 +1337,7 @@ function registerConfigCommands(program) {
1244
1337
  }
1245
1338
 
1246
1339
  // src/dynamic/params-parser.ts
1340
+ init_errors();
1247
1341
  function parseSetFlag(input4) {
1248
1342
  const eqIndex = input4.indexOf("=");
1249
1343
  if (eqIndex === -1) {
@@ -1256,7 +1350,8 @@ function parseSetFlag(input4) {
1256
1350
  const rawValue = input4.slice(eqIndex + 1);
1257
1351
  let value;
1258
1352
  try {
1259
- value = JSON.parse(rawValue);
1353
+ const parsed = JSON.parse(rawValue);
1354
+ value = typeof parsed === "object" && parsed !== null ? parsed : rawValue;
1260
1355
  } catch {
1261
1356
  value = rawValue;
1262
1357
  }
@@ -1332,6 +1427,9 @@ function resolveDotPath(current, segments) {
1332
1427
  return current;
1333
1428
  }
1334
1429
 
1430
+ // src/dynamic/response.ts
1431
+ init_errors();
1432
+
1335
1433
  // src/core/logger.ts
1336
1434
  import chalk from "chalk";
1337
1435
  var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
@@ -1363,6 +1461,11 @@ function unwrapResponse(raw) {
1363
1461
  const response = raw;
1364
1462
  const content = response.content;
1365
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
+ }
1366
1469
  const dataTexts = [];
1367
1470
  for (const entry of content) {
1368
1471
  if (entry.type !== "text" || !entry.text) continue;
@@ -1372,10 +1475,6 @@ function unwrapResponse(raw) {
1372
1475
  dataTexts.push(entry.text);
1373
1476
  }
1374
1477
  }
1375
- if (response.isError) {
1376
- const message = dataTexts.join("\n") || "\u670D\u52A1\u7AEF\u8FD4\u56DE\u9519\u8BEF";
1377
- throw new ServerError(message, "SERVER_CALL_FAILED");
1378
- }
1379
1478
  if (dataTexts.length === 0) return void 0;
1380
1479
  const text = dataTexts[0];
1381
1480
  try {
@@ -1386,6 +1485,21 @@ function unwrapResponse(raw) {
1386
1485
  }
1387
1486
 
1388
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
+ };
1389
1503
  var registeredCommands = [];
1390
1504
  function registerDynamicCommands(program, commands, client) {
1391
1505
  registeredCommands = commands;
@@ -1396,7 +1510,7 @@ function registerDynamicCommands(program, commands, client) {
1396
1510
  groups.set(cmd.resource, list);
1397
1511
  }
1398
1512
  for (const [resource, cmds] of groups) {
1399
- const group = program.command(resource).description(`${resource} \u64CD\u4F5C`);
1513
+ const group = program.command(resource).description(RESOURCE_DESCRIPTIONS[resource] ?? `${resource} \u64CD\u4F5C`);
1400
1514
  for (const cmd of cmds) {
1401
1515
  const flagSummary = buildFlagSummary(cmd);
1402
1516
  const sub = group.command(cmd.method).description(`${cmd.description ?? ""} ${flagSummary}`);
@@ -1404,22 +1518,25 @@ function registerDynamicCommands(program, commands, client) {
1404
1518
  if (param.name === "url") continue;
1405
1519
  const kebab = param.name.replace(/_/g, "-");
1406
1520
  let desc = param.description ?? "";
1407
- if (param.type === "array") desc += "\uFF08\u591A\u4E2A\u503C\u7528\u9017\u53F7\u5206\u9694\uFF09";
1408
- if (param.type === "object") desc += "\uFF08JSON \u683C\u5F0F\uFF09";
1409
- if (param.type === "boolean") {
1410
- const flag = `--${kebab}`;
1411
- if (param.required) {
1412
- 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";
1413
1528
  } else {
1414
- sub.option(flag, desc);
1529
+ desc += "\uFF08\u591A\u4E2A\u503C\uFF0C\u9017\u53F7\u5206\u9694\u6216\u91CD\u590D\u4F20\u5165\uFF09";
1415
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, []);
1416
1538
  } else {
1417
- const flag = `--${kebab} <value>`;
1418
- if (param.required) {
1419
- sub.requiredOption(flag, desc);
1420
- } else {
1421
- sub.option(flag, desc);
1422
- }
1539
+ sub.option(`--${kebab} <${typeLabel}>`, desc);
1423
1540
  }
1424
1541
  }
1425
1542
  sub.option("--params <json>", "\u5B8C\u6574 JSON \u53C2\u6570\uFF08--set \u548C flag \u4F1A\u8986\u76D6\u540C\u540D\u5B57\u6BB5\uFF09");
@@ -1433,10 +1550,23 @@ function registerDynamicCommands(program, commands, client) {
1433
1550
  const camelKey = snakeToCamel(param.name);
1434
1551
  const value = cliFlags[camelKey];
1435
1552
  if (value === void 0) continue;
1436
- if (param.type === "array" && typeof value === "string") {
1437
- 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
+ }
1438
1561
  } else if (param.type === "number" && typeof value === "string") {
1439
- 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;
1440
1570
  } else {
1441
1571
  snakeFlags[param.name] = value;
1442
1572
  }
@@ -1444,6 +1574,14 @@ function registerDynamicCommands(program, commands, client) {
1444
1574
  const setFields = (rawSet ?? []).map(parseSetFlag);
1445
1575
  const parsedParams = rawParams ? parseParams(rawParams) : void 0;
1446
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
+ }
1447
1585
  const raw = await client.callTool(cmd.toolName, finalParams);
1448
1586
  let result = unwrapResponse(raw);
1449
1587
  if (result !== void 0) {
@@ -1463,8 +1601,41 @@ function registerDynamicCommands(program, commands, client) {
1463
1601
  function collect(value, previous) {
1464
1602
  return [...previous, value];
1465
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
+ }
1466
1637
  function snakeToCamel(s) {
1467
- return s.replace(/[_-](\w)/g, (_, c) => c.toUpperCase());
1638
+ return s.replace(/_(\w)/g, (_, c) => c.toUpperCase());
1468
1639
  }
1469
1640
  function buildFlagSummary(cmd) {
1470
1641
  const flags = [];
@@ -1505,7 +1676,7 @@ function registerInspectCommand(program) {
1505
1676
  }
1506
1677
  const params = cmd.parameters.filter((p) => p.name !== "url").map((p) => ({
1507
1678
  flag: `--${p.name.replace(/_/g, "-")}`,
1508
- type: p.type,
1679
+ type: formatTypeLabel(p),
1509
1680
  required: p.required ?? false,
1510
1681
  description: p.description ?? ""
1511
1682
  }));
@@ -1555,62 +1726,52 @@ function registerStaticCommands(program) {
1555
1726
  registerInspectCommand(program);
1556
1727
  }
1557
1728
 
1558
- // src/helpers/todo.ts
1559
- function registerTodo(program) {
1560
- program.command("+todo").description("\u67E5\u770B\u672C\u5468\u4E2A\u4EBA\u5F85\u529E").option("--project-key <key>", "\u9879\u76EE\u6807\u8BC6").action(async (options) => {
1561
- console.log("Fetching todos...", options.projectKey ?? "(all projects)");
1562
- });
1563
- }
1564
-
1565
- // src/helpers/index.ts
1566
- function registerHelperCommands(program) {
1567
- registerTodo(program);
1568
- }
1569
-
1570
1729
  // src/dynamic/mapper.ts
1571
1730
  var FALLBACK_TABLE = {
1572
- // workitem (14)
1731
+ // WorkItem 工作项域 (9)
1573
1732
  create_workitem: { resource: "workitem", method: "create", hasFields: true },
1574
- get_workitem_brief: { resource: "workitem", method: "brief" },
1575
- get_workitem_field_meta: { resource: "workitem", method: "field-meta" },
1576
- get_workitem_man_hour_records: { resource: "workitem", method: "man-hours" },
1577
- get_workitem_op_record: { resource: "workitem", method: "op-record" },
1578
- update_field: { resource: "workitem", method: "update-field", hasFields: true },
1579
- list_todo: { resource: "workitem", method: "todo" },
1580
- list_workitem_comments: { resource: "workitem", method: "comments" },
1581
- list_workitem_field_config: { resource: "workitem", method: "field-config" },
1582
- list_workitem_relations: { resource: "workitem", method: "relations" },
1583
- list_workitem_role_config: { resource: "workitem", method: "role-config" },
1584
- list_workitem_types: { resource: "workitem", method: "types" },
1585
- list_related_workitems: { resource: "workitem", method: "related" },
1586
- add_comment: { resource: "workitem", method: "add-comment" },
1587
- // node (7)
1588
- get_node_detail: { resource: "node", method: "detail" },
1589
- transition_node: { resource: "node", method: "transition" },
1590
- update_node: { resource: "node", method: "update", hasFields: true },
1591
- update_node_subtask: { resource: "node", method: "update-subtask", hasFields: true },
1592
- list_node_field_config: { resource: "node", method: "field-config" },
1593
- get_transitable_states: { resource: "node", method: "transitable-states" },
1594
- get_transition_required: { resource: "node", method: "transition-required" },
1595
- // view (4)
1596
- get_view_detail: { resource: "view", method: "detail" },
1597
- create_fixed_view: { resource: "view", method: "create" },
1598
- 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" },
1599
1765
  search_view_by_title: { resource: "view", method: "search" },
1600
- // chart (2)
1601
- get_chart_detail: { resource: "chart", method: "detail" },
1766
+ // Chart 度量域 (2)
1767
+ get_chart_detail: { resource: "chart", method: "get" },
1602
1768
  list_charts: { resource: "chart", method: "list" },
1603
- // team (2)
1769
+ // UserGroup 人员域 (3)
1604
1770
  list_project_team: { resource: "team", method: "list" },
1605
- list_team_members: { resource: "team", method: "members" },
1606
- // search → workitem
1607
- search_by_mql: { resource: "workitem", method: "search" },
1608
- // schedule (1)
1609
- list_schedule: { resource: "schedule", method: "list" },
1610
- // project (1)
1611
- search_project_info: { resource: "project", method: "info" },
1612
- // user (1)
1613
- 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" }
1614
1775
  };
1615
1776
  function mapTool(tool) {
1616
1777
  if (tool.metadata?.resource && tool.metadata?.method) {
@@ -1633,6 +1794,7 @@ function mapTool(tool) {
1633
1794
  hasFields: fallback.hasFields
1634
1795
  };
1635
1796
  }
1797
+ logger.debug(`\u672A\u77E5 tool "${tool.name}" \u4E0D\u5728\u6620\u5C04\u8868\u4E2D\uFF0C\u4F7F\u7528 name parsing \u56DE\u9000`);
1636
1798
  const parts = tool.name.split("_");
1637
1799
  const resource = parts[0];
1638
1800
  const method = parts.slice(1).join("-");
@@ -1649,6 +1811,7 @@ function mapTools(tools) {
1649
1811
  }
1650
1812
 
1651
1813
  // src/dynamic/cache.ts
1814
+ init_profile_validation();
1652
1815
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1653
1816
  import { join as join3, dirname } from "path";
1654
1817
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -1656,6 +1819,7 @@ var ToolCache = class {
1656
1819
  filePath;
1657
1820
  ttlMs;
1658
1821
  constructor(cacheDir, profile = "default", ttlMs = DEFAULT_TTL_MS) {
1822
+ validateProfileName(profile);
1659
1823
  const filename = profile === "default" ? "tools.json" : `tools-${profile}.json`;
1660
1824
  this.filePath = join3(cacheDir, filename);
1661
1825
  this.ttlMs = ttlMs;
@@ -1664,6 +1828,9 @@ var ToolCache = class {
1664
1828
  try {
1665
1829
  const raw = await readFile3(this.filePath, "utf-8");
1666
1830
  const data = JSON.parse(raw);
1831
+ if (!Array.isArray(data.tools) || typeof data.timestamp !== "number") {
1832
+ return null;
1833
+ }
1667
1834
  const stale = Date.now() - data.timestamp > this.ttlMs;
1668
1835
  return { tools: data.tools, stale };
1669
1836
  } catch {
@@ -1688,12 +1855,16 @@ function convertTool(entry) {
1688
1855
  const props = entry.inputSchema?.properties ?? {};
1689
1856
  const required = new Set(entry.inputSchema?.required ?? []);
1690
1857
  for (const [name, schema] of Object.entries(props)) {
1691
- parameters.push({
1858
+ const param = {
1692
1859
  name,
1693
1860
  type: schema.type ?? "string",
1694
1861
  description: schema.description,
1695
1862
  required: required.has(name)
1696
- });
1863
+ };
1864
+ if (schema.type === "array" && schema.items?.type) {
1865
+ param.items = { type: schema.items.type };
1866
+ }
1867
+ parameters.push(param);
1697
1868
  }
1698
1869
  return {
1699
1870
  name: entry.name,
@@ -1704,13 +1875,15 @@ function convertTool(entry) {
1704
1875
 
1705
1876
  // src/cli.ts
1706
1877
  init_config();
1878
+ init_errors();
1707
1879
 
1708
1880
  // src/core/mcp-client.ts
1709
- var requestId = 0;
1881
+ init_errors();
1710
1882
  var McpClient = class {
1711
1883
  baseUrl;
1712
1884
  tokenManager;
1713
1885
  customHeaders;
1886
+ requestId = 0;
1714
1887
  constructor(baseUrl, tokenManager, customHeaders) {
1715
1888
  this.baseUrl = baseUrl;
1716
1889
  this.tokenManager = tokenManager;
@@ -1720,7 +1893,7 @@ var McpClient = class {
1720
1893
  try {
1721
1894
  return await this._doCall(method, params);
1722
1895
  } catch (err) {
1723
- if (err instanceof ServerError && err.message.includes("401")) {
1896
+ if (err instanceof HttpError && err.httpStatus === 401) {
1724
1897
  const store = this.tokenManager.getStore();
1725
1898
  const tokenData = await store.load();
1726
1899
  if (tokenData) {
@@ -1736,10 +1909,11 @@ var McpClient = class {
1736
1909
  }
1737
1910
  async _doCall(method, params) {
1738
1911
  const token = await this.tokenManager.getToken();
1739
- const id = ++requestId;
1912
+ const id = ++this.requestId;
1740
1913
  const body = { jsonrpc: "2.0", id, method, params };
1741
1914
  const headers = {
1742
1915
  "Content-Type": "application/json",
1916
+ "User-Agent": "meegle-cli",
1743
1917
  ...this.customHeaders
1744
1918
  };
1745
1919
  if (token) {
@@ -1751,9 +1925,10 @@ var McpClient = class {
1751
1925
  body: JSON.stringify(body)
1752
1926
  });
1753
1927
  if (!response.ok) {
1754
- throw new ServerError(
1928
+ throw new HttpError(
1755
1929
  `\u670D\u52A1\u7AEF\u8FD4\u56DE\u9519\u8BEF (${response.status})`,
1756
- "SERVER_HTTP_ERROR"
1930
+ "SERVER_HTTP_ERROR",
1931
+ response.status
1757
1932
  );
1758
1933
  }
1759
1934
  const data = await response.json();
@@ -1800,23 +1975,53 @@ function getCommandChain(cmd) {
1800
1975
  }
1801
1976
  return chain;
1802
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
+ }
1803
1989
  async function createProgram() {
1804
1990
  const program = new Command();
1805
- 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) => {
1806
2011
  const opts = thisCommand.opts();
1807
2012
  if (opts.verbose) {
1808
2013
  setLogLevel("debug");
1809
2014
  }
1810
2015
  const chain = getCommandChain(actionCommand);
1811
2016
  const topCmd = chain[0] ?? "";
1812
- const profileName = thisCommand.opts().profile;
2017
+ const profileName2 = thisCommand.opts().profile;
1813
2018
  if (!SKIP_FIRST_RUN.includes(topCmd)) {
1814
- await checkFirstRun(profileName);
2019
+ await checkFirstRun(profileName2);
1815
2020
  }
1816
2021
  });
1817
2022
  registerStaticCommands(program);
2023
+ const profileName = extractProfileFromArgv(process.argv);
1818
2024
  try {
1819
- const profileName = program.opts().profile;
1820
2025
  const config = await loadConfig(profileName);
1821
2026
  const serverUrl = getServerUrl(config);
1822
2027
  const currentProfile = profileName ?? await getCurrentProfileName();
@@ -1848,7 +2053,6 @@ async function createProgram() {
1848
2053
  } catch (err) {
1849
2054
  logger.debug(`\u547D\u4EE4\u52A0\u8F7D\u8DF3\u8FC7: ${err}`);
1850
2055
  }
1851
- registerHelperCommands(program);
1852
2056
  return program;
1853
2057
  }
1854
2058
  async function run() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-project/meegle",
3
- "version": "0.0.2",
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",