@dench.com/cli 3.6.6-staging.386 → 3.8.0-staging.389

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.
@@ -0,0 +1,252 @@
1
+ import { CliArgError, missingFlagValue } from "./cli-args";
2
+
3
+ type PositionalArity = readonly [min: number, max: number | null];
4
+
5
+ type CommandArguments = {
6
+ command: string;
7
+ values: string[];
8
+ switches: string[];
9
+ positionals: PositionalArity;
10
+ flagPositionals: Record<string, PositionalArity>;
11
+ };
12
+ const spec = (
13
+ command: string,
14
+ positionals: PositionalArity,
15
+ values = "",
16
+ switches = "",
17
+ flagPositionals: Record<string, PositionalArity> = {},
18
+ ): CommandArguments => ({
19
+ command,
20
+ positionals,
21
+ flagPositionals,
22
+ values: values.split(" ").filter(Boolean),
23
+ switches: switches.split(" ").filter(Boolean),
24
+ });
25
+
26
+ const browserSpec = (
27
+ verb: string,
28
+ positionals: PositionalArity,
29
+ flagPositionals: Record<string, PositionalArity> = {},
30
+ ): CommandArguments =>
31
+ spec(
32
+ `browser ${verb}`,
33
+ positionals,
34
+ "--thread --selector --text --label --value --key --target-id --targetId --pixels --timeout-ms --timeoutMs --x --y --output --url --script",
35
+ "--full-page",
36
+ flagPositionals,
37
+ );
38
+
39
+ /** CLI spellings for dispatchers that do not reject leftovers themselves.
40
+ * Positional ranges follow the actual handlers; null deliberately allows free text
41
+ * or multiple IDs. Flag alternatives cover positional inputs replaced by flags.
42
+ * Shared by pre-dispatch validation and help; API property names are not flags. */
43
+ export const COMMAND_ARGUMENTS = [
44
+ spec(
45
+ "signin",
46
+ [0, 0],
47
+ "--kind --name --email --org-name --org --timeout-ms --otp-code",
48
+ "--new-workspace --no-open",
49
+ ),
50
+ spec("logout", [0, 1], "", "--all"),
51
+ spec("use", [1, 1]),
52
+ spec("backend set", [1, 1]),
53
+ spec("backend clear", [0, 0]),
54
+ spec("env list", [0, 0], "--limit --cursor"),
55
+ spec("env show", [1, 1], "--revision"),
56
+ spec("env save", [1, 1], "--path --wait", "--no-wait"),
57
+ spec("env run", [1, 1], "--revision --state-key", "--prepare-local"),
58
+ spec("env delete", [1, 1]),
59
+ spec("secrets set", [1, 1], "--file --kind --expected-version", "--stdin"),
60
+ spec("secrets list", [0, 0]),
61
+ spec("secrets delete", [1, 1], "--expected-version"),
62
+ ...["identity", "organisation", "user", "tools", "rules"].map((section) =>
63
+ spec(`context set ${section}`, [0, 0], "--content", "--stdin"),
64
+ ),
65
+ ...["install", "uninstall", "refresh"].map((verb) =>
66
+ spec(
67
+ `ads ${verb}`,
68
+ [0, 0],
69
+ "--target",
70
+ "--no-open --apply --color --no-color",
71
+ ),
72
+ ),
73
+ spec(
74
+ "ads payout setup",
75
+ [0, 0],
76
+ "--target",
77
+ "--no-open --apply --color --no-color",
78
+ ),
79
+ spec(
80
+ "ads payout onboard",
81
+ [0, 0],
82
+ "--target",
83
+ "--no-open --apply --color --no-color",
84
+ ),
85
+ spec(
86
+ "ads withdraw",
87
+ [0, 0],
88
+ "--amount --idempotency-key --target",
89
+ "--no-open --apply --color --no-color",
90
+ ),
91
+ spec(
92
+ "chat new",
93
+ [1, null],
94
+ "--model --file-context --visibility --title --environment",
95
+ "--yolo --follow",
96
+ ),
97
+ spec("chat send", [2, null], "--model", "--yolo --follow"),
98
+ spec("chat rename", [2, null], "", "--from-stdin", {
99
+ "--from-stdin": [0, 0],
100
+ }),
101
+ spec("chat delete", [1, null], "--ids", "--yes -y --include-automated", {
102
+ "--ids": [0, null],
103
+ }),
104
+ spec("apps create", [1, 1], "--name --icon --description", "", {
105
+ "--name": [0, 1],
106
+ }),
107
+ spec(
108
+ "apps publish",
109
+ [1, 1],
110
+ "--slug --name --description --icon --visibility --version",
111
+ ),
112
+ ...["install", "uninstall", "enable", "disable", "pin", "unpin"].map((verb) =>
113
+ spec(`apps ${verb}`, [1, 1]),
114
+ ),
115
+ spec("apps webhooks register", [2, 2], "", "--rotate"),
116
+ spec("apps webhooks delete", [2, 2]),
117
+ spec("apps validate", [1, 1]),
118
+ spec(
119
+ "members me",
120
+ [0, 0],
121
+ "--name --tagline --timezone --image-url --avatar",
122
+ "--clear-avatar --auto-timezone",
123
+ ),
124
+ spec(
125
+ "cron create",
126
+ [1, 1],
127
+ "--prompt --description --overlap --target --type --at --cron --tz --every --every-ms --connection --event --toolkit --trigger-config --object --where --where-logic --environment",
128
+ "--disabled --delete-after-run --no-secret --signed",
129
+ ),
130
+ spec(
131
+ "cron update",
132
+ [1, 1],
133
+ "--name --prompt --description --overlap --enabled --at --cron --tz --every --every-ms --environment",
134
+ "--clear-environment",
135
+ ),
136
+ spec("cron run", [1, 1], "", "--wait"),
137
+ spec("cron secret", [1, 1], "", "--off"),
138
+ ...["delete", "enable", "disable"].map((verb) =>
139
+ spec(`cron ${verb}`, [1, 1]),
140
+ ),
141
+ spec("files rm", [1, 1], "", "--recursive -r"),
142
+ spec("files mv", [2, 2]),
143
+ spec("files write", [1, 1]),
144
+ spec("stage", [1, 2], "", "--clean"),
145
+ ...["identity", "organisation", "user", "tools", "mem"].flatMap((group) =>
146
+ ["edit", "set"].map((verb) =>
147
+ spec(`${group} ${verb}`, [0, 0], "--content", "--stdin"),
148
+ ),
149
+ ),
150
+ spec("mem append", [1, null]),
151
+ spec("model default", [0, 1]),
152
+ spec("model thread", [2, 2]),
153
+ spec("bootstrap complete", [0, 0]),
154
+ spec("bootstrap reopen", [0, 0]),
155
+ spec("bootstrap template", [0, 0], "--content", "--stdin"),
156
+ spec(
157
+ "memory save",
158
+ [2, null],
159
+ "--kind --tags --sensitivity --scope --visibility",
160
+ ),
161
+ spec("project create", [1, 1], "--repo --root"),
162
+ spec("approval request", [1, 1], "--reason --risk --agent"),
163
+ spec("approval approve", [1, 1], "--evidence"),
164
+ spec("approval reject", [1, 1], "--evidence"),
165
+ spec("billing topup", [1, 1], "--amount", "--no-open", {
166
+ "--amount": [0, 0],
167
+ }),
168
+ spec("upgrade", [0, 0], "--tier --cycle --billing-cycle", "--no-open"),
169
+ spec("tool run", [1, 1], "--args --account --approval"),
170
+ spec("tool connect", [1, 1], "--callback-url"),
171
+ spec("tool disconnect", [1, 1]),
172
+ spec(
173
+ "browser session create",
174
+ [0, 0],
175
+ "--context-id --contextId --width --height",
176
+ ),
177
+ spec("browser session stop", [1, 1]),
178
+ // Value flags replace the joined positional input. The clickAt handler
179
+ // instead lets its first/second positionals override --x/--y individually.
180
+ browserSpec("open", [0, null], { "--url": [0, 0] }),
181
+ browserSpec("state", [0, 0]),
182
+ browserSpec("inspect", [0, 0]),
183
+ browserSpec("navigate", [0, null], { "--url": [0, 0] }),
184
+ browserSpec("click", [0, 0]),
185
+ browserSpec("clickAt", [0, 2]),
186
+ browserSpec("click-at", [0, 2]),
187
+ browserSpec("fill", [0, 0]),
188
+ browserSpec("type", [0, null], { "--value": [0, 0] }),
189
+ browserSpec("key", [0, null], { "--key": [0, 0] }),
190
+ browserSpec("scroll", [0, 1]),
191
+ browserSpec("wait", [0, 0]),
192
+ browserSpec("evaluate", [0, null], { "--script": [0, 0] }),
193
+ browserSpec("tabs", [0, 0]),
194
+ browserSpec("newTab", [0, null], { "--url": [0, 0] }),
195
+ browserSpec("new-tab", [0, null], { "--url": [0, 0] }),
196
+ ...["activateTab", "activate-tab", "closeTab", "close-tab"].map((verb) =>
197
+ browserSpec(verb, [0, 1], { "--target-id": [0, 0], "--targetId": [0, 0] }),
198
+ ),
199
+ browserSpec("screenshot", [0, 0]),
200
+ browserSpec("stop", [0, 0]),
201
+ ];
202
+
203
+ export function assertDeclaredCommandFlags(argv: readonly string[]): void {
204
+ const boundary = argv.indexOf("--");
205
+ const args = argv
206
+ .slice(0, boundary < 0 ? undefined : boundary)
207
+ .filter((arg) => arg !== "--json");
208
+ if (["routine", "routines"].includes(args[0])) args[0] = "cron";
209
+ if (["ad", "adline"].includes(args[0])) args[0] = "ads";
210
+ if (args[0] === "agent" && ["new", "send"].includes(args[1]))
211
+ args[0] = "chat";
212
+ if (args[0] === "crm" && args[1] === "members") args.shift();
213
+ const contract = COMMAND_ARGUMENTS.find(({ command }) =>
214
+ command.split(" ").every((part, index) => args[index] === part),
215
+ );
216
+ if (!contract) return; // Domain parsers keep their own consumed-argument validation.
217
+ const positionals: string[] = [];
218
+ const flags = new Set<string>();
219
+ for (let i = contract.command.split(" ").length; i < args.length; i++) {
220
+ const arg = args[i];
221
+ if (!/^--?[^\d.]/.test(arg)) {
222
+ positionals.push(arg);
223
+ continue;
224
+ }
225
+ flags.add(arg);
226
+ if (contract.switches.includes(arg)) continue;
227
+ if (contract.values.includes(arg)) {
228
+ const value = args[++i];
229
+ if (missingFlagValue(value, arg))
230
+ throw new CliArgError(`Missing value for ${arg}`);
231
+ continue;
232
+ }
233
+ throw new CliArgError(
234
+ `Unknown flag for ${contract.command}: ${arg}. Run \`dench ${contract.command} --help\` for usage.`,
235
+ );
236
+ }
237
+ const [min, max] =
238
+ Object.entries(contract.flagPositionals).find(([flag]) =>
239
+ flags.has(flag),
240
+ )?.[1] ?? contract.positionals;
241
+ if (positionals.length < min || (max !== null && positionals.length > max)) {
242
+ const expected =
243
+ max === null
244
+ ? `at least ${min}`
245
+ : min === max
246
+ ? String(min)
247
+ : `${min}–${max}`;
248
+ throw new CliArgError(
249
+ `${positionals.length < min ? "Missing" : "Unexpected"} positional argument for ${contract.command}: expected ${expected}, received ${positionals.length}. Run \`dench ${contract.command} --help\` for usage.`,
250
+ );
251
+ }
252
+ }
@@ -0,0 +1,93 @@
1
+ import { COMMAND_ARGUMENTS } from "./command-args";
2
+ import { apiOperations } from "./command-registry";
3
+
4
+ /** Literal command words only; never interpret a data value as a command. */
5
+ function words(usage: string): string[] {
6
+ const parts = usage
7
+ .replace(/^dench\s+/, "")
8
+ .split(/\s+/)
9
+ .filter(Boolean);
10
+ const end = parts.findIndex((part) => /^[<[(-]/.test(part));
11
+ return end < 0 ? parts : parts.slice(0, end);
12
+ }
13
+
14
+ function matches(
15
+ prefix: readonly string[],
16
+ candidate: readonly string[],
17
+ ): boolean {
18
+ return prefix.every((word, index) =>
19
+ candidate[index]?.split("|").includes(word),
20
+ );
21
+ }
22
+
23
+ /** Registry metadata supplies discovery, never invokes command implementations. */
24
+ export function commandHelp(argv: readonly string[], usageText = ""): string {
25
+ const args = [...argv];
26
+ if (args[0] === "help") args.shift();
27
+ const input = words(args.filter((arg) => arg !== "help").join(" "));
28
+ const usageLines = usageText
29
+ .split("\n")
30
+ .map((line) => line.trim())
31
+ .filter((line) => line.startsWith("dench "));
32
+ const candidates = [
33
+ ...usageLines.map((usage) => ({ usage, description: "" })),
34
+ ...COMMAND_ARGUMENTS.map((entry) => ({
35
+ usage:
36
+ `dench ${entry.command} ${[...entry.values.map((flag) => `[${flag} <value>]`), ...entry.switches.map((flag) => `[${flag}]`)].join(" ")}`.trim(),
37
+ description: "",
38
+ })),
39
+ ...apiOperations
40
+ .filter((op) => {
41
+ if (!op.deprecated) return true;
42
+ // Keep exact legacy leaf help available without adding those commands
43
+ // back to the broad index or a parent family's discovery.
44
+ const command = words(op.cli);
45
+ return (
46
+ command.length > 1 &&
47
+ input.length >= command.length &&
48
+ matches(input.slice(0, command.length), command)
49
+ );
50
+ })
51
+ .map((op) => ({
52
+ usage: op.cli,
53
+ description: op.deprecated
54
+ ? `Compatibility command. ${op.summary}`
55
+ : op.summary,
56
+ })),
57
+ ];
58
+ let prefix = input;
59
+ while (
60
+ prefix.length &&
61
+ !candidates.some((item) => matches(prefix, words(item.usage)))
62
+ ) {
63
+ prefix = prefix.slice(0, -1);
64
+ }
65
+ const selected = candidates.filter((item) =>
66
+ matches(prefix, words(item.usage)),
67
+ );
68
+ // Domain usage has real flag spellings; a backend schema is not a CLI parser.
69
+ const exactUsage = selected.filter((item) => !item.description);
70
+ const entries = exactUsage.length ? exactUsage : selected;
71
+ const seen = new Set<string>();
72
+ const lines = entries.filter((item) => {
73
+ if (seen.has(item.usage)) return false;
74
+ seen.add(item.usage);
75
+ return true;
76
+ });
77
+ const heading = `Usage: dench ${prefix.join(" ")}${prefix.length ? "" : "<command>"}`;
78
+ return [
79
+ heading,
80
+ ...lines
81
+ .slice(0, 12)
82
+ .map(
83
+ (item) =>
84
+ ` ${item.usage}${item.description ? `\n ${item.description}` : ""}`,
85
+ ),
86
+ ...(lines.length > 12
87
+ ? [
88
+ ` ${lines.length - 12} more commands; add a subcommand before --help.`,
89
+ ]
90
+ : []),
91
+ "Options: --help, -h (local help); --json (structured command results).",
92
+ ].join("\n");
93
+ }