@zenalexa/unicli 0.217.3 → 0.218.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/AGENTS.md +51 -194
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/bin/unicli-mcp.d.ts +10 -0
- package/dist/bin/unicli-mcp.d.ts.map +1 -0
- package/dist/bin/unicli-mcp.js +10 -0
- package/dist/bin/unicli-mcp.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +250 -0
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/dev.js +1 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +1 -0
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +15 -6
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/repair.d.ts +1 -0
- package/dist/commands/repair.d.ts.map +1 -1
- package/dist/commands/repair.js +103 -61
- package/dist/commands/repair.js.map +1 -1
- package/dist/engine/chromium-cookies-platform.d.ts +69 -0
- package/dist/engine/chromium-cookies-platform.d.ts.map +1 -0
- package/dist/engine/chromium-cookies-platform.js +315 -0
- package/dist/engine/chromium-cookies-platform.js.map +1 -0
- package/dist/engine/chromium-cookies-types.d.ts +26 -0
- package/dist/engine/chromium-cookies-types.d.ts.map +1 -0
- package/dist/engine/chromium-cookies-types.js +16 -0
- package/dist/engine/chromium-cookies-types.js.map +1 -0
- package/dist/engine/chromium-cookies.d.ts +56 -0
- package/dist/engine/chromium-cookies.d.ts.map +1 -0
- package/dist/engine/chromium-cookies.js +361 -0
- package/dist/engine/chromium-cookies.js.map +1 -0
- package/dist/engine/cookies.d.ts +13 -5
- package/dist/engine/cookies.d.ts.map +1 -1
- package/dist/engine/cookies.js +55 -9
- package/dist/engine/cookies.js.map +1 -1
- package/dist/engine/executor.d.ts +8 -0
- package/dist/engine/executor.d.ts.map +1 -1
- package/dist/engine/executor.js +1 -1
- package/dist/engine/executor.js.map +1 -1
- package/dist/engine/kernel/execute.d.ts.map +1 -1
- package/dist/engine/kernel/execute.js +1 -0
- package/dist/engine/kernel/execute.js.map +1 -1
- package/dist/engine/repair/quarantine-discovery.d.ts +25 -0
- package/dist/engine/repair/quarantine-discovery.d.ts.map +1 -0
- package/dist/engine/repair/quarantine-discovery.js +78 -0
- package/dist/engine/repair/quarantine-discovery.js.map +1 -0
- package/dist/engine/steps/navigate.d.ts +1 -1
- package/dist/engine/steps/navigate.d.ts.map +1 -1
- package/dist/engine/steps/navigate.js +5 -1
- package/dist/engine/steps/navigate.js.map +1 -1
- package/dist/engine/template.d.ts +14 -4
- package/dist/engine/template.d.ts.map +1 -1
- package/dist/engine/template.js +93 -65
- package/dist/engine/template.js.map +1 -1
- package/dist/engine/verify-row-shape.d.ts +17 -0
- package/dist/engine/verify-row-shape.d.ts.map +1 -0
- package/dist/engine/verify-row-shape.js +36 -0
- package/dist/engine/verify-row-shape.js.map +1 -0
- package/dist/fast-path/handlers/adapter.d.ts +15 -0
- package/dist/fast-path/handlers/adapter.d.ts.map +1 -0
- package/dist/fast-path/handlers/adapter.js +169 -0
- package/dist/fast-path/handlers/adapter.js.map +1 -0
- package/dist/fast-path/handlers/discovery.d.ts +14 -0
- package/dist/fast-path/handlers/discovery.d.ts.map +1 -0
- package/dist/fast-path/handlers/discovery.js +280 -0
- package/dist/fast-path/handlers/discovery.js.map +1 -0
- package/dist/fast-path/manifest.d.ts +47 -0
- package/dist/fast-path/manifest.d.ts.map +1 -0
- package/dist/fast-path/manifest.js +32 -0
- package/dist/fast-path/manifest.js.map +1 -0
- package/dist/fast-path/parsed-argv.d.ts +16 -0
- package/dist/fast-path/parsed-argv.d.ts.map +1 -0
- package/dist/fast-path/parsed-argv.js +6 -0
- package/dist/fast-path/parsed-argv.js.map +1 -0
- package/dist/fast-path/policy.d.ts +25 -0
- package/dist/fast-path/policy.d.ts.map +1 -0
- package/dist/fast-path/policy.js +96 -0
- package/dist/fast-path/policy.js.map +1 -0
- package/dist/fast-path/render.d.ts +26 -0
- package/dist/fast-path/render.d.ts.map +1 -0
- package/dist/fast-path/render.js +200 -0
- package/dist/fast-path/render.js.map +1 -0
- package/dist/fast-path.d.ts +8 -10
- package/dist/fast-path.d.ts.map +1 -1
- package/dist/fast-path.js +66 -810
- package/dist/fast-path.js.map +1 -1
- package/dist/manifest.json +7 -6
- package/dist/transport/adapters/cdp-browser.d.ts +6 -6
- package/dist/transport/adapters/cdp-browser.js +7 -7
- package/dist/transport/adapters/cdp-browser.js.map +1 -1
- package/dist/transport/adapters/subprocess.d.ts +4 -4
- package/dist/transport/adapters/subprocess.js +4 -4
- package/package.json +22 -8
- package/server.json +44 -0
- package/skills/README.md +39 -0
- package/skills/talk-normal/SKILL.md +25 -0
- package/skills/talk-normal/prompt.md +35 -0
- package/skills/unicli/SKILL.md +389 -0
- package/skills/unicli/references/auth.md +188 -0
- package/skills/unicli/references/output.md +238 -0
- package/skills/unicli/references/sites.md +259 -0
- package/skills/unicli-browser/SKILL.md +99 -0
- package/skills/unicli-claude-code/SKILL.md +172 -0
- package/skills/unicli-explorer/SKILL.md +90 -0
- package/skills/unicli-hermes/SKILL.md +83 -0
- package/skills/unicli-oneshot/SKILL.md +59 -0
- package/skills/unicli-operate/SKILL.md +113 -0
- package/skills/unicli-repair/SKILL.md +209 -0
- package/skills/unicli-repair/references/error-codes.md +149 -0
- package/skills/unicli-repair/references/yaml-patches.md +321 -0
- package/skills/unicli-smart-search/SKILL.md +80 -0
- package/skills/unicli-usage/SKILL.md +65 -0
- package/src/adapters/adguardhome/rules.yaml +1 -1
- package/src/adapters/adguardhome/stats.yaml +1 -1
- package/src/adapters/adguardhome/status.yaml +1 -1
- package/src/adapters/apple-music/rate-album.yaml +1 -2
- package/src/adapters/arxiv/trending.yaml +1 -1
- package/src/adapters/az/account.yaml +1 -1
- package/src/adapters/coupang/hot.yaml +2 -1
- package/src/adapters/ctrip/hot.yaml +2 -1
- package/src/adapters/ctrip/search.yaml +2 -1
- package/src/adapters/douban/top250.yaml +1 -1
- package/src/adapters/figma/export-selected.yaml +1 -2
- package/src/adapters/gcloud/projects.yaml +1 -1
- package/src/adapters/github-trending/developers.yaml +1 -1
- package/src/adapters/github-trending/weekly.yaml +1 -1
- package/src/adapters/homebrew/search.yaml +1 -1
- package/src/adapters/imdb/top.yaml +1 -1
- package/src/adapters/itch-io/popular.yaml +1 -1
- package/src/adapters/itch-io/top.yaml +1 -1
- package/src/adapters/mastodon/timeline.yaml +1 -1
- package/src/adapters/openrouter/search.yaml +1 -1
- package/src/adapters/pypi/search.yaml +2 -1
- package/src/adapters/sspai/hot.yaml +4 -1
- package/src/adapters/sspai/latest.yaml +3 -3
- package/src/adapters/tieba/hot.yaml +1 -1
- package/src/adapters/wikipedia/today.yaml +1 -1
- package/src/adapters/zoom/toggle-mute.yaml +1 -2
- package/dist/engine/yaml-runner.d.ts +0 -11
- package/dist/engine/yaml-runner.d.ts.map +0 -1
- package/dist/engine/yaml-runner.js +0 -18
- package/dist/engine/yaml-runner.js.map +0 -1
package/dist/fast-path.js
CHANGED
|
@@ -1,303 +1,83 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* @owner src/fast-path.ts
|
|
3
|
+
* @does Argv parsing and dispatch for discovery surfaces (list/search/describe/repair, plus pre-Commander adapter dry-run, policy gate, and site help).
|
|
4
|
+
* @needs ./fast-path/{manifest,parsed-argv,render,policy,handlers/discovery,handlers/adapter}, ./types (OutputFormat)
|
|
5
|
+
* @feeds src/main.ts (dispatched before the full Commander tree loads)
|
|
6
|
+
* @breaks Missing dist/manifest.json → returns false so caller falls through to Commander; structured errors propagate via emitStderrAndExit.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (io === DEFAULT_IO) {
|
|
26
|
-
writeSync(process.stderr.fd, `${text}\n`);
|
|
27
|
-
process.exit(code);
|
|
28
|
-
}
|
|
29
|
-
io.stderr(text);
|
|
30
|
-
process.exitCode = code;
|
|
31
|
-
}
|
|
32
|
-
function isOutputFormat(value) {
|
|
33
|
-
return (value === "json" ||
|
|
34
|
-
value === "yaml" ||
|
|
35
|
-
value === "md" ||
|
|
36
|
-
value === "csv" ||
|
|
37
|
-
value === "compact" ||
|
|
38
|
-
value === "table");
|
|
39
|
-
}
|
|
40
|
-
function isHelpToken(value) {
|
|
41
|
-
return value === "-h" || value === "--help" || value === "help";
|
|
42
|
-
}
|
|
43
|
-
function jsonSchemaType(type) {
|
|
44
|
-
switch (type) {
|
|
45
|
-
case "int":
|
|
46
|
-
return "integer";
|
|
47
|
-
case "float":
|
|
48
|
-
return "number";
|
|
49
|
-
case "bool":
|
|
50
|
-
return "boolean";
|
|
51
|
-
default:
|
|
52
|
-
return "string";
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function argsToJsonSchema(args) {
|
|
56
|
-
const properties = {};
|
|
57
|
-
const required = [];
|
|
58
|
-
for (const arg of args) {
|
|
59
|
-
const prop = {
|
|
60
|
-
type: jsonSchemaType(arg.type),
|
|
61
|
-
};
|
|
62
|
-
if (arg.description)
|
|
63
|
-
prop.description = arg.description;
|
|
64
|
-
if (arg.default !== undefined)
|
|
65
|
-
prop.default = arg.default;
|
|
66
|
-
if (arg.choices && arg.choices.length > 0)
|
|
67
|
-
prop.enum = arg.choices;
|
|
68
|
-
if (arg.format)
|
|
69
|
-
prop.format = arg.format;
|
|
70
|
-
if (arg["x-unicli-kind"])
|
|
71
|
-
prop["x-unicli-kind"] = arg["x-unicli-kind"];
|
|
72
|
-
if (arg["x-unicli-accepts"]) {
|
|
73
|
-
prop["x-unicli-accepts"] = arg["x-unicli-accepts"];
|
|
8
|
+
import { handleAdapterDryRun, handleAdapterPolicyGate, handleSiteHelp, } from "./fast-path/handlers/adapter.js";
|
|
9
|
+
import { handleDescribe, handleList, handleRepair, handleSearch, } from "./fast-path/handlers/discovery.js";
|
|
10
|
+
import { isMissingManifestError } from "./fast-path/manifest.js";
|
|
11
|
+
import { DEFAULT_IO, isOutputFormat } from "./fast-path/render.js";
|
|
12
|
+
/**
|
|
13
|
+
* Try to consume one global flag at `args[i]`. Returns the new index
|
|
14
|
+
* (i + skipped tokens) or `null` when the arg is not a recognized flag.
|
|
15
|
+
* The single source of truth — adding a flag means editing one branch
|
|
16
|
+
* here, not two parallel passes (rule 02 third-copy halt).
|
|
17
|
+
*/
|
|
18
|
+
function tryConsumeFlag(args, i, acc) {
|
|
19
|
+
const arg = args[i];
|
|
20
|
+
if (arg === "-f" || arg === "--format") {
|
|
21
|
+
const next = args[i + 1];
|
|
22
|
+
if (next && isOutputFormat(next)) {
|
|
23
|
+
acc.formatValue = next;
|
|
24
|
+
return i + 1;
|
|
74
25
|
}
|
|
75
|
-
|
|
76
|
-
if (arg.required)
|
|
77
|
-
required.push(arg.name);
|
|
26
|
+
return null;
|
|
78
27
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
additionalProperties: true,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
function buildExample(args) {
|
|
88
|
-
const example = {};
|
|
89
|
-
for (const arg of args) {
|
|
90
|
-
if (arg.default !== undefined) {
|
|
91
|
-
example[arg.name] = arg.default;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (arg.choices && arg.choices.length > 0) {
|
|
95
|
-
example[arg.name] = arg.choices[0];
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
switch (arg.type) {
|
|
99
|
-
case "int":
|
|
100
|
-
example[arg.name] = 10;
|
|
101
|
-
break;
|
|
102
|
-
case "float":
|
|
103
|
-
example[arg.name] = 0.5;
|
|
104
|
-
break;
|
|
105
|
-
case "bool":
|
|
106
|
-
example[arg.name] = false;
|
|
107
|
-
break;
|
|
108
|
-
default:
|
|
109
|
-
example[arg.name] = `<${arg.name}>`;
|
|
28
|
+
if (arg.startsWith("--format=")) {
|
|
29
|
+
const next = arg.slice("--format=".length);
|
|
30
|
+
if (isOutputFormat(next)) {
|
|
31
|
+
acc.formatValue = next;
|
|
32
|
+
return i;
|
|
110
33
|
}
|
|
34
|
+
return null;
|
|
111
35
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const positionals = args
|
|
116
|
-
.filter((arg) => arg.positional)
|
|
117
|
-
.map((arg) => (arg.required ? `<${arg.name}>` : `[${arg.name}]`))
|
|
118
|
-
.join(" ");
|
|
119
|
-
const options = args
|
|
120
|
-
.filter((arg) => !arg.positional)
|
|
121
|
-
.map((arg) => `[--${arg.name} <${arg.type ?? "value"}>]`)
|
|
122
|
-
.join(" ");
|
|
123
|
-
const shell = `unicli ${site} ${command}` +
|
|
124
|
-
(positionals ? ` ${positionals}` : "") +
|
|
125
|
-
(options ? ` ${options}` : "");
|
|
126
|
-
return {
|
|
127
|
-
shell: shell.trim(),
|
|
128
|
-
args_file: `unicli ${site} ${command} --args-file <path.json>`,
|
|
129
|
-
stdin: `echo '{...}' | unicli ${site} ${command}`,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
function summarizeArgs(args = []) {
|
|
133
|
-
return args.map((arg) => ({
|
|
134
|
-
name: arg.name,
|
|
135
|
-
type: arg.type ?? "str",
|
|
136
|
-
required: arg.required === true,
|
|
137
|
-
positional: arg.positional === true,
|
|
138
|
-
}));
|
|
139
|
-
}
|
|
140
|
-
function coerceArgValue(value, type) {
|
|
141
|
-
if (type === "int") {
|
|
142
|
-
const parsed = parseInt(String(value), 10);
|
|
143
|
-
return Number.isNaN(parsed) ? value : parsed;
|
|
36
|
+
if (arg === "--dry-run") {
|
|
37
|
+
acc.dryRun = true;
|
|
38
|
+
return i;
|
|
144
39
|
}
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
return
|
|
40
|
+
if (arg === "--permission-profile") {
|
|
41
|
+
acc.permissionProfile = args[i + 1];
|
|
42
|
+
return i + 1;
|
|
148
43
|
}
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const text = String(value).toLowerCase();
|
|
153
|
-
return text === "1" || text === "true" || text === "yes";
|
|
44
|
+
if (arg.startsWith("--permission-profile=")) {
|
|
45
|
+
acc.permissionProfile = arg.slice("--permission-profile=".length);
|
|
46
|
+
return i;
|
|
154
47
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const values = {};
|
|
159
|
-
for (const arg of schema) {
|
|
160
|
-
if (arg.default !== undefined)
|
|
161
|
-
values[arg.name] = arg.default;
|
|
48
|
+
if (arg === "--yes") {
|
|
49
|
+
acc.yes = true;
|
|
50
|
+
return i;
|
|
162
51
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const token = tokens[i];
|
|
167
|
-
if (token.startsWith("--")) {
|
|
168
|
-
const eqIdx = token.indexOf("=");
|
|
169
|
-
const name = eqIdx === -1 ? token.slice(2) : token.slice(2, Math.max(2, eqIdx));
|
|
170
|
-
const arg = schema.find((candidate) => candidate.name === name);
|
|
171
|
-
if (!arg) {
|
|
172
|
-
if (eqIdx === -1 && tokens[i + 1] && !tokens[i + 1].startsWith("-")) {
|
|
173
|
-
i += 1;
|
|
174
|
-
}
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
let raw;
|
|
178
|
-
if (arg.type === "bool" && eqIdx === -1) {
|
|
179
|
-
raw = true;
|
|
180
|
-
}
|
|
181
|
-
else if (eqIdx !== -1) {
|
|
182
|
-
raw = token.slice(eqIdx + 1);
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
raw = tokens[i + 1];
|
|
186
|
-
i += 1;
|
|
187
|
-
}
|
|
188
|
-
values[arg.name] = coerceArgValue(raw, arg.type);
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
const arg = positionals[positionalIndex];
|
|
192
|
-
if (arg) {
|
|
193
|
-
values[arg.name] = coerceArgValue(token, arg.type);
|
|
194
|
-
positionalIndex += 1;
|
|
195
|
-
}
|
|
52
|
+
if (arg === "--remember-approval") {
|
|
53
|
+
acc.rememberApproval = true;
|
|
54
|
+
return i;
|
|
196
55
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
56
|
+
if (arg === "--record") {
|
|
57
|
+
acc.record = true;
|
|
58
|
+
return i;
|
|
200
59
|
}
|
|
201
|
-
return
|
|
60
|
+
return null;
|
|
202
61
|
}
|
|
203
62
|
function parseArgv(argv) {
|
|
204
63
|
const args = argv.slice(2);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
64
|
+
const acc = {
|
|
65
|
+
dryRun: false,
|
|
66
|
+
yes: false,
|
|
67
|
+
rememberApproval: false,
|
|
68
|
+
record: false,
|
|
69
|
+
};
|
|
211
70
|
let command;
|
|
212
71
|
const rest = [];
|
|
213
72
|
for (let i = 0; i < args.length; i += 1) {
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
const next = args[i + 1];
|
|
218
|
-
if (next && isOutputFormat(next)) {
|
|
219
|
-
formatValue = next;
|
|
220
|
-
i += 1;
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
if (arg.startsWith("--format=")) {
|
|
225
|
-
const next = arg.slice("--format=".length);
|
|
226
|
-
if (isOutputFormat(next)) {
|
|
227
|
-
formatValue = next;
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (arg === "--dry-run") {
|
|
232
|
-
dryRun = true;
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
if (arg === "--permission-profile") {
|
|
236
|
-
permissionProfile = args[i + 1];
|
|
237
|
-
i += 1;
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
if (arg.startsWith("--permission-profile=")) {
|
|
241
|
-
permissionProfile = arg.slice("--permission-profile=".length);
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
if (arg === "--yes") {
|
|
245
|
-
yes = true;
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
if (arg === "--remember-approval") {
|
|
249
|
-
rememberApproval = true;
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
if (arg === "--record") {
|
|
253
|
-
record = true;
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
if (!arg.startsWith("-")) {
|
|
257
|
-
command = arg;
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
rest.push(arg);
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
if (arg === "-f" || arg === "--format") {
|
|
264
|
-
const next = args[i + 1];
|
|
265
|
-
if (next && isOutputFormat(next)) {
|
|
266
|
-
formatValue = next;
|
|
267
|
-
i += 1;
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
if (arg.startsWith("--format=")) {
|
|
272
|
-
const next = arg.slice("--format=".length);
|
|
273
|
-
if (isOutputFormat(next)) {
|
|
274
|
-
formatValue = next;
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
if (arg === "--dry-run") {
|
|
279
|
-
dryRun = true;
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
if (arg === "--permission-profile") {
|
|
283
|
-
permissionProfile = args[i + 1];
|
|
284
|
-
i += 1;
|
|
73
|
+
const consumed = tryConsumeFlag(args, i, acc);
|
|
74
|
+
if (consumed !== null) {
|
|
75
|
+
i = consumed;
|
|
285
76
|
continue;
|
|
286
77
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
if (arg === "--yes") {
|
|
292
|
-
yes = true;
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
if (arg === "--remember-approval") {
|
|
296
|
-
rememberApproval = true;
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
if (arg === "--record") {
|
|
300
|
-
record = true;
|
|
78
|
+
const arg = args[i];
|
|
79
|
+
if (!command && !arg.startsWith("-")) {
|
|
80
|
+
command = arg;
|
|
301
81
|
continue;
|
|
302
82
|
}
|
|
303
83
|
rest.push(arg);
|
|
@@ -305,538 +85,14 @@ function parseArgv(argv) {
|
|
|
305
85
|
return {
|
|
306
86
|
command,
|
|
307
87
|
rest,
|
|
308
|
-
format: formatValue,
|
|
309
|
-
dryRun,
|
|
310
|
-
permissionProfile,
|
|
311
|
-
yes,
|
|
312
|
-
rememberApproval,
|
|
313
|
-
record,
|
|
88
|
+
format: acc.formatValue,
|
|
89
|
+
dryRun: acc.dryRun,
|
|
90
|
+
permissionProfile: acc.permissionProfile,
|
|
91
|
+
yes: acc.yes,
|
|
92
|
+
rememberApproval: acc.rememberApproval,
|
|
93
|
+
record: acc.record,
|
|
314
94
|
};
|
|
315
95
|
}
|
|
316
|
-
function manifestPath() {
|
|
317
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
318
|
-
const candidates = [
|
|
319
|
-
join(here, "manifest.json"),
|
|
320
|
-
join(here, "..", "dist", "manifest.json"),
|
|
321
|
-
join(here, "..", "..", "dist", "manifest.json"),
|
|
322
|
-
];
|
|
323
|
-
const found = candidates.find((candidate) => existsSync(candidate));
|
|
324
|
-
if (!found) {
|
|
325
|
-
throw new Error("Missing dist/manifest.json. Run: npm run build:manifest");
|
|
326
|
-
}
|
|
327
|
-
return found;
|
|
328
|
-
}
|
|
329
|
-
function readManifest() {
|
|
330
|
-
return JSON.parse(readFileSync(manifestPath(), "utf8"));
|
|
331
|
-
}
|
|
332
|
-
function isMissingManifestError(error) {
|
|
333
|
-
return (error instanceof Error &&
|
|
334
|
-
error.message.includes("Missing dist/manifest.json"));
|
|
335
|
-
}
|
|
336
|
-
function emit(io, data, columns, fmt, command, startedAt) {
|
|
337
|
-
io.stdout(format(data, columns, detectFormat(fmt), {
|
|
338
|
-
command,
|
|
339
|
-
duration_ms: Date.now() - startedAt,
|
|
340
|
-
surface: "web",
|
|
341
|
-
}));
|
|
342
|
-
}
|
|
343
|
-
function evaluateManifestOperationPolicy(input) {
|
|
344
|
-
try {
|
|
345
|
-
const policyInput = {
|
|
346
|
-
site: input.site,
|
|
347
|
-
command: input.commandName,
|
|
348
|
-
description: input.command.description,
|
|
349
|
-
adapterType: input.adapterType,
|
|
350
|
-
targetSurface: input.targetSurface,
|
|
351
|
-
strategy: input.command.strategy,
|
|
352
|
-
domain: input.command.domain,
|
|
353
|
-
base: input.command.base,
|
|
354
|
-
browser: input.command.browser === true,
|
|
355
|
-
args: input.command.args,
|
|
356
|
-
profile: input.parsed.permissionProfile,
|
|
357
|
-
approved: input.parsed.yes,
|
|
358
|
-
};
|
|
359
|
-
const policy = evaluateOperationPolicy(policyInput);
|
|
360
|
-
const denyRule = findDenyRuleForPolicySync(policy);
|
|
361
|
-
if (denyRule)
|
|
362
|
-
return applyDenyRuleToPolicy(policy, denyRule);
|
|
363
|
-
if (policy.enforcement === "needs_approval" &&
|
|
364
|
-
hasStoredApproval(policy.approval_memory.key)) {
|
|
365
|
-
return evaluateOperationPolicy({
|
|
366
|
-
...policyInput,
|
|
367
|
-
approvalSource: "memory",
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
return policy;
|
|
371
|
-
}
|
|
372
|
-
catch (error) {
|
|
373
|
-
if (!(error instanceof InvalidPermissionProfileError ||
|
|
374
|
-
error instanceof PermissionRulesConfigError)) {
|
|
375
|
-
throw error;
|
|
376
|
-
}
|
|
377
|
-
emitStderrAndExit(input.io, format([], input.command.columns, detectFormat(input.parsed.format), {
|
|
378
|
-
command: `${input.site}.${input.commandName}`,
|
|
379
|
-
duration_ms: Date.now() - input.startedAt,
|
|
380
|
-
surface: input.targetSurface,
|
|
381
|
-
error: {
|
|
382
|
-
code: "invalid_input",
|
|
383
|
-
message: error.message,
|
|
384
|
-
adapter_path: input.adapterPath,
|
|
385
|
-
step: 0,
|
|
386
|
-
suggestion: error instanceof PermissionRulesConfigError
|
|
387
|
-
? error.suggestion
|
|
388
|
-
: "use one of: open, confirm, locked",
|
|
389
|
-
retryable: false,
|
|
390
|
-
},
|
|
391
|
-
next_actions: defaultErrorNextActions(input.site, input.commandName, "invalid_input"),
|
|
392
|
-
}), ExitCode.USAGE_ERROR);
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
function hasStoredApproval(key) {
|
|
397
|
-
const store = createApprovalStore();
|
|
398
|
-
if (!existsSync(store.path))
|
|
399
|
-
return false;
|
|
400
|
-
try {
|
|
401
|
-
const raw = readFileSync(store.path, "utf-8");
|
|
402
|
-
const lines = raw.split(/\r?\n/);
|
|
403
|
-
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
404
|
-
const line = lines[i];
|
|
405
|
-
if (line.trim().length === 0)
|
|
406
|
-
continue;
|
|
407
|
-
try {
|
|
408
|
-
const entry = JSON.parse(line);
|
|
409
|
-
if (isStoredApproval(entry) && entry.key === key) {
|
|
410
|
-
return entry.decision === "allow";
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
catch {
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
catch {
|
|
420
|
-
return false;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
function handleList(parsed, io) {
|
|
424
|
-
const startedAt = Date.now();
|
|
425
|
-
let siteFilter;
|
|
426
|
-
let typeFilter;
|
|
427
|
-
for (let i = 0; i < parsed.rest.length; i += 1) {
|
|
428
|
-
const arg = parsed.rest[i];
|
|
429
|
-
if (arg === "--site") {
|
|
430
|
-
siteFilter = parsed.rest[i + 1];
|
|
431
|
-
i += 1;
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
if (arg.startsWith("--site=")) {
|
|
435
|
-
siteFilter = arg.slice("--site=".length);
|
|
436
|
-
continue;
|
|
437
|
-
}
|
|
438
|
-
if (arg === "--type") {
|
|
439
|
-
typeFilter = parsed.rest[i + 1];
|
|
440
|
-
i += 1;
|
|
441
|
-
continue;
|
|
442
|
-
}
|
|
443
|
-
if (arg.startsWith("--type=")) {
|
|
444
|
-
typeFilter = arg.slice("--type=".length);
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
return false;
|
|
448
|
-
}
|
|
449
|
-
const manifest = readManifest();
|
|
450
|
-
const rows = Object.entries(manifest.sites)
|
|
451
|
-
.flatMap(([site, info]) => info.commands.map((command) => {
|
|
452
|
-
const strategy = command.strategy ?? "public";
|
|
453
|
-
const tags = [];
|
|
454
|
-
if (strategy !== "public")
|
|
455
|
-
tags.push("[auth]");
|
|
456
|
-
if (command.quarantined === true)
|
|
457
|
-
tags.push("[quarantined]");
|
|
458
|
-
return {
|
|
459
|
-
site,
|
|
460
|
-
command: command.name,
|
|
461
|
-
description: command.description ?? "",
|
|
462
|
-
type: command.type ?? "web-api",
|
|
463
|
-
auth: tags.join(" "),
|
|
464
|
-
};
|
|
465
|
-
}))
|
|
466
|
-
.concat(dynamicListRows())
|
|
467
|
-
.filter((row) => !siteFilter || row.site.includes(siteFilter))
|
|
468
|
-
.filter((row) => !typeFilter || row.type === typeFilter)
|
|
469
|
-
.sort((a, b) => a.site.localeCompare(b.site) || a.command.localeCompare(b.command));
|
|
470
|
-
emit(io, rows, ["site", "command", "description", "type", "auth"], parsed.format, "core.list", startedAt);
|
|
471
|
-
return true;
|
|
472
|
-
}
|
|
473
|
-
function dynamicListRows() {
|
|
474
|
-
if (!dynamicMacosDiscoveryEnabled())
|
|
475
|
-
return [];
|
|
476
|
-
return Object.values(buildMacosDynamicCommands(discoverMacosDynamicData())).map((command) => ({
|
|
477
|
-
site: "macos",
|
|
478
|
-
command: command.name,
|
|
479
|
-
description: command.description ?? "",
|
|
480
|
-
type: "desktop",
|
|
481
|
-
auth: "",
|
|
482
|
-
}));
|
|
483
|
-
}
|
|
484
|
-
function handleSearch(parsed, io) {
|
|
485
|
-
const startedAt = Date.now();
|
|
486
|
-
let limit = 8;
|
|
487
|
-
let category;
|
|
488
|
-
const queryParts = [];
|
|
489
|
-
for (let i = 0; i < parsed.rest.length; i += 1) {
|
|
490
|
-
const arg = parsed.rest[i];
|
|
491
|
-
if (arg === "-n" || arg === "--limit") {
|
|
492
|
-
limit = parseInt(parsed.rest[i + 1] ?? "", 10) || 8;
|
|
493
|
-
i += 1;
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
if (arg.startsWith("--limit=")) {
|
|
497
|
-
limit = parseInt(arg.slice("--limit=".length), 10) || 8;
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
if (arg === "--category") {
|
|
501
|
-
category = parsed.rest[i + 1];
|
|
502
|
-
i += 1;
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
if (arg.startsWith("--category=")) {
|
|
506
|
-
category = arg.slice("--category=".length);
|
|
507
|
-
continue;
|
|
508
|
-
}
|
|
509
|
-
queryParts.push(arg);
|
|
510
|
-
}
|
|
511
|
-
const query = queryParts.join(" ");
|
|
512
|
-
if (!query && !category) {
|
|
513
|
-
io.stderr("Usage: unicli search <query> or unicli search --category <cat>");
|
|
514
|
-
process.exitCode = 2;
|
|
515
|
-
return true;
|
|
516
|
-
}
|
|
517
|
-
const effectiveQuery = category ? `${category} ${query}`.trim() : query;
|
|
518
|
-
const results = search(effectiveQuery, limit);
|
|
519
|
-
if (results.length === 0) {
|
|
520
|
-
io.stderr(`No commands found for: ${effectiveQuery}`);
|
|
521
|
-
process.exitCode = 66;
|
|
522
|
-
return true;
|
|
523
|
-
}
|
|
524
|
-
const rows = results.map((result) => ({
|
|
525
|
-
command: `${result.site} ${result.command}`,
|
|
526
|
-
description: result.description || `${result.command} for ${result.site}`,
|
|
527
|
-
score: result.score,
|
|
528
|
-
category: result.category,
|
|
529
|
-
usage: result.usage,
|
|
530
|
-
}));
|
|
531
|
-
emit(io, rows, ["command", "description", "score", "usage"], parsed.format, "core.search", startedAt);
|
|
532
|
-
return true;
|
|
533
|
-
}
|
|
534
|
-
function handleDescribe(parsed, io) {
|
|
535
|
-
const startedAt = Date.now();
|
|
536
|
-
const manifest = readManifest();
|
|
537
|
-
const [site, cmdName] = parsed.rest;
|
|
538
|
-
if (!site) {
|
|
539
|
-
const sites = Object.entries(manifest.sites).map(([name, info]) => ({
|
|
540
|
-
name,
|
|
541
|
-
display_name: name,
|
|
542
|
-
type: info.commands[0]?.type ?? "web-api",
|
|
543
|
-
strategy: info.commands[0]?.strategy ?? "public",
|
|
544
|
-
commands_count: info.commands.length,
|
|
545
|
-
description: "",
|
|
546
|
-
}));
|
|
547
|
-
io.stdout(JSON.stringify({ sites, total: sites.length }, null, 2));
|
|
548
|
-
return true;
|
|
549
|
-
}
|
|
550
|
-
const info = manifest.sites[site];
|
|
551
|
-
if (!info) {
|
|
552
|
-
io.stdout(JSON.stringify({ error: `unknown site: ${site}` }, null, 2));
|
|
553
|
-
process.exitCode = 64;
|
|
554
|
-
return true;
|
|
555
|
-
}
|
|
556
|
-
if (!cmdName) {
|
|
557
|
-
const commands = info.commands.map((command) => ({
|
|
558
|
-
name: command.name,
|
|
559
|
-
description: command.description ?? "",
|
|
560
|
-
quarantined: command.quarantined === true,
|
|
561
|
-
strategy: command.strategy ?? "public",
|
|
562
|
-
auth: (command.strategy ?? "public") !== "public",
|
|
563
|
-
browser: command.browser === true,
|
|
564
|
-
args: summarizeArgs(command.args),
|
|
565
|
-
}));
|
|
566
|
-
io.stdout(JSON.stringify({
|
|
567
|
-
site,
|
|
568
|
-
display_name: site,
|
|
569
|
-
type: info.commands[0]?.type ?? "web-api",
|
|
570
|
-
strategy: info.commands[0]?.strategy ?? "public",
|
|
571
|
-
commands,
|
|
572
|
-
}, null, 2));
|
|
573
|
-
return true;
|
|
574
|
-
}
|
|
575
|
-
const command = info.commands.find((candidate) => candidate.name === cmdName);
|
|
576
|
-
if (!command) {
|
|
577
|
-
io.stdout(JSON.stringify({ error: `unknown command: ${site} ${cmdName}` }, null, 2));
|
|
578
|
-
process.exitCode = 64;
|
|
579
|
-
return true;
|
|
580
|
-
}
|
|
581
|
-
const adapterType = command.type ?? info.commands[0]?.type ?? "web-api";
|
|
582
|
-
const targetSurface = resolveOperationTargetSurface({
|
|
583
|
-
adapterType,
|
|
584
|
-
targetSurface: command.target_surface,
|
|
585
|
-
});
|
|
586
|
-
const adapterPath = resolveOperationAdapterPath(site, cmdName, command.adapter_path);
|
|
587
|
-
const operationPolicy = evaluateManifestOperationPolicy({
|
|
588
|
-
parsed,
|
|
589
|
-
io,
|
|
590
|
-
site,
|
|
591
|
-
commandName: cmdName,
|
|
592
|
-
command,
|
|
593
|
-
adapterType,
|
|
594
|
-
targetSurface,
|
|
595
|
-
adapterPath,
|
|
596
|
-
startedAt,
|
|
597
|
-
});
|
|
598
|
-
if (!operationPolicy)
|
|
599
|
-
return true;
|
|
600
|
-
io.stdout(JSON.stringify({
|
|
601
|
-
command: `unicli ${site} ${cmdName}`,
|
|
602
|
-
description: command.description ?? "",
|
|
603
|
-
quarantined: command.quarantined === true,
|
|
604
|
-
strategy: command.strategy ?? "public",
|
|
605
|
-
auth: (command.strategy ?? "public") !== "public",
|
|
606
|
-
browser: command.browser === true,
|
|
607
|
-
target_surface: targetSurface,
|
|
608
|
-
adapter_path: adapterPath,
|
|
609
|
-
operation_policy: operationPolicy,
|
|
610
|
-
args_schema: argsToJsonSchema(command.args ?? []),
|
|
611
|
-
example_stdin: buildExample(command.args ?? []),
|
|
612
|
-
channels: buildChannels(site, cmdName, command.args ?? []),
|
|
613
|
-
next_actions: [
|
|
614
|
-
{
|
|
615
|
-
command: `unicli ${site} ${cmdName} --dry-run`,
|
|
616
|
-
description: "Preview the resolved argument bag and pipeline plan",
|
|
617
|
-
},
|
|
618
|
-
{
|
|
619
|
-
command: `unicli ${site} ${cmdName}`,
|
|
620
|
-
description: "Run the command (shell channel)",
|
|
621
|
-
params: {
|
|
622
|
-
note: {
|
|
623
|
-
description: "For payloads with quotes/emoji/JSON, pipe stdin-JSON instead.",
|
|
624
|
-
},
|
|
625
|
-
},
|
|
626
|
-
},
|
|
627
|
-
{
|
|
628
|
-
command: `unicli repair ${site} ${cmdName}`,
|
|
629
|
-
description: "If the command fails due to upstream drift",
|
|
630
|
-
},
|
|
631
|
-
],
|
|
632
|
-
}, null, 2));
|
|
633
|
-
return true;
|
|
634
|
-
}
|
|
635
|
-
function handleRepair(parsed, io) {
|
|
636
|
-
const startedAt = Date.now();
|
|
637
|
-
let dryRun = parsed.dryRun;
|
|
638
|
-
let max = 20;
|
|
639
|
-
let timeout = 90;
|
|
640
|
-
const positionals = [];
|
|
641
|
-
for (let i = 0; i < parsed.rest.length; i += 1) {
|
|
642
|
-
const arg = parsed.rest[i];
|
|
643
|
-
if (arg === "--dry-run") {
|
|
644
|
-
dryRun = true;
|
|
645
|
-
continue;
|
|
646
|
-
}
|
|
647
|
-
if (arg === "--max") {
|
|
648
|
-
max = parseInt(parsed.rest[i + 1] ?? "", 10) || 20;
|
|
649
|
-
i += 1;
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
if (arg.startsWith("--max=")) {
|
|
653
|
-
max = parseInt(arg.slice("--max=".length), 10) || 20;
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
if (arg === "--timeout") {
|
|
657
|
-
timeout = parseInt(parsed.rest[i + 1] ?? "", 10) || 90;
|
|
658
|
-
i += 1;
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
if (arg.startsWith("--timeout=")) {
|
|
662
|
-
timeout = parseInt(arg.slice("--timeout=".length), 10) || 90;
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
if (arg.startsWith("-"))
|
|
666
|
-
return false;
|
|
667
|
-
positionals.push(arg);
|
|
668
|
-
}
|
|
669
|
-
if (!dryRun)
|
|
670
|
-
return false;
|
|
671
|
-
const [site, command] = positionals;
|
|
672
|
-
if (!site)
|
|
673
|
-
return false;
|
|
674
|
-
const config = buildDefaultConfig(site, command);
|
|
675
|
-
config.maxIterations = max;
|
|
676
|
-
config.timeout = timeout * 1000;
|
|
677
|
-
emit(io, {
|
|
678
|
-
mode: "dry-run",
|
|
679
|
-
site,
|
|
680
|
-
command: command ?? null,
|
|
681
|
-
config: {
|
|
682
|
-
...config,
|
|
683
|
-
metricPattern: config.metricPattern.source,
|
|
684
|
-
},
|
|
685
|
-
}, undefined, parsed.format, "repair.run", startedAt);
|
|
686
|
-
return true;
|
|
687
|
-
}
|
|
688
|
-
function handleAdapterDryRun(parsed, io) {
|
|
689
|
-
if (!parsed.command || !parsed.dryRun || parsed.rest.length === 0) {
|
|
690
|
-
return false;
|
|
691
|
-
}
|
|
692
|
-
const manifest = readManifest();
|
|
693
|
-
const info = manifest.sites[parsed.command];
|
|
694
|
-
if (!info)
|
|
695
|
-
return false;
|
|
696
|
-
const [cmdName, ...tokens] = parsed.rest;
|
|
697
|
-
if (!cmdName || cmdName === "help" || cmdName.startsWith("-"))
|
|
698
|
-
return false;
|
|
699
|
-
const command = info.commands.find((candidate) => candidate.name === cmdName);
|
|
700
|
-
if (!command)
|
|
701
|
-
return false;
|
|
702
|
-
const startedAt = Date.now();
|
|
703
|
-
const args = resolveDryRunArgs(command.args, tokens);
|
|
704
|
-
if (!args)
|
|
705
|
-
return false;
|
|
706
|
-
const adapterType = command.type ?? info.commands[0]?.type ?? "web-api";
|
|
707
|
-
const targetSurface = resolveOperationTargetSurface({
|
|
708
|
-
adapterType,
|
|
709
|
-
targetSurface: command.target_surface,
|
|
710
|
-
});
|
|
711
|
-
const adapterPath = resolveOperationAdapterPath(parsed.command, cmdName, command.adapter_path);
|
|
712
|
-
const operationPolicy = evaluateManifestOperationPolicy({
|
|
713
|
-
parsed,
|
|
714
|
-
io,
|
|
715
|
-
site: parsed.command,
|
|
716
|
-
commandName: cmdName,
|
|
717
|
-
command,
|
|
718
|
-
adapterType,
|
|
719
|
-
targetSurface,
|
|
720
|
-
adapterPath,
|
|
721
|
-
startedAt,
|
|
722
|
-
});
|
|
723
|
-
if (!operationPolicy)
|
|
724
|
-
return true;
|
|
725
|
-
io.stdout(JSON.stringify({
|
|
726
|
-
command: `${parsed.command}.${cmdName}`,
|
|
727
|
-
adapter_type: adapterType,
|
|
728
|
-
strategy: command.strategy ?? "public",
|
|
729
|
-
args,
|
|
730
|
-
args_source: tokens.length > 0 ? "shell" : "defaults",
|
|
731
|
-
operation_policy: operationPolicy,
|
|
732
|
-
trace_id: `fast-${Date.now().toString(36)}`,
|
|
733
|
-
surface: "cli",
|
|
734
|
-
target_surface: targetSurface,
|
|
735
|
-
pipeline_steps: command.pipeline_steps ?? 0,
|
|
736
|
-
adapter_path: adapterPath,
|
|
737
|
-
}, null, 2));
|
|
738
|
-
return true;
|
|
739
|
-
}
|
|
740
|
-
function handleAdapterPolicyGate(parsed, io) {
|
|
741
|
-
if (!parsed.command || parsed.dryRun || parsed.rest.length === 0) {
|
|
742
|
-
return false;
|
|
743
|
-
}
|
|
744
|
-
const [cmdName] = parsed.rest;
|
|
745
|
-
if (!cmdName || cmdName === "help" || cmdName.startsWith("-"))
|
|
746
|
-
return false;
|
|
747
|
-
if (parsed.rest.slice(1).some(isHelpToken))
|
|
748
|
-
return false;
|
|
749
|
-
const startedAt = Date.now();
|
|
750
|
-
const manifest = readManifest();
|
|
751
|
-
const info = manifest.sites[parsed.command];
|
|
752
|
-
if (!info)
|
|
753
|
-
return false;
|
|
754
|
-
const command = info.commands.find((candidate) => candidate.name === cmdName);
|
|
755
|
-
if (!command)
|
|
756
|
-
return false;
|
|
757
|
-
const adapterType = command.type ?? info.commands[0]?.type ?? "web-api";
|
|
758
|
-
const targetSurface = resolveOperationTargetSurface({
|
|
759
|
-
adapterType,
|
|
760
|
-
targetSurface: command.target_surface,
|
|
761
|
-
});
|
|
762
|
-
const adapterPath = resolveOperationAdapterPath(parsed.command, cmdName, command.adapter_path);
|
|
763
|
-
const operationPolicy = evaluateManifestOperationPolicy({
|
|
764
|
-
parsed,
|
|
765
|
-
io,
|
|
766
|
-
site: parsed.command,
|
|
767
|
-
commandName: cmdName,
|
|
768
|
-
command,
|
|
769
|
-
adapterType,
|
|
770
|
-
targetSurface,
|
|
771
|
-
adapterPath,
|
|
772
|
-
startedAt,
|
|
773
|
-
});
|
|
774
|
-
if (!operationPolicy)
|
|
775
|
-
return true;
|
|
776
|
-
if (operationPolicy.enforcement !== "needs_approval" &&
|
|
777
|
-
operationPolicy.enforcement !== "deny") {
|
|
778
|
-
return false;
|
|
779
|
-
}
|
|
780
|
-
const isDenyRule = operationPolicy.enforcement === "deny";
|
|
781
|
-
const ruleId = operationPolicy.deny_rule?.id ?? "unknown";
|
|
782
|
-
const ruleReason = operationPolicy.deny_rule?.reason ?? "permission rule matched";
|
|
783
|
-
emitStderrAndExit(io, format([], command.columns, detectFormat(parsed.format), {
|
|
784
|
-
command: `${parsed.command}.${cmdName}`,
|
|
785
|
-
duration_ms: Date.now() - startedAt,
|
|
786
|
-
surface: targetSurface,
|
|
787
|
-
error: {
|
|
788
|
-
code: "permission_denied",
|
|
789
|
-
message: isDenyRule
|
|
790
|
-
? `permission rule "${ruleId}" denies ${operationPolicy.effect}: ${ruleReason}`
|
|
791
|
-
: `permission profile "${operationPolicy.profile}" requires approval for ${operationPolicy.effect}`,
|
|
792
|
-
adapter_path: adapterPath,
|
|
793
|
-
step: 0,
|
|
794
|
-
suggestion: operationPolicy.approval_hint ??
|
|
795
|
-
(isDenyRule
|
|
796
|
-
? "edit or remove the matching permission rule"
|
|
797
|
-
: "rerun with --yes or use --permission-profile open"),
|
|
798
|
-
retryable: false,
|
|
799
|
-
alternatives: isDenyRule
|
|
800
|
-
? [
|
|
801
|
-
`unicli --dry-run ${parsed.command} ${cmdName}`,
|
|
802
|
-
"edit ~/.unicli/permission-rules.json",
|
|
803
|
-
]
|
|
804
|
-
: [
|
|
805
|
-
`unicli --dry-run ${parsed.command} ${cmdName}`,
|
|
806
|
-
`unicli --yes --permission-profile ${operationPolicy.profile} ${parsed.command} ${cmdName}`,
|
|
807
|
-
`unicli --permission-profile open ${parsed.command} ${cmdName}`,
|
|
808
|
-
],
|
|
809
|
-
},
|
|
810
|
-
next_actions: defaultErrorNextActions(parsed.command, cmdName, "permission_denied"),
|
|
811
|
-
}), ExitCode.AUTH_REQUIRED);
|
|
812
|
-
return true;
|
|
813
|
-
}
|
|
814
|
-
function handleSiteHelp(parsed, io) {
|
|
815
|
-
const wantsHelp = parsed.rest.length === 0 || parsed.rest.every(isHelpToken);
|
|
816
|
-
if (!parsed.command || !wantsHelp)
|
|
817
|
-
return false;
|
|
818
|
-
const manifest = readManifest();
|
|
819
|
-
const info = manifest.sites[parsed.command];
|
|
820
|
-
if (!info)
|
|
821
|
-
return false;
|
|
822
|
-
const commandWidth = Math.max(7, ...info.commands.map((command) => command.name.length));
|
|
823
|
-
const lines = [
|
|
824
|
-
`Usage: unicli ${parsed.command} [options] [command]`,
|
|
825
|
-
"",
|
|
826
|
-
`Commands for ${parsed.command}`,
|
|
827
|
-
"",
|
|
828
|
-
"Options:",
|
|
829
|
-
" -h, --help".padEnd(commandWidth + 6) + "display help for command",
|
|
830
|
-
"",
|
|
831
|
-
"Commands:",
|
|
832
|
-
];
|
|
833
|
-
for (const command of info.commands) {
|
|
834
|
-
lines.push(` ${command.name.padEnd(commandWidth)} ${command.description ?? ""}`.trimEnd());
|
|
835
|
-
}
|
|
836
|
-
lines.push(` ${"help [command]".padEnd(commandWidth)} display help for command`);
|
|
837
|
-
io.stdout(lines.join("\n"));
|
|
838
|
-
return true;
|
|
839
|
-
}
|
|
840
96
|
export function tryRunFastPath(argv = process.argv, io = DEFAULT_IO) {
|
|
841
97
|
const parsed = parseArgv(argv);
|
|
842
98
|
try {
|