meegle-cli 0.1.1 → 0.1.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.
package/README-en.md ADDED
@@ -0,0 +1,222 @@
1
+ # meegle-cli
2
+
3
+ CLI for Feishu Project (Meegle).
4
+ Current mode is `plugin-only`: it supports `plugin_access_token / virtual_plugin_token`, but not `user_access_token`.
5
+
6
+ ## Risk Notes
7
+
8
+ This CLI can directly create, update, and delete Meegle data.
9
+ In a B2B environment, the main risks are:
10
+
11
+ - using the wrong `profile`
12
+ - using the wrong `projectKey`
13
+ - letting scripts or AI tools write to production by mistake
14
+ - running bulk mutations without checking the target scope first
15
+
16
+ Starting from `0.1.3`:
17
+
18
+ - write commands print a risk summary first
19
+ - interactive terminals require confirmation
20
+ - non-interactive environments (scripts / AI / CI) must pass `--yes`
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install -g meegle-cli
26
+ meegle --help
27
+ ```
28
+
29
+ ## What You Need
30
+
31
+ - `pluginId`
32
+ - `pluginSecret`
33
+ - `userKey`
34
+ - `projectKey`
35
+
36
+ Optional:
37
+
38
+ - `baseURL`
39
+ - default: `https://project.feishu.cn`
40
+
41
+ ## Quick Start
42
+
43
+ ### 1. Initialize a profile
44
+
45
+ ```bash
46
+ meegle auth init \
47
+ --target-profile default \
48
+ --plugin-id <PLUGIN_ID> \
49
+ --plugin-secret <PLUGIN_SECRET> \
50
+ --default-user-key <USER_KEY>
51
+ ```
52
+
53
+ Or use environment variables:
54
+
55
+ ```bash
56
+ export MEEGLE_PLUGIN_ID=<PLUGIN_ID>
57
+ export MEEGLE_PLUGIN_SECRET=<PLUGIN_SECRET>
58
+ export MEEGLE_USER_KEY=<USER_KEY>
59
+
60
+ meegle auth init --target-profile default
61
+ ```
62
+
63
+ ### 2. Verify credentials
64
+
65
+ ```bash
66
+ meegle --profile default auth status --json
67
+ ```
68
+
69
+ ### 3. List spaces
70
+
71
+ ```bash
72
+ meegle --profile default space list --json
73
+ ```
74
+
75
+ ### 4. Query yourself
76
+
77
+ ```bash
78
+ meegle --profile default user query --self --json
79
+ ```
80
+
81
+ ## Common Tasks
82
+
83
+ ### Get a work item
84
+
85
+ ```bash
86
+ meegle --profile default workitem get \
87
+ --project-key <PROJECT_KEY> \
88
+ --type story \
89
+ --id 6300034462 \
90
+ --json
91
+ ```
92
+
93
+ ### Create a work item
94
+
95
+ ```bash
96
+ meegle --profile default workitem create \
97
+ --yes \
98
+ --project-key <PROJECT_KEY> \
99
+ --type story \
100
+ --name "Login improvement" \
101
+ --desc "Improve error messages" \
102
+ --owner <USER_KEY> \
103
+ --priority P2 \
104
+ --json
105
+ ```
106
+
107
+ ### Add a comment
108
+
109
+ ```bash
110
+ meegle --profile default comment add \
111
+ --yes \
112
+ --project-key <PROJECT_KEY> \
113
+ --type story \
114
+ --id 6300034462 \
115
+ --content "Received, starting now" \
116
+ --json
117
+ ```
118
+
119
+ ## View Usage
120
+
121
+ Do not infer view type from the page URL.
122
+ Always call `view list` first, then choose the correct API.
123
+
124
+ ### Resolve a view
125
+
126
+ ```bash
127
+ meegle --profile default view list \
128
+ --project-key 69zyer \
129
+ --body-file ./examples/view-list.json \
130
+ --json
131
+ ```
132
+
133
+ Check:
134
+
135
+ - `view_id`
136
+ - `name`
137
+ - `view_type`
138
+ - `1` = condition view
139
+ - `2` = fixed view
140
+
141
+ ### Fixed view items
142
+
143
+ ```bash
144
+ meegle --profile default view fix-items \
145
+ --project-key 69zyer \
146
+ --view-id sypbi-z60 \
147
+ --query-file ./examples/view-fix-items.json \
148
+ --json
149
+ ```
150
+
151
+ ## Related Field Filtering
152
+
153
+ For related fields such as `planning_version`, do not use:
154
+
155
+ - `workitem search filter` with `field_value_pairs`
156
+
157
+ Use this flow instead:
158
+
159
+ 1. find the related work item ID
160
+ 2. run `workitem search by-params`
161
+ 3. pass the related work item **ID list**, not the display name
162
+
163
+ ### Example: find stories by planning version
164
+
165
+ Step 1: search the `version` work item:
166
+
167
+ ```bash
168
+ meegle --profile default workitem search filter \
169
+ --project-key 69zyer \
170
+ --body-file ./examples/version-search-filter.json \
171
+ --json
172
+ ```
173
+
174
+ Step 2: use the returned version `id` in `planning_version`:
175
+
176
+ ```bash
177
+ meegle --profile default workitem search by-params \
178
+ --project-key 69zyer \
179
+ --type story \
180
+ --body-file ./examples/story-by-planning-version.json \
181
+ --json
182
+ ```
183
+
184
+ Replace `1234567890` in the example file with the real version work item ID.
185
+
186
+ ## Write Safety
187
+
188
+ In non-interactive environments, write commands must pass `--yes`.
189
+
190
+ Example:
191
+
192
+ ```bash
193
+ meegle --profile default --yes workitem update ...
194
+ ```
195
+
196
+ ## Agent Notes
197
+
198
+ If you use this CLI from an AI agent or automation:
199
+
200
+ 1. run `meegle --help` or subcommand `--help` first
201
+ 2. do not guess request shapes from web URLs
202
+ 3. use `workitem search by-params` for custom fields and related fields
203
+ 4. `field_value_pairs` is for create/update, not for search
204
+ 5. in non-interactive environments, write commands must pass `--yes`
205
+
206
+ ## Examples
207
+
208
+ - `examples/view-list.json`
209
+ - `examples/view-fix-items.json`
210
+ - `examples/version-search-filter.json`
211
+ - `examples/story-by-planning-version.json`
212
+
213
+ ## Limits
214
+
215
+ This CLI currently rejects user-token-only commands such as:
216
+
217
+ - `user search`
218
+ - `user group create`
219
+ - `user group update-members`
220
+ - `user group query-members`
221
+
222
+ That is expected behavior in `plugin-only` mode.
package/README.md CHANGED
@@ -3,6 +3,33 @@
3
3
  面向飞书项目(Meegle)的命令行工具。
4
4
  当前版本运行在 `plugin-only` 模式:只支持 `plugin_access_token / virtual_plugin_token`,不支持 `user_access_token`。
5
5
 
6
+ ## 风险提示
7
+
8
+ 这是一个可直接对空间数据执行增删改的 CLI。
9
+ 在 toB 场景里,误操作成本很高,尤其是:
10
+
11
+ - 打错 `profile`
12
+ - 打错 `projectKey`
13
+ - 把脚本或 AI 的写操作直接打到线上
14
+ - 批量更新 / 删除目标超出预期
15
+
16
+ 从 `0.1.3` 起:
17
+
18
+ - 写操作会先输出风险摘要
19
+ - 交互终端里默认要求确认
20
+ - 非交互环境(脚本 / AI / CI)必须显式传 `--yes`
21
+
22
+ 交互终端的目标体验是:
23
+
24
+ - 人类用户只需要回复简短确认词,例如 `确认删除`
25
+ - Agent / 脚本才需要显式 `--yes`
26
+
27
+ 建议:
28
+
29
+ 1. 先在测试空间验证命令
30
+ 2. 再切生产 profile
31
+ 3. 对写操作显式确认 `profile / projectKey / id`
32
+
6
33
  ## 适用对象
7
34
 
8
35
  - 想在终端里查询空间、工作项、视图、评论、子任务
@@ -159,6 +186,7 @@ meegle --profile default workitem get \
159
186
 
160
187
  ```bash
161
188
  meegle --profile default workitem create \
189
+ --yes \
162
190
  --project-key <PROJECT_KEY> \
163
191
  --type story \
164
192
  --name "登录优化" \
@@ -172,6 +200,7 @@ meegle --profile default workitem create \
172
200
 
173
201
  ```bash
174
202
  meegle --profile default workitem update \
203
+ --yes \
175
204
  --project-key <PROJECT_KEY> \
176
205
  --type story \
177
206
  --id 6300034462 \
@@ -183,6 +212,7 @@ meegle --profile default workitem update \
183
212
 
184
213
  ```bash
185
214
  meegle --profile default comment add \
215
+ --yes \
186
216
  --project-key <PROJECT_KEY> \
187
217
  --type story \
188
218
  --id 6300034462 \
@@ -194,6 +224,7 @@ meegle --profile default comment add \
194
224
 
195
225
  ```bash
196
226
  meegle --profile default comment add \
227
+ --yes \
197
228
  --project-key <PROJECT_KEY> \
198
229
  --type story \
199
230
  --id 6300034462 \
@@ -205,6 +236,7 @@ meegle --profile default comment add \
205
236
 
206
237
  ```bash
207
238
  meegle --profile default subtask create \
239
+ --yes \
208
240
  --project-key <PROJECT_KEY> \
209
241
  --type story \
210
242
  --id 6300034462 \
@@ -222,6 +254,7 @@ meegle --profile default subtask create \
222
254
 
223
255
  ```bash
224
256
  meegle --profile default workflow state-change \
257
+ --yes \
225
258
  --project-key <PROJECT_KEY> \
226
259
  --type story \
227
260
  --id 6300034462 \
@@ -234,6 +267,7 @@ meegle --profile default workflow state-change \
234
267
 
235
268
  ```bash
236
269
  meegle --profile default workflow node-operate \
270
+ --yes \
237
271
  --project-key <PROJECT_KEY> \
238
272
  --type story \
239
273
  --id 6300034462 \
@@ -331,10 +365,96 @@ meegle --profile default view panoramic-items \
331
365
 
332
366
  不要直接拿整个空间总池代替“这个视图”的口径。
333
367
 
368
+ ## 关联字段筛选
369
+
370
+ ### 结论先说
371
+
372
+ 像 `planning_version` 这类字段,不要用:
373
+
374
+ - `workitem search filter` + `field_value_pairs`
375
+
376
+ 正确做法是:
377
+
378
+ 1. 先找到被关联工作项的实例 ID
379
+ 2. 再用 `workitem search by-params`
380
+ 3. 对关联字段传 **ID 列表**,不要传名称
381
+
382
+ ### 为什么
383
+
384
+ `planning_version` 的字段类型是:
385
+
386
+ - `work_item_related_multi_select`
387
+
388
+ 这类字段的筛选值格式是:
389
+
390
+ - `list<int64>`,也就是关联工作项 ID 列表
391
+
392
+ 不是:
393
+
394
+ - 版本名称字符串
395
+ - `field_value_pairs`
396
+
397
+ ### 典型例子:按“规划版本”筛需求
398
+
399
+ 目标:找出 `planning_version` 关联到某个版本实例的全部 story。
400
+
401
+ #### 第一步:先找版本实例
402
+
403
+ 先在 `version` 类型里按名称查候选:
404
+
405
+ ```bash
406
+ meegle --profile default workitem search filter \
407
+ --project-key 69zyer \
408
+ --body-file ./examples/version-search-filter.json \
409
+ --json
410
+ ```
411
+
412
+ 示例文件:
413
+
414
+ - [examples/version-search-filter.json](./examples/version-search-filter.json)
415
+
416
+ 你要从返回结果里拿到真正的版本实例 `id`。
417
+
418
+ #### 第二步:再按 `planning_version` 查 story
419
+
420
+ ```bash
421
+ meegle --profile default workitem search by-params \
422
+ --project-key 69zyer \
423
+ --type story \
424
+ --body-file ./examples/story-by-planning-version.json \
425
+ --json
426
+ ```
427
+
428
+ 示例文件:
429
+
430
+ - [examples/story-by-planning-version.json](./examples/story-by-planning-version.json)
431
+
432
+ 重点:
433
+
434
+ - 把 `1234567890` 替换成真实版本实例 ID
435
+ - 不要直接写版本名称
436
+
437
+ ### 常见误用
438
+
439
+ 错误方向:
440
+
441
+ 1. 用 `workitem search filter` 查自定义关联字段
442
+ 2. 在筛选请求体里传 `field_value_pairs`
443
+ 3. 直接把版本名称塞给 `planning_version`
444
+
445
+ 这些场景下,接口常见表现是:
446
+
447
+ - 条件被忽略
448
+ - 返回全部需求
449
+ - 让人误以为 CLI 不支持
450
+
451
+ 根因通常不是 CLI 丢参,而是请求体结构不符合接口契约。
452
+
334
453
  ## 参数约定
335
454
 
336
455
  - 常用查询优先走直接参数,不强制写 JSON 文件
337
456
  - 常用写操作优先走直接参数,例如 `--name`、`--desc`、`--content`
457
+ - 非交互环境下,写操作必须显式传 `--yes`
338
458
  - 复杂请求体可使用 `--body <json>` 或 `--body-file <path>`
339
459
  - 复杂查询参数可使用 `--query-file <path>`
340
460
  - 自定义字段统一使用 `--field field_key=value`
@@ -400,6 +520,33 @@ meegle auth init \
400
520
  先回到 `view list` 看 `view_type`。
401
521
  不要只凭页面 URL 去猜视图类型。
402
522
 
523
+ ### 5. 明明传了条件,结果返回全部数据
524
+
525
+ 优先检查三件事:
526
+
527
+ 1. 用的命令是不是对
528
+ - 内置字段简单筛选:`workitem search filter`
529
+ - 自定义字段 / 关联字段:`workitem search by-params`
530
+ 2. 值格式是不是对
531
+ - 关联工作项字段通常要传实例 ID,不是名称
532
+ 3. 请求体键是不是对
533
+ - `field_value_pairs` 用于创建 / 更新,不是搜索条件
534
+
535
+ ### 6. 写操作在脚本 / Agent 里被拒绝
536
+
537
+ 如果报错类似:
538
+
539
+ - `非交互环境下,写操作需要显式传 --yes`
540
+
541
+ 说明当前进程不是交互终端。
542
+ 这是预期行为,用来拦截脚本和 AI 的误操作。
543
+
544
+ 处理方式:
545
+
546
+ ```bash
547
+ meegle --profile default --yes workitem update ...
548
+ ```
549
+
403
550
  ## 命令总览
404
551
 
405
552
  - `auth`: `init`, `status`
@@ -428,3 +575,23 @@ npm test
428
575
  ```
429
576
 
430
577
  发布说明见 [RELEASE.md](./RELEASE.md)。
578
+
579
+ ## Examples
580
+
581
+ 可直接复制修改的模板在:
582
+
583
+ - [examples/view-list.json](./examples/view-list.json)
584
+ - [examples/view-fix-items.json](./examples/view-fix-items.json)
585
+ - [examples/version-search-filter.json](./examples/version-search-filter.json)
586
+ - [examples/story-by-planning-version.json](./examples/story-by-planning-version.json)
587
+
588
+ ## AI / Agent 提示
589
+
590
+ 如果你在 Agent、Openclaw、脚本平台里调用 `meegle-cli`,建议遵守这几条:
591
+
592
+ 1. 先跑 `meegle --help` 或子命令 `--help`,不要凭印象猜参数名
593
+ 2. 不要从页面 URL 直接猜视图类型;先调用 `view list`
594
+ 3. 自定义字段和关联字段优先走 `workitem search by-params`
595
+ 4. `planning_version` 这类关联字段,先查出被关联工作项的 `id`,再筛主工作项
596
+ 5. `field_value_pairs` 是写操作格式,不是搜索格式
597
+ 6. 非交互环境里,写操作必须显式传 `--yes`
package/dist/cli.js CHANGED
@@ -708,6 +708,32 @@ async function buildWorkflowNodeOperateRequest(options) {
708
708
  fields: fields.length > 0 ? fields : undefined,
709
709
  };
710
710
  }
711
+ /**
712
+ * 删除前先读出目标摘要,能明显降低误删概率。
713
+ *
714
+ * @param ctx 守卫上下文
715
+ * @param args 工作项定位参数
716
+ */
717
+ async function buildWorkItemRemovePreview(ctx, args) {
718
+ const workItemId = parseInteger(args.id, 'id');
719
+ const items = await ctx.client.workItem.query(args.projectKey, args.type, {
720
+ work_item_ids: [workItemId],
721
+ fields: ['name'],
722
+ }, { auth: ctx.auth });
723
+ const target = items[0];
724
+ if (!target) {
725
+ throw new CliError('删除前未找到目标工作项,请先确认 projectKey、type 和 id 是否正确。', 2);
726
+ }
727
+ return {
728
+ lines: [
729
+ `projectKey: ${args.projectKey}`,
730
+ `workItemType: ${args.type}`,
731
+ `workItemId: ${workItemId}`,
732
+ `workItemName: ${target.name}`,
733
+ ],
734
+ confirmPhrase: '确认删除',
735
+ };
736
+ }
711
737
  function resolveGlobal(program) {
712
738
  return program.opts();
713
739
  }
@@ -960,7 +986,9 @@ function registerWorkItem(program) {
960
986
  id: 'workitem.remove',
961
987
  authPolicy: 'PLUGIN_WITH_USER_KEY',
962
988
  endpoint: { method: 'DELETE', path: '/open_api/:project_key/work_item/:work_item_type_key/:work_item_id' },
963
- }, global, async (ctx) => ctx.client.workItem.remove(options.projectKey, options.type, parseInteger(options.id, 'id'), { auth: ctx.auth }));
989
+ }, global, async (ctx) => ctx.client.workItem.remove(options.projectKey, options.type, parseInteger(options.id, 'id'), { auth: ctx.auth }), {
990
+ riskPreview: async (ctx) => buildWorkItemRemovePreview(ctx, options),
991
+ });
964
992
  printOk(Boolean(global.json));
965
993
  });
966
994
  workItem
@@ -2028,6 +2056,7 @@ export function buildCli() {
2028
2056
  .option('--user-key <userKey>', '本次命令覆盖 userKey')
2029
2057
  .option('--compat-auth', '读接口使用兼容模式 (x-auth-mode=0),默认严格模式')
2030
2058
  .option('--json', '输出 JSON')
2059
+ .option('--yes', '跳过写操作确认,脚本 / AI / CI 使用')
2031
2060
  .addHelpText('after', `
2032
2061
  常用示例:
2033
2062
  meegle auth init --target-profile demo --plugin-id <id> --plugin-secret <secret> --default-user-key <userKey>
@@ -1,5 +1,6 @@
1
1
  export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
2
2
  export type AuthPolicy = 'NONE' | 'PLUGIN_WITH_USER_KEY' | 'PLUGIN_OPTIONAL_USER_KEY' | 'USER_TOKEN_REQUIRED';
3
+ export type RiskLevel = 'READ_ONLY' | 'WRITE' | 'DANGEROUS';
3
4
  export interface EndpointRef {
4
5
  method: HttpMethod;
5
6
  path: string;
@@ -12,3 +13,7 @@ export interface CommandMeta {
12
13
  export declare function isUserTokenOnlyEndpoint(path: string): boolean;
13
14
  export declare function assertCommandSupported(meta: CommandMeta): void;
14
15
  export declare function assertEndpointAllowed(endpoint?: EndpointRef): void;
16
+ /**
17
+ * 风险级别由命令语义决定,比按 HTTP 方法推断更稳定。
18
+ */
19
+ export declare function getCommandRiskLevel(meta: CommandMeta): RiskLevel;
@@ -1,4 +1,20 @@
1
1
  import { CliError } from './cli-error.js';
2
+ const DANGEROUS_COMMAND_PATTERNS = [
3
+ /^workitem\.(remove|freeze|unfreeze|abort|restore|update-compound)$/,
4
+ /^view\.delete$/,
5
+ /^attachment\.delete$/,
6
+ /^workhour\.delete$/,
7
+ ];
8
+ const WRITE_COMMAND_PATTERNS = [
9
+ /^workitem\.create$/,
10
+ /^workitem\.update$/,
11
+ /^workflow\.(state-change|node-operate|node-update)$/,
12
+ /^comment\.(add|update|remove)$/,
13
+ /^subtask\.(create|update|remove|operate)$/,
14
+ /^attachment\.(upload-file|upload)$/,
15
+ /^workhour\.(create|update)$/,
16
+ /^view\.(create-fix|update-fix|create-condition|update-condition)$/,
17
+ ];
2
18
  const USER_TOKEN_ONLY_TEMPLATES = new Set([
3
19
  '/open_api/user/search',
4
20
  '/open_api/:project_key/user_group',
@@ -35,3 +51,15 @@ export function assertEndpointAllowed(endpoint) {
35
51
  throw new CliError(`接口 ${endpoint.method} ${endpoint.path} 在文档中标注为仅支持 user_access_token,plugin-only 模式已拒绝。`, 2);
36
52
  }
37
53
  }
54
+ /**
55
+ * 风险级别由命令语义决定,比按 HTTP 方法推断更稳定。
56
+ */
57
+ export function getCommandRiskLevel(meta) {
58
+ if (DANGEROUS_COMMAND_PATTERNS.some((pattern) => pattern.test(meta.id))) {
59
+ return 'DANGEROUS';
60
+ }
61
+ if (WRITE_COMMAND_PATTERNS.some((pattern) => pattern.test(meta.id))) {
62
+ return 'WRITE';
63
+ }
64
+ return 'READ_ONLY';
65
+ }
@@ -5,6 +5,7 @@ export interface RuntimeOptions {
5
5
  profile?: string;
6
6
  userKey?: string;
7
7
  compatAuth?: boolean;
8
+ yes?: boolean;
8
9
  }
9
10
  export interface GuardContext {
10
11
  profileName: string;
@@ -13,4 +14,11 @@ export interface GuardContext {
13
14
  auth: AuthContext;
14
15
  userKey?: string;
15
16
  }
16
- export declare function runGuarded<T>(meta: CommandMeta, runtime: RuntimeOptions, action: (ctx: GuardContext) => Promise<T>): Promise<T>;
17
+ export interface RiskPreview {
18
+ lines?: string[];
19
+ confirmPhrase?: string;
20
+ }
21
+ export interface GuardHooks {
22
+ riskPreview?: (ctx: GuardContext) => Promise<RiskPreview | undefined>;
23
+ }
24
+ export declare function runGuarded<T>(meta: CommandMeta, runtime: RuntimeOptions, action: (ctx: GuardContext) => Promise<T>, hooks?: GuardHooks): Promise<T>;
@@ -1,5 +1,6 @@
1
+ import { createInterface } from 'node:readline/promises';
1
2
  import { CliError } from './cli-error.js';
2
- import { assertCommandSupported, assertEndpointAllowed } from './auth-policy.js';
3
+ import { assertCommandSupported, assertEndpointAllowed, getCommandRiskLevel, } from './auth-policy.js';
3
4
  import { getProfile } from './config-store.js';
4
5
  import { createClient } from './client-factory.js';
5
6
  function resolveUserKey(runtimeUserKey, defaultUserKey) {
@@ -24,7 +25,78 @@ function buildPluginAuth(userKey, compatAuth) {
24
25
  authMode: compatAuth ? 0 : 1,
25
26
  };
26
27
  }
27
- export async function runGuarded(meta, runtime, action) {
28
+ function isInteractiveShell() {
29
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
30
+ }
31
+ function isHighRiskProfile(profileName) {
32
+ return /(^|[-_])(prod|production|live)([-_]|$)/i.test(profileName);
33
+ }
34
+ function escalateRiskLevel(riskLevel, profileName) {
35
+ if (!isHighRiskProfile(profileName)) {
36
+ return riskLevel;
37
+ }
38
+ if (riskLevel === 'WRITE') {
39
+ return 'DANGEROUS';
40
+ }
41
+ return riskLevel;
42
+ }
43
+ function buildRiskSummary(meta, profileName, riskLevel) {
44
+ const args = process.argv.slice(2).join(' ');
45
+ return [
46
+ `命令: meegle ${args}`,
47
+ `profile: ${profileName}`,
48
+ `风险级别: ${riskLevel}`,
49
+ meta.endpoint ? `接口: ${meta.endpoint.method} ${meta.endpoint.path}` : '接口: N/A',
50
+ ];
51
+ }
52
+ /**
53
+ * 在统一守卫里注入风险预览,避免每个高风险命令各自实现一套确认流程。
54
+ *
55
+ * @param meta 命令元数据
56
+ * @param runtime 运行时选项
57
+ * @param profileName 当前 profile 名称
58
+ * @param preview 命令特定的风险预览
59
+ */
60
+ async function confirmWriteRisk(meta, runtime, profileName, preview) {
61
+ const originalRisk = getCommandRiskLevel(meta);
62
+ const riskLevel = escalateRiskLevel(originalRisk, profileName);
63
+ if (riskLevel === 'READ_ONLY') {
64
+ return;
65
+ }
66
+ const summary = [...buildRiskSummary(meta, profileName, riskLevel), ...(preview?.lines ?? [])];
67
+ process.stderr.write('即将执行写操作:\n');
68
+ for (const line of summary) {
69
+ process.stderr.write(`- ${line}\n`);
70
+ }
71
+ if (runtime.yes) {
72
+ return;
73
+ }
74
+ if (!isInteractiveShell()) {
75
+ throw new CliError('非交互环境下,写操作需要显式传 --yes。', 2);
76
+ }
77
+ const rl = createInterface({
78
+ input: process.stdin,
79
+ output: process.stderr,
80
+ });
81
+ try {
82
+ if (riskLevel === 'DANGEROUS') {
83
+ const confirmPhrase = preview?.confirmPhrase ?? '确认执行';
84
+ const answer = (await rl.question(`危险操作。请输入“${confirmPhrase}”确认继续执行: `)).trim();
85
+ if (answer !== confirmPhrase) {
86
+ throw new CliError('已取消执行。', 2);
87
+ }
88
+ return;
89
+ }
90
+ const answer = (await rl.question('确认继续执行写操作?[y/N] ')).trim().toLowerCase();
91
+ if (answer !== 'y' && answer !== 'yes') {
92
+ throw new CliError('已取消执行。', 2);
93
+ }
94
+ }
95
+ finally {
96
+ rl.close();
97
+ }
98
+ }
99
+ export async function runGuarded(meta, runtime, action, hooks) {
28
100
  assertCommandSupported(meta);
29
101
  assertEndpointAllowed(meta.endpoint);
30
102
  const { name: profileName, profile } = await getProfile(runtime.profile);
@@ -34,5 +106,8 @@ export async function runGuarded(meta, runtime, action) {
34
106
  }
35
107
  const client = createClient(profile);
36
108
  const auth = buildPluginAuth(userKey, runtime.compatAuth);
37
- return action({ profileName, profile, client, auth, userKey });
109
+ const ctx = { profileName, profile, client, auth, userKey };
110
+ const preview = hooks?.riskPreview ? await hooks.riskPreview(ctx) : undefined;
111
+ await confirmWriteRisk(meta, runtime, profileName, preview);
112
+ return action(ctx);
38
113
  }
@@ -0,0 +1,23 @@
1
+ {
2
+ "search_group": {
3
+ "conjunction": "AND",
4
+ "search_params": [
5
+ {
6
+ "param_key": "planning_version",
7
+ "operator": "CONTAINS",
8
+ "value": [
9
+ 1234567890
10
+ ]
11
+ }
12
+ ]
13
+ },
14
+ "page_size": 200,
15
+ "page_num": 1,
16
+ "fields": [
17
+ "name",
18
+ "owner",
19
+ "priority",
20
+ "planning_version",
21
+ "work_item_status"
22
+ ]
23
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "work_item_type_keys": [
3
+ "version"
4
+ ],
5
+ "work_item_name": "260312",
6
+ "page_size": 50,
7
+ "page_num": 1
8
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "page_size": 50,
3
+ "page_num": 1
4
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "work_item_type_key": "story",
3
+ "view_ids": [
4
+ "sypbi-z60"
5
+ ],
6
+ "is_query_quick_filter": true,
7
+ "page_size": 10,
8
+ "page_num": 1
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meegle-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Plugin-only CLI for Feishu Project (Meegle) based on meeglesdk",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,10 @@
9
9
  "files": [
10
10
  "dist",
11
11
  "README.md",
12
- "RELEASE.md"
12
+ "README-en.md",
13
+ "RELEASE.md",
14
+ "examples",
15
+ "skills"
13
16
  ],
14
17
  "scripts": {
15
18
  "clean": "rm -rf dist",
@@ -0,0 +1,108 @@
1
+ ---
2
+ name: meegle-cli-usage
3
+ description: Use the meegle CLI shipped in this package instead of raw SDK or guessed API calls when an agent needs to query spaces, work items, views, PMO baselines, or related-field searches such as planning_version. Use for CLI-based Meegle automation, debugging, and analysis. Includes safety rules for write operations (`--yes` in non-interactive environments), view-type resolution, and relation-ID based filtering.
4
+ ---
5
+
6
+ # Meegle CLI Usage
7
+
8
+ Use `meegle` as the default entrypoint for Meegle operations in this package. Prefer invoking the CLI over reconstructing request payloads from memory.
9
+
10
+ ## Quick Start
11
+
12
+ 1. Resolve the command path.
13
+ - Try `meegle --help` first.
14
+ - If `meegle` is not in PATH, use `which meegle` or `npm bin -g`.
15
+ - If working inside this package without a global install, use `node_modules/.bin/meegle`.
16
+ 2. Resolve the command shape before guessing.
17
+ - Run `meegle --help` or `<subcommand> --help`.
18
+ 3. Separate read and write intent.
19
+ - Treat read commands as default-safe.
20
+ - Treat write commands as opt-in only.
21
+ - In non-interactive environments, only add `--yes` when the user explicitly approved the write.
22
+
23
+ ## Safety Rules
24
+
25
+ 1. Refuse to add `--yes` implicitly.
26
+ 2. Prefer read-only discovery before any write.
27
+ 3. Echo the target scope in reasoning before running a write:
28
+ - `profile`
29
+ - `projectKey`
30
+ - `workItemType`
31
+ - target `id` or `ids`
32
+ 4. If the task can be solved with `get`, `list`, `search`, or `query`, do that first.
33
+
34
+ ## Choose the Right Command
35
+
36
+ ### Query a view
37
+
38
+ Do not infer the view type from a page URL.
39
+
40
+ Always:
41
+
42
+ 1. Run `view list` with the candidate `view_id`
43
+ 2. Inspect `view_type`
44
+ 3. Use:
45
+ - `view fix-items` for fixed view (`view_type = 2`)
46
+ - `view panoramic-items` only when the view is not fixed
47
+
48
+ ### Filter by a related field
49
+
50
+ For fields such as `planning_version` or `planning_sprint`:
51
+
52
+ 1. Do not use `workitem search filter` with `field_value_pairs`
53
+ 2. Resolve the related work item instance ID first
54
+ 3. Use `workitem search by-params`
55
+ 4. Pass the related work item **ID list**, not the display name
56
+
57
+ This is the most common agent mistake. `field_value_pairs` belongs to create/update flows, not search flows.
58
+
59
+ ### Build a PMO baseline
60
+
61
+ For PMO-style analysis:
62
+
63
+ 1. Resolve the target view
64
+ 2. Fetch the item IDs from the view
65
+ 3. Fetch work item details with selected fields
66
+ 4. Summarize:
67
+ - state distribution
68
+ - owner distribution
69
+ - priority coverage
70
+ - due-date coverage
71
+ - age / stale updates
72
+
73
+ Do not replace a view-scoped baseline with the whole space backlog unless the user explicitly asks for that.
74
+
75
+ ## Write Operations
76
+
77
+ Use write commands only after explicit user approval.
78
+
79
+ Typical write commands:
80
+
81
+ - `workitem create`
82
+ - `workitem update`
83
+ - `comment add/update/remove`
84
+ - `subtask create/update/remove/operate`
85
+ - `workflow state-change`
86
+ - `workflow node-operate`
87
+
88
+ In scripts, CI, or agent runners:
89
+
90
+ - pass `--yes` only when approved
91
+ - expect non-interactive write rejection if `--yes` is missing
92
+
93
+ ## Use the Bundled Examples
94
+
95
+ Read [references/command-recipes.md](./references/command-recipes.md) when you need:
96
+
97
+ - view lookup examples
98
+ - fixed view item queries
99
+ - version lookup by name
100
+ - story lookup by `planning_version`
101
+ - PMO baseline query order
102
+
103
+ Use the package examples directly when possible:
104
+
105
+ - `../../examples/view-list.json`
106
+ - `../../examples/view-fix-items.json`
107
+ - `../../examples/version-search-filter.json`
108
+ - `../../examples/story-by-planning-version.json`
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Meegle CLI Usage"
3
+ short_description: "Agent guide for safe meegle-cli usage."
4
+ default_prompt: "Use $meegle-cli-usage to operate Meegle safely through the CLI."
@@ -0,0 +1,99 @@
1
+ # Command Recipes
2
+
3
+ ## Resolve a View
4
+
5
+ Use this first whenever the user gives a page URL or a `view_id`.
6
+
7
+ ```bash
8
+ meegle --profile default view list \
9
+ --project-key 69zyer \
10
+ --body-file ./examples/view-list.json \
11
+ --json
12
+ ```
13
+
14
+ Interpretation:
15
+
16
+ - `view_type = 2` -> fixed view -> use `view fix-items`
17
+ - otherwise -> inspect whether `view panoramic-items` is the intended path
18
+
19
+ ## Fetch Fixed View Items
20
+
21
+ ```bash
22
+ meegle --profile default view fix-items \
23
+ --project-key 69zyer \
24
+ --view-id sypbi-z60 \
25
+ --query-file ./examples/view-fix-items.json \
26
+ --json
27
+ ```
28
+
29
+ Use the returned `work_item_id_list` as input to `workitem get`.
30
+
31
+ ## Find a Version Work Item by Name
32
+
33
+ Use this when the user speaks in business names such as “260312 version” or “gray release”.
34
+
35
+ ```bash
36
+ meegle --profile default workitem search filter \
37
+ --project-key 69zyer \
38
+ --body-file ./examples/version-search-filter.json \
39
+ --json
40
+ ```
41
+
42
+ If multiple version items match:
43
+
44
+ 1. surface the candidates
45
+ 2. ask the user to confirm the intended version or pick the exact `id`
46
+
47
+ ## Find Stories by `planning_version`
48
+
49
+ Never pass the version display name directly into `planning_version`.
50
+
51
+ Correct flow:
52
+
53
+ 1. resolve the target version work item `id`
54
+ 2. place that numeric `id` into `examples/story-by-planning-version.json`
55
+ 3. run:
56
+
57
+ ```bash
58
+ meegle --profile default workitem search by-params \
59
+ --project-key 69zyer \
60
+ --type story \
61
+ --body-file ./examples/story-by-planning-version.json \
62
+ --json
63
+ ```
64
+
65
+ ## PMO Baseline Workflow
66
+
67
+ Use this order:
68
+
69
+ 1. `view list`
70
+ 2. `view fix-items` / `view panoramic-items`
71
+ 3. `workitem get --fields ...`
72
+ 4. summarize the returned dataset
73
+
74
+ Recommended PMO fields:
75
+
76
+ - `name`
77
+ - `owner`
78
+ - `priority`
79
+ - `description`
80
+ - `work_item_status`
81
+ - `start_time`
82
+ - `exp_time`
83
+
84
+ Recommended output dimensions:
85
+
86
+ - total count
87
+ - state distribution
88
+ - current node distribution
89
+ - owner concentration
90
+ - missing priority
91
+ - missing due date
92
+ - creation/update time range
93
+
94
+ ## Write Command Reminder
95
+
96
+ In non-interactive environments:
97
+
98
+ - add `--yes` only when the user explicitly approved a mutation
99
+ - do not silently convert a read request into a write request