hey-axi 0.2.0 → 0.3.1
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 +31 -15
- package/package.json +1 -1
- package/skills/hey-axi/SKILL.md +48 -45
- package/skills/hey-axi/references/commands.md +39 -36
- package/src/activity.js +160 -0
- package/src/args.js +75 -10
- package/src/arity.js +94 -0
- package/src/cli.js +373 -136
- package/src/errors.js +128 -31
- package/src/examples.js +57 -0
- package/src/guide.js +21 -2
- package/src/home-path.js +12 -0
- package/src/home.js +48 -16
- package/src/hooks.js +176 -23
- package/src/manifest.json +699 -251
- package/src/policy.js +154 -11
- package/src/router.js +45 -11
- package/src/scope.js +177 -0
- package/src/shape.js +197 -56
- package/src/text.js +3 -3
- package/src/version.js +1 -1
package/src/cli.js
CHANGED
|
@@ -1,27 +1,57 @@
|
|
|
1
1
|
// hey-axi's CLI. Loaded by src/hey-axi.js only after the --version fast path.
|
|
2
|
+
//
|
|
3
|
+
// Order of checks, all before HEY runs: the command must exist, its flags must be known,
|
|
4
|
+
// have their values and the right types, its positional arguments must fit HEY's USAGE,
|
|
5
|
+
// the safety policy must allow it, and content that would otherwise open an editor must
|
|
6
|
+
// be given. Only then is HEY started, non-interactively.
|
|
2
7
|
|
|
3
8
|
import { spawn } from "node:child_process";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
4
10
|
import { createInterface } from "node:readline";
|
|
5
11
|
import { encode } from "@toon-format/toon";
|
|
6
|
-
import {
|
|
7
|
-
import { checkPolicy, runMode, sendStaging } from "./policy.js";
|
|
8
|
-
import {
|
|
9
|
-
import { RAW_OUTPUT_FLAGS, flagLabel, flagValue, hasAny, scanArgs, stripAxiFlags, validateFlags, valueFlagSet } from "./args.js";
|
|
10
|
-
import {
|
|
12
|
+
import { closestNames, loadBundledManifest, resolveCommand } from "./router.js";
|
|
13
|
+
import { STDIN_VALUE_FLAGS, checkPolicy, contentProblem, noopFor, runMode, sendCommands, sendStaging, userOnlyPaths } from "./policy.js";
|
|
14
|
+
import { translateFailure, cleanLine, warningLines } from "./errors.js";
|
|
15
|
+
import { RAW_OUTPUT_FLAGS, flagLabel, flagValue, hasAny, nodeValueFlags, scanArgs, stripAxiFlags, validateFlags, valueFlagSet } from "./args.js";
|
|
16
|
+
import { argumentHelp, checkArity, oneOfHelp } from "./arity.js";
|
|
17
|
+
import { FieldError, LIST_FIELDS, DETAIL_FIELDS, carrySelectors, withSelectors, shapeEnvelope } from "./shape.js";
|
|
11
18
|
import { homeView } from "./home.js";
|
|
12
19
|
import { DESCRIPTION } from "./guide.js";
|
|
13
20
|
import { heyToAxi, shellWord } from "./text.js";
|
|
21
|
+
import { examplesFor, flagDefault } from "./examples.js";
|
|
14
22
|
import { VERSION } from "./version.js";
|
|
15
23
|
|
|
16
24
|
// Resolve the HEY CLI: an explicit HEY_BIN wins, otherwise `hey` is looked up on PATH
|
|
17
25
|
// by spawn(), the same way a shell would find it.
|
|
18
26
|
const HEY = process.env.HEY_BIN || "hey";
|
|
19
27
|
|
|
28
|
+
// HEY never prompts or opens an editor under hey-axi: stdin is closed and these are set.
|
|
29
|
+
const QUIET_ENV = { HEY_NONINTERACTIVE: "1", EDITOR: "false", VISUAL: "false" };
|
|
30
|
+
const heyEnv = () => ({ ...process.env, ...QUIET_ENV });
|
|
31
|
+
|
|
32
|
+
const ALWAYS_ALLOWED = "--help, --json, --quiet, --fields, --full, --account, --ids-only, --count, --markdown, --html, --styled, --jq, --stats, --verbose, --base-url, --allow-send, --allow-secret, --interactive";
|
|
33
|
+
|
|
34
|
+
const GLOBAL_HELP = [
|
|
35
|
+
"--account <id|all> linked account (default: HEY's default account)",
|
|
36
|
+
"--base-url <url> HEY server (default: HEY's configured server)",
|
|
37
|
+
"--json the same shaped result as compact JSON (default false: TOON)",
|
|
38
|
+
"--quiet drop HEY's summary/notice/breadcrumbs/meta, keep data, count and hints (default false)",
|
|
39
|
+
"--fields <a,b|all> columns to show (default: the default fields)",
|
|
40
|
+
"--full HEY's complete, untruncated result (default false)",
|
|
41
|
+
"--ids-only, --count, --markdown, --html, --styled, --jq <expr> HEY's own output format, printed as-is (default: none)",
|
|
42
|
+
"--stats, --verbose request stats / debug logging on stderr (default false)",
|
|
43
|
+
];
|
|
44
|
+
const OWN_FLAG_HELP = [
|
|
45
|
+
[(path) => sendCommands().includes(path), "--allow-send really send (default false: saved as a draft, or refused when there is no draft mode)"],
|
|
46
|
+
[(path) => path === "auth token", "--allow-secret print the token (default false: refused)"],
|
|
47
|
+
[(path) => userOnlyPaths().includes(path), "--interactive hand HEY the terminal; for a person at a terminal (default false: refused)"],
|
|
48
|
+
];
|
|
49
|
+
|
|
20
50
|
function usage(manifest) {
|
|
21
51
|
const lines = [
|
|
22
52
|
`hey-axi ${VERSION} — ${DESCRIPTION}`,
|
|
23
53
|
"",
|
|
24
|
-
"Run `hey-axi` with no arguments for a live home view (
|
|
54
|
+
"Run `hey-axi` with no arguments for a live home view (mail for this directory's scope + next commands).",
|
|
25
55
|
"",
|
|
26
56
|
`commands (from hey ${manifest.hey_version}; run \`hey-axi <command> --help\` for details):`,
|
|
27
57
|
];
|
|
@@ -30,22 +60,26 @@ function usage(manifest) {
|
|
|
30
60
|
lines.push(` ${node.name}${subs} — ${node.short}`);
|
|
31
61
|
}
|
|
32
62
|
lines.push(
|
|
63
|
+
" setup hooks — session start/end hooks for Claude Code, Codex and OpenCode (hey-axi's own)",
|
|
64
|
+
" setup scope — focus the home view in this directory on a box, label or search (hey-axi's own)",
|
|
33
65
|
"",
|
|
34
|
-
"output (
|
|
66
|
+
"output (global flags may go anywhere on the line):",
|
|
35
67
|
" default HEY's JSON envelope (data, summary, notice, breadcrumbs, meta) rendered as TOON",
|
|
36
|
-
" --json
|
|
37
|
-
" --quiet Drop
|
|
68
|
+
" --json The same shaped result as compact JSON instead of TOON",
|
|
69
|
+
" --quiet Drop HEY's summary/notice/breadcrumbs/meta; keep data, count, empty state and hints",
|
|
38
70
|
" --ids-only, --count, --markdown, --html, --styled, --jq <expr>",
|
|
39
71
|
" Passed to HEY untouched; HEY's output is printed as-is",
|
|
40
72
|
" --fields a,b,c Choose list columns (aliases from the defaults or dotted paths); --fields all keeps every field",
|
|
41
73
|
" --full HEY's complete, untruncated result (no field selection, no truncation)",
|
|
42
|
-
" --account <id|
|
|
74
|
+
" --account <id|all> Linked account (default: HEY's default account); also --base-url <url>, --stats, --verbose",
|
|
75
|
+
" --interactive Hand HEY the terminal for tui, setup, browser auth login and mcp (refused without it)",
|
|
43
76
|
" --help Show this help (or `hey-axi <command> --help`)",
|
|
44
77
|
" -v, -V, --version Print hey-axi's version (`hey-axi version` also shows HEY's)",
|
|
45
78
|
"",
|
|
46
|
-
"lists
|
|
47
|
-
"",
|
|
48
|
-
"
|
|
79
|
+
"lists show at most 4 columns, a count, and cut long text (with its total size).",
|
|
80
|
+
"unknown commands, flags or arguments, flags missing a value, and wrong value types are rejected before HEY runs (exit 2).",
|
|
81
|
+
"exit codes: 0 success (including no-ops), 1 error, 2 usage error or refusal. Errors are TOON on stdout with `kind` and `help`.",
|
|
82
|
+
"HEY never prompts: content that would open an editor must be passed (--message, or --message - for stdin).",
|
|
49
83
|
"",
|
|
50
84
|
"safety:",
|
|
51
85
|
" Nothing is sent without --allow-send (or HEY_AXI_ALLOW_SEND=1): compose and reply are saved",
|
|
@@ -55,10 +89,15 @@ function usage(manifest) {
|
|
|
55
89
|
return lines.join("\n");
|
|
56
90
|
}
|
|
57
91
|
|
|
58
|
-
function commandHelp(node) {
|
|
92
|
+
export function commandHelp(node) {
|
|
59
93
|
const lines = [`hey-axi ${node.path} — ${node.short}`];
|
|
60
|
-
for (const synopsis of node.synopsis || []) lines.push(`usage: ${heyToAxi(synopsis)}`);
|
|
94
|
+
for (const synopsis of node.synopsis || []) if (!synopsis.includes("<command>") || !node.usage) lines.push(`usage: ${heyToAxi(synopsis)}`);
|
|
61
95
|
if (node.usage) lines.push(`shortcut: hey-axi ${node.usage}`);
|
|
96
|
+
const args = [...argumentHelp(node), ...oneOfHelp(node)];
|
|
97
|
+
if (args.length) {
|
|
98
|
+
lines.push("", "arguments:");
|
|
99
|
+
for (const arg of args) lines.push(` ${arg}`);
|
|
100
|
+
}
|
|
62
101
|
if (node.subcommands?.length) {
|
|
63
102
|
lines.push("", "subcommands:");
|
|
64
103
|
for (const child of node.subcommands) lines.push(` ${child.path} — ${child.short}`);
|
|
@@ -66,25 +105,28 @@ function commandHelp(node) {
|
|
|
66
105
|
if (node.flags?.length) {
|
|
67
106
|
lines.push("", "flags:");
|
|
68
107
|
for (const flag of node.flags) {
|
|
69
|
-
const extra = [flag.desc,
|
|
70
|
-
lines.push(` ${flagLabel(flag)}
|
|
108
|
+
const extra = [flag.desc, `(${flagDefault(flag)})`].filter(Boolean).join(" ");
|
|
109
|
+
lines.push(` ${flagLabel(flag)} ${extra}`);
|
|
71
110
|
}
|
|
72
111
|
}
|
|
112
|
+
lines.push("", "global flags (allowed on every command):", ...GLOBAL_HELP.map((line) => ` ${line}`));
|
|
113
|
+
const own = OWN_FLAG_HELP.filter(([applies]) => applies(node.path)).map(([, line]) => ` ${line}`);
|
|
114
|
+
if (own.length) lines.push(...own);
|
|
73
115
|
const fields = LIST_FIELDS[node.path] || DETAIL_FIELDS[node.path];
|
|
74
116
|
if (fields) {
|
|
75
117
|
lines.push("", `default fields: ${fields.map((spec) => spec.split("=")[0]).join(", ")} (--fields a,b,c to choose, --fields all for every field, --full for HEY's untouched result)`);
|
|
76
118
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (const example of node.examples) lines.push(` ${heyToAxi(example)}`);
|
|
80
|
-
}
|
|
119
|
+
lines.push("", "examples:");
|
|
120
|
+
for (const example of examplesFor(node)) lines.push(` ${example}`);
|
|
81
121
|
if (node.notes) lines.push("", `notes: ${heyToAxi(node.notes)}`);
|
|
82
|
-
const mode = runMode(node.path, new Set());
|
|
83
|
-
if (mode === "interactive") lines.push("", "
|
|
84
|
-
if (mode === "stream") lines.push("", "streams HEY's NDJSON
|
|
122
|
+
const mode = runMode(node.path, new Set(["--interactive"]));
|
|
123
|
+
if (mode === "interactive") lines.push("", "needs a person (or, for mcp, an MCP client): refused (exit 2) unless --interactive is passed; then HEY gets the terminal");
|
|
124
|
+
if (mode === "stream") lines.push("", "streams each event as it arrives (a TOON block per event; --json for HEY's NDJSON) until it exits or is interrupted");
|
|
85
125
|
if (mode === "raw") lines.push("", "prints HEY's output as-is (not JSON)");
|
|
86
126
|
const gate = checkPolicy(node.path, new Set(), {}, { stdin: true, stdout: true });
|
|
87
127
|
if (gate) lines.push("", `${gate.error}: ${gate.reason}; ${gate.hint}`);
|
|
128
|
+
const content = contentProblem(node, new Set(), []);
|
|
129
|
+
if (content) lines.push("", `required: ${content.error.replace("missing ", "")} (${content.hint}); HEY never opens an editor under hey-axi`);
|
|
88
130
|
if (sendStaging(node.path, new Set(), {})) lines.push("", "without --allow-send (or HEY_AXI_ALLOW_SEND=1) hey-axi adds --draft: it's saved as a draft, not sent");
|
|
89
131
|
return lines.join("\n");
|
|
90
132
|
}
|
|
@@ -93,6 +135,41 @@ function output(value) {
|
|
|
93
135
|
process.stdout.write(`${encode(value)}\n`);
|
|
94
136
|
}
|
|
95
137
|
|
|
138
|
+
// Only HEY's real warnings reach stderr; debug and progress noise is dropped.
|
|
139
|
+
function writeWarnings(text) {
|
|
140
|
+
for (const line of warningLines(text)) process.stderr.write(`${line}\n`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const HOOK_END_HELP = [
|
|
144
|
+
"hey-axi hook session-end — record what this agent session did (run by the session-end hooks)",
|
|
145
|
+
"usage: hey-axi hook session-end (reads the hook's JSON payload from stdin: session_id, cwd)",
|
|
146
|
+
"",
|
|
147
|
+
"Folds this session's hey-axi activity in the current directory scope (command names and numeric",
|
|
148
|
+
"ids only) into a one-line summary that the next home view shows as last_session. Does nothing",
|
|
149
|
+
"unless `hey-axi setup hooks` turned capture on. Takes no flags; always exits 0.",
|
|
150
|
+
"",
|
|
151
|
+
"examples:",
|
|
152
|
+
" echo '{\"session_id\":\"abc\",\"cwd\":\"/path/to/project\"}' | hey-axi hook session-end",
|
|
153
|
+
" hey-axi setup hooks # installs this hook for Claude Code, Codex and OpenCode",
|
|
154
|
+
].join("\n");
|
|
155
|
+
|
|
156
|
+
// The invocation's --account/--base-url, carried into every suggested command, including
|
|
157
|
+
// those in refusals.
|
|
158
|
+
let helpCarry = [];
|
|
159
|
+
|
|
160
|
+
function fail(value, code) {
|
|
161
|
+
// Every refusal says what kind of failure it is (exit 2 is always a usage error).
|
|
162
|
+
if (value && value.ok === false && !value.kind) value = { ok: false, kind: code === 2 ? "usage" : "command_error", ...value };
|
|
163
|
+
if (value && value.ok === false && helpCarry.length) {
|
|
164
|
+
for (const key of ["help", "hint"]) {
|
|
165
|
+
if (Array.isArray(value[key])) value[key] = value[key].map((line) => carrySelectors(line, helpCarry));
|
|
166
|
+
else if (typeof value[key] === "string") value[key] = carrySelectors(value[key], helpCarry);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
output(value);
|
|
170
|
+
process.exit(code);
|
|
171
|
+
}
|
|
172
|
+
|
|
96
173
|
function spawnErrorMessage(error) {
|
|
97
174
|
if (error.code === "ENOENT") {
|
|
98
175
|
return process.env.HEY_BIN
|
|
@@ -102,16 +179,17 @@ function spawnErrorMessage(error) {
|
|
|
102
179
|
return error.message;
|
|
103
180
|
}
|
|
104
181
|
|
|
105
|
-
// Run HEY with captured output. `extra` holds the format flags hey-axi adds.
|
|
182
|
+
// Run HEY with captured output and no stdin. `extra` holds the format flags hey-axi adds.
|
|
106
183
|
function runHey(args, extra = ["--json"]) {
|
|
107
184
|
return new Promise((resolve) => {
|
|
108
|
-
const child = spawn(HEY, [...args, ...extra], { stdio: ["
|
|
185
|
+
const child = spawn(HEY, [...args, ...extra], { stdio: ["ignore", "pipe", "pipe"], env: heyEnv() });
|
|
109
186
|
let stdout = "";
|
|
110
187
|
let stderr = "";
|
|
111
188
|
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
112
189
|
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
113
190
|
child.on("error", (error) => resolve({ status: 127, error: spawnErrorMessage(error) }));
|
|
114
|
-
|
|
191
|
+
// Killed by a signal: no exit status, and not a success.
|
|
192
|
+
child.on("close", (status, signal) => resolve({ status: status ?? (signal ? 1 : 0), signal, stdout, stderr }));
|
|
115
193
|
});
|
|
116
194
|
}
|
|
117
195
|
|
|
@@ -125,47 +203,80 @@ function forwardSignals(child) {
|
|
|
125
203
|
return () => handlers.forEach(([signal, handler]) => process.off(signal, handler));
|
|
126
204
|
}
|
|
127
205
|
|
|
128
|
-
|
|
206
|
+
// Interactive commands (a person at a terminal): HEY gets the terminal.
|
|
207
|
+
function runHeyInteractive(args) {
|
|
208
|
+
return new Promise((resolve) => {
|
|
209
|
+
const child = spawn(HEY, args, { stdio: "inherit" });
|
|
210
|
+
const release = forwardSignals(child);
|
|
211
|
+
child.on("error", (error) => { release(); resolve({ status: 127, error: spawnErrorMessage(error) }); });
|
|
212
|
+
child.on("close", (status, signal) => { release(); resolve({ status: status ?? (signal ? 1 : 0), signal }); });
|
|
213
|
+
});
|
|
214
|
+
}
|
|
129
215
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
216
|
+
// Raw output (a script, a CSV, --ids-only ...): stdout is held until HEY exits, then
|
|
217
|
+
// printed untouched on success. On failure nothing of HEY's is printed; the failure is
|
|
218
|
+
// reported as a structured error on stdout instead.
|
|
132
219
|
function runHeyRaw(args) {
|
|
133
220
|
return new Promise((resolve) => {
|
|
134
|
-
const child = spawn(HEY, args, { stdio: "
|
|
221
|
+
const child = spawn(HEY, args, { stdio: ["ignore", "pipe", "pipe"], env: heyEnv() });
|
|
135
222
|
const release = forwardSignals(child);
|
|
223
|
+
const out = [];
|
|
224
|
+
let stderr = "";
|
|
225
|
+
child.stdout.on("data", (chunk) => { out.push(chunk); });
|
|
226
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
136
227
|
child.on("error", (error) => { release(); resolve({ status: 127, error: spawnErrorMessage(error) }); });
|
|
137
|
-
child.on("close", (status, signal) => { release(); resolve({ status:
|
|
228
|
+
child.on("close", (status, signal) => { release(); resolve({ status: status ?? (signal ? 1 : 0), signal, stderr, stdout: Buffer.concat(out) }); });
|
|
138
229
|
});
|
|
139
230
|
}
|
|
140
231
|
|
|
141
|
-
// Run a long-lived NDJSON producer (hey watch), relaying each complete
|
|
142
|
-
// it arrives so consumers never see a partial event
|
|
143
|
-
|
|
232
|
+
// Run a long-lived NDJSON producer (hey watch), relaying each complete event the moment
|
|
233
|
+
// it arrives so consumers never see a partial event: as a TOON block followed by a blank
|
|
234
|
+
// line by default, or as HEY's NDJSON line with --json.
|
|
235
|
+
function runHeyStream(args, { toon = true } = {}) {
|
|
144
236
|
return new Promise((resolve) => {
|
|
145
|
-
const child = spawn(HEY, args, { stdio: ["
|
|
237
|
+
const child = spawn(HEY, args, { stdio: ["ignore", "pipe", "pipe"], env: heyEnv() });
|
|
146
238
|
const release = forwardSignals(child);
|
|
239
|
+
let stderr = "";
|
|
240
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
147
241
|
const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
148
|
-
lines.on("line", (line) =>
|
|
242
|
+
lines.on("line", (line) => {
|
|
243
|
+
if (!toon) return process.stdout.write(`${line}\n`);
|
|
244
|
+
let event;
|
|
245
|
+
try {
|
|
246
|
+
event = JSON.parse(line);
|
|
247
|
+
} catch {
|
|
248
|
+
// Not an event: a diagnostic. It goes to stderr, cleaned, never into the stream.
|
|
249
|
+
const clean = cleanLine(line);
|
|
250
|
+
if (clean) process.stderr.write(`hey-axi: ${clean}\n`);
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
process.stdout.write(`${encode(event)}\n\n`);
|
|
254
|
+
});
|
|
149
255
|
// A consumer that stops reading (e.g. `| head -1`) ends the watch cleanly.
|
|
150
256
|
process.stdout.on("error", (error) => {
|
|
151
257
|
if (error.code === "EPIPE") child.kill("SIGTERM");
|
|
152
258
|
});
|
|
153
259
|
child.on("error", (error) => { release(); resolve({ status: 127, error: spawnErrorMessage(error) }); });
|
|
154
|
-
child.on("close", (status, signal) => { release(); lines.close(); resolve({ status:
|
|
260
|
+
child.on("close", (status, signal) => { release(); lines.close(); resolve({ status: status ?? 0, signal, stderr, stdout: "" }); });
|
|
155
261
|
});
|
|
156
262
|
}
|
|
157
263
|
|
|
264
|
+
// Read the session-end hook's JSON payload from stdin, if one is piped (never waits on a terminal).
|
|
265
|
+
function readHookInput() {
|
|
266
|
+
if (process.stdin.isTTY) return {};
|
|
267
|
+
try {
|
|
268
|
+
const text = readFileSync(0, "utf8");
|
|
269
|
+
return text.trim() ? JSON.parse(text) : {};
|
|
270
|
+
} catch {
|
|
271
|
+
return {};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
158
274
|
|
|
159
|
-
|
|
160
|
-
async function discoverCommands() {
|
|
161
|
-
const result = await runHey(["commands"]);
|
|
162
|
-
if (result.status !== 0) return null;
|
|
275
|
+
function scopeDirFor(cwd, findScope) {
|
|
163
276
|
try {
|
|
164
|
-
|
|
165
|
-
const data = Array.isArray(parsed) ? parsed : parsed?.data;
|
|
166
|
-
return Array.isArray(data) ? normalizeCatalog(data) : null;
|
|
277
|
+
return findScope(cwd)?.dir || cwd;
|
|
167
278
|
} catch {
|
|
168
|
-
return
|
|
279
|
+
return cwd;
|
|
169
280
|
}
|
|
170
281
|
}
|
|
171
282
|
|
|
@@ -176,83 +287,163 @@ if (args.length === 0) {
|
|
|
176
287
|
process.exit(0);
|
|
177
288
|
}
|
|
178
289
|
if (args[0] === "--help" || args[0] === "-h") {
|
|
179
|
-
|
|
180
|
-
|
|
290
|
+
// Top-level help takes nothing else: `hey-axi --help box` is `hey-axi box --help`.
|
|
291
|
+
const rest = args.slice(1).filter((arg) => arg !== "--help" && arg !== "-h");
|
|
292
|
+
if (rest.length && !rest[0].startsWith("-")) {
|
|
293
|
+
args = [...rest, "--help"];
|
|
294
|
+
} else if (rest.length) {
|
|
295
|
+
fail({ ok: false, error: `unknown flag ${rest[0]} for \`hey-axi --help\``, help: ["Run `hey-axi --help` for all commands", "Run `hey-axi <command> --help` for one command"] }, 2);
|
|
296
|
+
} else {
|
|
297
|
+
process.stdout.write(`${usage(manifest)}\n`);
|
|
298
|
+
process.exit(0);
|
|
299
|
+
}
|
|
181
300
|
}
|
|
182
301
|
|
|
183
|
-
|
|
302
|
+
// hey-axi's own commands: the session-end hook, `setup hooks` and `setup scope`.
|
|
303
|
+
if (args[0] === "hook" && args[1] === "session-end" && args.length === 3 && (args[2] === "--help" || args[2] === "-h")) {
|
|
304
|
+
process.stdout.write(`${HOOK_END_HELP}\n`);
|
|
305
|
+
process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
if (args[0] === "hook" && args[1] === "session-end" && args.length === 2) {
|
|
308
|
+
const { endSession } = await import("./activity.js");
|
|
309
|
+
const { findScope } = await import("./scope.js");
|
|
310
|
+
const input = readHookInput();
|
|
311
|
+
const cwd = typeof input.cwd === "string" && input.cwd ? input.cwd : process.cwd();
|
|
312
|
+
const { sessionIdFrom } = await import("./activity.js");
|
|
313
|
+
const session = [input.session_id, input.sessionID, input.sessionId].find((value) => typeof value === "string" && value) || sessionIdFrom();
|
|
314
|
+
output(endSession(scopeDirFor(cwd, findScope), session));
|
|
315
|
+
process.exit(0);
|
|
316
|
+
}
|
|
317
|
+
if (args[0] === "setup" && args[1] === "scope") {
|
|
318
|
+
const { SCOPE_HELP, SCOPE_FLAGS, setupScope } = await import("./scope.js");
|
|
319
|
+
const strange = args.slice(2).find((arg) => arg.startsWith("-") && !SCOPE_FLAGS.has(arg.split("=", 1)[0]));
|
|
320
|
+
if (strange) fail({ ok: false, error: `unknown flag ${strange.split("=", 1)[0]} for \`setup scope\``, help: ["valid flags for `setup scope`: --box, --label, --search, --account, --limit, --status, --remove", "Run `hey-axi setup scope --help`"] }, 2);
|
|
321
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
322
|
+
process.stdout.write(`${SCOPE_HELP}\n`);
|
|
323
|
+
process.exit(0);
|
|
324
|
+
}
|
|
325
|
+
const { output: result, code } = setupScope(args.slice(2));
|
|
326
|
+
fail(result, code);
|
|
327
|
+
}
|
|
184
328
|
|
|
185
|
-
|
|
186
|
-
|
|
329
|
+
const first = scanArgs(args, valueFlagSet(manifest.commands));
|
|
330
|
+
for (const name of ["account", "base-url"]) {
|
|
331
|
+
const value = flagValue(args, name);
|
|
332
|
+
if (value !== undefined) helpCarry.push(`--${name}`, shellWord(value));
|
|
333
|
+
}
|
|
334
|
+
if (first.words[0] === "setup" && first.words[1] === "hooks") {
|
|
187
335
|
const { HOOKS_HELP, setupHooks } = await import("./hooks.js");
|
|
188
|
-
if (
|
|
336
|
+
if (first.words.length > 2) fail({ ok: false, error: `unexpected argument "${first.words[2]}" for \`setup hooks\``, help: "Run `hey-axi setup hooks --help`" }, 2);
|
|
337
|
+
// Its flags are all switches: `--remove=false` is refused, not read as --remove.
|
|
338
|
+
const switchValue = first.values.find(([name]) => ["--project", "--status", "--remove"].includes(name));
|
|
339
|
+
if (switchValue) fail({ ok: false, error: `${switchValue[0]} is a switch and takes no value: pass ${switchValue[0]}, or leave it out`, help: "Run `hey-axi setup hooks --help`" }, 2);
|
|
340
|
+
if (first.flags.has("--help") || first.flags.has("-h")) {
|
|
189
341
|
process.stdout.write(`${HOOKS_HELP}\n`);
|
|
190
342
|
process.exit(0);
|
|
191
343
|
}
|
|
192
|
-
const { output: result, code } = await setupHooks(flags);
|
|
193
|
-
|
|
194
|
-
process.exit(code);
|
|
344
|
+
const { output: result, code } = await setupHooks(first.flags);
|
|
345
|
+
fail(result, code);
|
|
195
346
|
}
|
|
196
347
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const raw = hasAny(flags, RAW_OUTPUT_FLAGS);
|
|
202
|
-
const command = heyArgs.filter((arg) => arg !== "--json" && arg !== "--quiet");
|
|
203
|
-
const fieldsArg = flagValue(args, "fields");
|
|
204
|
-
const fields = fieldsArg === undefined ? null : fieldsArg.trim() === "all" ? "all" : fieldsArg.split(",").map((name) => name.trim()).filter(Boolean);
|
|
205
|
-
// The command as the agent typed it, minus output/shaping flags, for `--full` hints.
|
|
206
|
-
const commandLine = ["hey-axi", ...stripAxiFlags(args).filter((arg) => arg !== "--json" && arg !== "--quiet")].map(shellWord).join(" ");
|
|
207
|
-
|
|
208
|
-
let resolved = resolveCommand(manifest.commands, words);
|
|
209
|
-
if (resolved.error === "unknown command" || resolved.error === "unknown subcommand") {
|
|
210
|
-
const live = await discoverCommands();
|
|
211
|
-
const retry = live && resolveCommand(live, words);
|
|
212
|
-
if (retry && !retry.error) resolved = retry;
|
|
348
|
+
if (first.words.length === 0) {
|
|
349
|
+
// Only flags, no command (`hey-axi --account 5`).
|
|
350
|
+
if (first.missing.length) fail({ ok: false, kind: "usage", error: `missing value for ${first.missing.join(", ")}`, help: "Run `hey-axi --help` for all commands" }, 2);
|
|
351
|
+
fail({ ok: false, kind: "usage", error: "no command given", help: ["Run `hey-axi` with no arguments for the home view", "Run `hey-axi --help` for all commands"] }, 2);
|
|
213
352
|
}
|
|
214
353
|
|
|
354
|
+
const resolved = resolveCommand(manifest.commands, first.words);
|
|
215
355
|
if (resolved.error) {
|
|
216
356
|
const failure = { ok: false, error: resolved.error };
|
|
217
357
|
if (resolved.path) failure.command = resolved.path;
|
|
218
358
|
if (resolved.word) failure.word = resolved.word;
|
|
219
359
|
if (resolved.subcommands) failure.subcommands = resolved.subcommands;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
360
|
+
let level = manifest.commands;
|
|
361
|
+
if (resolved.path) for (const word of resolved.path.split(" ")) level = level.find((node) => node.name === word)?.subcommands || [];
|
|
362
|
+
const close = closestNames(resolved.word, level);
|
|
363
|
+
failure.help = [
|
|
364
|
+
...close.map((name) => `Did you mean \`hey-axi ${resolved.path ? `${resolved.path} ` : ""}${name}\`?`),
|
|
365
|
+
resolved.path ? `Run \`hey-axi ${resolved.path} --help\`` : "Run `hey-axi --help` for all commands",
|
|
366
|
+
];
|
|
367
|
+
fail(failure, 2);
|
|
223
368
|
}
|
|
369
|
+
const { node, path } = resolved;
|
|
370
|
+
|
|
371
|
+
// Re-read argv with this command's own value flags, for positionals and flag values.
|
|
372
|
+
let scan = scanArgs(args, nodeValueFlags(node));
|
|
373
|
+
if (scan.words.slice(0, resolved.consumed).join(" ") !== first.words.slice(0, resolved.consumed).join(" ")) scan = first;
|
|
374
|
+
const { flags } = scan;
|
|
375
|
+
const positionals = scan.words.slice(resolved.consumed);
|
|
376
|
+
const usageLines = (node.synopsis || []).filter((line) => !line.includes("<command>") || !node.usage).map((line) => `usage: ${heyToAxi(line)}`);
|
|
224
377
|
|
|
225
|
-
const invalid = validateFlags(
|
|
378
|
+
const invalid = validateFlags(node, flags, { values: scan.values, missingValues: scan.missing });
|
|
226
379
|
if (invalid) {
|
|
227
|
-
const valid = (
|
|
228
|
-
const failure = { ok: false, command:
|
|
229
|
-
if (invalid.unknown) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
380
|
+
const valid = (node.flags || []).map(flagLabel);
|
|
381
|
+
const failure = { ok: false, command: path, kind: "usage" };
|
|
382
|
+
if (invalid.unknown) failure.error = `unknown flag ${invalid.unknown.join(", ")} for \`${path}\``;
|
|
383
|
+
else if (invalid.missingValue) failure.error = `missing value for ${invalid.missingValue.join(", ")} (use ${invalid.missingValue[0]}=<value> if the value starts with "-")`;
|
|
384
|
+
else if (invalid.badValue) failure.error = invalid.badValue;
|
|
385
|
+
else failure.error = `missing required flag ${invalid.missing.join(", ")} for \`${path}\``;
|
|
234
386
|
failure.help = [
|
|
235
|
-
valid.length ? `valid flags for \`${
|
|
236
|
-
|
|
237
|
-
...
|
|
387
|
+
valid.length ? `valid flags for \`${path}\`: ${valid.join(", ")}` : `\`${path}\` takes no flags of its own`,
|
|
388
|
+
`always allowed: ${ALWAYS_ALLOWED}`,
|
|
389
|
+
...usageLines,
|
|
238
390
|
];
|
|
239
|
-
|
|
240
|
-
process.exit(2);
|
|
391
|
+
fail(failure, 2);
|
|
241
392
|
}
|
|
242
393
|
|
|
243
394
|
if (flags.has("--help") || flags.has("-h")) {
|
|
244
|
-
process.stdout.write(`${commandHelp(
|
|
395
|
+
process.stdout.write(`${commandHelp(node)}\n`);
|
|
245
396
|
process.exit(0);
|
|
246
397
|
}
|
|
247
398
|
|
|
248
|
-
const
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
|
|
399
|
+
const arity = checkArity(node, positionals, flags);
|
|
400
|
+
if (arity) {
|
|
401
|
+
const failure = { ok: false, command: path, kind: "usage", error: arity.error };
|
|
402
|
+
if (node.subcommands?.length && positionals.length === 0) failure.subcommands = node.subcommands.map((child) => child.name);
|
|
403
|
+
const close = node.subcommands?.length && positionals.length ? closestNames(positionals[0], node.subcommands) : [];
|
|
404
|
+
failure.help = [
|
|
405
|
+
...close.map((name) => `Did you mean \`hey-axi ${path} ${name}\`?`),...usageLines, ...(node.usage ? [`shortcut: hey-axi ${node.usage}`] : []), ...(arity.hint ? [arity.hint] : []), `example: ${examplesFor(node)[0]}`];
|
|
406
|
+
fail(failure, 2);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const refusal = checkPolicy(path, flags);
|
|
410
|
+
if (refusal) fail({ ok: false, ...refusal }, 2);
|
|
411
|
+
|
|
412
|
+
const missingContent = contentProblem(node, flags, positionals);
|
|
413
|
+
if (missingContent) fail({ ok: false, kind: "usage", ...missingContent }, 2);
|
|
414
|
+
|
|
415
|
+
let heyArgs = stripAxiFlags(args);
|
|
416
|
+
// `--message -` (and --note -, --content -): read the content from stdin.
|
|
417
|
+
const fromStdin = scan.values.filter(([name, value]) => STDIN_VALUE_FLAGS.includes(name) && value === "-").map(([name]) => name);
|
|
418
|
+
if (fromStdin.length) {
|
|
419
|
+
if (process.stdin.isTTY) fail({ ok: false, kind: "usage", error: `${fromStdin[0]} - reads from stdin, but nothing is piped in`, help: `pipe the text in (e.g. \`cat note.md | hey-axi ${path} ... ${fromStdin[0]} -\`) or pass it as ${fromStdin[0]} "..."` }, 2);
|
|
420
|
+
const text = readFileSync(0, "utf8");
|
|
421
|
+
heyArgs = heyArgs.map((arg, index) => {
|
|
422
|
+
if (arg === "-" && fromStdin.includes(heyArgs[index - 1])) return text;
|
|
423
|
+
const eq = arg.indexOf("=");
|
|
424
|
+
return eq !== -1 && arg.slice(eq + 1) === "-" && fromStdin.includes(arg.slice(0, eq)) ? `${arg.slice(0, eq)}=${text}` : arg;
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const json = flags.has("--json");
|
|
429
|
+
const quiet = flags.has("--quiet");
|
|
430
|
+
const full = flags.has("--full");
|
|
431
|
+
const raw = hasAny(flags, RAW_OUTPUT_FLAGS);
|
|
432
|
+
const command = heyArgs.filter((arg) => arg !== "--json" && arg !== "--quiet");
|
|
433
|
+
const fieldsArg = flagValue(args, "fields");
|
|
434
|
+
const fields = fieldsArg === undefined ? null : fieldsArg.trim() === "all" ? "all" : fieldsArg.split(",").map((name) => name.trim()).filter(Boolean);
|
|
435
|
+
// The command as the agent typed it, minus output/shaping flags, for `--full`/`--all` hints.
|
|
436
|
+
const commandLine = ["hey-axi", ...stripAxiFlags(args).filter((arg) => arg !== "--json" && arg !== "--quiet")].map(shellWord).join(" ");
|
|
437
|
+
// Selectors to carry into suggested commands.
|
|
438
|
+
const carry = [];
|
|
439
|
+
for (const name of ["account", "base-url"]) {
|
|
440
|
+
const value = flagValue(args, name);
|
|
441
|
+
if (value !== undefined) carry.push(`--${name}`, shellWord(value));
|
|
252
442
|
}
|
|
443
|
+
const pageFlags = (node.flags || []).map((flag) => flag.name).filter((name) => ["all", "page", "limit"].includes(name));
|
|
253
444
|
|
|
254
445
|
// compose/reply without a send opt-in: save a draft instead of sending.
|
|
255
|
-
const staging = sendStaging(
|
|
446
|
+
const staging = sendStaging(path, flags);
|
|
256
447
|
if (staging) {
|
|
257
448
|
heyArgs.push(staging.flag);
|
|
258
449
|
command.push(staging.flag);
|
|
@@ -270,76 +461,122 @@ function markStaged(parsed) {
|
|
|
270
461
|
return { ...marker, data: parsed };
|
|
271
462
|
}
|
|
272
463
|
|
|
273
|
-
|
|
464
|
+
function reportFailure(result) {
|
|
465
|
+
const { failure, exitCode, warnings } = translateFailure(result, { path });
|
|
466
|
+
// The fix-it commands keep the invocation's --account/--base-url.
|
|
467
|
+
for (const key of ["hint", "help"]) {
|
|
468
|
+
if (Array.isArray(failure[key])) failure[key] = failure[key].map((line) => carrySelectors(line, carry));
|
|
469
|
+
else if (failure[key]) failure[key] = carrySelectors(failure[key], carry);
|
|
470
|
+
}
|
|
471
|
+
for (const line of warnings) process.stderr.write(`${line}\n`);
|
|
472
|
+
const noop = exitCode !== 0 && noopFor(path, failure, positionals, { to: flagValue(args, "to"), from: flagValue(args, "from") });
|
|
473
|
+
if (noop) {
|
|
474
|
+
if (json) process.stdout.write(`${JSON.stringify(noop)}\n`);
|
|
475
|
+
else output(noop);
|
|
476
|
+
process.exit(0);
|
|
477
|
+
}
|
|
478
|
+
if (json) process.stdout.write(`${JSON.stringify(failure)}\n`);
|
|
479
|
+
else output(failure);
|
|
480
|
+
process.exit(exitCode);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const mode = runMode(path, flags);
|
|
484
|
+
if (mode === "interactive") {
|
|
485
|
+
// Only reached with the explicit --interactive opt-in (and a terminal, except for mcp).
|
|
486
|
+
const result = await runHeyInteractive(heyArgs);
|
|
487
|
+
if (result.error) reportFailure(result);
|
|
488
|
+
process.exit(result.status === 0 ? 0 : 1);
|
|
489
|
+
}
|
|
274
490
|
if (raw || mode !== "json") {
|
|
275
|
-
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (result.
|
|
279
|
-
|
|
491
|
+
// stream: watch; raw: scripts, CSV, and --ids-only/--count/--markdown/--html/--styled/--jq.
|
|
492
|
+
const result = mode === "stream" ? await runHeyStream(heyArgs, { toon: !json }) : await runHeyRaw(heyArgs);
|
|
493
|
+
// A watch stopped by a signal ended the way the agent asked: not an error.
|
|
494
|
+
if (mode === "stream" && (result.signal || result.status === 130 || result.status === 143)) process.exit(0);
|
|
495
|
+
// HEY's stdout here is data, not an error message: it never goes into the failure.
|
|
496
|
+
if (result.error || result.status !== 0) reportFailure({ ...result, stdout: "" });
|
|
497
|
+
writeWarnings(result.stderr);
|
|
498
|
+
if (mode !== "stream" && result.stdout?.length) process.stdout.write(result.stdout);
|
|
499
|
+
process.exit(0);
|
|
280
500
|
}
|
|
281
501
|
|
|
282
502
|
// `hey-axi version`: hey-axi's own version next to HEY's.
|
|
283
|
-
if (
|
|
503
|
+
if (path === "version") {
|
|
284
504
|
const result = await runHey(command, ["--json"]);
|
|
285
505
|
const versions = { hey_axi: VERSION };
|
|
286
|
-
if (result.status
|
|
287
|
-
try {
|
|
288
|
-
const parsed = JSON.parse(result.stdout);
|
|
289
|
-
const data = parsed && typeof parsed === "object" && "data" in parsed ? parsed.data : parsed;
|
|
290
|
-
versions.hey = data?.version ?? data;
|
|
291
|
-
if (data?.commit) versions.hey_commit = data.commit;
|
|
292
|
-
} catch {
|
|
293
|
-
versions.hey = result.stdout.trim();
|
|
294
|
-
}
|
|
295
|
-
} else {
|
|
506
|
+
if (result.status !== 0) {
|
|
296
507
|
// hey-axi still knows its own version; the failure says what's wrong with HEY.
|
|
297
|
-
const failure
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
508
|
+
const { failure, exitCode } = translateFailure(result, { path });
|
|
509
|
+
fail({ ok: false, hey_axi: VERSION, ...failure }, exitCode);
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
const parsed = JSON.parse(result.stdout);
|
|
513
|
+
const data = parsed && typeof parsed === "object" && "data" in parsed ? parsed.data : parsed;
|
|
514
|
+
versions.hey = data?.version ?? data;
|
|
515
|
+
if (data?.commit) versions.hey_commit = data.commit;
|
|
516
|
+
} catch {
|
|
517
|
+
versions.hey = cleanLine(result.stdout);
|
|
301
518
|
}
|
|
302
519
|
if (json) process.stdout.write(`${JSON.stringify(versions)}\n`);
|
|
303
520
|
else output(versions);
|
|
304
521
|
process.exit(0);
|
|
305
522
|
}
|
|
306
523
|
|
|
307
|
-
const result = await runHey(command,
|
|
308
|
-
if (result.status
|
|
309
|
-
|
|
310
|
-
if (result.status !== 0) {
|
|
311
|
-
// Pass HEY's exit code through: 1 usage, 2 not found, 3 auth, 4 forbidden,
|
|
312
|
-
// 5 rate limit, 6 network, 7 API, 8 ambiguous (see `hey help exit-codes`).
|
|
313
|
-
output(heyFailure(result));
|
|
314
|
-
process.exit(result.status);
|
|
315
|
-
}
|
|
524
|
+
const result = await runHey(command, ["--json"]);
|
|
525
|
+
if (result.status !== 0) reportFailure(result);
|
|
526
|
+
writeWarnings(result.stderr);
|
|
316
527
|
|
|
317
528
|
let parsed;
|
|
529
|
+
let plainText = false;
|
|
318
530
|
try {
|
|
319
531
|
parsed = JSON.parse(result.stdout);
|
|
320
|
-
} catch
|
|
321
|
-
|
|
322
|
-
|
|
532
|
+
} catch {
|
|
533
|
+
plainText = true;
|
|
534
|
+
// HEY succeeded but printed text: show it, cleaned and cut, rather than fail.
|
|
535
|
+
const text = String(result.stdout || "").trim();
|
|
536
|
+
if (!text) parsed = { ok: true, result: "done (HEY printed nothing)" };
|
|
537
|
+
else if (full || text.length <= 1000) parsed = { ok: true, output: text };
|
|
538
|
+
else parsed = { ok: true, output: `${text.slice(0, 1000)}… (truncated, ${text.length} chars total)`, help: [`Run \`${commandLine} --full\` to see the complete output`] };
|
|
323
539
|
}
|
|
324
540
|
|
|
325
541
|
let shaped;
|
|
326
542
|
try {
|
|
327
|
-
|
|
328
|
-
else if (quiet) {
|
|
329
|
-
const { data, truncated } = shapeData(parsed, { path: resolved.path, fields });
|
|
330
|
-
shaped = data;
|
|
331
|
-
if (truncated) process.stderr.write(`hey-axi: long text was truncated; run \`${commandLine} --full\` to see all of it\n`);
|
|
332
|
-
} else shaped = shapeEnvelope(parsed, { path: resolved.path, fields, commandLine });
|
|
543
|
+
shaped = full || plainText ? parsed : shapeEnvelope(parsed, { path, fields, commandLine, carry, pageFlags, quiet, all: flags.has("--all") });
|
|
333
544
|
} catch (error) {
|
|
334
545
|
if (!(error instanceof FieldError)) throw error;
|
|
335
|
-
|
|
546
|
+
fail({
|
|
336
547
|
ok: false,
|
|
337
|
-
|
|
548
|
+
kind: "usage",
|
|
549
|
+
error: `unknown field ${error.unknown.join(", ")} for \`${path}\``,
|
|
338
550
|
help: [`available fields: ${error.available.join(", ")}`, "dotted paths (creator.email_address) and --fields all also work"],
|
|
339
|
-
});
|
|
340
|
-
process.exit(2);
|
|
551
|
+
}, 2);
|
|
341
552
|
}
|
|
342
553
|
shaped = markStaged(shaped);
|
|
554
|
+
|
|
555
|
+
// A list whose HEY result carried no next steps still gets one (AXI principle 9): the
|
|
556
|
+
// matching view/show command when there is one, else how to see every field.
|
|
557
|
+
if (!full && !quiet && !plainText && shaped && typeof shaped === "object" && shaped.count !== undefined) {
|
|
558
|
+
const help = Array.isArray(shaped.help) ? shaped.help : [];
|
|
559
|
+
const hasNext = help.some((line) => /^Run `hey-axi /.test(line) && !/ --(all|full|page|limit)\b/.test(line));
|
|
560
|
+
if (!hasNext) {
|
|
561
|
+
const words = path.split(" ");
|
|
562
|
+
const parent = resolveCommand(manifest.commands, words.slice(0, -1));
|
|
563
|
+
const siblings = words.length > 1 && !parent.error ? parent.node.subcommands || [] : [];
|
|
564
|
+
// After an empty list, suggest creating; after a list, suggest viewing.
|
|
565
|
+
const wanted = shaped.empty ? ["create", "add"] : ["view", "show", "read"];
|
|
566
|
+
const sibling = wanted.map((name) => siblings.find((child) => child.name === name && child.path !== path)).find(Boolean);
|
|
567
|
+
const next = sibling && shaped.empty
|
|
568
|
+
? `Run \`${withSelectors(`hey-axi ${sibling.path} --help`, carry)}\` to see how to add one`
|
|
569
|
+
: sibling
|
|
570
|
+
? `Run \`${withSelectors(`hey-axi ${sibling.path} <id>`, carry)}\` to see one in full`
|
|
571
|
+
: shaped.empty ? `Run \`${withSelectors(`hey-axi ${path} --help`, carry)}\` to check the filters` : `Run \`${commandLine} --fields all\` to see every field`;
|
|
572
|
+
shaped.help = [next, ...help];
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const { recordActivity } = await import("./activity.js");
|
|
577
|
+
const { findScope } = await import("./scope.js");
|
|
578
|
+
recordActivity({ path, positionals, staged: Boolean(staging), sent: sendCommands().includes(path) && !staging && !flags.has("--draft") && !flags.has("--dry-run"), scopeDir: scopeDirFor(process.cwd(), findScope) });
|
|
579
|
+
|
|
343
580
|
if (json) process.stdout.write(`${JSON.stringify(shaped)}\n`);
|
|
344
581
|
else output(shaped);
|
|
345
582
|
}
|