@dac-tech-team-tw/itsaplan-cli 0.1.0 → 0.2.0
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 +69 -48
- package/dist/cli.js +203 -88
- package/dist/commands.js +183 -23
- package/dist/config.d.ts +9 -0
- package/dist/config.js +62 -2
- package/dist/issues.d.ts +2 -0
- package/dist/issues.js +97 -6
- package/dist/output.d.ts +12 -0
- package/dist/output.js +260 -0
- package/dist/project-config.d.ts +2 -0
- package/dist/project-config.js +56 -0
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command, CommanderError, Option } from "commander";
|
|
2
|
+
import { Command, CommanderError, Help, Option } from "commander";
|
|
3
3
|
import { CliError, CLI_VERSION, API_BASELINE, } from "./contracts.js";
|
|
4
4
|
import { createContext } from "./config.js";
|
|
5
5
|
import { redact } from "./http.js";
|
|
@@ -7,20 +7,81 @@ import { runIssue } from "./issues.js";
|
|
|
7
7
|
import { runDoctor, runAuth, runProject, runInit, runApi } from "./commands.js";
|
|
8
8
|
import { runDocument, runIssueDocument, } from "./documents.js";
|
|
9
9
|
import { runDevelopment } from "./development.js";
|
|
10
|
+
import { runProjectConfig } from "./project-config.js";
|
|
11
|
+
import { validateOutput, projectOutput, needsIssueStates, } from "./output.js";
|
|
10
12
|
const program = new Command()
|
|
11
13
|
.name("itsaplan")
|
|
12
|
-
.description("It's a Plan
|
|
13
|
-
.version(CLI_VERSION)
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
|
|
14
|
+
.description("It's a Plan:探索專案、讀取工作項目、預覽並執行已授權的寫入操作")
|
|
15
|
+
.version(CLI_VERSION, "-V, --version", "顯示版本")
|
|
16
|
+
.helpOption("-h, --help", "顯示指令說明")
|
|
17
|
+
.helpCommand("help [command]", "顯示指令說明")
|
|
18
|
+
.configureHelp({
|
|
19
|
+
showGlobalOptions: true,
|
|
20
|
+
styleTitle(title) {
|
|
21
|
+
return ({
|
|
22
|
+
"Usage:": "用法:",
|
|
23
|
+
"Arguments:": "引數:",
|
|
24
|
+
"Options:": "選項:",
|
|
25
|
+
"Global Options:": "全域選項:",
|
|
26
|
+
"Commands:": "指令:",
|
|
27
|
+
}[title] ?? title);
|
|
28
|
+
},
|
|
29
|
+
optionDescription(option) {
|
|
30
|
+
if (option.long === "--help")
|
|
31
|
+
return "顯示指令說明";
|
|
32
|
+
if (option.long === "--version")
|
|
33
|
+
return "顯示版本";
|
|
34
|
+
const description = Help.prototype.optionDescription.call(this, option);
|
|
35
|
+
return description
|
|
36
|
+
.replace(/\bchoices:/g, "可選值:")
|
|
37
|
+
.replace(/\bdefault:/g, "預設值:")
|
|
38
|
+
.replace(/\bpreset:/g, "預設參數:");
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
.addHelpText("afterAll", `
|
|
42
|
+
全域選項可放在子指令之前或之後;完整說明請見 iap --help。
|
|
43
|
+
指令說明:iap <command> <subcommand> --help。
|
|
44
|
+
專案來源優先序:--project > ITSAPLAN_PROJECT > 最近的 .itsaplan.toml
|
|
45
|
+
> 目前 API 服務網址(含基底路徑)的使用者預設值。
|
|
46
|
+
使用 iap init --project <team.KEY> 為此目錄設定專案。
|
|
47
|
+
使用者預設值:iap config set project <team.KEY>;
|
|
48
|
+
使用 iap config get project 檢視,或以 iap config unset project 清除。`)
|
|
49
|
+
.option("--json", "輸出單一固定格式的 JSON 結果封裝")
|
|
50
|
+
.option("--summary", "精簡 project/issue 讀取資料;不截斷正文,不支援寫入")
|
|
51
|
+
.option("--fields <paths>", "選取 project/issue 讀取欄位(逗號分隔點路徑);與 --summary 互斥")
|
|
52
|
+
.option("--api-url <url>", "API 服務網址(部署網域,含 https://;非網頁登入網址)")
|
|
53
|
+
.option("--api-key <key>", "一次性金鑰;建議使用環境變數/設定檔;必須明確指定 API 服務網址")
|
|
54
|
+
.option("--project <team.KEY>", "專案參照;優先於環境變數、目錄與使用者設定")
|
|
18
55
|
.showHelpAfterError()
|
|
19
56
|
.exitOverride();
|
|
20
57
|
let activeWrite;
|
|
21
58
|
function globals(command) {
|
|
22
59
|
return command.optsWithGlobals();
|
|
23
60
|
}
|
|
61
|
+
function outputKind(command) {
|
|
62
|
+
const key = `${command.parent?.name()}.${command.name()}`;
|
|
63
|
+
return [
|
|
64
|
+
"project.list",
|
|
65
|
+
"project.view",
|
|
66
|
+
"issue.list",
|
|
67
|
+
"issue.view",
|
|
68
|
+
"issue.resolve",
|
|
69
|
+
"issue.comments",
|
|
70
|
+
"issue.activity",
|
|
71
|
+
].includes(key)
|
|
72
|
+
? key
|
|
73
|
+
: undefined;
|
|
74
|
+
}
|
|
75
|
+
function issueOutputOptions(command) {
|
|
76
|
+
const options = command.optsWithGlobals();
|
|
77
|
+
const kind = outputKind(command);
|
|
78
|
+
if (!kind)
|
|
79
|
+
return { outputSelected: false, includeState: false };
|
|
80
|
+
return {
|
|
81
|
+
outputSelected: options.summary === true || options.fields !== undefined,
|
|
82
|
+
includeState: needsIssueStates(kind, options),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
24
85
|
const wrap = (handler) => async (...args) => {
|
|
25
86
|
const command = args.at(-1);
|
|
26
87
|
if (!(command instanceof Command))
|
|
@@ -36,12 +97,22 @@ const wrap = (handler) => async (...args) => {
|
|
|
36
97
|
"init",
|
|
37
98
|
].includes(command.name()) ||
|
|
38
99
|
(command.parent?.name() === "document" && command.name() === "create") ||
|
|
100
|
+
(command.name() === "project" &&
|
|
101
|
+
["set", "unset"].includes(command.parent?.name() ?? "") &&
|
|
102
|
+
command.parent?.parent?.name() === "config") ||
|
|
39
103
|
(command.name() === "api" &&
|
|
40
104
|
!["GET", "HEAD"].includes(command.opts().method));
|
|
105
|
+
const selection = command.optsWithGlobals();
|
|
106
|
+
const kind = outputKind(command);
|
|
107
|
+
if (selection.summary || selection.fields !== undefined) {
|
|
108
|
+
if (!kind)
|
|
109
|
+
throw new CliError("UNSUPPORTED_OUTPUT", "--summary and --fields only support project list/view and issue list/view/resolve/comments/activity");
|
|
110
|
+
validateOutput(kind, selection);
|
|
111
|
+
}
|
|
41
112
|
const result = await handler(command);
|
|
42
|
-
emit(result);
|
|
113
|
+
emit(kind ? projectOutput(kind, result, selection) : result, command);
|
|
43
114
|
};
|
|
44
|
-
function emit(result) {
|
|
115
|
+
function emit(result, command) {
|
|
45
116
|
const envelope = redact({
|
|
46
117
|
ok: !result.error,
|
|
47
118
|
...(result.outcome
|
|
@@ -55,6 +126,16 @@ function emit(result) {
|
|
|
55
126
|
console.log(JSON.stringify(envelope));
|
|
56
127
|
else if (result.error)
|
|
57
128
|
console.error(JSON.stringify(envelope, null, 2));
|
|
129
|
+
else if (command?.name() === "login" && command.parent?.name() === "auth") {
|
|
130
|
+
const data = result.data;
|
|
131
|
+
console.log("OK:登入憑證已驗證並儲存。");
|
|
132
|
+
const warning = data.warning ??
|
|
133
|
+
(data.effective.usable !== true
|
|
134
|
+
? "目前生效的登入尚未確認可用,請執行 iap auth status --json 查詢。"
|
|
135
|
+
: undefined);
|
|
136
|
+
if (warning)
|
|
137
|
+
console.error(`警告:${redact(warning)}`);
|
|
138
|
+
}
|
|
58
139
|
else
|
|
59
140
|
console.log(typeof result.data === "string"
|
|
60
141
|
? result.data
|
|
@@ -69,16 +150,16 @@ program.configureOutput({
|
|
|
69
150
|
});
|
|
70
151
|
program
|
|
71
152
|
.command("doctor")
|
|
72
|
-
.description("
|
|
153
|
+
.description("診斷設定、認證、連線狀態與 API 版本")
|
|
73
154
|
.action(wrap((c) => runDoctor(globals(c))));
|
|
74
155
|
const auth = program
|
|
75
156
|
.command("auth")
|
|
76
|
-
.description("
|
|
157
|
+
.description("管理綁定 API 服務網址的個人 API 金鑰憑證");
|
|
77
158
|
auth
|
|
78
159
|
.command("login")
|
|
79
|
-
.description("
|
|
80
|
-
.option("--with-token", "
|
|
81
|
-
.addOption(new Option("--storage <kind>", "
|
|
160
|
+
.description("驗證並儲存個人 API 金鑰;隱藏輸入或明確從標準輸入讀取")
|
|
161
|
+
.option("--with-token", "從標準輸入讀取 API 金鑰")
|
|
162
|
+
.addOption(new Option("--storage <kind>", "憑證儲存方式;file 明確選用明文檔案").choices(["file", "keyring"]))
|
|
82
163
|
.action(wrap((c) => runAuth("login", {
|
|
83
164
|
...globals(c),
|
|
84
165
|
...c.opts(),
|
|
@@ -87,12 +168,30 @@ for (const action of ["status", "logout"])
|
|
|
87
168
|
auth
|
|
88
169
|
.command(action)
|
|
89
170
|
.description(action === "status"
|
|
90
|
-
? "
|
|
91
|
-
: "
|
|
171
|
+
? "顯示生效中的認證來源與身份,不顯示金鑰"
|
|
172
|
+
: "移除選取的本機憑證;不會撤銷環境變數中的金鑰")
|
|
92
173
|
.action(wrap((c) => runAuth(action, globals(c))));
|
|
174
|
+
const config = program
|
|
175
|
+
.command("config")
|
|
176
|
+
.description("管理依 API 服務網址區分的使用者預設值");
|
|
177
|
+
config
|
|
178
|
+
.command("set")
|
|
179
|
+
.description("儲存經驗證的使用者預設值")
|
|
180
|
+
.command("project <ref>")
|
|
181
|
+
.description("驗證並儲存此 API 服務網址的標準專案參照")
|
|
182
|
+
.action(wrap((c) => runProjectConfig("set", globals(c), c.args[0])));
|
|
183
|
+
for (const action of ["get", "unset"])
|
|
184
|
+
config
|
|
185
|
+
.command(action)
|
|
186
|
+
.description(action === "get" ? "檢視使用者預設值" : "移除使用者預設值")
|
|
187
|
+
.command("project")
|
|
188
|
+
.description(action === "get"
|
|
189
|
+
? "不發出請求,顯示已儲存及生效中的專案與來源"
|
|
190
|
+
: "只移除此 API 服務網址儲存的專案預設值")
|
|
191
|
+
.action(wrap((c) => runProjectConfig(action, globals(c))));
|
|
93
192
|
program
|
|
94
193
|
.command("init")
|
|
95
|
-
.description("
|
|
194
|
+
.description("驗證 --project <team.KEY>,並只在此目錄儲存 project_ref")
|
|
96
195
|
.action(wrap(async (c) => {
|
|
97
196
|
const options = globals(c);
|
|
98
197
|
if (!options.project) {
|
|
@@ -102,21 +201,30 @@ program
|
|
|
102
201
|
}));
|
|
103
202
|
const project = program
|
|
104
203
|
.command("project")
|
|
105
|
-
.description("
|
|
204
|
+
.description("探索專案並檢視工作流程值");
|
|
106
205
|
project
|
|
107
206
|
.command("list")
|
|
108
|
-
.description("
|
|
109
|
-
.option("--limit <number>", "
|
|
207
|
+
.description("列出可用專案(有筆數上限)")
|
|
208
|
+
.option("--limit <number>", "專案數量上限", "30")
|
|
110
209
|
.action(wrap(async (c) => runProject(await createContext(globals(c)), "list", undefined, {
|
|
111
210
|
limit: Number(c.opts().limit),
|
|
112
211
|
})));
|
|
113
212
|
project
|
|
114
213
|
.command("view <ref>")
|
|
115
|
-
.description("
|
|
214
|
+
.description("讀取欄、標籤、成員、類型與自訂欄位")
|
|
116
215
|
.action(wrap(async (c) => runProject(await createContext(globals(c)), "view", c.args[0])));
|
|
117
216
|
const issue = program
|
|
118
217
|
.command("issue")
|
|
119
|
-
.description("
|
|
218
|
+
.description("解析、檢視、搜尋、留言與編輯工作項目");
|
|
219
|
+
const issueReadExamples = `
|
|
220
|
+
範例使用虛構識別碼;請從清單中選擇識別碼或內部 ID:
|
|
221
|
+
iap issue list --project acme.MKT --limit 30 --summary --json
|
|
222
|
+
iap issue view MKT-42 --project acme.MKT --comments --summary --json
|
|
223
|
+
iap issue view --id 123 --fields identifier,title,state --json
|
|
224
|
+
選取不改變 --json 序列化;省略 --summary/--fields 取得完整資料。
|
|
225
|
+
--fields 對列表的每個 item 選欄位;保留範圍、完整性與游標。
|
|
226
|
+
MKT-42 使用專案序號 42;--id 123 是內部數值工作項目 ID。
|
|
227
|
+
請明確傳入其中一種:view 不會從標準輸入讀取 list 的 JSON。`;
|
|
120
228
|
for (const action of [
|
|
121
229
|
"resolve",
|
|
122
230
|
"view",
|
|
@@ -128,75 +236,80 @@ for (const action of [
|
|
|
128
236
|
const command = issue
|
|
129
237
|
.command(action)
|
|
130
238
|
.description({
|
|
131
|
-
resolve: "
|
|
132
|
-
view: "
|
|
133
|
-
activity: "
|
|
134
|
-
comments: "
|
|
135
|
-
edit: "
|
|
136
|
-
comment: "
|
|
239
|
+
resolve: "直接解析識別碼並取得穩定工作項目 ID",
|
|
240
|
+
view: "讀取完整工作項目背景與專案名稱",
|
|
241
|
+
activity: "讀取有分頁游標筆數上限的工作項目活動動態",
|
|
242
|
+
comments: "讀取有分頁游標筆數上限且包含回覆的討論",
|
|
243
|
+
edit: "只更新明確指定的工作項目欄位",
|
|
244
|
+
comment: "發布一則留言或回覆",
|
|
137
245
|
}[action] ?? action)
|
|
138
|
-
.argument("[identifier]", "KEY-42
|
|
139
|
-
.option("--id <id>", "
|
|
246
|
+
.argument("[identifier]", "KEY-42 或專案範圍內的序號")
|
|
247
|
+
.option("--id <id>", "內部工作項目 ID;不可與 identifier 同時使用");
|
|
140
248
|
if (action === "view")
|
|
141
|
-
command
|
|
249
|
+
command
|
|
250
|
+
.option("--comments", "包含最新 25 個留言討論串及下一個分頁游標")
|
|
251
|
+
.addHelpText("after", issueReadExamples);
|
|
142
252
|
if (action === "activity" || action === "comments")
|
|
143
253
|
command
|
|
144
|
-
.option("--limit <number>", "
|
|
145
|
-
.option("--cursor <cursor>", "
|
|
254
|
+
.option("--limit <number>", "頂層項目數量上限", "25")
|
|
255
|
+
.option("--cursor <cursor>", "此 CLI 回傳的不透明分頁游標");
|
|
146
256
|
if (action === "edit" || action === "comment") {
|
|
147
257
|
command
|
|
148
|
-
.option("--body <text>", "Markdown
|
|
149
|
-
.option("--body-file <file|->", "UTF-8 Markdown
|
|
150
|
-
.option("--agent <name>", "
|
|
151
|
-
.option("--human", "
|
|
152
|
-
.option("--dry-run", "
|
|
258
|
+
.option("--body <text>", "Markdown 內容")
|
|
259
|
+
.option("--body-file <file|->", "UTF-8 Markdown 檔案,或明確指定標準輸入 -")
|
|
260
|
+
.option("--agent <name>", "宣告代理:codex 或 claude")
|
|
261
|
+
.option("--human", "明確宣告人工作業;不可與代理環境變數並用")
|
|
262
|
+
.option("--dry-run", "只讀取與驗證;顯示確切請求但不寫入");
|
|
153
263
|
if (action === "comment")
|
|
154
|
-
command.option("--reply-to <id>", "
|
|
264
|
+
command.option("--reply-to <id>", "回覆此工作項目的留言");
|
|
155
265
|
else
|
|
156
266
|
command
|
|
157
|
-
.option("--title <text>", "
|
|
158
|
-
.option("--state <name>", "
|
|
159
|
-
.option("--state-type <type>", "
|
|
160
|
-
.option("--column-id <id>", "
|
|
161
|
-
.option("--assignee <username|@me>", "
|
|
162
|
-
.option("--remove-assignee", "
|
|
267
|
+
.option("--title <text>", "新標題")
|
|
268
|
+
.option("--state <name>", "精確欄位名稱")
|
|
269
|
+
.option("--state-type <type>", "唯一符合的 backlog/unstarted/started/completed/canceled 欄位")
|
|
270
|
+
.option("--column-id <id>", "屬於此專案的欄位 ID")
|
|
271
|
+
.option("--assignee <username|@me>", "指派工作項目負責人")
|
|
272
|
+
.option("--remove-assignee", "清除負責人")
|
|
163
273
|
.option("--priority <priority>", "urgent/high/medium/low")
|
|
164
|
-
.option("--remove-priority", "
|
|
165
|
-
.option("--type <name>", "
|
|
166
|
-
.option("--remove-type", "
|
|
167
|
-
.option("--add-label <name>", "
|
|
168
|
-
.option("--remove-label <name>", "
|
|
274
|
+
.option("--remove-priority", "清除優先級")
|
|
275
|
+
.option("--type <name>", "精確工作項目類型名稱")
|
|
276
|
+
.option("--remove-type", "清除工作項目類型")
|
|
277
|
+
.option("--add-label <name>", "加入精確標籤,可重複指定", (value, previous) => [...previous, value], [])
|
|
278
|
+
.option("--remove-label <name>", "移除精確標籤,可重複指定", (value, previous) => [...previous, value], []);
|
|
169
279
|
}
|
|
170
280
|
command.action(wrap(async (c) => runIssue(await createContext(globals(c)), action, c.args[0], {
|
|
171
281
|
...c.opts(),
|
|
172
282
|
project: globals(c).project,
|
|
283
|
+
...issueOutputOptions(c),
|
|
173
284
|
})));
|
|
174
285
|
}
|
|
175
286
|
issue
|
|
176
287
|
.command("list")
|
|
177
|
-
.description("
|
|
178
|
-
.option("--search <text>", "
|
|
179
|
-
.option("--assignee <username|@me>", "
|
|
180
|
-
.option("--limit <number>", "
|
|
288
|
+
.description("列出或搜尋專案工作項目;僅支援有筆數上限的查詢,不提供接續分頁;完整性未知(unknown)")
|
|
289
|
+
.option("--search <text>", "專案文字搜尋;不可與負責人篩選條件並用")
|
|
290
|
+
.option("--assignee <username|@me>", "依專案成員篩選")
|
|
291
|
+
.option("--limit <number>", "本地範圍為 1–500;伺服器可能回傳較少項目", "30")
|
|
292
|
+
.addHelpText("after", issueReadExamples)
|
|
181
293
|
.action(wrap(async (c) => runIssue(await createContext(globals(c)), "list", undefined, {
|
|
182
294
|
...c.opts(),
|
|
183
295
|
project: globals(c).project,
|
|
296
|
+
...issueOutputOptions(c),
|
|
184
297
|
})));
|
|
185
298
|
const document = program
|
|
186
299
|
.command("document")
|
|
187
|
-
.description("
|
|
300
|
+
.description("發布 Markdown 文件;專案可見不等於網際網路公開");
|
|
188
301
|
document
|
|
189
302
|
.command("create")
|
|
190
|
-
.description("
|
|
191
|
-
.requiredOption("--title <text>", "
|
|
192
|
-
.addOption(new Option("--visibility <scope>", "project
|
|
303
|
+
.description("建立明確設定 private 或 project 可見範圍的文件頁面")
|
|
304
|
+
.requiredOption("--title <text>", "文件標題(1–255 個字元)")
|
|
305
|
+
.addOption(new Option("--visibility <scope>", "project:專案可見,並非網際網路公開;private:僅擁有者可見")
|
|
193
306
|
.choices(["project", "private"])
|
|
194
307
|
.makeOptionMandatory())
|
|
195
|
-
.option("--body <text>", "Markdown
|
|
196
|
-
.option("--body-file <file|->", "UTF-8 Markdown
|
|
197
|
-
.option("--agent <name>", "
|
|
198
|
-
.option("--human", "
|
|
199
|
-
.option("--dry-run", "
|
|
308
|
+
.option("--body <text>", "Markdown 內容")
|
|
309
|
+
.option("--body-file <file|->", "UTF-8 Markdown 檔案,或明確指定標準輸入 -")
|
|
310
|
+
.option("--agent <name>", "宣告代理:codex 或 claude")
|
|
311
|
+
.option("--human", "明確宣告人工作業;不可與代理環境變數並用")
|
|
312
|
+
.option("--dry-run", "只讀取與驗證;顯示確切請求但不寫入")
|
|
200
313
|
.action(wrap(async (c) => runDocument(await createContext(globals(c)), "create", undefined, {
|
|
201
314
|
...c.opts(),
|
|
202
315
|
project: globals(c).project,
|
|
@@ -205,8 +318,8 @@ for (const action of ["view", "issues"]) {
|
|
|
205
318
|
document
|
|
206
319
|
.command(`${action} <document-id>`)
|
|
207
320
|
.description(action === "view"
|
|
208
|
-
? "
|
|
209
|
-
: "
|
|
321
|
+
? "依穩定 ID 讀取可見文件"
|
|
322
|
+
: "列出連結至此文件的工作項目")
|
|
210
323
|
.action(wrap(async (c) => runDocument(await createContext(globals(c)), action, c.args[0], {
|
|
211
324
|
project: globals(c).project,
|
|
212
325
|
})));
|
|
@@ -215,15 +328,15 @@ for (const action of ["documents", "link-document"]) {
|
|
|
215
328
|
const command = issue
|
|
216
329
|
.command(`${action} [identifier]`)
|
|
217
330
|
.description(action === "documents"
|
|
218
|
-
? "
|
|
219
|
-
: "
|
|
220
|
-
.option("--id <id>", "
|
|
331
|
+
? "列出連結至工作項目的可見文件"
|
|
332
|
+
: "將同專案文件連結至工作項目;已存在連結時不執行操作")
|
|
333
|
+
.option("--id <id>", "內部工作項目 ID;不可與 identifier 同時使用");
|
|
221
334
|
if (action === "link-document")
|
|
222
335
|
command
|
|
223
|
-
.requiredOption("--document <id>", "
|
|
224
|
-
.option("--agent <name>", "
|
|
225
|
-
.option("--human", "
|
|
226
|
-
.option("--dry-run", "
|
|
336
|
+
.requiredOption("--document <id>", "同一專案中的穩定文件 ID")
|
|
337
|
+
.option("--agent <name>", "宣告代理:codex 或 claude")
|
|
338
|
+
.option("--human", "明確宣告人工作業;不可與代理環境變數並用")
|
|
339
|
+
.option("--dry-run", "預覽確切關聯內容,不執行寫入");
|
|
227
340
|
command.action(wrap(async (c) => runIssueDocument(await createContext(globals(c)), action, c.args[0], {
|
|
228
341
|
...c.opts(),
|
|
229
342
|
project: globals(c).project,
|
|
@@ -236,29 +349,29 @@ for (const action of [
|
|
|
236
349
|
"development",
|
|
237
350
|
]) {
|
|
238
351
|
const descriptions = {
|
|
239
|
-
repositories: "
|
|
240
|
-
"pull-requests": "
|
|
241
|
-
"link-pr": "
|
|
242
|
-
development: "
|
|
352
|
+
repositories: "探索可供工作項目使用的已連接儲存庫",
|
|
353
|
+
"pull-requests": "列出已連接儲存庫的現有 PR/MR(單頁)",
|
|
354
|
+
"link-pr": "依已連接儲存庫 ID 與編號連結現有 PR/MR;不會建立 PR",
|
|
355
|
+
development: "列出工作項目現有的開發連結",
|
|
243
356
|
};
|
|
244
357
|
const command = issue
|
|
245
358
|
.command(`${action} [identifier]`)
|
|
246
359
|
.description(descriptions[action])
|
|
247
|
-
.option("--id <id>", "
|
|
360
|
+
.option("--id <id>", "內部工作項目 ID;不可與 identifier 同時使用");
|
|
248
361
|
if (action === "pull-requests" || action === "link-pr")
|
|
249
|
-
command.requiredOption("--repository <id>", "
|
|
362
|
+
command.requiredOption("--repository <id>", "由 iap issue repositories 列出的已連接儲存庫 ID");
|
|
250
363
|
if (action === "pull-requests")
|
|
251
364
|
command
|
|
252
|
-
.option("--page <number>", "API
|
|
253
|
-
.addOption(new Option("--state <state>", "PR
|
|
365
|
+
.option("--page <number>", "API 頁碼(依 nextPage 繼續)", "1")
|
|
366
|
+
.addOption(new Option("--state <state>", "PR 狀態篩選")
|
|
254
367
|
.choices(["open", "all"])
|
|
255
368
|
.default("open"));
|
|
256
369
|
if (action === "link-pr")
|
|
257
370
|
command
|
|
258
|
-
.requiredOption("--number <number>", "
|
|
259
|
-
.option("--agent <name>", "
|
|
260
|
-
.option("--human", "
|
|
261
|
-
.option("--dry-run", "
|
|
371
|
+
.requiredOption("--number <number>", "現有 PR/MR 編號;不是任意 URL")
|
|
372
|
+
.option("--agent <name>", "宣告代理:codex 或 claude")
|
|
373
|
+
.option("--human", "明確宣告人工作業;不可與代理環境變數並用")
|
|
374
|
+
.option("--dry-run", "預覽但不連結或建立 PR");
|
|
262
375
|
command.action(wrap(async (c) => runDevelopment(await createContext(globals(c)), action, c.args[0], {
|
|
263
376
|
...c.opts(),
|
|
264
377
|
project: globals(c).project,
|
|
@@ -266,12 +379,12 @@ for (const action of [
|
|
|
266
379
|
}
|
|
267
380
|
program
|
|
268
381
|
.command("api <relative-path>")
|
|
269
|
-
.description("
|
|
270
|
-
.addOption(new Option("--method <method>", "HTTP
|
|
382
|
+
.description("原始 API 入口;工作項目與文件寫入必須使用高階指令")
|
|
383
|
+
.addOption(new Option("--method <method>", "HTTP 方法")
|
|
271
384
|
.choices(["GET", "HEAD", "POST", "PATCH", "PUT", "DELETE"])
|
|
272
385
|
.default("GET"))
|
|
273
|
-
.option("--input <file|->", "JSON
|
|
274
|
-
.option("--dry-run", "
|
|
386
|
+
.option("--input <file|->", "從檔案或明確指定的標準輸入讀取 JSON 請求本文")
|
|
387
|
+
.option("--dry-run", "預覽但不送出寫入請求")
|
|
275
388
|
.action(wrap(async (c) => runApi(await createContext(globals(c)), c.args[0] ?? "", c.opts())));
|
|
276
389
|
try {
|
|
277
390
|
await program.parseAsync();
|
|
@@ -299,6 +412,8 @@ catch (error) {
|
|
|
299
412
|
words.includes("init") ||
|
|
300
413
|
(words.includes("auth") &&
|
|
301
414
|
(words.includes("login") || words.includes("logout"))) ||
|
|
415
|
+
(words.includes("config") &&
|
|
416
|
+
(words.includes("set") || words.includes("unset"))) ||
|
|
302
417
|
(words.includes("api") &&
|
|
303
418
|
/^(POST|PATCH|PUT|DELETE)$/.test(rawMethod ?? "GET")));
|
|
304
419
|
const { outcome, verification, data, meta, ...details } = known.details;
|