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