jirallm 0.5.0 → 0.6.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/dist/cli/commands/assign.d.ts.map +1 -1
- package/dist/cli/commands/assign.js +23 -21
- package/dist/cli/commands/assign.js.map +1 -1
- package/dist/cli/commands/assign.test.js +78 -13
- package/dist/cli/commands/assign.test.js.map +1 -1
- package/dist/cli/commands/attach.d.ts +3 -2
- package/dist/cli/commands/attach.d.ts.map +1 -1
- package/dist/cli/commands/attach.js +110 -14
- package/dist/cli/commands/attach.js.map +1 -1
- package/dist/cli/commands/attach.test.js +135 -10
- package/dist/cli/commands/attach.test.js.map +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +5 -3
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/auth.test.d.ts +2 -0
- package/dist/cli/commands/auth.test.d.ts.map +1 -0
- package/dist/cli/commands/auth.test.js +191 -0
- package/dist/cli/commands/auth.test.js.map +1 -0
- package/dist/cli/commands/board.d.ts +1 -1
- package/dist/cli/commands/board.d.ts.map +1 -1
- package/dist/cli/commands/board.js +23 -7
- package/dist/cli/commands/board.js.map +1 -1
- package/dist/cli/commands/board.test.d.ts +2 -0
- package/dist/cli/commands/board.test.d.ts.map +1 -0
- package/dist/cli/commands/board.test.js +106 -0
- package/dist/cli/commands/board.test.js.map +1 -0
- package/dist/cli/commands/boards.d.ts +1 -1
- package/dist/cli/commands/boards.d.ts.map +1 -1
- package/dist/cli/commands/boards.js +19 -3
- package/dist/cli/commands/boards.js.map +1 -1
- package/dist/cli/commands/boards.test.js +78 -5
- package/dist/cli/commands/boards.test.js.map +1 -1
- package/dist/cli/commands/comment.d.ts +13 -3
- package/dist/cli/commands/comment.d.ts.map +1 -1
- package/dist/cli/commands/comment.js +97 -16
- package/dist/cli/commands/comment.js.map +1 -1
- package/dist/cli/commands/comment.test.d.ts +2 -0
- package/dist/cli/commands/comment.test.d.ts.map +1 -0
- package/dist/cli/commands/comment.test.js +170 -0
- package/dist/cli/commands/comment.test.js.map +1 -0
- package/dist/cli/commands/components.d.ts +1 -1
- package/dist/cli/commands/components.d.ts.map +1 -1
- package/dist/cli/commands/components.js +27 -15
- package/dist/cli/commands/components.js.map +1 -1
- package/dist/cli/commands/components.test.js +40 -5
- package/dist/cli/commands/components.test.js.map +1 -1
- package/dist/cli/commands/create.d.ts +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +20 -7
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/commands/create.test.js +92 -13
- package/dist/cli/commands/create.test.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +39 -11
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/doctor.test.d.ts +2 -0
- package/dist/cli/commands/doctor.test.d.ts.map +1 -0
- package/dist/cli/commands/doctor.test.js +271 -0
- package/dist/cli/commands/doctor.test.js.map +1 -0
- package/dist/cli/commands/edit.d.ts.map +1 -1
- package/dist/cli/commands/edit.js +19 -6
- package/dist/cli/commands/edit.js.map +1 -1
- package/dist/cli/commands/edit.test.js +76 -3
- package/dist/cli/commands/edit.test.js.map +1 -1
- package/dist/cli/commands/fields.d.ts +1 -1
- package/dist/cli/commands/fields.d.ts.map +1 -1
- package/dist/cli/commands/fields.js +9 -3
- package/dist/cli/commands/fields.js.map +1 -1
- package/dist/cli/commands/fields.test.js +72 -7
- package/dist/cli/commands/fields.test.js.map +1 -1
- package/dist/cli/commands/init.d.ts +9 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +107 -4
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.d.ts +2 -0
- package/dist/cli/commands/init.test.d.ts.map +1 -0
- package/dist/cli/commands/init.test.js +272 -0
- package/dist/cli/commands/init.test.js.map +1 -0
- package/dist/cli/commands/issuetypes.d.ts +1 -1
- package/dist/cli/commands/issuetypes.d.ts.map +1 -1
- package/dist/cli/commands/issuetypes.js +5 -3
- package/dist/cli/commands/issuetypes.js.map +1 -1
- package/dist/cli/commands/issuetypes.test.js +39 -7
- package/dist/cli/commands/issuetypes.test.js.map +1 -1
- package/dist/cli/commands/link.d.ts +2 -1
- package/dist/cli/commands/link.d.ts.map +1 -1
- package/dist/cli/commands/link.js +116 -14
- package/dist/cli/commands/link.js.map +1 -1
- package/dist/cli/commands/link.test.js +178 -4
- package/dist/cli/commands/link.test.js.map +1 -1
- package/dist/cli/commands/linktypes.js +2 -2
- package/dist/cli/commands/linktypes.js.map +1 -1
- package/dist/cli/commands/linktypes.test.js +15 -4
- package/dist/cli/commands/linktypes.test.js.map +1 -1
- package/dist/cli/commands/me.js +2 -2
- package/dist/cli/commands/me.js.map +1 -1
- package/dist/cli/commands/me.test.js +20 -3
- package/dist/cli/commands/me.test.js.map +1 -1
- package/dist/cli/commands/orgs.d.ts +13 -5
- package/dist/cli/commands/orgs.d.ts.map +1 -1
- package/dist/cli/commands/orgs.js +68 -37
- package/dist/cli/commands/orgs.js.map +1 -1
- package/dist/cli/commands/orgs.test.d.ts +2 -0
- package/dist/cli/commands/orgs.test.d.ts.map +1 -0
- package/dist/cli/commands/orgs.test.js +230 -0
- package/dist/cli/commands/orgs.test.js.map +1 -0
- package/dist/cli/commands/projects.js +2 -2
- package/dist/cli/commands/projects.js.map +1 -1
- package/dist/cli/commands/projects.test.js +24 -3
- package/dist/cli/commands/projects.test.js.map +1 -1
- package/dist/cli/commands/search.d.ts +1 -1
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/search.js +4 -4
- package/dist/cli/commands/search.js.map +1 -1
- package/dist/cli/commands/search.test.js +28 -4
- package/dist/cli/commands/search.test.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +2 -0
- package/dist/cli/commands/setup.d.ts.map +1 -1
- package/dist/cli/commands/setup.js +50 -4
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/setup.test.d.ts +2 -0
- package/dist/cli/commands/setup.test.d.ts.map +1 -0
- package/dist/cli/commands/setup.test.js +274 -0
- package/dist/cli/commands/setup.test.js.map +1 -0
- package/dist/cli/commands/sprints.js +2 -2
- package/dist/cli/commands/sprints.js.map +1 -1
- package/dist/cli/commands/sprints.test.js +17 -5
- package/dist/cli/commands/sprints.test.js.map +1 -1
- package/dist/cli/commands/transition.d.ts +1 -0
- package/dist/cli/commands/transition.d.ts.map +1 -1
- package/dist/cli/commands/transition.js +13 -16
- package/dist/cli/commands/transition.js.map +1 -1
- package/dist/cli/commands/transition.test.d.ts +2 -0
- package/dist/cli/commands/transition.test.d.ts.map +1 -0
- package/dist/cli/commands/transition.test.js +100 -0
- package/dist/cli/commands/transition.test.js.map +1 -0
- package/dist/cli/commands/upgrade.d.ts +1 -0
- package/dist/cli/commands/upgrade.d.ts.map +1 -1
- package/dist/cli/commands/upgrade.js +10 -5
- package/dist/cli/commands/upgrade.js.map +1 -1
- package/dist/cli/commands/upgrade.test.js +22 -0
- package/dist/cli/commands/upgrade.test.js.map +1 -1
- package/dist/cli/commands/watchers.d.ts.map +1 -1
- package/dist/cli/commands/watchers.js +17 -10
- package/dist/cli/commands/watchers.js.map +1 -1
- package/dist/cli/commands/watchers.test.js +69 -9
- package/dist/cli/commands/watchers.test.js.map +1 -1
- package/dist/cli/commands/worklog.d.ts +3 -0
- package/dist/cli/commands/worklog.d.ts.map +1 -1
- package/dist/cli/commands/worklog.js +63 -21
- package/dist/cli/commands/worklog.js.map +1 -1
- package/dist/cli/commands/worklog.test.js +54 -0
- package/dist/cli/commands/worklog.test.js.map +1 -1
- package/dist/cli/confirm.d.ts +7 -0
- package/dist/cli/confirm.d.ts.map +1 -0
- package/dist/cli/confirm.js +27 -0
- package/dist/cli/confirm.js.map +1 -0
- package/dist/cli/confirm.test.d.ts +2 -0
- package/dist/cli/confirm.test.d.ts.map +1 -0
- package/dist/cli/confirm.test.js +67 -0
- package/dist/cli/confirm.test.js.map +1 -0
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +701 -624
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.test.d.ts +2 -0
- package/dist/cli/index.test.d.ts.map +1 -0
- package/dist/cli/index.test.js +490 -0
- package/dist/cli/index.test.js.map +1 -0
- package/dist/cli/resolveOrg.d.ts +1 -0
- package/dist/cli/resolveOrg.d.ts.map +1 -1
- package/dist/cli/resolveOrg.js +36 -1
- package/dist/cli/resolveOrg.js.map +1 -1
- package/dist/cli/resolveOrg.test.d.ts +2 -0
- package/dist/cli/resolveOrg.test.d.ts.map +1 -0
- package/dist/cli/resolveOrg.test.js +76 -0
- package/dist/cli/resolveOrg.test.js.map +1 -0
- package/dist/cli/resolveUser.d.ts +28 -0
- package/dist/cli/resolveUser.d.ts.map +1 -0
- package/dist/cli/resolveUser.js +60 -0
- package/dist/cli/resolveUser.js.map +1 -0
- package/dist/cli/resolveUser.test.d.ts +2 -0
- package/dist/cli/resolveUser.test.d.ts.map +1 -0
- package/dist/cli/resolveUser.test.js +91 -0
- package/dist/cli/resolveUser.test.js.map +1 -0
- package/dist/lib/config.d.ts +10 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +28 -4
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.js +114 -3
- package/dist/lib/config.test.js.map +1 -1
- package/dist/lib/credentials.d.ts +1 -0
- package/dist/lib/credentials.d.ts.map +1 -1
- package/dist/lib/credentials.js +22 -1
- package/dist/lib/credentials.js.map +1 -1
- package/dist/lib/credentials.test.js +52 -1
- package/dist/lib/credentials.test.js.map +1 -1
- package/dist/lib/exporter.test.js +58 -3
- package/dist/lib/exporter.test.js.map +1 -1
- package/dist/lib/jiraClient.d.ts +35 -3
- package/dist/lib/jiraClient.d.ts.map +1 -1
- package/dist/lib/jiraClient.discovery.test.js +169 -0
- package/dist/lib/jiraClient.discovery.test.js.map +1 -1
- package/dist/lib/jiraClient.js +50 -5
- package/dist/lib/jiraClient.js.map +1 -1
- package/dist/lib/jiraClient.test.js +322 -2
- package/dist/lib/jiraClient.test.js.map +1 -1
- package/dist/lib/jiraClient.write.test.js +284 -1
- package/dist/lib/jiraClient.write.test.js.map +1 -1
- package/dist/lib/platform.test.d.ts +2 -0
- package/dist/lib/platform.test.d.ts.map +1 -0
- package/dist/lib/platform.test.js +161 -0
- package/dist/lib/platform.test.js.map +1 -0
- package/dist/lib/runCommand.test.d.ts +2 -0
- package/dist/lib/runCommand.test.d.ts.map +1 -0
- package/dist/lib/runCommand.test.js +95 -0
- package/dist/lib/runCommand.test.js.map +1 -0
- package/dist/lib/worklog.d.ts.map +1 -1
- package/dist/lib/worklog.js +3 -2
- package/dist/lib/worklog.js.map +1 -1
- package/dist/lib/worklog.test.js +11 -0
- package/dist/lib/worklog.test.js.map +1 -1
- package/package.json +3 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import {
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { Command, Option } from 'commander';
|
|
4
5
|
import updateNotifier from 'update-notifier';
|
|
5
|
-
import { select, isCancel, cancel } from '@clack/prompts';
|
|
6
6
|
import { JiraExporter } from '../lib/exporter.js';
|
|
7
|
-
import {
|
|
7
|
+
import { listOrgs, loadProfile } from '../lib/config.js';
|
|
8
|
+
import { resolveOrgInteractive } from './resolveOrg.js';
|
|
8
9
|
import { parseIssueKeyArgs } from './issueKey.js';
|
|
9
10
|
import { runInit } from './commands/init.js';
|
|
10
11
|
import { runAuthSet, runAuthRm, runAuthList, runAuthStatus } from './commands/auth.js';
|
|
11
12
|
import { runOrgsList, runOrgsRemove, runProjectRemove } from './commands/orgs.js';
|
|
12
13
|
import { runDoctor } from './commands/doctor.js';
|
|
13
14
|
import { runSetup } from './commands/setup.js';
|
|
14
|
-
import { runComment, runDeleteComment } from './commands/comment.js';
|
|
15
|
+
import { runComment, runCommentList, runDeleteComment } from './commands/comment.js';
|
|
15
16
|
import { runBoardIssues } from './commands/board.js';
|
|
16
17
|
import { runTransition } from './commands/transition.js';
|
|
17
18
|
import { runWorklog } from './commands/worklog.js';
|
|
@@ -33,22 +34,6 @@ import { runAttach, runAttachRemove } from './commands/attach.js';
|
|
|
33
34
|
import { runWatchers } from './commands/watchers.js';
|
|
34
35
|
import { runUpgrade } from './commands/upgrade.js';
|
|
35
36
|
import { parseFieldsFlag, resolveFieldSet } from '../lib/exportFields.js';
|
|
36
|
-
async function pickOrgInteractively(candidates, projectKey) {
|
|
37
|
-
const raw = readConfig();
|
|
38
|
-
const choice = await select({
|
|
39
|
-
message: `Multiple orgs have a "${projectKey}" project. Which one?`,
|
|
40
|
-
options: candidates.map((name) => ({
|
|
41
|
-
value: name,
|
|
42
|
-
label: name,
|
|
43
|
-
hint: raw.orgs?.[name]?.base_url,
|
|
44
|
-
})),
|
|
45
|
-
});
|
|
46
|
-
if (isCancel(choice)) {
|
|
47
|
-
cancel('Cancelled.');
|
|
48
|
-
process.exit(0);
|
|
49
|
-
}
|
|
50
|
-
return choice;
|
|
51
|
-
}
|
|
52
37
|
async function resolveOrgAndKeys(rawArgs, flags) {
|
|
53
38
|
const parsed = parseIssueKeyArgs(rawArgs);
|
|
54
39
|
const keys = parsed.keys;
|
|
@@ -56,25 +41,15 @@ async function resolveOrgAndKeys(rawArgs, flags) {
|
|
|
56
41
|
throw new Error(`--org "${flags.org}" conflicts with the org/ prefix "${parsed.org}/" in the issue keys.`);
|
|
57
42
|
}
|
|
58
43
|
const projectKey = flags.project ?? parsed.projectKey;
|
|
59
|
-
const
|
|
60
|
-
if (explicitOrg)
|
|
61
|
-
return { org: explicitOrg, projectKey, keys };
|
|
62
|
-
const matches = findOrgsByProjectKey(parsed.projectKey);
|
|
63
|
-
if (matches.length === 0) {
|
|
64
|
-
throw new Error(`Project "${parsed.projectKey}" not found in any configured org. ` +
|
|
65
|
-
'Run `jirallm init` to add it, or pass --org explicitly.');
|
|
66
|
-
}
|
|
67
|
-
if (matches.length === 1)
|
|
68
|
-
return { org: matches[0], projectKey, keys };
|
|
69
|
-
if (!process.stdin.isTTY) {
|
|
70
|
-
throw new Error(`Project "${parsed.projectKey}" exists in multiple orgs (${matches.join(', ')}). ` +
|
|
71
|
-
`Pass --org or use the org/${parsed.projectKey}-N syntax.`);
|
|
72
|
-
}
|
|
73
|
-
const org = await pickOrgInteractively(matches, parsed.projectKey);
|
|
44
|
+
const org = await resolveOrgInteractive(parsed.org, flags.org, parsed.projectKey);
|
|
74
45
|
return { org, projectKey, keys };
|
|
75
46
|
}
|
|
76
|
-
async function runExport(rawArgs, flags) {
|
|
47
|
+
async function runExport(rawArgs, flags, program) {
|
|
77
48
|
if (rawArgs.length === 0) {
|
|
49
|
+
if (listOrgs().length > 0) {
|
|
50
|
+
program.outputHelp();
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
78
53
|
await runInit();
|
|
79
54
|
process.exit(0);
|
|
80
55
|
}
|
|
@@ -146,6 +121,10 @@ async function runExport(rawArgs, flags) {
|
|
|
146
121
|
console.log(`Project: ${projectKey}`);
|
|
147
122
|
console.log(`Base URL: ${baseUrl}`);
|
|
148
123
|
console.log(`Output dir: ${outputDir}`);
|
|
124
|
+
console.log(`Include subtasks: ${includeSubtasks}`);
|
|
125
|
+
console.log(`With history: ${flags.withHistory ?? false}`);
|
|
126
|
+
console.log(`With worklog: ${flags.withWorklog ?? false}`);
|
|
127
|
+
console.log(`Video frames: ${videoEnabled ? `enabled (fps=${fps}, maxFrames=${maxFrames})` : 'disabled'}`);
|
|
149
128
|
console.log(`Issues (${keys.length}): ${keys.join(', ')}`);
|
|
150
129
|
console.log(`Field preset: ${fieldSelector?.preset ?? '(default)'}`);
|
|
151
130
|
if (fieldSelector?.include?.length)
|
|
@@ -194,49 +173,53 @@ async function runExport(rawArgs, flags) {
|
|
|
194
173
|
process.exit(1);
|
|
195
174
|
}
|
|
196
175
|
}
|
|
197
|
-
|
|
198
|
-
program
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
176
|
+
export function buildProgram() {
|
|
177
|
+
const program = new Command();
|
|
178
|
+
program.enablePositionalOptions();
|
|
179
|
+
const pkg = createRequire(import.meta.url)('../../package.json');
|
|
180
|
+
program.name(pkg.name).description(pkg.description).version(pkg.version);
|
|
181
|
+
const notifier = updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 24 });
|
|
182
|
+
if (!process.argv.includes('--json')) {
|
|
183
|
+
notifier.notify({
|
|
184
|
+
defer: true,
|
|
185
|
+
isGlobal: true,
|
|
186
|
+
message: `Update available {currentVersion} → {latestVersion}\nRun \`${pkg.name} upgrade\` to update.`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function addExportOptions(cmd) {
|
|
190
|
+
return cmd
|
|
191
|
+
.option('-o, --org <name>', 'Organization name from config (auto-resolved from issue prefix if unique)')
|
|
192
|
+
.option('-P, --project <key>', 'Project key override')
|
|
193
|
+
.option('--output-dir <path>', 'Output directory (default: ./jira-export)')
|
|
194
|
+
.option('--base-url <url>', 'Jira base URL (overrides config)')
|
|
195
|
+
.option('--user-email <email>', 'Jira user email (overrides config)')
|
|
196
|
+
.option('--api-token <token>', 'Jira API token (overrides keychain)')
|
|
197
|
+
.option('--no-video-frames', 'Disable video frame extraction')
|
|
198
|
+
.option('--fps <n>', 'Frame extraction FPS')
|
|
199
|
+
.option('--max-frames <n>', 'Max frames kept per video')
|
|
200
|
+
.option('--include-subtasks', 'Fetch subtask metadata (may already be enabled via org config).')
|
|
201
|
+
.option('--fields <list>', 'Comma-separated friendly field names to include in frontmatter. Use +name/-name to add/remove, or "all" | "default" | "minimal".')
|
|
202
|
+
.option('--with-history', 'Include full field-change history (not just status changes)')
|
|
203
|
+
.option('--with-worklog', 'Include worklogs')
|
|
204
|
+
.option('--dry-run', 'Resolve config & print plan without calling Jira or writing files');
|
|
205
|
+
}
|
|
206
|
+
addExportOptions(program.argument('[issue-keys...]', 'Jira issue keys, e.g. PROJ-123 or acme/PROJ-123'))
|
|
207
|
+
.action(async (keys, flags) => {
|
|
208
|
+
await runExport(keys, flags, program);
|
|
209
|
+
})
|
|
210
|
+
.addHelpText('after', `
|
|
211
|
+
Read-command output auto-switches to JSON when piped (stdout is not a TTY);
|
|
212
|
+
pass --json to force it explicitly.
|
|
213
|
+
|
|
231
214
|
Examples:
|
|
232
215
|
$ jirallm init
|
|
233
216
|
$ jirallm PROJ-123 # auto-resolves org from PROJ prefix
|
|
234
217
|
$ jirallm acme/PROJ-123 # disambiguate when multiple orgs have PROJ
|
|
218
|
+
$ jirallm --org acme PROJ-123 # or pass --org when multiple orgs have PROJ
|
|
235
219
|
$ jirallm PROJ-123 PROJ-124 --output-dir ./context
|
|
236
220
|
$ jirallm PROJ-123 --no-video-frames
|
|
237
221
|
$ jirallm PROJ-123 --fps 2 --max-frames 6
|
|
238
222
|
$ jirallm PROJ-123 --include-subtasks
|
|
239
|
-
$ jirallm --org acme PROJ-123
|
|
240
223
|
$ jirallm export PROJ-123
|
|
241
224
|
$ jirallm fetch PROJ-123 --with-comments --with-history
|
|
242
225
|
$ jirallm fetch PROJ-123 --full
|
|
@@ -246,235 +229,277 @@ Examples:
|
|
|
246
229
|
|
|
247
230
|
Run \`jirallm <command> --help\` for command-specific options.
|
|
248
231
|
`);
|
|
249
|
-
addExportOptions(program.command('export [issue-keys...]'))
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
254
|
-
program
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
});
|
|
283
|
-
auth
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
});
|
|
290
|
-
|
|
232
|
+
addExportOptions(program.command('export [issue-keys...]'))
|
|
233
|
+
.description('Export issues as LLM-ready context bundles (same as the default command).')
|
|
234
|
+
.action(async (keys, flags) => {
|
|
235
|
+
await runExport(keys, flags, program);
|
|
236
|
+
});
|
|
237
|
+
program
|
|
238
|
+
.command('init')
|
|
239
|
+
.description('Interactive setup wizard — creates an org or adds a project, and stores the API token in your OS keychain')
|
|
240
|
+
.option('--org <name>', 'Organization name (non-interactive)')
|
|
241
|
+
.option('--base-url <url>', 'Jira base URL (non-interactive)')
|
|
242
|
+
.option('--email <email>', 'Jira user email (non-interactive)')
|
|
243
|
+
.option('--project <key>', 'Project key (non-interactive)')
|
|
244
|
+
.option('--token-stdin', 'Read the API token from stdin (non-interactive)')
|
|
245
|
+
.option('-y, --yes', 'Skip confirmation prompts (non-interactive)')
|
|
246
|
+
.action(async (opts) => {
|
|
247
|
+
await runInit(opts);
|
|
248
|
+
})
|
|
249
|
+
.addHelpText('after', `
|
|
250
|
+
A token stored in your OS keychain is preferred. As a fallback, the API token may be
|
|
251
|
+
supplied via the JIRALLM_API_TOKEN environment variable (or JIRALLM_API_TOKEN_<ORG>
|
|
252
|
+
for a specific org) when no keychain token is stored.
|
|
253
|
+
|
|
254
|
+
Examples:
|
|
255
|
+
$ jirallm init
|
|
256
|
+
$ jirallm init --org acme --base-url https://acme.atlassian.net --email me@acme.com --project PROJ --token-stdin --yes
|
|
257
|
+
`);
|
|
258
|
+
const auth = program.command('auth').description('Manage stored API tokens (per organization)');
|
|
259
|
+
auth
|
|
260
|
+
.command('set')
|
|
261
|
+
.description('Store/replace the API token for an organization')
|
|
262
|
+
.requiredOption('-o, --org <name>', 'Organization name')
|
|
263
|
+
.action(async (opts) => {
|
|
264
|
+
await runAuthSet(opts.org);
|
|
265
|
+
});
|
|
266
|
+
auth
|
|
267
|
+
.command('rm')
|
|
268
|
+
.description('Remove the stored API token for an organization')
|
|
269
|
+
.requiredOption('-o, --org <name>', 'Organization name')
|
|
270
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
271
|
+
.action(async (opts) => {
|
|
272
|
+
await runAuthRm(opts.org, { yes: opts.yes });
|
|
273
|
+
});
|
|
274
|
+
auth
|
|
275
|
+
.command('list')
|
|
276
|
+
.description('List all orgs and the resolved state of their API tokens')
|
|
277
|
+
.action(async () => {
|
|
278
|
+
await runAuthList();
|
|
279
|
+
});
|
|
280
|
+
auth
|
|
281
|
+
.command('status')
|
|
282
|
+
.description('Show whether a token is resolvable for an org (exits non-zero if missing)')
|
|
283
|
+
.requiredOption('-o, --org <name>', 'Organization name')
|
|
284
|
+
.action(async (opts) => {
|
|
285
|
+
await runAuthStatus(opts.org);
|
|
286
|
+
});
|
|
287
|
+
auth.addHelpText('after', `
|
|
288
|
+
Tokens are stored in your OS keychain. As a fallback, an API token may be supplied via
|
|
289
|
+
the JIRALLM_API_TOKEN environment variable (or JIRALLM_API_TOKEN_<ORG> for a specific
|
|
290
|
+
org) when no keychain token is stored; \`auth status\`/\`auth list\` report which source is used.
|
|
291
|
+
|
|
291
292
|
Examples:
|
|
292
293
|
$ jirallm auth list
|
|
293
294
|
$ jirallm auth status --org acme
|
|
294
295
|
$ jirallm auth set --org acme
|
|
295
296
|
$ jirallm auth rm --org acme --yes
|
|
296
297
|
`);
|
|
297
|
-
const orgs = program.command('orgs').description('Inspect or modify organizations and their projects');
|
|
298
|
-
orgs
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
298
|
+
const orgs = program.command('orgs').description('Inspect or modify organizations and their projects');
|
|
299
|
+
orgs
|
|
300
|
+
.command('list')
|
|
301
|
+
.description('List all configured organizations and projects')
|
|
302
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
303
|
+
.action(async (opts) => {
|
|
304
|
+
await runOrgsList(opts);
|
|
305
|
+
});
|
|
306
|
+
orgs
|
|
307
|
+
.command('rm [org]')
|
|
308
|
+
.description('Remove an organization (and its keychain token) along with all its projects')
|
|
309
|
+
.option('-o, --org <name>', 'Organization name (alias for the positional)')
|
|
310
|
+
.option('--dry-run', 'Show what would be removed without changing config')
|
|
311
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
312
|
+
.action(async (orgArg, opts) => {
|
|
313
|
+
const org = orgArg ?? opts.org;
|
|
314
|
+
if (!org)
|
|
315
|
+
throw new Error('Organization name is required (positional or --org).');
|
|
316
|
+
await runOrgsRemove(org, { yes: opts.yes, dryRun: opts.dryRun });
|
|
317
|
+
});
|
|
318
|
+
const orgProject = orgs.command('project').description('Manage projects within an organization');
|
|
319
|
+
orgProject
|
|
320
|
+
.command('rm')
|
|
321
|
+
.description('Remove a project from an organization')
|
|
322
|
+
.option('-o, --org <name>', 'Organization name (auto-resolved from the project key if unique)')
|
|
323
|
+
.option('-P, --project <key>', 'Project key')
|
|
324
|
+
.addOption(new Option('-k, --key <key>', 'Project key (alias for --project)').hideHelp())
|
|
325
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
326
|
+
.action(async (opts) => {
|
|
327
|
+
const projectKey = opts.project ?? opts.key;
|
|
328
|
+
if (!projectKey)
|
|
329
|
+
throw new Error('Project key is required (-P/--project).');
|
|
330
|
+
await runProjectRemove(opts.org, projectKey, { yes: opts.yes });
|
|
331
|
+
});
|
|
332
|
+
orgs.addHelpText('after', `
|
|
323
333
|
Examples:
|
|
324
334
|
$ jirallm orgs list
|
|
325
|
-
$ jirallm orgs rm
|
|
326
|
-
$ jirallm orgs
|
|
335
|
+
$ jirallm orgs rm acme
|
|
336
|
+
$ jirallm orgs rm --org acme --dry-run
|
|
337
|
+
$ jirallm orgs project rm -P PROJ
|
|
338
|
+
$ jirallm orgs project rm --org acme -P PROJ
|
|
327
339
|
`);
|
|
328
|
-
program
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
340
|
+
program
|
|
341
|
+
.command('doctor')
|
|
342
|
+
.description('Check that everything jirallm needs is installed and reachable')
|
|
343
|
+
.option('-o, --org <name>', 'Organization to use for the Jira reachability check')
|
|
344
|
+
.option('-P, --project <key>', 'Project key to use for the Jira reachability check')
|
|
345
|
+
.option('--strict', 'Exit non-zero on warnings as well as failures')
|
|
346
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
347
|
+
.action(async (opts) => {
|
|
348
|
+
await runDoctor({ org: opts.org, project: opts.project, strict: opts.strict, json: opts.json });
|
|
349
|
+
})
|
|
350
|
+
.addHelpText('after', '\nExamples:\n $ jirallm doctor --org acme\n $ jirallm doctor --org acme --strict --json\n');
|
|
351
|
+
program
|
|
352
|
+
.command('setup')
|
|
353
|
+
.description('Install missing system dependencies (currently: ffmpeg) with cascading consent')
|
|
354
|
+
.option('--bundled', 'Install ffmpeg-static globally instead of system ffmpeg')
|
|
355
|
+
.option('-y, --yes', 'Auto-confirm package-manager install (does NOT auto-confirm Homebrew install)')
|
|
356
|
+
.option('--allow-brew', 'Also auto-confirm the Homebrew install step')
|
|
357
|
+
.option('--dry-run', 'Show what would be installed without running any installer')
|
|
358
|
+
.action(async (opts) => {
|
|
359
|
+
await runSetup({ bundled: opts.bundled, yes: opts.yes, allowBrew: opts.allowBrew, dryRun: opts.dryRun });
|
|
360
|
+
})
|
|
361
|
+
.addHelpText('after', `
|
|
346
362
|
Examples:
|
|
347
363
|
$ jirallm setup
|
|
348
364
|
$ jirallm setup --yes
|
|
349
365
|
$ jirallm setup --bundled
|
|
366
|
+
$ jirallm setup --yes --allow-brew
|
|
367
|
+
$ jirallm setup --dry-run
|
|
350
368
|
`);
|
|
351
|
-
program
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
})
|
|
365
|
-
|
|
369
|
+
program
|
|
370
|
+
.command('upgrade')
|
|
371
|
+
.description('Upgrade jirallm to the latest version (auto-detects npm/pnpm/yarn/Homebrew)')
|
|
372
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
373
|
+
.option('--check', 'Print whether an update is available without installing (exits non-zero if outdated)')
|
|
374
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
375
|
+
.action(async (opts) => {
|
|
376
|
+
try {
|
|
377
|
+
await runUpgrade(opts);
|
|
378
|
+
}
|
|
379
|
+
catch (err) {
|
|
380
|
+
exitOnError(err);
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
.addHelpText('after', `
|
|
366
384
|
Examples:
|
|
367
385
|
$ jirallm upgrade
|
|
368
386
|
$ jirallm upgrade --yes
|
|
369
387
|
$ jirallm upgrade --check
|
|
370
388
|
`);
|
|
371
|
-
program
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
389
|
+
program
|
|
390
|
+
.command('comment <issue-key>')
|
|
391
|
+
.description('Post a (possibly multi-part) comment on a Jira issue. Markdown converted to Jira wiki by default.')
|
|
392
|
+
.option('-f, --file <path>', 'Read comment body from a file')
|
|
393
|
+
.option('-t, --text <text>', 'Inline comment text (alternative to --file / stdin)')
|
|
394
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
395
|
+
.option('--max-chars <n>', 'Max chars per comment chunk (default 25000)')
|
|
396
|
+
.option('--no-wiki', 'Skip markdown→wiki conversion (post body as-is)')
|
|
397
|
+
.option('--reply-to <commentId>', 'Post as reply to an existing comment (threaded)')
|
|
398
|
+
.option('--no-thread', 'When posting multiple chunks, do not chain them as replies')
|
|
399
|
+
.option('--dry-run', 'Show what would be posted without calling Jira')
|
|
400
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
401
|
+
.action(async (issueKey, opts) => {
|
|
402
|
+
try {
|
|
403
|
+
await runComment(issueKey, {
|
|
404
|
+
file: opts.file,
|
|
405
|
+
text: opts.text,
|
|
406
|
+
org: opts.org,
|
|
407
|
+
maxChars: opts.maxChars,
|
|
408
|
+
noWiki: opts.wiki === false,
|
|
409
|
+
noThread: opts.thread === false,
|
|
410
|
+
replyTo: opts.replyTo,
|
|
411
|
+
dryRun: opts.dryRun,
|
|
412
|
+
json: opts.json,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
exitOnError(err);
|
|
417
|
+
}
|
|
418
|
+
})
|
|
419
|
+
.addHelpText('after', `
|
|
401
420
|
Examples:
|
|
402
|
-
$ jirallm comment
|
|
403
|
-
$ jirallm comment
|
|
404
|
-
$ cat summary.md | jirallm comment
|
|
405
|
-
$ jirallm comment
|
|
406
|
-
$ jirallm comment
|
|
421
|
+
$ jirallm comment PROJ-123 --file ./summary.md
|
|
422
|
+
$ jirallm comment PROJ-123 -t "Quick note"
|
|
423
|
+
$ cat summary.md | jirallm comment PROJ-123
|
|
424
|
+
$ jirallm comment PROJ-123 --file ./summary.md --dry-run
|
|
425
|
+
$ jirallm comment PROJ-123 --reply-to 10001 -t "follow-up"
|
|
407
426
|
`);
|
|
408
|
-
program
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
.addHelpText('after', `
|
|
427
|
+
program
|
|
428
|
+
.command('board:issues')
|
|
429
|
+
.description('List issues in a board column. Output JSON with --json for piping into other tools.')
|
|
430
|
+
.requiredOption('-b, --board <name>', 'Board name (case-insensitive match)')
|
|
431
|
+
.option('-c, --column <name>', 'Column name (case-insensitive match); lists all columns when omitted')
|
|
432
|
+
.requiredOption('-o, --org <name>', 'Organization name from config')
|
|
433
|
+
.option('-P, --project <key>', 'Project key override (auto-selected when the org has a single project)')
|
|
434
|
+
.option('-a, --assignee <accountIdOrMe>', 'Filter by assignee. Use "me" for the current user.')
|
|
435
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
436
|
+
.action(async (opts) => {
|
|
437
|
+
try {
|
|
438
|
+
await runBoardIssues(opts);
|
|
439
|
+
}
|
|
440
|
+
catch (err) {
|
|
441
|
+
exitOnError(err);
|
|
442
|
+
}
|
|
443
|
+
})
|
|
444
|
+
.addHelpText('after', `
|
|
427
445
|
Examples:
|
|
428
446
|
$ jirallm board:issues -o MyOrg -b "My Board" -c "In Review" -a me --json
|
|
429
447
|
$ jirallm board:issues -o MyOrg -b "My Board" -c "Done"
|
|
430
448
|
`);
|
|
431
|
-
program
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
449
|
+
program
|
|
450
|
+
.command('transition <issue-key>')
|
|
451
|
+
.description('Transition a Jira issue to a target status (case-insensitive match on transition to.name, then name).')
|
|
452
|
+
.option('-t, --to <status>', 'Target status name (e.g. "In Review")')
|
|
453
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
454
|
+
.option('-l, --list', 'List available transitions (matched case-insensitively by to.name then name) instead of performing one')
|
|
455
|
+
.option('--dry-run', 'Resolve the transition and print it without calling Jira')
|
|
456
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
457
|
+
.action(async (issueKey, opts) => {
|
|
458
|
+
try {
|
|
459
|
+
if (!opts.list && !opts.to) {
|
|
460
|
+
throw new Error('Either --to <status> or --list is required.');
|
|
461
|
+
}
|
|
462
|
+
await runTransition(issueKey, { to: opts.to ?? '', org: opts.org, list: opts.list, dryRun: opts.dryRun, json: opts.json });
|
|
442
463
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
})
|
|
450
|
-
.addHelpText('after', `
|
|
464
|
+
catch (err) {
|
|
465
|
+
exitOnError(err);
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
.addHelpText('after', `
|
|
451
469
|
Examples:
|
|
452
470
|
$ jirallm transition PROJ-123 --to "In Review"
|
|
471
|
+
$ jirallm transition PROJ-123 --to "In Review" --dry-run
|
|
453
472
|
$ jirallm transition PROJ-123 --list
|
|
454
473
|
`);
|
|
455
|
-
program
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
474
|
+
program
|
|
475
|
+
.command('worklog [issue-key] [duration]')
|
|
476
|
+
.description('Log work to Jira. Quick form: `worklog PROJ-123 "1h 30m"`. Batch form: JSON array on stdin or --file.')
|
|
477
|
+
.option('-f, --file <path>', 'Read JSON array from a file (default: stdin)')
|
|
478
|
+
.option('-o, --org <name>', 'Default org for entries without an org/ prefix or "org" field')
|
|
479
|
+
.option('--no-wiki', 'Skip markdown→wiki conversion of description')
|
|
480
|
+
.option('--dry-run', 'Validate and print what would be posted, without calling Jira')
|
|
481
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
482
|
+
.action(async (issueKey, duration, opts) => {
|
|
483
|
+
try {
|
|
484
|
+
await runWorklog({
|
|
485
|
+
issueKey,
|
|
486
|
+
duration,
|
|
487
|
+
file: opts.file,
|
|
488
|
+
org: opts.org,
|
|
489
|
+
noWiki: opts.wiki === false,
|
|
490
|
+
dryRun: opts.dryRun,
|
|
491
|
+
json: opts.json,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
catch (err) {
|
|
495
|
+
exitOnError(err);
|
|
496
|
+
}
|
|
497
|
+
})
|
|
498
|
+
.addHelpText('after', `
|
|
499
|
+
Quick single-entry form logs against "now": \`jirallm worklog PROJ-123 "1h 30m"\`.
|
|
500
|
+
For the batch form, pass a JSON array on stdin or via --file.
|
|
501
|
+
|
|
502
|
+
JSON entry shape (a duration alone is accepted — startTime defaults to now; or supply any 2 of startTime/endTime/duration):
|
|
478
503
|
{
|
|
479
504
|
"issueKey": "PROJ-123", // required; supports "org/PROJ-123"
|
|
480
505
|
"startTime": "2026-05-23T09:00:00+02:00",
|
|
@@ -490,50 +515,82 @@ Examples:
|
|
|
490
515
|
$ cat worklogs.json | jirallm worklog --dry-run
|
|
491
516
|
$ echo '[{"issueKey":"PROJ-1","startTime":"2026-05-23T09:00:00+02:00","duration":"1h"}]' | jirallm worklog
|
|
492
517
|
`);
|
|
493
|
-
program
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
|
|
518
|
+
program
|
|
519
|
+
.command('comment:rm <issue-key> <comment-id>')
|
|
520
|
+
.description('Delete a comment from a Jira issue')
|
|
521
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
522
|
+
.option('--dry-run', 'Show what would be deleted without calling Jira')
|
|
523
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
524
|
+
.action(async (issueKey, commentId, opts) => {
|
|
525
|
+
try {
|
|
526
|
+
await runDeleteComment(issueKey, commentId, { org: opts.org, dryRun: opts.dryRun, yes: opts.yes });
|
|
527
|
+
}
|
|
528
|
+
catch (err) {
|
|
529
|
+
exitOnError(err);
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
.addHelpText('after', `
|
|
507
533
|
Examples:
|
|
508
534
|
$ jirallm comment:rm PROJ-123 26215
|
|
509
|
-
$ jirallm comment:rm acme/PROJ-123 26215
|
|
535
|
+
$ jirallm comment:rm acme/PROJ-123 26215 --dry-run
|
|
536
|
+
$ jirallm comment:rm PROJ-123 26215 --yes
|
|
510
537
|
`);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
538
|
+
program
|
|
539
|
+
.command('comment:ls <issue-key>')
|
|
540
|
+
.description('List comments on a Jira issue.')
|
|
541
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
542
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
543
|
+
.action(async (issueKey, opts) => {
|
|
544
|
+
try {
|
|
545
|
+
await runCommentList(issueKey, { org: opts.org, json: opts.json });
|
|
546
|
+
}
|
|
547
|
+
catch (err) {
|
|
548
|
+
exitOnError(err);
|
|
549
|
+
}
|
|
550
|
+
})
|
|
551
|
+
.addHelpText('after', `
|
|
552
|
+
Examples:
|
|
553
|
+
$ jirallm comment:ls PROJ-123
|
|
554
|
+
$ jirallm comment:ls acme/PROJ-123 --json
|
|
555
|
+
`);
|
|
556
|
+
function jsonModeRequested() {
|
|
557
|
+
return process.argv.includes('--json');
|
|
531
558
|
}
|
|
532
|
-
|
|
533
|
-
|
|
559
|
+
function reportError(err) {
|
|
560
|
+
const message = err.message ?? String(err);
|
|
561
|
+
if (jsonModeRequested()) {
|
|
562
|
+
console.error(JSON.stringify({ error: message }));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
console.error(message);
|
|
566
|
+
}
|
|
567
|
+
function exitOnError(err) {
|
|
568
|
+
reportError(err);
|
|
569
|
+
process.exit(1);
|
|
534
570
|
}
|
|
535
|
-
|
|
536
|
-
|
|
571
|
+
/** Commander collector for repeatable options (e.g. --field a=1 --field b=2). */
|
|
572
|
+
function collect(value, previous = []) {
|
|
573
|
+
return [...previous, value];
|
|
574
|
+
}
|
|
575
|
+
program
|
|
576
|
+
.command('search <jql>')
|
|
577
|
+
.description('Search issues by JQL (single page; pass --cursor for next page).')
|
|
578
|
+
.option('-o, --org <name>', 'Organization name')
|
|
579
|
+
.option('--limit <n>', 'Page size (default 50)')
|
|
580
|
+
.option('--cursor <token>', 'Next page token from prior search')
|
|
581
|
+
.option('--next-page-token <token>', 'Alias for --cursor')
|
|
582
|
+
.option('--fields <list>', 'Comma-separated Jira field IDs to include')
|
|
583
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
584
|
+
.action(async (jql, opts) => {
|
|
585
|
+
try {
|
|
586
|
+
const { nextPageToken, ...rest } = opts;
|
|
587
|
+
await runSearch({ jql, ...rest, cursor: opts.cursor ?? nextPageToken });
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
exitOnError(err);
|
|
591
|
+
}
|
|
592
|
+
})
|
|
593
|
+
.addHelpText('after', `
|
|
537
594
|
JQL must be quoted in your shell (it usually contains spaces and shell metacharacters).
|
|
538
595
|
Output JSON includes "nextPageToken" — pass it back via --cursor for the next page.
|
|
539
596
|
|
|
@@ -543,48 +600,48 @@ Examples:
|
|
|
543
600
|
$ jirallm search 'project = PROJ' -o acme --cursor eyJsYXN0SXNzdWVLZXkiOi4uLn0= --json
|
|
544
601
|
$ jirallm search 'project = PROJ' -o acme --fields summary,status,assignee --json
|
|
545
602
|
`);
|
|
546
|
-
program
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
})
|
|
562
|
-
|
|
603
|
+
program
|
|
604
|
+
.command('projects')
|
|
605
|
+
.description('List projects accessible in an org.')
|
|
606
|
+
.requiredOption('-o, --org <name>', 'Organization name')
|
|
607
|
+
.option('--query <text>', 'Filter by name/key substring')
|
|
608
|
+
.option('--limit <n>', 'Page size')
|
|
609
|
+
.option('--start-at <n>', 'Pagination offset')
|
|
610
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
611
|
+
.action(async (opts) => {
|
|
612
|
+
try {
|
|
613
|
+
await runProjects(opts);
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
exitOnError(err);
|
|
617
|
+
}
|
|
618
|
+
})
|
|
619
|
+
.addHelpText('after', `
|
|
563
620
|
Examples:
|
|
564
621
|
$ jirallm projects -o acme
|
|
565
622
|
$ jirallm projects -o acme --query docs --json
|
|
566
623
|
$ jirallm projects -o acme --limit 50 --start-at 100 --json
|
|
567
624
|
`);
|
|
568
|
-
program
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
})
|
|
586
|
-
|
|
587
|
-
When --project is omitted
|
|
625
|
+
program
|
|
626
|
+
.command('boards')
|
|
627
|
+
.description('List agile boards in an org.')
|
|
628
|
+
.option('-o, --org <name>', 'Organization name (auto-resolved from the -P project key when it maps to a unique org)')
|
|
629
|
+
.option('-P, --project <key>', 'Filter by project key')
|
|
630
|
+
.option('-t, --type <type>', 'scrum | kanban | simple')
|
|
631
|
+
.option('-n, --name <name>', 'Name substring filter')
|
|
632
|
+
.option('--limit <n>', 'Page size')
|
|
633
|
+
.option('--start-at <n>', 'Pagination offset')
|
|
634
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
635
|
+
.action(async (opts) => {
|
|
636
|
+
try {
|
|
637
|
+
await runBoards(opts);
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
exitOnError(err);
|
|
641
|
+
}
|
|
642
|
+
})
|
|
643
|
+
.addHelpText('after', `
|
|
644
|
+
When --project is omitted and the org has a single project, that project is used automatically.
|
|
588
645
|
The board id printed here is what you pass to \`jirallm sprints <id>\`.
|
|
589
646
|
|
|
590
647
|
Examples:
|
|
@@ -592,87 +649,87 @@ Examples:
|
|
|
592
649
|
$ jirallm boards -o acme -P PROJ -t scrum --json
|
|
593
650
|
$ jirallm boards -o acme -n "Team Alpha" --json
|
|
594
651
|
`);
|
|
595
|
-
program
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
})
|
|
611
|
-
|
|
652
|
+
program
|
|
653
|
+
.command('sprints <board-id>')
|
|
654
|
+
.description('List sprints on a board.')
|
|
655
|
+
.requiredOption('-o, --org <name>', 'Organization name')
|
|
656
|
+
.option('-s, --state <state>', 'active | future | closed')
|
|
657
|
+
.option('--limit <n>', 'Page size')
|
|
658
|
+
.option('--start-at <n>', 'Pagination offset')
|
|
659
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
660
|
+
.action(async (boardId, opts) => {
|
|
661
|
+
try {
|
|
662
|
+
await runSprints({ boardId, ...opts });
|
|
663
|
+
}
|
|
664
|
+
catch (err) {
|
|
665
|
+
exitOnError(err);
|
|
666
|
+
}
|
|
667
|
+
})
|
|
668
|
+
.addHelpText('after', `
|
|
612
669
|
Get board ids from \`jirallm boards --org <name>\`.
|
|
613
670
|
|
|
614
671
|
Examples:
|
|
615
672
|
$ jirallm sprints 42 -o acme --state active --json
|
|
616
673
|
$ jirallm sprints 42 -o acme --state closed --limit 20
|
|
617
674
|
`);
|
|
618
|
-
program
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
})
|
|
632
|
-
|
|
675
|
+
program
|
|
676
|
+
.command('issuetypes')
|
|
677
|
+
.description('List issue types (project-scoped if --project provided).')
|
|
678
|
+
.option('-o, --org <name>', 'Organization name (auto-resolved from the -P project key when it maps to a unique org)')
|
|
679
|
+
.option('-P, --project <key>', 'Project key (auto-selected when the org has a single project)')
|
|
680
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
681
|
+
.action(async (opts) => {
|
|
682
|
+
try {
|
|
683
|
+
await runIssueTypes(opts);
|
|
684
|
+
}
|
|
685
|
+
catch (err) {
|
|
686
|
+
exitOnError(err);
|
|
687
|
+
}
|
|
688
|
+
})
|
|
689
|
+
.addHelpText('after', `
|
|
633
690
|
Use this before \`jirallm create\` to discover the valid --type values for a project.
|
|
634
691
|
|
|
635
692
|
Examples:
|
|
636
693
|
$ jirallm issuetypes -o acme --json
|
|
637
694
|
$ jirallm issuetypes -o acme -P PROJ --json
|
|
638
695
|
`);
|
|
639
|
-
program
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
})
|
|
653
|
-
|
|
696
|
+
program
|
|
697
|
+
.command('components')
|
|
698
|
+
.description('List components defined on a project.')
|
|
699
|
+
.option('-o, --org <name>', 'Organization name (auto-resolved from the project key if unique)')
|
|
700
|
+
.option('-P, --project <key>', 'Project key (auto-selected when the org has a single project)')
|
|
701
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
702
|
+
.action(async (opts) => {
|
|
703
|
+
try {
|
|
704
|
+
await runComponents(opts);
|
|
705
|
+
}
|
|
706
|
+
catch (err) {
|
|
707
|
+
exitOnError(err);
|
|
708
|
+
}
|
|
709
|
+
})
|
|
710
|
+
.addHelpText('after', `
|
|
654
711
|
The "name" values are what \`jirallm create/edit --components\` expects.
|
|
655
712
|
|
|
656
713
|
Examples:
|
|
657
714
|
$ jirallm components -o acme -P PROJ
|
|
658
715
|
$ jirallm components -o acme -P PROJ --json
|
|
659
716
|
`);
|
|
660
|
-
program
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
})
|
|
675
|
-
|
|
717
|
+
program
|
|
718
|
+
.command('fields')
|
|
719
|
+
.description('List custom fields (and select options with --type) for use with --field.')
|
|
720
|
+
.option('-o, --org <name>', 'Organization name (auto-resolved from the -P project key when it maps to a unique org)')
|
|
721
|
+
.option('-P, --project <key>', 'Project key (auto-selected when the org has a single project)')
|
|
722
|
+
.option('-t, --type <issueType>', 'Show create-screen fields + allowed values for this issue type')
|
|
723
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
724
|
+
.action(async (opts) => {
|
|
725
|
+
try {
|
|
726
|
+
await runFields(opts);
|
|
727
|
+
}
|
|
728
|
+
catch (err) {
|
|
729
|
+
exitOnError(err);
|
|
730
|
+
}
|
|
731
|
+
})
|
|
732
|
+
.addHelpText('after', `
|
|
676
733
|
The [customfield_NNNNN] ids are what \`jirallm create/edit --field\` expects.
|
|
677
734
|
Add --type Bug to see valid select options (e.g. for Severity, Environment).
|
|
678
735
|
|
|
@@ -681,40 +738,40 @@ Examples:
|
|
|
681
738
|
$ jirallm fields -o acme -P PROJ --type Bug
|
|
682
739
|
$ jirallm fields -o acme -P PROJ --type Bug --json
|
|
683
740
|
`);
|
|
684
|
-
program
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
})
|
|
697
|
-
|
|
741
|
+
program
|
|
742
|
+
.command('linktypes')
|
|
743
|
+
.description('List issue link types available in an org.')
|
|
744
|
+
.requiredOption('-o, --org <name>', 'Organization name')
|
|
745
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
746
|
+
.action(async (opts) => {
|
|
747
|
+
try {
|
|
748
|
+
await runLinkTypes(opts);
|
|
749
|
+
}
|
|
750
|
+
catch (err) {
|
|
751
|
+
exitOnError(err);
|
|
752
|
+
}
|
|
753
|
+
})
|
|
754
|
+
.addHelpText('after', `
|
|
698
755
|
The "name" column is what \`jirallm link\` expects as <type> (e.g. "Blocks", "Relates").
|
|
699
756
|
|
|
700
757
|
Examples:
|
|
701
758
|
$ jirallm linktypes -o acme
|
|
702
759
|
$ jirallm linktypes -o acme --json
|
|
703
760
|
`);
|
|
704
|
-
program
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
})
|
|
717
|
-
|
|
761
|
+
program
|
|
762
|
+
.command('me')
|
|
763
|
+
.description('Show the currently authenticated Jira user.')
|
|
764
|
+
.requiredOption('-o, --org <name>', 'Organization name')
|
|
765
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
766
|
+
.action(async (opts) => {
|
|
767
|
+
try {
|
|
768
|
+
await runMe(opts);
|
|
769
|
+
}
|
|
770
|
+
catch (err) {
|
|
771
|
+
exitOnError(err);
|
|
772
|
+
}
|
|
773
|
+
})
|
|
774
|
+
.addHelpText('after', `
|
|
718
775
|
Returns the accountId — use it as --assignee for \`create\` / \`edit\` / \`assign\`,
|
|
719
776
|
or pass "me" as a shorthand in commands that resolve it (\`assign\`, \`watchers\`).
|
|
720
777
|
|
|
@@ -722,27 +779,27 @@ Examples:
|
|
|
722
779
|
$ jirallm me -o acme
|
|
723
780
|
$ jirallm me -o acme --json | jq -r .accountId
|
|
724
781
|
`);
|
|
725
|
-
program
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
})
|
|
745
|
-
|
|
782
|
+
program
|
|
783
|
+
.command('fetch <issue-key>')
|
|
784
|
+
.description('Fetch a single issue as JSON or pretty text (no file output).')
|
|
785
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
786
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
787
|
+
.option('--with-comments', 'Include comments')
|
|
788
|
+
.option('--with-history', 'Include status + field-change history')
|
|
789
|
+
.option('--with-worklog', 'Include worklogs')
|
|
790
|
+
.option('--with-subtasks', 'Include subtask metadata')
|
|
791
|
+
.option('--with-links', 'Include issue links')
|
|
792
|
+
.option('--with-attachments', 'List attachment metadata (does not download)')
|
|
793
|
+
.option('--full', 'Include everything (comments, history, worklog, subtasks, links, attachments)')
|
|
794
|
+
.action(async (issueKey, opts) => {
|
|
795
|
+
try {
|
|
796
|
+
await runFetch({ issueKey, ...opts });
|
|
797
|
+
}
|
|
798
|
+
catch (err) {
|
|
799
|
+
exitOnError(err);
|
|
800
|
+
}
|
|
801
|
+
})
|
|
802
|
+
.addHelpText('after', `
|
|
746
803
|
For the full export bundle (attachments, video frames, on-disk folder),
|
|
747
804
|
use the default \`jirallm <key>\` command instead.
|
|
748
805
|
|
|
@@ -753,47 +810,47 @@ Examples:
|
|
|
753
810
|
$ jirallm fetch PROJ-123 --full
|
|
754
811
|
$ jirallm fetch PROJ-123 --json | jq .status
|
|
755
812
|
`);
|
|
756
|
-
program
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
})
|
|
796
|
-
|
|
813
|
+
program
|
|
814
|
+
.command('create')
|
|
815
|
+
.description('Create a new Jira issue.')
|
|
816
|
+
.option('-o, --org <name>', 'Organization name (auto-resolved from the project key if unique)')
|
|
817
|
+
.option('-P, --project <key>', 'Project key (auto-selected when the org has a single project)')
|
|
818
|
+
.requiredOption('-t, --type <type>', 'Issue type name (e.g. Task, Bug, Story)')
|
|
819
|
+
.requiredOption('-s, --summary <text>', 'Issue summary')
|
|
820
|
+
.option('-d, --description <text>', 'Description (markdown)')
|
|
821
|
+
.option('--description-file <path>', 'Read description (markdown) from a file')
|
|
822
|
+
.option('-a, --assignee <user>', 'Assignee: an accountId, email, display name, or "me"')
|
|
823
|
+
.option('-l, --labels <list>', 'Comma-separated labels')
|
|
824
|
+
.option('--priority <name>', 'Priority name (e.g. High)')
|
|
825
|
+
.option('--parent <key>', 'Parent issue key (for subtasks / epic children)')
|
|
826
|
+
.option('--components <names>', 'Comma-separated component names')
|
|
827
|
+
.option('-F, --field <pair>', 'Set a custom field: friendlyName=value or customfield_NNNNN[:type]=value (repeatable)', collect)
|
|
828
|
+
.option('--dry-run', 'Show what would be created without calling Jira')
|
|
829
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
830
|
+
.action(async (opts) => {
|
|
831
|
+
try {
|
|
832
|
+
await runCreate({
|
|
833
|
+
org: opts.org,
|
|
834
|
+
projectKey: opts.project,
|
|
835
|
+
type: opts.type,
|
|
836
|
+
summary: opts.summary,
|
|
837
|
+
description: opts.description,
|
|
838
|
+
descriptionFile: opts.descriptionFile,
|
|
839
|
+
assignee: opts.assignee,
|
|
840
|
+
labels: opts.labels,
|
|
841
|
+
priority: opts.priority,
|
|
842
|
+
parent: opts.parent,
|
|
843
|
+
components: opts.components,
|
|
844
|
+
field: opts.field,
|
|
845
|
+
dryRun: opts.dryRun,
|
|
846
|
+
json: opts.json,
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
catch (err) {
|
|
850
|
+
exitOnError(err);
|
|
851
|
+
}
|
|
852
|
+
})
|
|
853
|
+
.addHelpText('after', `
|
|
797
854
|
Run \`jirallm issuetypes -o <org>\` to discover valid --type values.
|
|
798
855
|
Run \`jirallm components -o <org> -P <project>\` to discover valid --components names.
|
|
799
856
|
Run \`jirallm fields -o <org> -P <project> --type Bug\` to discover custom field ids and select options.
|
|
@@ -813,30 +870,30 @@ Examples:
|
|
|
813
870
|
$ jirallm create -o acme -t Bug -s "Crash" --components Web,API --field severity=High --field environment=PROD
|
|
814
871
|
$ jirallm create -o acme -t Bug -s "test" --dry-run --json
|
|
815
872
|
`);
|
|
816
|
-
program
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
})
|
|
839
|
-
|
|
873
|
+
program
|
|
874
|
+
.command('edit <issue-key>')
|
|
875
|
+
.description('Edit fields on an existing Jira issue.')
|
|
876
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
877
|
+
.option('-s, --summary <text>', 'New summary')
|
|
878
|
+
.option('-d, --description <text>', 'New description (markdown)')
|
|
879
|
+
.option('--description-file <path>', 'Read description (markdown) from a file')
|
|
880
|
+
.option('-a, --assignee <user>', 'Assignee: an accountId, email, display name, or "me"')
|
|
881
|
+
.option('--unassign', 'Unassign the issue (clears assignee)')
|
|
882
|
+
.option('-l, --labels <list>', 'Comma-separated labels (replaces existing)')
|
|
883
|
+
.option('--priority <name>', 'Priority name')
|
|
884
|
+
.option('--components <names>', 'Comma-separated component names (replaces existing)')
|
|
885
|
+
.option('-F, --field <pair>', 'Set a custom field: friendlyName=value or customfield_NNNNN[:type]=value (repeatable)', collect)
|
|
886
|
+
.option('--dry-run', 'Show what would change without calling Jira')
|
|
887
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
888
|
+
.action(async (issueKey, opts) => {
|
|
889
|
+
try {
|
|
890
|
+
await runEdit({ issueKey, ...opts });
|
|
891
|
+
}
|
|
892
|
+
catch (err) {
|
|
893
|
+
exitOnError(err);
|
|
894
|
+
}
|
|
895
|
+
})
|
|
896
|
+
.addHelpText('after', `
|
|
840
897
|
--labels and --components REPLACE the existing sets; there is no add/remove syntax here.
|
|
841
898
|
--unassign and --assignee are mutually exclusive (use one).
|
|
842
899
|
Custom fields work like \`create\`: --field friendlyName=value or --field customfield_NNNNN[:type]=value.
|
|
@@ -849,23 +906,23 @@ Examples:
|
|
|
849
906
|
$ jirallm edit PROJ-123 --assignee 5ac1234567890abcdef
|
|
850
907
|
$ jirallm edit PROJ-123 --unassign --dry-run --json
|
|
851
908
|
`);
|
|
852
|
-
program
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
})
|
|
866
|
-
|
|
909
|
+
program
|
|
910
|
+
.command('assign <issue-key> <assignee>')
|
|
911
|
+
.description('Assign an issue. Use "me" for the current user, "none" to unassign.')
|
|
912
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
913
|
+
.option('--dry-run', 'Show what would change without calling Jira')
|
|
914
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
915
|
+
.action(async (issueKey, assignee, opts) => {
|
|
916
|
+
try {
|
|
917
|
+
await runAssign({ issueKey, assignee, ...opts });
|
|
918
|
+
}
|
|
919
|
+
catch (err) {
|
|
920
|
+
exitOnError(err);
|
|
921
|
+
}
|
|
922
|
+
})
|
|
923
|
+
.addHelpText('after', `
|
|
867
924
|
<assignee> shortcuts: "me" (current user) or "none"/"-" (unassign).
|
|
868
|
-
Any other value is
|
|
925
|
+
Any other value is resolved as an email, display name, or accountId.
|
|
869
926
|
|
|
870
927
|
Examples:
|
|
871
928
|
$ jirallm assign PROJ-123 me
|
|
@@ -873,22 +930,22 @@ Examples:
|
|
|
873
930
|
$ jirallm assign PROJ-123 5ac1234567890abcdef
|
|
874
931
|
$ jirallm assign PROJ-123 me --dry-run --json
|
|
875
932
|
`);
|
|
876
|
-
program
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
})
|
|
891
|
-
|
|
933
|
+
program
|
|
934
|
+
.command('link <inward-key> <type> <outward-key>')
|
|
935
|
+
.description('Create an issue link (e.g. `link FOO-1 "blocks" FOO-2`).')
|
|
936
|
+
.option('-c, --comment <text>', 'Add a comment when linking')
|
|
937
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
938
|
+
.option('--dry-run', 'Show what would be created without calling Jira')
|
|
939
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
940
|
+
.action(async (inwardKey, type, outwardKey, opts) => {
|
|
941
|
+
try {
|
|
942
|
+
await runLink({ inwardKey, type, outwardKey, ...opts });
|
|
943
|
+
}
|
|
944
|
+
catch (err) {
|
|
945
|
+
exitOnError(err);
|
|
946
|
+
}
|
|
947
|
+
})
|
|
948
|
+
.addHelpText('after', `
|
|
892
949
|
<type> is the link-type NAME (e.g. "Blocks", "Relates", "Duplicate"), not the inward/outward label.
|
|
893
950
|
Discover the available types with: \`jirallm linktypes -o <org>\`.
|
|
894
951
|
--comment is markdown; it gets converted to Jira wiki.
|
|
@@ -898,44 +955,50 @@ Examples:
|
|
|
898
955
|
$ jirallm link PROJ-1 "Relates" PROJ-2 --comment "see also **infra rewrite**"
|
|
899
956
|
$ jirallm link PROJ-1 "Blocks" PROJ-2 --dry-run --json
|
|
900
957
|
`);
|
|
901
|
-
program
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
}
|
|
915
|
-
|
|
958
|
+
program
|
|
959
|
+
.command('link:rm [issue-key] [link-id]')
|
|
960
|
+
.description('Remove an issue link by ID, or by issue key + --to target.')
|
|
961
|
+
.option('-o, --org <name>', 'Organization name (auto-resolved from the issue/project key if unique)')
|
|
962
|
+
.option('--to <key>', 'Remove the link between <issue-key> and this issue key')
|
|
963
|
+
.option('--dry-run', 'Show what would be deleted without calling Jira')
|
|
964
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
965
|
+
.action(async (issueKey, linkId, opts) => {
|
|
966
|
+
try {
|
|
967
|
+
const target = issueKey ?? linkId;
|
|
968
|
+
if (!target)
|
|
969
|
+
throw new Error('Provide a link id, or an issue key with --to.');
|
|
970
|
+
await runLinkRemove({ linkId: target, to: opts.to, org: opts.org, dryRun: opts.dryRun, json: opts.json });
|
|
971
|
+
}
|
|
972
|
+
catch (err) {
|
|
973
|
+
exitOnError(err);
|
|
974
|
+
}
|
|
975
|
+
})
|
|
976
|
+
.addHelpText('after', `
|
|
916
977
|
Get link IDs from the issueLinks array in \`jirallm fetch <key> --json\`.
|
|
978
|
+
Alternatively, drop the link between two issues with --to.
|
|
917
979
|
|
|
918
980
|
Examples:
|
|
919
981
|
$ jirallm link:rm 10042 -o acme
|
|
920
982
|
$ jirallm link:rm 10042 -o acme --dry-run --json
|
|
983
|
+
$ jirallm link:rm PROJ-1 --to PROJ-2
|
|
921
984
|
`);
|
|
922
|
-
program
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
})
|
|
938
|
-
|
|
985
|
+
program
|
|
986
|
+
.command('attach <issue-key> [files...]')
|
|
987
|
+
.description('Upload one or more file attachments to an issue. At least one file is required.')
|
|
988
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
989
|
+
.option('--dry-run', 'Show what would be uploaded without calling Jira')
|
|
990
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
991
|
+
.action(async (issueKey, files, opts) => {
|
|
992
|
+
try {
|
|
993
|
+
if (!files || files.length === 0)
|
|
994
|
+
throw new Error('At least one file is required.');
|
|
995
|
+
await runAttach({ issueKey, files, ...opts });
|
|
996
|
+
}
|
|
997
|
+
catch (err) {
|
|
998
|
+
exitOnError(err);
|
|
999
|
+
}
|
|
1000
|
+
})
|
|
1001
|
+
.addHelpText('after', `
|
|
939
1002
|
Files are uploaded sequentially; --json returns the aggregated attachments array.
|
|
940
1003
|
The id from the response is what \`jirallm attach:rm\` takes.
|
|
941
1004
|
|
|
@@ -944,44 +1007,47 @@ Examples:
|
|
|
944
1007
|
$ jirallm attach PROJ-123 ./a.png ./b.png ./recording.mp4 --json
|
|
945
1008
|
$ jirallm attach PROJ-123 ./screenshot.png --dry-run
|
|
946
1009
|
`);
|
|
947
|
-
program
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
})
|
|
961
|
-
|
|
962
|
-
|
|
1010
|
+
program
|
|
1011
|
+
.command('attach:rm [target] [filename]')
|
|
1012
|
+
.description('Delete an attachment by ID, or by issue key + filename.')
|
|
1013
|
+
.option('-o, --org <name>', 'Organization name (auto-resolved from the issue/project key if unique)')
|
|
1014
|
+
.option('--dry-run', 'Show what would be deleted without calling Jira')
|
|
1015
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
1016
|
+
.action(async (target, filename, opts) => {
|
|
1017
|
+
try {
|
|
1018
|
+
await runAttachRemove({ target, filename, org: opts.org, dryRun: opts.dryRun, json: opts.json });
|
|
1019
|
+
}
|
|
1020
|
+
catch (err) {
|
|
1021
|
+
exitOnError(err);
|
|
1022
|
+
}
|
|
1023
|
+
})
|
|
1024
|
+
.addHelpText('after', `
|
|
1025
|
+
Delete by attachment id (get ids from the attachments array in \`jirallm fetch <key> --json\`);
|
|
1026
|
+
a bare numeric id needs -o (or an org/KEY) since it cannot infer the org on its own.
|
|
1027
|
+
Alternatively, delete by issue key + filename — the org is inferred from the key prefix.
|
|
963
1028
|
|
|
964
1029
|
Examples:
|
|
965
1030
|
$ jirallm attach:rm 99021 -o acme
|
|
966
1031
|
$ jirallm attach:rm 99021 -o acme --dry-run --json
|
|
1032
|
+
$ jirallm attach:rm PROJ-123 screenshot.png
|
|
967
1033
|
`);
|
|
968
|
-
program
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
})
|
|
984
|
-
|
|
1034
|
+
program
|
|
1035
|
+
.command('watchers <issue-key>')
|
|
1036
|
+
.description('List, add, or remove watchers on an issue.')
|
|
1037
|
+
.option('-o, --org <name>', 'Organization name override')
|
|
1038
|
+
.option('--add <user>', 'Add watcher: an accountId, email, display name, or "me"')
|
|
1039
|
+
.option('--rm <user>', 'Remove watcher: an accountId, email, display name, or "me"')
|
|
1040
|
+
.option('--dry-run', 'Show what would change without calling Jira')
|
|
1041
|
+
.option('--json', 'Output JSON instead of human-readable')
|
|
1042
|
+
.action(async (issueKey, opts) => {
|
|
1043
|
+
try {
|
|
1044
|
+
await runWatchers({ issueKey, ...opts });
|
|
1045
|
+
}
|
|
1046
|
+
catch (err) {
|
|
1047
|
+
exitOnError(err);
|
|
1048
|
+
}
|
|
1049
|
+
})
|
|
1050
|
+
.addHelpText('after', `
|
|
985
1051
|
With no --add/--rm, just lists current watchers.
|
|
986
1052
|
You can pass both --add and --rm in one call; both run before the final listing.
|
|
987
1053
|
Pass "me" to add/remove yourself (resolved via \`/myself\`).
|
|
@@ -992,8 +1058,19 @@ Examples:
|
|
|
992
1058
|
$ jirallm watchers PROJ-123 --rm 5ac1234567890abcdef
|
|
993
1059
|
$ jirallm watchers PROJ-123 --add me --rm 5acOldUser --json
|
|
994
1060
|
`);
|
|
995
|
-
program
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1061
|
+
return program;
|
|
1062
|
+
}
|
|
1063
|
+
const isDirectRun = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
1064
|
+
if (isDirectRun) {
|
|
1065
|
+
buildProgram()
|
|
1066
|
+
.parseAsync(process.argv)
|
|
1067
|
+
.catch((error) => {
|
|
1068
|
+
if (process.argv.includes('--json')) {
|
|
1069
|
+
console.error(JSON.stringify({ error: error.message ?? String(error) }));
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
}
|
|
1072
|
+
console.error('Unexpected error:', error);
|
|
1073
|
+
process.exit(1);
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
999
1076
|
//# sourceMappingURL=index.js.map
|