gh-axi 0.1.27 → 0.1.28
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 +15 -0
- package/dist/src/args.d.ts +9 -1
- package/dist/src/args.js +39 -7
- package/dist/src/args.js.map +1 -1
- package/dist/src/commands/api.d.ts +1 -1
- package/dist/src/commands/api.js +138 -42
- package/dist/src/commands/api.js.map +1 -1
- package/dist/src/commands/issue.d.ts +1 -1
- package/dist/src/commands/issue.js +36 -41
- package/dist/src/commands/issue.js.map +1 -1
- package/dist/src/commands/pr.d.ts +1 -1
- package/dist/src/commands/pr.js +63 -61
- package/dist/src/commands/pr.js.map +1 -1
- package/dist/src/format.d.ts +1 -1
- package/dist/src/format.js +5 -3
- package/dist/src/format.js.map +1 -1
- package/dist/src/skill.js +1 -0
- package/dist/src/skill.js.map +1 -1
- package/dist/src/totals.d.ts +88 -0
- package/dist/src/totals.js +161 -0
- package/dist/src/totals.js.map +1 -0
- package/package.json +1 -1
- package/skills/gh-axi/SKILL.md +1 -0
package/README.md
CHANGED
|
@@ -85,6 +85,7 @@ gh-axi # dashboard - live state, no args needed
|
|
|
85
85
|
gh-axi issue list # list issues in current repo
|
|
86
86
|
gh-axi issue subissue list 16 # list sub-issues for issue #16
|
|
87
87
|
gh-axi pr view 42 # view pull request #42
|
|
88
|
+
gh-axi issue edit 42 --add-label bug --add-label chore # repeat a flag per value
|
|
88
89
|
gh-axi run list -R owner/repo # list workflow runs for a specific repo
|
|
89
90
|
gh-axi issue list --hostname git.example.com # target a GitHub Enterprise host
|
|
90
91
|
gh-axi run view 123456 --job 789012 # inspect a single job within a run
|
|
@@ -103,10 +104,21 @@ For multi-line issue, PR, review, or comment text, write Markdown to a UTF-8 fil
|
|
|
103
104
|
For releases, `--body` and `--body-file` are aliases for release notes, alongside `--notes` and `--notes-file`.
|
|
104
105
|
For multi-line variable values, pipe stdin to `gh-axi variable set <name>`; `--body`/`-b` is for inline values only.
|
|
105
106
|
|
|
107
|
+
The label, assignee, reviewer, and project flags of `issue create`/`edit` and `pr create`/`edit` are repeatable: pass the flag once per value and every value reaches `gh`.
|
|
108
|
+
`issue list` and `pr list` accept repeated `--label` filters the same way.
|
|
109
|
+
A repeated flag with a missing or empty value (`--add-label` with nothing after it, or `--add-label=`) fails with a validation error instead of being silently dropped.
|
|
110
|
+
|
|
111
|
+
In `gh-axi issue list` and `gh-axi pr list`, the `count: N of M total` line counts only what the filters you passed (`--label`, `--assignee`, `--author`, and the other list filters) match, not every issue or PR in the repository.
|
|
112
|
+
When a total cannot be expressed faithfully — a numeric `--milestone`, since search matches milestone titles only, or a filter value containing a double quote, which search has no way to escape — gh-axi omits it and falls back to `showing first N` rather than printing a number that does not match the query.
|
|
113
|
+
A total smaller than the page it accompanies is dropped the same way, since GitHub's search index lags behind freshly created issues and pull requests and can undercount them.
|
|
114
|
+
|
|
106
115
|
Long `run view --log` and `run view --log-failed` output shows the last 20,000 characters so CI failures stay visible.
|
|
107
116
|
When truncation happens, gh-axi best-effort saves the complete log to a temp file, includes it as `full_log`, and prints a `help:` hint telling agents to grep that file for earlier context.
|
|
108
117
|
`gh-axi run` manages existing workflow runs; use `gh-axi workflow run <name> --ref <ref>` to trigger (dispatch) a workflow.
|
|
109
118
|
|
|
119
|
+
`gh-axi pr checks <number>` and the `checks` summary of `gh-axi pr view <number>` bucket every entry of the PR's status-check rollup as `pass`, `fail`, `skip`, or `pending`, covering both check runs (GitHub Actions and similar) and legacy commit statuses (Vercel, `ci/circleci`, and similar).
|
|
120
|
+
Cancelled, stale, timed-out, action-required, and startup-failure check runs count as failed, so a red PR is never reported as merely unfinished.
|
|
121
|
+
|
|
110
122
|
`gh-axi secret set <name>` reads the value only from piped stdin because secret flags would be visible in the `gh-axi` process argv.
|
|
111
123
|
`gh-axi secret list` never prints values, matching `gh secret list`.
|
|
112
124
|
`gh-axi secret list`, `set`, and `delete` accept `--env`/`-e <environment>` to scope a secret to a deployment environment; without it the repository scope is used.
|
|
@@ -115,6 +127,9 @@ Other `gh secret` scopes (`--org`, `--user`, `--app`) are rejected with a clear
|
|
|
115
127
|
`gh-axi project` wraps GitHub Projects (v2) and requires the `project` (or `read:project`) OAuth scope; if a call fails on a missing scope, gh-axi tells you the `gh auth refresh -s <scope>` command to run.
|
|
116
128
|
`--owner` defaults to the current repo's owner, falling back to explicit `@me` for the authenticated user.
|
|
117
129
|
|
|
130
|
+
`gh-axi api` accepts `--field`, `--header`, `--paginate`, `--jq <expression>`, and `--template <format>`; any other flag, an extra positional argument, or a repeated `--jq`/`--template` is rejected with a clear error instead of being silently dropped.
|
|
131
|
+
JSON responses are normally stripped of noisy fields before TOON encoding, but a response you shaped yourself with `--jq` or `--template` keeps every key and value verbatim — only over-long strings are still truncated so one field cannot flood an agent's context.
|
|
132
|
+
|
|
118
133
|
### Commands
|
|
119
134
|
|
|
120
135
|
| Command | Description |
|
package/dist/src/args.d.ts
CHANGED
|
@@ -6,8 +6,16 @@ export declare function takeFlag(args: string[], flag: string): string | undefin
|
|
|
6
6
|
export declare function hasFlag(args: string[], flag: string): boolean;
|
|
7
7
|
/** Check if a boolean flag is present and remove it from args. */
|
|
8
8
|
export declare function takeBoolFlag(args: string[], flag: string): boolean;
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Collect all values for a repeatable flag in --flag value or --flag=value form
|
|
11
|
+
* without modifying args. Throws VALIDATION_ERROR if any occurrence has a
|
|
12
|
+
* missing or blank value, rather than silently dropping it.
|
|
13
|
+
*/
|
|
10
14
|
export declare function getAllFlags(args: string[], flag: string): string[];
|
|
15
|
+
/** Like getAllFlags, but also removes every occurrence from args. */
|
|
16
|
+
export declare function takeAllFlags(args: string[], flag: string): string[];
|
|
17
|
+
/** Append a repeatable flag once per value onto a gh argv array. */
|
|
18
|
+
export declare function pushRepeated(ghArgs: string[], flag: string, values: string[]): void;
|
|
11
19
|
/** Get the first positional arg (non-flag) starting from startIndex. */
|
|
12
20
|
export declare function getPositional(args: string[], startIndex: number): string | undefined;
|
|
13
21
|
/** Parse and validate a required numeric argument. */
|
package/dist/src/args.js
CHANGED
|
@@ -48,22 +48,54 @@ export function takeBoolFlag(args, flag) {
|
|
|
48
48
|
args.splice(idx, 1);
|
|
49
49
|
return true;
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
function requireFlagValue(value, flag) {
|
|
52
|
+
if (value.trim() === "")
|
|
53
|
+
throw new AxiError(`${flag} requires a value`, "VALIDATION_ERROR");
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
function collectAllFlags(args, flag, consume) {
|
|
53
57
|
const result = [];
|
|
54
58
|
const equalsPrefix = flagEqualsPrefix(flag);
|
|
55
|
-
|
|
59
|
+
let i = 0;
|
|
60
|
+
while (i < args.length) {
|
|
56
61
|
const arg = args[i];
|
|
57
|
-
if (arg === flag
|
|
58
|
-
result.push(args[i + 1]);
|
|
59
|
-
|
|
62
|
+
if (arg === flag) {
|
|
63
|
+
result.push(requireFlagValue(args[i + 1] ?? "", flag));
|
|
64
|
+
if (consume)
|
|
65
|
+
args.splice(i, 2);
|
|
66
|
+
else
|
|
67
|
+
i += 2;
|
|
60
68
|
}
|
|
61
69
|
else if (arg.startsWith(equalsPrefix)) {
|
|
62
|
-
result.push(arg.slice(equalsPrefix.length));
|
|
70
|
+
result.push(requireFlagValue(arg.slice(equalsPrefix.length), flag));
|
|
71
|
+
if (consume)
|
|
72
|
+
args.splice(i, 1);
|
|
73
|
+
else
|
|
74
|
+
i++;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
i++;
|
|
63
78
|
}
|
|
64
79
|
}
|
|
65
80
|
return result;
|
|
66
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Collect all values for a repeatable flag in --flag value or --flag=value form
|
|
84
|
+
* without modifying args. Throws VALIDATION_ERROR if any occurrence has a
|
|
85
|
+
* missing or blank value, rather than silently dropping it.
|
|
86
|
+
*/
|
|
87
|
+
export function getAllFlags(args, flag) {
|
|
88
|
+
return collectAllFlags(args, flag, false);
|
|
89
|
+
}
|
|
90
|
+
/** Like getAllFlags, but also removes every occurrence from args. */
|
|
91
|
+
export function takeAllFlags(args, flag) {
|
|
92
|
+
return collectAllFlags(args, flag, true);
|
|
93
|
+
}
|
|
94
|
+
/** Append a repeatable flag once per value onto a gh argv array. */
|
|
95
|
+
export function pushRepeated(ghArgs, flag, values) {
|
|
96
|
+
for (const value of values)
|
|
97
|
+
ghArgs.push(flag, value);
|
|
98
|
+
}
|
|
67
99
|
/** Get the first positional arg (non-flag) starting from startIndex. */
|
|
68
100
|
export function getPositional(args, startIndex) {
|
|
69
101
|
for (let i = startIndex; i < args.length; i++) {
|
package/dist/src/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,GAAG,IAAI,GAAG,CAAC;AACpB,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,OAAO,CAAC,IAAc,EAAE,IAAY;IAClD,MAAM,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM;gBAAE,OAAO,SAAS,CAAC;YAC3C,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACrB,CAAC;QACD,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,OAAO,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,QAAQ,CAAC,IAAc,EAAE,IAAY;IACnD,MAAM,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAClB,OAAO,GAAG,CAAC;QACb,CAAC;QACD,IAAI,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAClB,OAAO,GAAG,CAAC;QACb,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,OAAO,CAAC,IAAc,EAAE,IAAY;IAClD,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,IAAc,EAAE,IAAY;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,GAAG,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/args.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { RepoContext } from '../context.js';
|
|
2
|
-
export declare const API_HELP = "usage: gh-axi api [<method>] <path>\ndescription: Make an authenticated GitHub API request. Defaults to GET if no method specified.\nmethods[6]:\n GET, POST, PUT, PATCH, DELETE, HEAD\nflags[
|
|
2
|
+
export declare const API_HELP = "usage: gh-axi api [<method>] <path>\ndescription: Make an authenticated GitHub API request. Defaults to GET if no method specified.\nmethods[6]:\n GET, POST, PUT, PATCH, DELETE, HEAD\nflags[5]:\n --field <key=value> (repeatable), --header <key:value> (repeatable), --paginate, --jq <expression>, --template <format>\nexamples:\n gh-axi api /repos/{owner}/{repo}\n gh-axi api POST /repos/{owner}/{repo}/issues --field title=\"Bug report\"\n gh-axi api /repos/{owner}/{repo}/pulls --paginate\n gh-axi api /repos/{owner}/{repo}/issues/1 --jq '[.labels[].name]'";
|
|
3
3
|
export declare function apiCommand(args: string[], ctx?: RepoContext): Promise<string>;
|
package/dist/src/commands/api.js
CHANGED
|
@@ -1,19 +1,100 @@
|
|
|
1
1
|
import { encode } from '@toon-format/toon';
|
|
2
2
|
import { ghExec } from '../gh.js';
|
|
3
3
|
import { AxiError } from '../errors.js';
|
|
4
|
-
import { hasFlag, getAllFlags } from '../args.js';
|
|
5
4
|
import { cleanBody } from '../body.js';
|
|
6
5
|
export const API_HELP = `usage: gh-axi api [<method>] <path>
|
|
7
6
|
description: Make an authenticated GitHub API request. Defaults to GET if no method specified.
|
|
8
7
|
methods[6]:
|
|
9
8
|
GET, POST, PUT, PATCH, DELETE, HEAD
|
|
10
|
-
flags[
|
|
11
|
-
--field <key=value> (repeatable), --header <key:value> (repeatable), --paginate
|
|
9
|
+
flags[5]:
|
|
10
|
+
--field <key=value> (repeatable), --header <key:value> (repeatable), --paginate, --jq <expression>, --template <format>
|
|
12
11
|
examples:
|
|
13
12
|
gh-axi api /repos/{owner}/{repo}
|
|
14
13
|
gh-axi api POST /repos/{owner}/{repo}/issues --field title="Bug report"
|
|
15
|
-
gh-axi api /repos/{owner}/{repo}/pulls --paginate
|
|
14
|
+
gh-axi api /repos/{owner}/{repo}/pulls --paginate
|
|
15
|
+
gh-axi api /repos/{owner}/{repo}/issues/1 --jq '[.labels[].name]'`;
|
|
16
16
|
const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD']);
|
|
17
|
+
/** Value flags that may be given more than once, each occurrence forwarded to gh. */
|
|
18
|
+
const REPEATABLE_VALUE_FLAGS = new Set(['--field', '--header']);
|
|
19
|
+
/** Value flags that gh accepts only one of, so a repeat is a caller mistake. */
|
|
20
|
+
const SINGLE_VALUE_FLAGS = new Set(['--jq', '--template']);
|
|
21
|
+
/** Flags that stand alone and must not consume the following argument. */
|
|
22
|
+
const BOOL_FLAGS = new Set(['--paginate']);
|
|
23
|
+
const SUPPORTED_FLAGS = [...REPEATABLE_VALUE_FLAGS, ...SINGLE_VALUE_FLAGS, ...BOOL_FLAGS];
|
|
24
|
+
/** The flag's name without any `=value` suffix, so errors never echo a value. */
|
|
25
|
+
function flagName(arg) {
|
|
26
|
+
const equals = arg.indexOf('=');
|
|
27
|
+
return equals === -1 ? arg : arg.slice(0, equals);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Walk args once, collecting positionals and flag values and rejecting anything
|
|
31
|
+
* unrecognised.
|
|
32
|
+
*
|
|
33
|
+
* Unknown flags used to be skipped along with the following argument, so a flag
|
|
34
|
+
* `gh-axi api` did not implement — `--jq` above all — silently vanished together
|
|
35
|
+
* with its value and the caller got an unfiltered response that looked plausible.
|
|
36
|
+
* Only flags known to take a value consume the next argument, which also keeps
|
|
37
|
+
* `--paginate <path>` from swallowing the path.
|
|
38
|
+
*
|
|
39
|
+
* This single pass is also the only place that decides which flags consume a
|
|
40
|
+
* value: re-scanning args afterwards gave a second, value-blind notion of the
|
|
41
|
+
* same thing, so `--header --jq=.x` both consumed `--jq=.x` as the header value
|
|
42
|
+
* and forwarded it as a jq expression.
|
|
43
|
+
*/
|
|
44
|
+
function parseArgs(args) {
|
|
45
|
+
const parsed = {
|
|
46
|
+
positionals: [],
|
|
47
|
+
fields: [],
|
|
48
|
+
headers: [],
|
|
49
|
+
paginate: false,
|
|
50
|
+
};
|
|
51
|
+
for (let i = 0; i < args.length; i++) {
|
|
52
|
+
const arg = args[i];
|
|
53
|
+
if (!arg.startsWith('-')) {
|
|
54
|
+
parsed.positionals.push(arg);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const name = flagName(arg);
|
|
58
|
+
if (BOOL_FLAGS.has(name)) {
|
|
59
|
+
if (name !== arg)
|
|
60
|
+
throw new AxiError(`${name} does not take a value`, 'VALIDATION_ERROR');
|
|
61
|
+
parsed.paginate = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (!REPEATABLE_VALUE_FLAGS.has(name) && !SINGLE_VALUE_FLAGS.has(name)) {
|
|
65
|
+
throw new AxiError(`unknown flag ${name} for gh-axi api. Supported flags: ${SUPPORTED_FLAGS.join(', ')}`, 'VALIDATION_ERROR');
|
|
66
|
+
}
|
|
67
|
+
// `--flag=value` carries its own value; `--flag value` consumes the next arg.
|
|
68
|
+
let value;
|
|
69
|
+
if (name === arg) {
|
|
70
|
+
if (i + 1 >= args.length)
|
|
71
|
+
throw new AxiError(`${name} requires a value`, 'VALIDATION_ERROR');
|
|
72
|
+
value = args[++i];
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
value = arg.slice(name.length + 1);
|
|
76
|
+
}
|
|
77
|
+
if (name === '--field') {
|
|
78
|
+
parsed.fields.push(value);
|
|
79
|
+
}
|
|
80
|
+
else if (name === '--header') {
|
|
81
|
+
parsed.headers.push(value);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// gh takes the last occurrence; discarding the earlier expression silently
|
|
85
|
+
// is the same failure this parser exists to prevent, so reject instead.
|
|
86
|
+
const key = name === '--jq' ? 'jq' : 'template';
|
|
87
|
+
if (parsed[key] !== undefined)
|
|
88
|
+
throw new AxiError(`${name} may only be given once`, 'VALIDATION_ERROR');
|
|
89
|
+
// An empty value (an unset shell variable) is a no-op filter for gh that
|
|
90
|
+
// would still suppress the default field stripping here.
|
|
91
|
+
if (value.trim() === '')
|
|
92
|
+
throw new AxiError(`${name} requires a value`, 'VALIDATION_ERROR');
|
|
93
|
+
parsed[key] = value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
17
98
|
/** Maximum length for raw (non-JSON) API output before truncation. */
|
|
18
99
|
const RAW_OUTPUT_TRUNCATION_LIMIT = 4000;
|
|
19
100
|
/** Strings longer than this threshold are cleaned up (image/URL stripping). */
|
|
@@ -23,46 +104,43 @@ const STRING_VALUE_TRUNCATION_LIMIT = 2000;
|
|
|
23
104
|
export async function apiCommand(args, ctx) {
|
|
24
105
|
if (args[0] === '--help' || args.length === 0)
|
|
25
106
|
return API_HELP;
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
let method;
|
|
37
|
-
let path;
|
|
38
|
-
if (positionals.length >= 2 && HTTP_METHODS.has(positionals[0].toUpperCase())) {
|
|
39
|
-
method = positionals[0].toUpperCase();
|
|
40
|
-
path = positionals[1];
|
|
41
|
-
}
|
|
42
|
-
else if (positionals.length >= 1) {
|
|
43
|
-
method = 'GET';
|
|
44
|
-
path = positionals[0];
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
throw new AxiError('API path is required: gh-axi api [<method>] <path>', 'VALIDATION_ERROR');
|
|
107
|
+
const { positionals, fields, headers, jq, template, paginate } = parseArgs(args);
|
|
108
|
+
const pathRequired = new AxiError('API path is required: gh-axi api [<method>] <path>', 'VALIDATION_ERROR');
|
|
109
|
+
if (positionals.length === 0)
|
|
110
|
+
throw pathRequired;
|
|
111
|
+
// A positional the command cannot place is a caller typo, and dropping it
|
|
112
|
+
// silently requests a different endpoint than the one that was asked for.
|
|
113
|
+
const methodGiven = HTTP_METHODS.has(positionals[0].toUpperCase());
|
|
114
|
+
if (positionals.length > (methodGiven ? 2 : 1)) {
|
|
115
|
+
throw new AxiError('too many arguments for gh-axi api: expected [<method>] <path>', 'VALIDATION_ERROR');
|
|
48
116
|
}
|
|
117
|
+
if (methodGiven && positionals.length < 2)
|
|
118
|
+
throw pathRequired;
|
|
119
|
+
const method = methodGiven ? positionals[0].toUpperCase() : 'GET';
|
|
120
|
+
const path = methodGiven ? positionals[1] : positionals[0];
|
|
49
121
|
const ghArgs = ['api', path, '--method', method];
|
|
50
|
-
const fields = getAllFlags(args, '--field');
|
|
51
122
|
for (const f of fields) {
|
|
52
123
|
ghArgs.push('--field', f);
|
|
53
124
|
}
|
|
54
|
-
const headers = getAllFlags(args, '--header');
|
|
55
125
|
for (const h of headers) {
|
|
56
126
|
ghArgs.push('--header', h);
|
|
57
127
|
}
|
|
58
|
-
if (
|
|
128
|
+
if (paginate)
|
|
59
129
|
ghArgs.push('--paginate');
|
|
130
|
+
if (jq !== undefined)
|
|
131
|
+
ghArgs.push('--jq', jq);
|
|
132
|
+
if (template !== undefined)
|
|
133
|
+
ghArgs.push('--template', template);
|
|
134
|
+
// A caller who wrote a jq expression or template already chose the exact shape
|
|
135
|
+
// they want, so noisy-field stripping would silently delete fields they asked
|
|
136
|
+
// for by name (`url`, `node_id`, ...). The length clamp still applies, so a
|
|
137
|
+
// selected field cannot blow the caller's context with an unbounded blob.
|
|
138
|
+
const callerShapedOutput = jq !== undefined || template !== undefined;
|
|
60
139
|
// Try to parse as JSON, strip noisy fields, encode to TOON; fall back to raw output
|
|
61
140
|
const raw = await ghExec(ghArgs, ctx);
|
|
62
141
|
try {
|
|
63
142
|
const data = JSON.parse(raw);
|
|
64
|
-
|
|
65
|
-
return encode(cleaned);
|
|
143
|
+
return encode(shapeOutput(data, !callerShapedOutput));
|
|
66
144
|
}
|
|
67
145
|
catch {
|
|
68
146
|
// Not JSON — wrap in TOON envelope with truncation metadata
|
|
@@ -114,16 +192,40 @@ function collapseRepo(obj) {
|
|
|
114
192
|
}
|
|
115
193
|
return obj;
|
|
116
194
|
}
|
|
117
|
-
|
|
195
|
+
/** Bound a string value's length, leaving its content untouched. */
|
|
196
|
+
function truncateString(value) {
|
|
197
|
+
if (value.length <= STRING_VALUE_TRUNCATION_LIMIT)
|
|
198
|
+
return value;
|
|
199
|
+
return value.slice(0, STRING_VALUE_TRUNCATION_LIMIT) + '... (truncated)';
|
|
200
|
+
}
|
|
201
|
+
/** Clean and truncate a long string value (e.g. bodies, comments, blobs). */
|
|
202
|
+
function clampString(value) {
|
|
203
|
+
if (value.length <= LONG_STRING_CLEANUP_THRESHOLD)
|
|
204
|
+
return value;
|
|
205
|
+
return truncateString(cleanBody(value));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Walk a decoded API response, bounding every string value.
|
|
209
|
+
*
|
|
210
|
+
* With `stripNoisyKeys` the noisy keys are dropped and long strings are cleaned
|
|
211
|
+
* as well. Output the caller shaped with --jq/--template keeps both its keys and
|
|
212
|
+
* its content verbatim and is only length-bounded, since rewriting a field they
|
|
213
|
+
* selected by name is the same silent mutation as deleting it.
|
|
214
|
+
*/
|
|
215
|
+
function shapeOutput(obj, stripNoisyKeys, depth = 0) {
|
|
118
216
|
if (depth > 8)
|
|
119
217
|
return obj;
|
|
120
218
|
if (Array.isArray(obj)) {
|
|
121
|
-
return obj.map((item) =>
|
|
219
|
+
return obj.map((item) => shapeOutput(item, stripNoisyKeys, depth + 1));
|
|
122
220
|
}
|
|
123
221
|
if (obj !== null && typeof obj === 'object') {
|
|
124
222
|
const record = obj;
|
|
125
223
|
const result = {};
|
|
126
224
|
for (const [key, value] of Object.entries(record)) {
|
|
225
|
+
if (!stripNoisyKeys) {
|
|
226
|
+
result[key] = shapeOutput(value, stripNoisyKeys, depth + 1);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
127
229
|
if (NOISY_KEYS.has(key))
|
|
128
230
|
continue;
|
|
129
231
|
if (isTemplateUrlKey(key))
|
|
@@ -138,18 +240,12 @@ function stripNoisyFields(obj, depth = 0) {
|
|
|
138
240
|
result[key] = collapseRepo(value);
|
|
139
241
|
continue;
|
|
140
242
|
}
|
|
141
|
-
result[key] =
|
|
243
|
+
result[key] = shapeOutput(value, stripNoisyKeys, depth + 1);
|
|
142
244
|
}
|
|
143
245
|
return result;
|
|
144
246
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const s = cleanBody(obj);
|
|
148
|
-
if (s.length > STRING_VALUE_TRUNCATION_LIMIT) {
|
|
149
|
-
return s.slice(0, STRING_VALUE_TRUNCATION_LIMIT) + '... (truncated)';
|
|
150
|
-
}
|
|
151
|
-
return s;
|
|
152
|
-
}
|
|
247
|
+
if (typeof obj === 'string')
|
|
248
|
+
return stripNoisyKeys ? clampString(obj) : truncateString(obj);
|
|
153
249
|
return obj;
|
|
154
250
|
}
|
|
155
251
|
//# sourceMappingURL=api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/commands/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/commands/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { RepoContext } from "../context.js";
|
|
2
|
-
export declare const ISSUE_HELP = "usage: gh-axi issue <subcommand> [flags]\nsubcommands[14]:\n list, view <number>, create, edit <number>, close <number>, reopen <number>, comment <number>, delete <number>, lock <number>, unlock <number>, pin <number>, unpin <number>, transfer <number>, subissue <add|remove|list>\nflags{list}:\n --state <open|closed|all>, --label <name
|
|
2
|
+
export declare const ISSUE_HELP = "usage: gh-axi issue <subcommand> [flags]\nsubcommands[14]:\n list, view <number>, create, edit <number>, close <number>, reopen <number>, comment <number>, delete <number>, lock <number>, unlock <number>, pin <number>, unpin <number>, transfer <number>, subissue <add|remove|list>\nflags{list}:\n --state <open|closed|all>, --label <name> (repeatable), --assignee <login>, --author <login>, --milestone <name>, --sort <created|updated|comments>, --limit <n> (default 30), --fields <a,b,c>\nflags{view}:\n --comments, --full (show complete body without truncation)\nflags{create}:\n --title <text> (required), --body <text> or --body-file <path>, --assignee <login> (repeatable), --label <name> (repeatable), --milestone <name>, --project <name> (repeatable), --type <name>\nflags{edit}:\n --title, --body <text> or --body-file <path>, --add-label <name> (repeatable), --remove-label <name> (repeatable), --add-assignee <login> (repeatable), --remove-assignee <login> (repeatable), --milestone, --type <name>, --no-type\nflags{close}:\n --reason <completed|not_planned>, --comment <text>\nflags{comment}:\n --body <text> or --body-file <path> (required)\nflags{transfer}:\n --to-repo <owner/name> (required)\nsubissue:\n add <parent> <child> [<child> ...], remove <parent> <child>, list <parent>\nexamples:\n gh-axi issue list --state closed --label bug\n gh-axi issue view 42 --comments\n gh-axi issue create --title \"Fix login\" --body \"Steps to reproduce...\"\n gh-axi issue comment 42 --body-file comment.md\n gh-axi issue close 42 --reason completed\n gh-axi issue transfer 42 -R source/repo --to-repo dest/repo\n gh-axi issue subissue add 16 20 101 125\n gh-axi issue subissue list 16";
|
|
3
3
|
export declare const SUBISSUE_HELP = "usage: gh-axi issue subissue <add|remove|list> <parent> [child...]\nsubcommands[3]:\n add <parent> <child> [<child> ...], remove <parent> <child>, list <parent>\nexamples:\n gh-axi issue subissue add 16 20 101 125\n gh-axi issue subissue remove 16 101\n gh-axi issue subissue list 16";
|
|
4
4
|
export declare function issueCommand(args: string[], ctx?: RepoContext): Promise<string>;
|
|
@@ -2,10 +2,11 @@ import { resolveHost } from "../host.js";
|
|
|
2
2
|
import { ghJson, ghExec, ghRaw } from "../gh.js";
|
|
3
3
|
import { AxiError, mapGhError } from "../errors.js";
|
|
4
4
|
import { getSuggestions } from "../suggestions.js";
|
|
5
|
-
import { hasFlag, getFlag, getAllFlags, getPositional, requireNumber, takeFlag, takeBoolFlag, } from "../args.js";
|
|
5
|
+
import { hasFlag, getFlag, getAllFlags, pushRepeated, getPositional, requireNumber, takeFlag, takeBoolFlag, } from "../args.js";
|
|
6
6
|
import { takeBody, truncateBody } from "../body.js";
|
|
7
7
|
import { parseFields } from "../fields.js";
|
|
8
8
|
import { formatCountLine } from "../format.js";
|
|
9
|
+
import { fetchListTotal, isSearchableMilestone, } from "../totals.js";
|
|
9
10
|
import { field, pluck, joinArray, relativeTime, lower, custom, renderList, renderDetail, renderHelp, renderError, renderOutput, } from "../toon.js";
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Help
|
|
@@ -14,13 +15,13 @@ export const ISSUE_HELP = `usage: gh-axi issue <subcommand> [flags]
|
|
|
14
15
|
subcommands[14]:
|
|
15
16
|
list, view <number>, create, edit <number>, close <number>, reopen <number>, comment <number>, delete <number>, lock <number>, unlock <number>, pin <number>, unpin <number>, transfer <number>, subissue <add|remove|list>
|
|
16
17
|
flags{list}:
|
|
17
|
-
--state <open|closed|all>, --label <name
|
|
18
|
+
--state <open|closed|all>, --label <name> (repeatable), --assignee <login>, --author <login>, --milestone <name>, --sort <created|updated|comments>, --limit <n> (default 30), --fields <a,b,c>
|
|
18
19
|
flags{view}:
|
|
19
20
|
--comments, --full (show complete body without truncation)
|
|
20
21
|
flags{create}:
|
|
21
|
-
--title <text> (required), --body <text> or --body-file <path>, --assignee <login
|
|
22
|
+
--title <text> (required), --body <text> or --body-file <path>, --assignee <login> (repeatable), --label <name> (repeatable), --milestone <name>, --project <name> (repeatable), --type <name>
|
|
22
23
|
flags{edit}:
|
|
23
|
-
--title, --body <text> or --body-file <path>, --add-label, --remove-label, --add-assignee, --remove-assignee, --milestone, --type <name>, --no-type
|
|
24
|
+
--title, --body <text> or --body-file <path>, --add-label <name> (repeatable), --remove-label <name> (repeatable), --add-assignee <login> (repeatable), --remove-assignee <login> (repeatable), --milestone, --type <name>, --no-type
|
|
24
25
|
flags{close}:
|
|
25
26
|
--reason <completed|not_planned>, --comment <text>
|
|
26
27
|
flags{comment}:
|
|
@@ -138,7 +139,7 @@ async function listIssues(args, ctx) {
|
|
|
138
139
|
const fieldsArg = takeFlag(args, "--fields");
|
|
139
140
|
const { extraDefs, extraJsonKeys } = parseFields(fieldsArg, ISSUE_LIST_EXTRA_FIELDS);
|
|
140
141
|
const state = getFlag(args, "--state");
|
|
141
|
-
const
|
|
142
|
+
const labels = getAllFlags(args, "--label");
|
|
142
143
|
const assignee = getFlag(args, "--assignee");
|
|
143
144
|
const author = getFlag(args, "--author");
|
|
144
145
|
const milestone = getFlag(args, "--milestone");
|
|
@@ -159,8 +160,7 @@ async function listIssues(args, ctx) {
|
|
|
159
160
|
];
|
|
160
161
|
if (state)
|
|
161
162
|
ghArgs.push("--state", state);
|
|
162
|
-
|
|
163
|
-
ghArgs.push("--label", label);
|
|
163
|
+
pushRepeated(ghArgs, "--label", labels);
|
|
164
164
|
if (assignee)
|
|
165
165
|
ghArgs.push("--assignee", assignee);
|
|
166
166
|
if (author)
|
|
@@ -171,21 +171,23 @@ async function listIssues(args, ctx) {
|
|
|
171
171
|
ghArgs.push("--search", `sort:${sort}-desc`);
|
|
172
172
|
const items = await ghJson(ghArgs, ctx);
|
|
173
173
|
const isEmpty = items.length === 0;
|
|
174
|
-
//
|
|
174
|
+
// Only a page truncated by the limit needs a total; a short page already
|
|
175
|
+
// shows every match.
|
|
175
176
|
let totalCount;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
// A milestone number cannot be expressed as a search qualifier, so no total
|
|
178
|
+
// can be counted for it — no total beats a wrong one.
|
|
179
|
+
const countable = !milestone || isSearchableMilestone(milestone);
|
|
180
|
+
if (items.length === limit && ctx && countable) {
|
|
181
|
+
const filters = [];
|
|
182
|
+
for (const label of labels)
|
|
183
|
+
filters.push({ key: "label", value: label, list: true });
|
|
184
|
+
if (assignee)
|
|
185
|
+
filters.push({ key: "assignee", value: assignee });
|
|
186
|
+
if (author)
|
|
187
|
+
filters.push({ key: "author", value: author });
|
|
188
|
+
if (milestone)
|
|
189
|
+
filters.push({ key: "milestone", value: milestone });
|
|
190
|
+
totalCount = await fetchListTotal(ctx, "issues", state, filters);
|
|
189
191
|
}
|
|
190
192
|
const countLine = formatCountLine({ count: items.length, limit, totalCount });
|
|
191
193
|
const extendedSchema = extraDefs.length > 0 ? [...listSchema, ...extraDefs] : listSchema;
|
|
@@ -337,10 +339,10 @@ async function createIssue(args, ctx) {
|
|
|
337
339
|
if (!title)
|
|
338
340
|
throw new AxiError("--title is required", "VALIDATION_ERROR");
|
|
339
341
|
const body = takeBody(args);
|
|
340
|
-
const
|
|
342
|
+
const assignees = getAllFlags(args, "--assignee");
|
|
341
343
|
const labels = getAllFlags(args, "--label");
|
|
342
344
|
const milestone = getFlag(args, "--milestone");
|
|
343
|
-
const
|
|
345
|
+
const projects = getAllFlags(args, "--project");
|
|
344
346
|
const typeName = getOptionalRequiredFlag(args, "--type");
|
|
345
347
|
// Resolve type up front so an invalid value fails before creating the issue.
|
|
346
348
|
let resolvedType;
|
|
@@ -350,14 +352,11 @@ async function createIssue(args, ctx) {
|
|
|
350
352
|
const ghArgs = ["issue", "create", "--title", title];
|
|
351
353
|
if (body !== undefined)
|
|
352
354
|
ghArgs.push("--body", body);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
for (const l of labels)
|
|
356
|
-
ghArgs.push("--label", l);
|
|
355
|
+
pushRepeated(ghArgs, "--assignee", assignees);
|
|
356
|
+
pushRepeated(ghArgs, "--label", labels);
|
|
357
357
|
if (milestone)
|
|
358
358
|
ghArgs.push("--milestone", milestone);
|
|
359
|
-
|
|
360
|
-
ghArgs.push("--project", project);
|
|
359
|
+
pushRepeated(ghArgs, "--project", projects);
|
|
361
360
|
// gh issue create outputs the URL; use --json to get structured data
|
|
362
361
|
// Unfortunately gh issue create doesn't support --json, so we parse the URL
|
|
363
362
|
const output = await ghExec(ghArgs, ctx);
|
|
@@ -391,10 +390,10 @@ async function editIssue(args, ctx) {
|
|
|
391
390
|
const num = requireNumber(getPositional(args, 1), "issue");
|
|
392
391
|
const title = getFlag(args, "--title");
|
|
393
392
|
const body = takeBody(args);
|
|
394
|
-
const
|
|
395
|
-
const
|
|
396
|
-
const
|
|
397
|
-
const
|
|
393
|
+
const addLabels = getAllFlags(args, "--add-label");
|
|
394
|
+
const removeLabels = getAllFlags(args, "--remove-label");
|
|
395
|
+
const addAssignees = getAllFlags(args, "--add-assignee");
|
|
396
|
+
const removeAssignees = getAllFlags(args, "--remove-assignee");
|
|
398
397
|
const milestone = getFlag(args, "--milestone");
|
|
399
398
|
const clearType = takeBoolFlag(args, "--no-type");
|
|
400
399
|
const typeName = getOptionalRequiredFlag(args, "--type");
|
|
@@ -409,14 +408,10 @@ async function editIssue(args, ctx) {
|
|
|
409
408
|
ghArgs.push("--title", title);
|
|
410
409
|
if (body !== undefined)
|
|
411
410
|
ghArgs.push("--body", body);
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (addAssignee)
|
|
417
|
-
ghArgs.push("--add-assignee", addAssignee);
|
|
418
|
-
if (removeAssignee)
|
|
419
|
-
ghArgs.push("--remove-assignee", removeAssignee);
|
|
411
|
+
pushRepeated(ghArgs, "--add-label", addLabels);
|
|
412
|
+
pushRepeated(ghArgs, "--remove-label", removeLabels);
|
|
413
|
+
pushRepeated(ghArgs, "--add-assignee", addAssignees);
|
|
414
|
+
pushRepeated(ghArgs, "--remove-assignee", removeAssignees);
|
|
420
415
|
if (milestone)
|
|
421
416
|
ghArgs.push("--milestone", milestone);
|
|
422
417
|
// Only call `gh issue edit` if there is a non-type field to update; otherwise
|