@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.
- package/README.md +82 -57
- package/dist/bin/meegle.js +337 -133
- 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
|
|
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
|
|
40
|
-
| `workitem update
|
|
41
|
-
| `workitem
|
|
42
|
-
| `workitem
|
|
43
|
-
| `workitem
|
|
44
|
-
| `workitem
|
|
45
|
-
| `workitem
|
|
46
|
-
| `workitem
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
52
|
-
| `
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
| `
|
|
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
|
|
71
|
-
| `view
|
|
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
|
|
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
|
-
| `
|
|
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
|
|
130
|
+
meegle mywork todo --action this_week --page-num 1
|
|
106
131
|
|
|
107
132
|
# 已办事项
|
|
108
|
-
meegle
|
|
133
|
+
meegle mywork todo --action done --page-num 1
|
|
109
134
|
|
|
110
135
|
# 逾期事项
|
|
111
|
-
meegle
|
|
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
|
|
143
|
+
meegle workitem get --work-item-id 12345
|
|
119
144
|
|
|
120
145
|
# 查看工作项的节点详情
|
|
121
|
-
meegle node
|
|
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
|
|
167
|
+
meegle workitem update --work-item-id 12345 \
|
|
143
168
|
--set name=新标题
|
|
144
169
|
|
|
145
170
|
# 同时修改多个字段
|
|
146
|
-
meegle workitem update
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
260
|
+
meegle workitem get --work-item-id 12345
|
|
236
261
|
|
|
237
262
|
# 选取输出属性(支持 dot path)
|
|
238
|
-
meegle workitem
|
|
239
|
-
meegle
|
|
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
|
|
267
|
+
meegle mywork todo --action this_week --page-num 1 --format ndjson
|
|
243
268
|
|
|
244
269
|
# 表格
|
|
245
|
-
meegle
|
|
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
|
|
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
|
|
402
|
+
meegle mywork todo --action this_week --page-num 1
|
|
378
403
|
```
|
|
379
404
|
|
|
380
405
|
## License
|
package/dist/bin/meegle.js
CHANGED
|
@@ -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
|
|
273
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
1408
|
-
if (param.type === "
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
1437
|
-
|
|
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
|
-
|
|
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(/
|
|
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
|
|
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
|
-
//
|
|
1731
|
+
// WorkItem 工作项域 (9)
|
|
1573
1732
|
create_workitem: { resource: "workitem", method: "create", hasFields: true },
|
|
1574
|
-
get_workitem_brief: { resource: "workitem", method: "
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
get_workitem_op_record: { resource: "workitem", method: "op-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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
|
-
//
|
|
1601
|
-
get_chart_detail: { resource: "chart", method: "
|
|
1766
|
+
// Chart 度量域 (2)
|
|
1767
|
+
get_chart_detail: { resource: "chart", method: "get" },
|
|
1602
1768
|
list_charts: { resource: "chart", method: "list" },
|
|
1603
|
-
//
|
|
1769
|
+
// UserGroup 人员域 (3)
|
|
1604
1770
|
list_project_team: { resource: "team", method: "list" },
|
|
1605
|
-
list_team_members: { resource: "team", method: "members" },
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
2017
|
+
const profileName2 = thisCommand.opts().profile;
|
|
1813
2018
|
if (!SKIP_FIRST_RUN.includes(topCmd)) {
|
|
1814
|
-
await checkFirstRun(
|
|
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.
|
|
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": "
|
|
16
|
+
"tag": "beta"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsup",
|