@lark-project/meegle 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -57
- package/dist/bin/meegle.js +393 -160
- 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) => {
|
|
@@ -738,45 +825,57 @@ async function startDeviceCodeInit(host, customHeaders) {
|
|
|
738
825
|
client_id: client.client_id
|
|
739
826
|
};
|
|
740
827
|
}
|
|
741
|
-
async function
|
|
828
|
+
async function pollDeviceCodeOnce(options) {
|
|
742
829
|
const metadata = await fetchOAuthMetadata(options.host, options.customHeaders);
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
if (data.access_token) {
|
|
759
|
-
return {
|
|
830
|
+
const tokenRes = await fetch(metadata.token_endpoint, {
|
|
831
|
+
method: "POST",
|
|
832
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", ...options.customHeaders },
|
|
833
|
+
body: new URLSearchParams({
|
|
834
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
835
|
+
device_code: options.device_code,
|
|
836
|
+
client_id: options.client_id
|
|
837
|
+
})
|
|
838
|
+
});
|
|
839
|
+
const tokenRaw = await tokenRes.json();
|
|
840
|
+
const data = tokenRaw.access_token || tokenRaw.error ? tokenRaw : tokenRaw.data ?? tokenRaw;
|
|
841
|
+
if (data.access_token) {
|
|
842
|
+
return {
|
|
843
|
+
status: "ok",
|
|
844
|
+
token_data: {
|
|
760
845
|
accessToken: data.access_token,
|
|
761
846
|
refreshToken: data.refresh_token,
|
|
762
847
|
clientId: options.client_id,
|
|
763
848
|
expiresAt: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
|
|
764
|
-
}
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
if (data.error === "slow_down") return { status: "slow_down" };
|
|
853
|
+
if (data.error === "authorization_pending") return { status: "authorization_pending" };
|
|
854
|
+
if (data.error === "expired_token") return { status: "expired_token" };
|
|
855
|
+
throw new ServerError(
|
|
856
|
+
`Device Code \u6388\u6743\u5931\u8D25: ${data.error ?? "\u672A\u77E5\u9519\u8BEF"}`,
|
|
857
|
+
"DEVICE_CODE_FAILED"
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
async function startDeviceCodePoll(options) {
|
|
861
|
+
let interval = options.interval * 1e3;
|
|
862
|
+
const deadline = Date.now() + options.expires_in * 1e3;
|
|
863
|
+
while (Date.now() < deadline) {
|
|
864
|
+
await sleep(interval);
|
|
865
|
+
const result = await pollDeviceCodeOnce(options);
|
|
866
|
+
if (result.status === "ok" && result.token_data) {
|
|
867
|
+
return result.token_data;
|
|
765
868
|
}
|
|
766
|
-
if (
|
|
869
|
+
if (result.status === "slow_down") {
|
|
767
870
|
interval += 5e3;
|
|
768
871
|
continue;
|
|
769
872
|
}
|
|
770
|
-
if (
|
|
873
|
+
if (result.status === "authorization_pending") {
|
|
771
874
|
continue;
|
|
772
875
|
}
|
|
773
|
-
if (
|
|
876
|
+
if (result.status === "expired_token") {
|
|
774
877
|
throw new ClientError("\u6388\u6743\u5DF2\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C meegle auth login --device-code", "DEVICE_CODE_EXPIRED");
|
|
775
878
|
}
|
|
776
|
-
throw new ServerError(
|
|
777
|
-
`Device Code \u6388\u6743\u5931\u8D25: ${data.error ?? "\u672A\u77E5\u9519\u8BEF"}`,
|
|
778
|
-
"DEVICE_CODE_FAILED"
|
|
779
|
-
);
|
|
780
879
|
}
|
|
781
880
|
throw new ClientError("\u6388\u6743\u5DF2\u8D85\u65F6\uFF0C\u8BF7\u91CD\u65B0\u6267\u884C meegle auth login --device-code", "DEVICE_CODE_EXPIRED");
|
|
782
881
|
}
|
|
@@ -866,6 +965,9 @@ async function checkFirstRun(profileName) {
|
|
|
866
965
|
process.exit(0);
|
|
867
966
|
}
|
|
868
967
|
|
|
968
|
+
// src/commands/auth/login.ts
|
|
969
|
+
init_errors();
|
|
970
|
+
|
|
869
971
|
// src/output/table.ts
|
|
870
972
|
import Table from "cli-table3";
|
|
871
973
|
function renderTable(data) {
|
|
@@ -957,7 +1059,7 @@ async function resolveHost(profile, hostFlag) {
|
|
|
957
1059
|
return host;
|
|
958
1060
|
}
|
|
959
1061
|
function registerLogin(auth) {
|
|
960
|
-
auth.command("login").description("\u767B\u5F55\u98DE\u4E66\u9879\u76EE").option("--device-code", "\u4F7F\u7528 Device Code \u6D41\u7A0B\uFF08\u65E0\u6D4F\u89C8\u5668\u73AF\u5883\uFF09").option("--phase <phase>", "Device Code \u9636\u6BB5: init \u6216 poll").option("--host <host>", "\u6307\u5B9A\u7AD9\u70B9\u57DF\u540D\uFF08\u8DF3\u8FC7\u4EA4\u4E92\u5F0F\u9009\u62E9\uFF09").option("--device-code-value <code>", "poll \u9636\u6BB5\u7684 device_code").option("--client-id <id>", "poll \u9636\u6BB5\u7684 client_id").option("--interval <seconds>", "poll \u8F6E\u8BE2\u95F4\u9694\uFF08\u79D2\uFF09", "5").option("--expires-in <seconds>", "poll \u8D85\u65F6\u65F6\u95F4\uFF08\u79D2\uFF09", "600").option("--format <format>", "\u8F93\u51FA\u683C\u5F0F: json, table, ndjson", "table").action(async (options) => {
|
|
1062
|
+
auth.command("login").description("\u767B\u5F55\u98DE\u4E66\u9879\u76EE").option("--device-code", "\u4F7F\u7528 Device Code \u6D41\u7A0B\uFF08\u65E0\u6D4F\u89C8\u5668\u73AF\u5883\uFF09").option("--phase <phase>", "Device Code \u9636\u6BB5: init \u6216 poll").option("--host <host>", "\u6307\u5B9A\u7AD9\u70B9\u57DF\u540D\uFF08\u8DF3\u8FC7\u4EA4\u4E92\u5F0F\u9009\u62E9\uFF09").option("--device-code-value <code>", "poll \u9636\u6BB5\u7684 device_code").option("--client-id <id>", "poll \u9636\u6BB5\u7684 client_id").option("--interval <seconds>", "poll \u8F6E\u8BE2\u95F4\u9694\uFF08\u79D2\uFF09", "5").option("--expires-in <seconds>", "poll \u8D85\u65F6\u65F6\u95F4\uFF08\u79D2\uFF09", "600").option("--once", "poll \u9636\u6BB5\u5355\u6B21\u5C1D\u8BD5\uFF0C\u7ACB\u5373\u8FD4\u56DE\u7ED3\u679C\uFF08\u4E0D\u963B\u585E\uFF09").option("--format <format>", "\u8F93\u51FA\u683C\u5F0F: json, table, ndjson", "table").action(async (options) => {
|
|
961
1063
|
const profileName = auth.parent?.opts().profile ?? await getCurrentProfileName();
|
|
962
1064
|
const format = validateFormat(options.format);
|
|
963
1065
|
const config = await loadConfig(profileName);
|
|
@@ -978,6 +1080,23 @@ function registerLogin(auth) {
|
|
|
978
1080
|
throw new ClientError("--phase poll \u9700\u8981 --device-code-value \u548C --client-id", "MISSING_OPTION");
|
|
979
1081
|
}
|
|
980
1082
|
const host2 = await resolveHost(profileName, options.host);
|
|
1083
|
+
if (options.once) {
|
|
1084
|
+
const result = await pollDeviceCodeOnce({
|
|
1085
|
+
host: host2,
|
|
1086
|
+
device_code: options.deviceCodeValue,
|
|
1087
|
+
client_id: options.clientId,
|
|
1088
|
+
customHeaders: config.headers
|
|
1089
|
+
});
|
|
1090
|
+
if (result.status === "ok" && result.token_data) {
|
|
1091
|
+
const store3 = await createTokenStore(profileName);
|
|
1092
|
+
const tokenManager3 = new TokenManager(store3, host2);
|
|
1093
|
+
await tokenManager3.saveToken(result.token_data);
|
|
1094
|
+
console.log(formatOutput({ status: "ok", message: "\u767B\u5F55\u6210\u529F" }, format));
|
|
1095
|
+
} else {
|
|
1096
|
+
console.log(formatOutput({ status: result.status }, format));
|
|
1097
|
+
}
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
981
1100
|
const tokenData2 = await startDeviceCodePoll({
|
|
982
1101
|
host: host2,
|
|
983
1102
|
device_code: options.deviceCodeValue,
|
|
@@ -1002,9 +1121,12 @@ function registerLogin(auth) {
|
|
|
1002
1121
|
}
|
|
1003
1122
|
|
|
1004
1123
|
// src/commands/auth/logout.ts
|
|
1124
|
+
init_config();
|
|
1005
1125
|
function registerLogout(auth) {
|
|
1006
1126
|
auth.command("logout").description("\u9000\u51FA\u767B\u5F55\uFF0C\u6E05\u9664\u672C\u5730\u51ED\u636E").action(async () => {
|
|
1007
|
-
const
|
|
1127
|
+
const profileName = auth.parent?.opts().profile;
|
|
1128
|
+
const profile = profileName ?? await getCurrentProfileName();
|
|
1129
|
+
const store = await createTokenStore(profile);
|
|
1008
1130
|
await store.clear();
|
|
1009
1131
|
console.log("\u2713 \u5DF2\u9000\u51FA\u767B\u5F55");
|
|
1010
1132
|
});
|
|
@@ -1215,6 +1337,7 @@ function registerConfigCommands(program) {
|
|
|
1215
1337
|
}
|
|
1216
1338
|
|
|
1217
1339
|
// src/dynamic/params-parser.ts
|
|
1340
|
+
init_errors();
|
|
1218
1341
|
function parseSetFlag(input4) {
|
|
1219
1342
|
const eqIndex = input4.indexOf("=");
|
|
1220
1343
|
if (eqIndex === -1) {
|
|
@@ -1227,7 +1350,8 @@ function parseSetFlag(input4) {
|
|
|
1227
1350
|
const rawValue = input4.slice(eqIndex + 1);
|
|
1228
1351
|
let value;
|
|
1229
1352
|
try {
|
|
1230
|
-
|
|
1353
|
+
const parsed = JSON.parse(rawValue);
|
|
1354
|
+
value = typeof parsed === "object" && parsed !== null ? parsed : rawValue;
|
|
1231
1355
|
} catch {
|
|
1232
1356
|
value = rawValue;
|
|
1233
1357
|
}
|
|
@@ -1303,6 +1427,9 @@ function resolveDotPath(current, segments) {
|
|
|
1303
1427
|
return current;
|
|
1304
1428
|
}
|
|
1305
1429
|
|
|
1430
|
+
// src/dynamic/response.ts
|
|
1431
|
+
init_errors();
|
|
1432
|
+
|
|
1306
1433
|
// src/core/logger.ts
|
|
1307
1434
|
import chalk from "chalk";
|
|
1308
1435
|
var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
@@ -1334,6 +1461,11 @@ function unwrapResponse(raw) {
|
|
|
1334
1461
|
const response = raw;
|
|
1335
1462
|
const content = response.content;
|
|
1336
1463
|
if (!Array.isArray(content)) return void 0;
|
|
1464
|
+
if (response.isError) {
|
|
1465
|
+
const allTexts = content.filter((e) => e.type === "text" && e.text).map((e) => e.text);
|
|
1466
|
+
const message = allTexts.join("\n") || "\u670D\u52A1\u7AEF\u8FD4\u56DE\u9519\u8BEF";
|
|
1467
|
+
throw new ServerError(message, "SERVER_CALL_FAILED");
|
|
1468
|
+
}
|
|
1337
1469
|
const dataTexts = [];
|
|
1338
1470
|
for (const entry of content) {
|
|
1339
1471
|
if (entry.type !== "text" || !entry.text) continue;
|
|
@@ -1343,10 +1475,6 @@ function unwrapResponse(raw) {
|
|
|
1343
1475
|
dataTexts.push(entry.text);
|
|
1344
1476
|
}
|
|
1345
1477
|
}
|
|
1346
|
-
if (response.isError) {
|
|
1347
|
-
const message = dataTexts.join("\n") || "\u670D\u52A1\u7AEF\u8FD4\u56DE\u9519\u8BEF";
|
|
1348
|
-
throw new ServerError(message, "SERVER_CALL_FAILED");
|
|
1349
|
-
}
|
|
1350
1478
|
if (dataTexts.length === 0) return void 0;
|
|
1351
1479
|
const text = dataTexts[0];
|
|
1352
1480
|
try {
|
|
@@ -1357,6 +1485,21 @@ function unwrapResponse(raw) {
|
|
|
1357
1485
|
}
|
|
1358
1486
|
|
|
1359
1487
|
// src/dynamic/register.ts
|
|
1488
|
+
init_errors();
|
|
1489
|
+
var RESOURCE_DESCRIPTIONS = {
|
|
1490
|
+
workitem: "\u5DE5\u4F5C\u9879\u57DF \u2014 \u5DE5\u4F5C\u9879\u5B9E\u4F8B\u7684 CRUD \u548C\u6A21\u578B\u914D\u7F6E\uFF0C\u901A\u8FC7 work-item-id \u6807\u8BC6",
|
|
1491
|
+
workflow: "\u5DE5\u4F5C\u6D41\u57DF \u2014 \u8282\u70B9\u6D41\u8F6C\u4E0E\u72B6\u6001\u6D41\u8F6C\uFF0C\u4F9D\u8D56 workitem \u7684 work-item-id",
|
|
1492
|
+
subtask: "\u5B50\u4EFB\u52A1 \u2014 \u8282\u70B9\u4E0B\u7684\u5B50\u4EFB\u52A1\u64CD\u4F5C\uFF0C\u4F9D\u8D56 workflow \u7684 node-id",
|
|
1493
|
+
comment: "\u8BC4\u8BBA\u57DF \u2014 \u8DE8\u5B9E\u4F53\u8BC4\u8BBA\uFF0C\u5F53\u524D\u901A\u8FC7 work-item-id \u5173\u8054",
|
|
1494
|
+
workhour: "\u5DE5\u65F6\u57DF \u2014 \u5DE5\u65F6\u8BB0\u5F55\u4E0E\u6392\u671F\uFF0C\u4F9D\u8D56 workitem \u7684 work-item-id",
|
|
1495
|
+
relation: "\u5173\u7CFB\u57DF \u2014 \u5DE5\u4F5C\u9879\u4E4B\u95F4\u7684\u5173\u8054\u5173\u7CFB\u5B9A\u4E49\u4E0E\u67E5\u8BE2",
|
|
1496
|
+
mywork: "\u5DE5\u4F5C\u53F0\u57DF \u2014 \u8DE8\u7A7A\u95F4\u7684\u4E2A\u4EBA\u5F85\u529E/\u5DF2\u529E",
|
|
1497
|
+
view: "\u89C6\u56FE\u57DF \u2014 \u89C6\u56FE\u7684\u521B\u5EFA\u3001\u67E5\u770B\u3001\u66F4\u65B0\u3001\u641C\u7D22",
|
|
1498
|
+
chart: "\u5EA6\u91CF\u57DF \u2014 \u56FE\u8868\u67E5\u770B\u4E0E\u5217\u8868\uFF0C\u4F9D\u8D56 view \u7684 view-id",
|
|
1499
|
+
team: "\u4EBA\u5458\u57DF \u2014 \u56E2\u961F\u5217\u8868\u4E0E\u6210\u5458\u67E5\u8BE2",
|
|
1500
|
+
user: "\u4EBA\u5458\u57DF \u2014 \u7528\u6237\u641C\u7D22\uFF0C\u652F\u6301\u540D\u79F0\u6A21\u7CCA\u5339\u914D",
|
|
1501
|
+
project: "\u7A7A\u95F4\u57DF \u2014 \u7A7A\u95F4\u4FE1\u606F\u641C\u7D22"
|
|
1502
|
+
};
|
|
1360
1503
|
var registeredCommands = [];
|
|
1361
1504
|
function registerDynamicCommands(program, commands, client) {
|
|
1362
1505
|
registeredCommands = commands;
|
|
@@ -1367,7 +1510,7 @@ function registerDynamicCommands(program, commands, client) {
|
|
|
1367
1510
|
groups.set(cmd.resource, list);
|
|
1368
1511
|
}
|
|
1369
1512
|
for (const [resource, cmds] of groups) {
|
|
1370
|
-
const group = program.command(resource).description(`${resource} \u64CD\u4F5C`);
|
|
1513
|
+
const group = program.command(resource).description(RESOURCE_DESCRIPTIONS[resource] ?? `${resource} \u64CD\u4F5C`);
|
|
1371
1514
|
for (const cmd of cmds) {
|
|
1372
1515
|
const flagSummary = buildFlagSummary(cmd);
|
|
1373
1516
|
const sub = group.command(cmd.method).description(`${cmd.description ?? ""} ${flagSummary}`);
|
|
@@ -1375,22 +1518,25 @@ function registerDynamicCommands(program, commands, client) {
|
|
|
1375
1518
|
if (param.name === "url") continue;
|
|
1376
1519
|
const kebab = param.name.replace(/_/g, "-");
|
|
1377
1520
|
let desc = param.description ?? "";
|
|
1378
|
-
if (param.
|
|
1379
|
-
if (param.type === "
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1521
|
+
if (param.required) desc = `[\u5FC5\u586B] ${desc}`;
|
|
1522
|
+
if (param.type === "array") {
|
|
1523
|
+
const itemType = param.items?.type;
|
|
1524
|
+
if (itemType === "object") {
|
|
1525
|
+
desc += "\uFF08\u591A\u4E2A JSON \u5BF9\u8C61\uFF0C\u91CD\u590D\u4F20\u5165\uFF09";
|
|
1526
|
+
} else if (itemType === "number") {
|
|
1527
|
+
desc += "\uFF08\u591A\u4E2A\u6570\u5B57\uFF0C\u9017\u53F7\u5206\u9694\u6216\u91CD\u590D\u4F20\u5165\uFF09";
|
|
1384
1528
|
} else {
|
|
1385
|
-
|
|
1529
|
+
desc += "\uFF08\u591A\u4E2A\u503C\uFF0C\u9017\u53F7\u5206\u9694\u6216\u91CD\u590D\u4F20\u5165\uFF09";
|
|
1386
1530
|
}
|
|
1531
|
+
}
|
|
1532
|
+
if (param.type === "object") desc += "\uFF08JSON \u683C\u5F0F\uFF09";
|
|
1533
|
+
const typeLabel = formatTypeLabel(param);
|
|
1534
|
+
if (param.type === "boolean") {
|
|
1535
|
+
sub.option(`--${kebab}`, desc);
|
|
1536
|
+
} else if (param.type === "array") {
|
|
1537
|
+
sub.option(`--${kebab} <${typeLabel}>`, desc, collect, []);
|
|
1387
1538
|
} else {
|
|
1388
|
-
|
|
1389
|
-
if (param.required) {
|
|
1390
|
-
sub.requiredOption(flag, desc);
|
|
1391
|
-
} else {
|
|
1392
|
-
sub.option(flag, desc);
|
|
1393
|
-
}
|
|
1539
|
+
sub.option(`--${kebab} <${typeLabel}>`, desc);
|
|
1394
1540
|
}
|
|
1395
1541
|
}
|
|
1396
1542
|
sub.option("--params <json>", "\u5B8C\u6574 JSON \u53C2\u6570\uFF08--set \u548C flag \u4F1A\u8986\u76D6\u540C\u540D\u5B57\u6BB5\uFF09");
|
|
@@ -1404,10 +1550,23 @@ function registerDynamicCommands(program, commands, client) {
|
|
|
1404
1550
|
const camelKey = snakeToCamel(param.name);
|
|
1405
1551
|
const value = cliFlags[camelKey];
|
|
1406
1552
|
if (value === void 0) continue;
|
|
1407
|
-
if (param.type === "array"
|
|
1408
|
-
|
|
1553
|
+
if (param.type === "array") {
|
|
1554
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
1555
|
+
const itemType = param.items?.type;
|
|
1556
|
+
const expanded = itemType === "object" ? arr : arr.flatMap((s) => s.split(",").map((v) => v.trim()));
|
|
1557
|
+
const filtered = expanded.filter((s) => s !== "");
|
|
1558
|
+
if (filtered.length > 0) {
|
|
1559
|
+
snakeFlags[param.name] = convertArrayItems(filtered, itemType);
|
|
1560
|
+
}
|
|
1409
1561
|
} else if (param.type === "number" && typeof value === "string") {
|
|
1410
|
-
|
|
1562
|
+
const num = Number(value);
|
|
1563
|
+
if (Number.isNaN(num)) {
|
|
1564
|
+
throw new ClientError(
|
|
1565
|
+
`\u53C2\u6570 --${param.name.replace(/_/g, "-")} \u9700\u8981\u6570\u5B57\uFF0C\u6536\u5230: "${value}"`,
|
|
1566
|
+
"CLIENT_INVALID_TYPE"
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
1569
|
+
snakeFlags[param.name] = num;
|
|
1411
1570
|
} else {
|
|
1412
1571
|
snakeFlags[param.name] = value;
|
|
1413
1572
|
}
|
|
@@ -1415,6 +1574,14 @@ function registerDynamicCommands(program, commands, client) {
|
|
|
1415
1574
|
const setFields = (rawSet ?? []).map(parseSetFlag);
|
|
1416
1575
|
const parsedParams = rawParams ? parseParams(rawParams) : void 0;
|
|
1417
1576
|
const finalParams = mergeParams(setFields, parsedParams, snakeFlags);
|
|
1577
|
+
const missing = cmd.parameters.filter((p) => p.required && p.name !== "url").filter((p) => finalParams[p.name] === void 0 || finalParams[p.name] === null).map((p) => `--${p.name.replace(/_/g, "-")}`);
|
|
1578
|
+
if (missing.length > 0) {
|
|
1579
|
+
throw new ClientError(
|
|
1580
|
+
`\u7F3A\u5C11\u5FC5\u586B\u53C2\u6570: ${missing.join(", ")}`,
|
|
1581
|
+
"CLIENT_MISSING_REQUIRED",
|
|
1582
|
+
`\u4F7F\u7528 meegle inspect ${cmd.resource}.${cmd.method} \u67E5\u770B\u53C2\u6570\u8BE6\u60C5`
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1418
1585
|
const raw = await client.callTool(cmd.toolName, finalParams);
|
|
1419
1586
|
let result = unwrapResponse(raw);
|
|
1420
1587
|
if (result !== void 0) {
|
|
@@ -1434,8 +1601,41 @@ function registerDynamicCommands(program, commands, client) {
|
|
|
1434
1601
|
function collect(value, previous) {
|
|
1435
1602
|
return [...previous, value];
|
|
1436
1603
|
}
|
|
1604
|
+
function formatTypeLabel(param) {
|
|
1605
|
+
if (param.type === "array" && param.items?.type) {
|
|
1606
|
+
return `array<${param.items.type}>`;
|
|
1607
|
+
}
|
|
1608
|
+
return param.type;
|
|
1609
|
+
}
|
|
1610
|
+
function convertArrayItems(values, itemType) {
|
|
1611
|
+
if (itemType === "number") {
|
|
1612
|
+
return values.map((v) => {
|
|
1613
|
+
const n = Number(v);
|
|
1614
|
+
if (Number.isNaN(n)) {
|
|
1615
|
+
throw new ClientError(
|
|
1616
|
+
`\u6570\u7EC4\u5143\u7D20\u9700\u8981\u6570\u5B57\uFF0C\u6536\u5230: "${v}"`,
|
|
1617
|
+
"CLIENT_INVALID_TYPE"
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
return n;
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
if (itemType === "object") {
|
|
1624
|
+
return values.map((v) => {
|
|
1625
|
+
try {
|
|
1626
|
+
return JSON.parse(v);
|
|
1627
|
+
} catch {
|
|
1628
|
+
throw new ClientError(
|
|
1629
|
+
`\u65E0\u6CD5\u89E3\u6790 JSON: ${v}`,
|
|
1630
|
+
"CLIENT_INVALID_JSON"
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
return values;
|
|
1636
|
+
}
|
|
1437
1637
|
function snakeToCamel(s) {
|
|
1438
|
-
return s.replace(/
|
|
1638
|
+
return s.replace(/_(\w)/g, (_, c) => c.toUpperCase());
|
|
1439
1639
|
}
|
|
1440
1640
|
function buildFlagSummary(cmd) {
|
|
1441
1641
|
const flags = [];
|
|
@@ -1476,7 +1676,7 @@ function registerInspectCommand(program) {
|
|
|
1476
1676
|
}
|
|
1477
1677
|
const params = cmd.parameters.filter((p) => p.name !== "url").map((p) => ({
|
|
1478
1678
|
flag: `--${p.name.replace(/_/g, "-")}`,
|
|
1479
|
-
type: p
|
|
1679
|
+
type: formatTypeLabel(p),
|
|
1480
1680
|
required: p.required ?? false,
|
|
1481
1681
|
description: p.description ?? ""
|
|
1482
1682
|
}));
|
|
@@ -1526,62 +1726,52 @@ function registerStaticCommands(program) {
|
|
|
1526
1726
|
registerInspectCommand(program);
|
|
1527
1727
|
}
|
|
1528
1728
|
|
|
1529
|
-
// src/helpers/todo.ts
|
|
1530
|
-
function registerTodo(program) {
|
|
1531
|
-
program.command("+todo").description("\u67E5\u770B\u672C\u5468\u4E2A\u4EBA\u5F85\u529E").option("--project-key <key>", "\u9879\u76EE\u6807\u8BC6").action(async (options) => {
|
|
1532
|
-
console.log("Fetching todos...", options.projectKey ?? "(all projects)");
|
|
1533
|
-
});
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
// src/helpers/index.ts
|
|
1537
|
-
function registerHelperCommands(program) {
|
|
1538
|
-
registerTodo(program);
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
1729
|
// src/dynamic/mapper.ts
|
|
1542
1730
|
var FALLBACK_TABLE = {
|
|
1543
|
-
//
|
|
1731
|
+
// WorkItem 工作项域 (9)
|
|
1544
1732
|
create_workitem: { resource: "workitem", method: "create", hasFields: true },
|
|
1545
|
-
get_workitem_brief: { resource: "workitem", method: "
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
get_workitem_op_record: { resource: "workitem", method: "op-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1733
|
+
get_workitem_brief: { resource: "workitem", method: "get" },
|
|
1734
|
+
update_field: { resource: "workitem", method: "update", hasFields: true },
|
|
1735
|
+
search_by_mql: { resource: "workitem", method: "query" },
|
|
1736
|
+
get_workitem_op_record: { resource: "workitem", method: "list-op-records" },
|
|
1737
|
+
list_workitem_types: { resource: "workitem", method: "meta-types" },
|
|
1738
|
+
get_workitem_field_meta: { resource: "workitem", method: "meta-create-fields" },
|
|
1739
|
+
list_workitem_field_config: { resource: "workitem", method: "meta-fields" },
|
|
1740
|
+
list_workitem_role_config: { resource: "workitem", method: "meta-roles" },
|
|
1741
|
+
// WorkFlow 工作流域 (6)
|
|
1742
|
+
transition_node: { resource: "workflow", method: "transition" },
|
|
1743
|
+
get_node_detail: { resource: "workflow", method: "get-node" },
|
|
1744
|
+
update_node: { resource: "workflow", method: "update-node", hasFields: true },
|
|
1745
|
+
list_node_field_config: { resource: "workflow", method: "meta-node-fields" },
|
|
1746
|
+
get_transitable_states: { resource: "workflow", method: "list-state-transitions" },
|
|
1747
|
+
get_transition_required: { resource: "workflow", method: "list-state-required" },
|
|
1748
|
+
// SubTask 子任务 (1)
|
|
1749
|
+
update_node_subtask: { resource: "subtask", method: "update", hasFields: true },
|
|
1750
|
+
// Comment 评论域 (2)
|
|
1751
|
+
add_comment: { resource: "comment", method: "add" },
|
|
1752
|
+
list_workitem_comments: { resource: "comment", method: "list" },
|
|
1753
|
+
// WorkHour 工时域 (2)
|
|
1754
|
+
get_workitem_man_hour_records: { resource: "workhour", method: "list-records" },
|
|
1755
|
+
list_schedule: { resource: "workhour", method: "list-schedule" },
|
|
1756
|
+
// Relation 关系域 (2)
|
|
1757
|
+
list_workitem_relations: { resource: "relation", method: "meta-definitions" },
|
|
1758
|
+
list_related_workitems: { resource: "relation", method: "list" },
|
|
1759
|
+
// MyWork 工作台域 (1)
|
|
1760
|
+
list_todo: { resource: "mywork", method: "todo" },
|
|
1761
|
+
// View 视图域 (4)
|
|
1762
|
+
create_fixed_view: { resource: "view", method: "create-fixed" },
|
|
1763
|
+
get_view_detail: { resource: "view", method: "get" },
|
|
1764
|
+
update_fixed_view: { resource: "view", method: "update-fixed" },
|
|
1570
1765
|
search_view_by_title: { resource: "view", method: "search" },
|
|
1571
|
-
//
|
|
1572
|
-
get_chart_detail: { resource: "chart", method: "
|
|
1766
|
+
// Chart 度量域 (2)
|
|
1767
|
+
get_chart_detail: { resource: "chart", method: "get" },
|
|
1573
1768
|
list_charts: { resource: "chart", method: "list" },
|
|
1574
|
-
//
|
|
1769
|
+
// UserGroup 人员域 (3)
|
|
1575
1770
|
list_project_team: { resource: "team", method: "list" },
|
|
1576
|
-
list_team_members: { resource: "team", method: "members" },
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
list_schedule: { resource: "schedule", method: "list" },
|
|
1581
|
-
// project (1)
|
|
1582
|
-
search_project_info: { resource: "project", method: "info" },
|
|
1583
|
-
// user (1)
|
|
1584
|
-
search_user_info: { resource: "user", method: "info" }
|
|
1771
|
+
list_team_members: { resource: "team", method: "list-members" },
|
|
1772
|
+
search_user_info: { resource: "user", method: "search" },
|
|
1773
|
+
// Project 空间域 (1)
|
|
1774
|
+
search_project_info: { resource: "project", method: "search" }
|
|
1585
1775
|
};
|
|
1586
1776
|
function mapTool(tool) {
|
|
1587
1777
|
if (tool.metadata?.resource && tool.metadata?.method) {
|
|
@@ -1604,6 +1794,7 @@ function mapTool(tool) {
|
|
|
1604
1794
|
hasFields: fallback.hasFields
|
|
1605
1795
|
};
|
|
1606
1796
|
}
|
|
1797
|
+
logger.debug(`\u672A\u77E5 tool "${tool.name}" \u4E0D\u5728\u6620\u5C04\u8868\u4E2D\uFF0C\u4F7F\u7528 name parsing \u56DE\u9000`);
|
|
1607
1798
|
const parts = tool.name.split("_");
|
|
1608
1799
|
const resource = parts[0];
|
|
1609
1800
|
const method = parts.slice(1).join("-");
|
|
@@ -1620,6 +1811,7 @@ function mapTools(tools) {
|
|
|
1620
1811
|
}
|
|
1621
1812
|
|
|
1622
1813
|
// src/dynamic/cache.ts
|
|
1814
|
+
init_profile_validation();
|
|
1623
1815
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
1624
1816
|
import { join as join3, dirname } from "path";
|
|
1625
1817
|
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -1627,6 +1819,7 @@ var ToolCache = class {
|
|
|
1627
1819
|
filePath;
|
|
1628
1820
|
ttlMs;
|
|
1629
1821
|
constructor(cacheDir, profile = "default", ttlMs = DEFAULT_TTL_MS) {
|
|
1822
|
+
validateProfileName(profile);
|
|
1630
1823
|
const filename = profile === "default" ? "tools.json" : `tools-${profile}.json`;
|
|
1631
1824
|
this.filePath = join3(cacheDir, filename);
|
|
1632
1825
|
this.ttlMs = ttlMs;
|
|
@@ -1635,6 +1828,9 @@ var ToolCache = class {
|
|
|
1635
1828
|
try {
|
|
1636
1829
|
const raw = await readFile3(this.filePath, "utf-8");
|
|
1637
1830
|
const data = JSON.parse(raw);
|
|
1831
|
+
if (!Array.isArray(data.tools) || typeof data.timestamp !== "number") {
|
|
1832
|
+
return null;
|
|
1833
|
+
}
|
|
1638
1834
|
const stale = Date.now() - data.timestamp > this.ttlMs;
|
|
1639
1835
|
return { tools: data.tools, stale };
|
|
1640
1836
|
} catch {
|
|
@@ -1659,12 +1855,16 @@ function convertTool(entry) {
|
|
|
1659
1855
|
const props = entry.inputSchema?.properties ?? {};
|
|
1660
1856
|
const required = new Set(entry.inputSchema?.required ?? []);
|
|
1661
1857
|
for (const [name, schema] of Object.entries(props)) {
|
|
1662
|
-
|
|
1858
|
+
const param = {
|
|
1663
1859
|
name,
|
|
1664
1860
|
type: schema.type ?? "string",
|
|
1665
1861
|
description: schema.description,
|
|
1666
1862
|
required: required.has(name)
|
|
1667
|
-
}
|
|
1863
|
+
};
|
|
1864
|
+
if (schema.type === "array" && schema.items?.type) {
|
|
1865
|
+
param.items = { type: schema.items.type };
|
|
1866
|
+
}
|
|
1867
|
+
parameters.push(param);
|
|
1668
1868
|
}
|
|
1669
1869
|
return {
|
|
1670
1870
|
name: entry.name,
|
|
@@ -1675,13 +1875,15 @@ function convertTool(entry) {
|
|
|
1675
1875
|
|
|
1676
1876
|
// src/cli.ts
|
|
1677
1877
|
init_config();
|
|
1878
|
+
init_errors();
|
|
1678
1879
|
|
|
1679
1880
|
// src/core/mcp-client.ts
|
|
1680
|
-
|
|
1881
|
+
init_errors();
|
|
1681
1882
|
var McpClient = class {
|
|
1682
1883
|
baseUrl;
|
|
1683
1884
|
tokenManager;
|
|
1684
1885
|
customHeaders;
|
|
1886
|
+
requestId = 0;
|
|
1685
1887
|
constructor(baseUrl, tokenManager, customHeaders) {
|
|
1686
1888
|
this.baseUrl = baseUrl;
|
|
1687
1889
|
this.tokenManager = tokenManager;
|
|
@@ -1691,7 +1893,7 @@ var McpClient = class {
|
|
|
1691
1893
|
try {
|
|
1692
1894
|
return await this._doCall(method, params);
|
|
1693
1895
|
} catch (err) {
|
|
1694
|
-
if (err instanceof
|
|
1896
|
+
if (err instanceof HttpError && err.httpStatus === 401) {
|
|
1695
1897
|
const store = this.tokenManager.getStore();
|
|
1696
1898
|
const tokenData = await store.load();
|
|
1697
1899
|
if (tokenData) {
|
|
@@ -1707,10 +1909,11 @@ var McpClient = class {
|
|
|
1707
1909
|
}
|
|
1708
1910
|
async _doCall(method, params) {
|
|
1709
1911
|
const token = await this.tokenManager.getToken();
|
|
1710
|
-
const id = ++requestId;
|
|
1912
|
+
const id = ++this.requestId;
|
|
1711
1913
|
const body = { jsonrpc: "2.0", id, method, params };
|
|
1712
1914
|
const headers = {
|
|
1713
1915
|
"Content-Type": "application/json",
|
|
1916
|
+
"User-Agent": "meegle-cli",
|
|
1714
1917
|
...this.customHeaders
|
|
1715
1918
|
};
|
|
1716
1919
|
if (token) {
|
|
@@ -1722,9 +1925,10 @@ var McpClient = class {
|
|
|
1722
1925
|
body: JSON.stringify(body)
|
|
1723
1926
|
});
|
|
1724
1927
|
if (!response.ok) {
|
|
1725
|
-
throw new
|
|
1928
|
+
throw new HttpError(
|
|
1726
1929
|
`\u670D\u52A1\u7AEF\u8FD4\u56DE\u9519\u8BEF (${response.status})`,
|
|
1727
|
-
"SERVER_HTTP_ERROR"
|
|
1930
|
+
"SERVER_HTTP_ERROR",
|
|
1931
|
+
response.status
|
|
1728
1932
|
);
|
|
1729
1933
|
}
|
|
1730
1934
|
const data = await response.json();
|
|
@@ -1771,23 +1975,53 @@ function getCommandChain(cmd) {
|
|
|
1771
1975
|
}
|
|
1772
1976
|
return chain;
|
|
1773
1977
|
}
|
|
1978
|
+
function extractProfileFromArgv(argv) {
|
|
1979
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1980
|
+
if (argv[i] === "--profile" && i + 1 < argv.length) {
|
|
1981
|
+
return argv[i + 1];
|
|
1982
|
+
}
|
|
1983
|
+
if (argv[i]?.startsWith("--profile=")) {
|
|
1984
|
+
return argv[i].slice("--profile=".length);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
return void 0;
|
|
1988
|
+
}
|
|
1774
1989
|
async function createProgram() {
|
|
1775
1990
|
const program = new Command();
|
|
1776
|
-
program.name("meegle").description(
|
|
1991
|
+
program.name("meegle").description(`Agent-First CLI for Meegle (Lark Project)
|
|
1992
|
+
|
|
1993
|
+
\u9886\u57DF\u6A21\u578B:
|
|
1994
|
+
workitem\uFF08\u5DE5\u4F5C\u9879\uFF09\u2500 \u6838\u5FC3\u5B9E\u4F53\uFF0C\u901A\u8FC7 work-item-id \u6807\u8BC6
|
|
1995
|
+
\u251C\u2500 workflow\uFF08\u5DE5\u4F5C\u6D41\uFF09\u2500 \u8282\u70B9\u6D41\u8F6C\u4E0E\u72B6\u6001\u6D41\u8F6C\uFF0C\u4F9D\u8D56 work-item-id
|
|
1996
|
+
\u2502 \u2514\u2500 subtask\uFF08\u5B50\u4EFB\u52A1\uFF09\u2500 \u8282\u70B9\u4E0B\u7684\u5B50\u4EFB\u52A1\uFF0C\u4F9D\u8D56 node-id
|
|
1997
|
+
\u251C\u2500 comment\uFF08\u8BC4\u8BBA\uFF09\u2500 \u5DE5\u4F5C\u9879\u7684\u8BC4\u8BBA\uFF0C\u4F9D\u8D56 work-item-id
|
|
1998
|
+
\u251C\u2500 workhour\uFF08\u5DE5\u65F6\uFF09\u2500 \u5DE5\u65F6\u8BB0\u5F55\u4E0E\u6392\u671F\uFF0C\u4F9D\u8D56 work-item-id
|
|
1999
|
+
\u2514\u2500 relation\uFF08\u5173\u7CFB\uFF09\u2500 \u5DE5\u4F5C\u9879\u4E4B\u95F4\u7684\u5173\u8054\u5173\u7CFB
|
|
2000
|
+
|
|
2001
|
+
mywork\uFF08\u5DE5\u4F5C\u53F0\uFF09\u2500 \u8DE8\u7A7A\u95F4\u7684\u4E2A\u4EBA\u5F85\u529E/\u5DF2\u529E
|
|
2002
|
+
view\uFF08\u89C6\u56FE\uFF09\u2500 \u56FA\u5B9A\u89C6\u56FE\u3001\u7B5B\u9009\u89C6\u56FE
|
|
2003
|
+
chart\uFF08\u5EA6\u91CF\uFF09\u2500 \u89C6\u56FE\u4E0B\u7684\u56FE\u8868\uFF0C\u4F9D\u8D56 view-id
|
|
2004
|
+
team / user\uFF08\u4EBA\u5458\uFF09\u2500 \u56E2\u961F\u4E0E\u7528\u6237
|
|
2005
|
+
project\uFF08\u7A7A\u95F4\uFF09\u2500 \u7A7A\u95F4\u4FE1\u606F
|
|
2006
|
+
|
|
2007
|
+
\u5178\u578B\u64CD\u4F5C\u94FE\u8DEF:
|
|
2008
|
+
\u521B\u5EFA\u5DE5\u4F5C\u9879: workitem meta-types \u2192 workitem meta-create-fields \u2192 workitem create
|
|
2009
|
+
\u6D41\u8F6C\u8282\u70B9: workflow get-node \u2192 workflow list-state-transitions \u2192 workflow transition
|
|
2010
|
+
\u67E5\u770B\u5173\u8054: relation meta-definitions \u2192 relation list`).version(version).option("--format <format>", "\u8F93\u51FA\u683C\u5F0F: json | table | ndjson", "json").option("--select <props>", "\u9009\u53D6\u8F93\u51FA\u5C5E\u6027\uFF0C\u9017\u53F7\u5206\u9694").option("--verbose", "\u663E\u793A\u8BE6\u7EC6\u65E5\u5FD7").option("--profile <name>", "\u4F7F\u7528\u6307\u5B9A profile\uFF08\u4E34\u65F6\uFF0C\u4E0D\u6539\u53D8\u5168\u5C40\uFF09").hook("preAction", async (thisCommand, actionCommand) => {
|
|
1777
2011
|
const opts = thisCommand.opts();
|
|
1778
2012
|
if (opts.verbose) {
|
|
1779
2013
|
setLogLevel("debug");
|
|
1780
2014
|
}
|
|
1781
2015
|
const chain = getCommandChain(actionCommand);
|
|
1782
2016
|
const topCmd = chain[0] ?? "";
|
|
1783
|
-
const
|
|
2017
|
+
const profileName2 = thisCommand.opts().profile;
|
|
1784
2018
|
if (!SKIP_FIRST_RUN.includes(topCmd)) {
|
|
1785
|
-
await checkFirstRun(
|
|
2019
|
+
await checkFirstRun(profileName2);
|
|
1786
2020
|
}
|
|
1787
2021
|
});
|
|
1788
2022
|
registerStaticCommands(program);
|
|
2023
|
+
const profileName = extractProfileFromArgv(process.argv);
|
|
1789
2024
|
try {
|
|
1790
|
-
const profileName = program.opts().profile;
|
|
1791
2025
|
const config = await loadConfig(profileName);
|
|
1792
2026
|
const serverUrl = getServerUrl(config);
|
|
1793
2027
|
const currentProfile = profileName ?? await getCurrentProfileName();
|
|
@@ -1819,7 +2053,6 @@ async function createProgram() {
|
|
|
1819
2053
|
} catch (err) {
|
|
1820
2054
|
logger.debug(`\u547D\u4EE4\u52A0\u8F7D\u8DF3\u8FC7: ${err}`);
|
|
1821
2055
|
}
|
|
1822
|
-
registerHelperCommands(program);
|
|
1823
2056
|
return program;
|
|
1824
2057
|
}
|
|
1825
2058
|
async function run() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-project/meegle",
|
|
3
|
-
"version": "0.0.
|
|
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",
|