requestshield 0.1.6 → 0.1.8
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 +177 -68
- package/config/.env.prod +1 -0
- package/package.json +1 -1
- package/skills/requestshield/SKILL.md +20 -17
- package/skills/requestshield/assets/AGENTS.codex.md +5 -3
- package/skills/requestshield/references/backend-java-core.md +3 -3
- package/skills/requestshield/references/backend-spring-boot.md +3 -3
- package/skills/requestshield/references/browser-manual.md +1 -1
- package/skills/requestshield/references/cli.md +92 -28
- package/skills/requestshield/references/integration-planning.md +10 -8
- package/skills/requestshield/references/troubleshooting.md +2 -2
- package/src/api-client.mjs +1 -1
- package/src/args.mjs +98 -106
- package/src/cli.mjs +69 -243
- package/src/command-registry.mjs +97 -0
- package/src/commands/agent-setup.mjs +24 -17
- package/src/commands/agent-status.mjs +60 -0
- package/src/commands/application-mutations.mjs +8 -4
- package/src/commands/apps-get.mjs +12 -4
- package/src/commands/apps-list.mjs +20 -13
- package/src/commands/contract.mjs +25 -0
- package/src/commands/keys-create.mjs +5 -2
- package/src/commands/mutation-support.mjs +26 -12
- package/src/commands/output.mjs +21 -0
- package/src/commands/secret-commands.mjs +13 -6
- package/src/commands/signin.mjs +17 -8
- package/src/commands/signout.mjs +7 -3
- package/src/commands/update-check.mjs +97 -43
- package/src/config.mjs +29 -3
- package/src/entrypoint.mjs +20 -13
- package/src/integration-contract-client.mjs +81 -0
- package/src/integration-contract.mjs +104 -0
- package/src/oauth-client.mjs +2 -2
- package/src/oauth-loopback.mjs +1 -1
- package/src/session-files.mjs +4 -4
- package/src/session-store.mjs +2 -2
|
@@ -3,14 +3,42 @@
|
|
|
3
3
|
Check `requestshield --version` and `requestshield --help` against this guide.
|
|
4
4
|
An older published version may not contain these commands. Use the CLI for its
|
|
5
5
|
supported application and credential operations; do not invent endpoints for
|
|
6
|
-
unavailable commands.
|
|
6
|
+
unavailable commands. Command/group help comes from the same registry as parsing.
|
|
7
|
+
Legacy aliases are removed; use singular app/secret groups and positional names.
|
|
8
|
+
All result commands support --json, with one document on stdout and diagnostics
|
|
9
|
+
on stderr. Human output is the default; help/version remain text. JSON does not
|
|
10
|
+
skip confirmation: supply --yes only when the action is authorized.
|
|
11
|
+
|
|
12
|
+
## Public integration contract
|
|
13
|
+
|
|
14
|
+
```console
|
|
15
|
+
requestshield contract
|
|
16
|
+
requestshield contract --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This public read fetches the selected environment's customer-docs manifest without
|
|
20
|
+
login, local session access or file writes. It accepts no version-selection flag.
|
|
21
|
+
JSON returns `{data: ...}` with `schemaVersion`, `contractVersion`, `environment`,
|
|
22
|
+
`services.challengeUrl`, `browser`, `backend` and `documentation`.
|
|
23
|
+
Use `browser.scriptUrl` with its matching `integrity`, `tokenHeader` and
|
|
24
|
+
`availableModes`. Merge `browser.cspAdditions` into the application's existing CSP.
|
|
25
|
+
Use `backend.sdkVersion`, `minJdk`, `mavenRepository`, `core` and `springBoot3`
|
|
26
|
+
coordinates for the selected backend path. Configure the same returned challenge
|
|
27
|
+
URL in both SDKs. Metadata describes a selected release, not service health or
|
|
28
|
+
application protection.
|
|
29
|
+
|
|
30
|
+
`CONTRACT_NOT_PUBLISHED` means the docs host returned 404; `CONTRACT_FETCH_FAILED`
|
|
31
|
+
means network/HTTP failure; `CONTRACT_INVALID` means the response failed validation.
|
|
32
|
+
These exit 1. Do not invent a manifest or silently substitute another environment
|
|
33
|
+
or SDK version. An older CLI may not implement this command: use verified release
|
|
34
|
+
documentation for a manual integration and report the missing command or manifest.
|
|
7
35
|
|
|
8
36
|
## Authentication and local commands
|
|
9
37
|
|
|
10
38
|
```console
|
|
11
|
-
requestshield
|
|
39
|
+
requestshield login [--no-open]
|
|
12
40
|
requestshield auth status [--json]
|
|
13
|
-
requestshield
|
|
41
|
+
requestshield logout
|
|
14
42
|
requestshield --help
|
|
15
43
|
requestshield --version
|
|
16
44
|
```
|
|
@@ -28,11 +56,11 @@ provider grant.
|
|
|
28
56
|
## Applications
|
|
29
57
|
|
|
30
58
|
```console
|
|
31
|
-
requestshield
|
|
32
|
-
requestshield
|
|
33
|
-
requestshield
|
|
34
|
-
requestshield
|
|
35
|
-
requestshield
|
|
59
|
+
requestshield app list [--json] [--limit <1-100>] [--cursor <cursor> | --all]
|
|
60
|
+
requestshield app get <app-key>
|
|
61
|
+
requestshield app rename <app-key> <name> [--idempotency-key <key>]
|
|
62
|
+
requestshield app enable <app-key> [--idempotency-key <key>]
|
|
63
|
+
requestshield app disable <app-key> [--idempotency-key <key>] [--yes]
|
|
36
64
|
```
|
|
37
65
|
|
|
38
66
|
These commands require sign-in. Existing-app commands use exact App Keys; names
|
|
@@ -41,8 +69,8 @@ fails for repeated cursors or unfinished traversal at that bound. It cannot be
|
|
|
41
69
|
combined with `--cursor`. For name discovery, inspect all necessary pages and
|
|
42
70
|
resolve ambiguity with the user before choosing an App Key.
|
|
43
71
|
|
|
44
|
-
JSON list output is `{data:Application[],nextCursor}`. Detail and rename
|
|
45
|
-
|
|
72
|
+
JSON list output is `{data:Application[],nextCursor}`. Detail and rename with
|
|
73
|
+
`--json` return `{data:Application}`. Application has exactly `appKey`, `name`, `status`,
|
|
46
74
|
`createdAt`, `updatedAt`. Status is `pending`, `enabled`, `disabled`, `revoked`
|
|
47
75
|
or `attention_required`; it describes configuration, not traffic or enforcement.
|
|
48
76
|
Pending, attention and disabled states do not expose the exact credential state.
|
|
@@ -55,10 +83,10 @@ values stay out of tool output and transcripts. Give the exact command and
|
|
|
55
83
|
confirm storage by presence only.
|
|
56
84
|
|
|
57
85
|
```console
|
|
58
|
-
requestshield
|
|
59
|
-
requestshield
|
|
60
|
-
requestshield
|
|
61
|
-
requestshield
|
|
86
|
+
requestshield app create <name> [--idempotency-key <key>]
|
|
87
|
+
requestshield secret rotate <app-key> [--idempotency-key <key>] [--yes]
|
|
88
|
+
requestshield secret reveal <app-key> [--yes]
|
|
89
|
+
requestshield secret revoke <app-key> [--idempotency-key <key>] [--yes]
|
|
62
90
|
```
|
|
63
91
|
|
|
64
92
|
Create makes a new application and initial secret. Rotate preserves the App Key
|
|
@@ -83,7 +111,9 @@ without running it. HTTP 202 and `enabled` do not prove global propagation.
|
|
|
83
111
|
|
|
84
112
|
```console
|
|
85
113
|
requestshield agent setup [--codex | --claude] [--force]
|
|
114
|
+
requestshield agent status [--codex | --claude]
|
|
86
115
|
requestshield update check
|
|
116
|
+
requestshield update apply [--yes]
|
|
87
117
|
```
|
|
88
118
|
|
|
89
119
|
Agent setup copies the skill to `~/.agents/skills/requestshield` for Codex or
|
|
@@ -91,17 +121,51 @@ Agent setup copies the skill to `~/.agents/skills/requestshield` for Codex or
|
|
|
91
121
|
agent or asks if both are available. `--force` replaces an existing installation.
|
|
92
122
|
It does not edit repository `AGENTS.md` or customer application code.
|
|
93
123
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
Agent status inspects both agents by default and returns `installed`, `missing`
|
|
125
|
+
or `invalid` file state. It does not establish that an agent has loaded the Skill.
|
|
126
|
+
|
|
127
|
+
Production update check queries npm without prompting or installing. Update apply
|
|
128
|
+
checks and asks before installing the exact available version globally; --yes
|
|
129
|
+
explicitly confirms. Explain the target when an update is requested. A global
|
|
130
|
+
update does not change a pinned invocation or source checkout. Repository-only
|
|
131
|
+
QAT/STG runners return local source-update guidance for both commands without
|
|
132
|
+
registry access or installation.
|
|
133
|
+
|
|
134
|
+
## Complete command catalog
|
|
135
|
+
|
|
136
|
+
All result commands support --json. See command help for additional flags.
|
|
137
|
+
|
|
138
|
+
| Command after `requestshield` | Availability |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `--help`, `-h` | Implemented |
|
|
141
|
+
| `--version`, `-v` | Implemented |
|
|
142
|
+
| `login [--no-open]` | Implemented |
|
|
143
|
+
| `logout` | Implemented |
|
|
144
|
+
| `auth status` | Implemented |
|
|
145
|
+
| `app create <name>` | Implemented |
|
|
146
|
+
| `app list` | Implemented |
|
|
147
|
+
| `app get <app-key>` | Implemented |
|
|
148
|
+
| `app rename <app-key> <name>` | Implemented |
|
|
149
|
+
| `app enable <app-key>` | Implemented |
|
|
150
|
+
| `app disable <app-key>` | Implemented |
|
|
151
|
+
| `secret rotate <app-key>` | Implemented |
|
|
152
|
+
| `secret reveal <app-key>` | Implemented |
|
|
153
|
+
| `secret revoke <app-key>` | Implemented |
|
|
154
|
+
| `agent setup [--codex \| --claude] [--force]` | Implemented |
|
|
155
|
+
| `agent status [--codex \| --claude]` | Implemented |
|
|
156
|
+
| `update check` | Implemented |
|
|
157
|
+
| `update apply [--yes]` | Implemented |
|
|
158
|
+
| `contract` | Implemented |
|
|
159
|
+
| `service status` | [coming soon] |
|
|
160
|
+
| `secret status <app-key>` | [coming soon] |
|
|
161
|
+
| `usage challenges <app-key>` | [coming soon] |
|
|
162
|
+
| `billing get <app-key>` | [coming soon] |
|
|
163
|
+
|
|
164
|
+
The four placeholders validate syntax and return COMMAND_UNAVAILABLE with exit
|
|
165
|
+
code 2 before configuration, authentication, network or writes. Usage accepts
|
|
166
|
+
--from, --to and --granularity hour|day as placeholder syntax only. Never use
|
|
167
|
+
placeholder results as evidence, infer secret state from application status or
|
|
168
|
+
call reveal as a status probe. Doctor and app watch are not commands.
|
|
169
|
+
|
|
170
|
+
Use published SDK documentation for the selected release and safe browser/backend
|
|
171
|
+
observations for integration checks. App status is not traffic or enforcement.
|
|
@@ -72,7 +72,7 @@ Confirm that the backend actually reads or maps that variable before treating it
|
|
|
72
72
|
valid RequestShield credential.
|
|
73
73
|
|
|
74
74
|
If the App Key is known, inspect its aggregate application configuration with
|
|
75
|
-
`requestshield
|
|
75
|
+
`requestshield app get <app-key>`. That status does not prove the exact credential
|
|
76
76
|
state or that the deployed backend has received its secret. The CLI has no separate
|
|
77
77
|
credential-status command; check runtime injection by presence only.
|
|
78
78
|
|
|
@@ -91,7 +91,7 @@ Report only what was confirmed:
|
|
|
91
91
|
| No credential pair has been provisioned | Ask the user to provision credentials before continuing with backend setup. |
|
|
92
92
|
|
|
93
93
|
If no pair has been provisioned yet, the user creates it. You may run the read-only
|
|
94
|
-
`requestshield
|
|
94
|
+
`requestshield app list` to see whether an application already exists. Everything that
|
|
95
95
|
mints or changes a secret stays with the user; see **Key and secret management** in
|
|
96
96
|
`SKILL.md`.
|
|
97
97
|
|
|
@@ -180,11 +180,13 @@ last one to run wins rather than the two merging.
|
|
|
180
180
|
|
|
181
181
|
## Contract check
|
|
182
182
|
|
|
183
|
-
After detecting the current integration,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
After detecting the current integration, run `requestshield contract --json`
|
|
184
|
+
with the runner for the intended environment. It requires no login. Confirm the
|
|
185
|
+
script URL and matching integrity, token header, available modes, CSP additions,
|
|
186
|
+
challenge URL and Java/Maven requirements, then consult its documentation links.
|
|
187
|
+
See `cli.md` for field names and failure handling. If the command or manifest is
|
|
188
|
+
unavailable, report that gap and use verified customer documentation for the
|
|
189
|
+
selected release; never fabricate values or substitute another environment.
|
|
188
190
|
|
|
189
191
|
The documented backend paths here require Java 17 or newer. The Spring Boot starter
|
|
190
192
|
also requires Spring Boot 3 with Spring MVC; Java core is a separate supported path.
|
|
@@ -320,7 +322,7 @@ the page issues the protected request.
|
|
|
320
322
|
Always report the evidence with file and line references:
|
|
321
323
|
|
|
322
324
|
```text
|
|
323
|
-
Backend:
|
|
325
|
+
Backend: customer-backend/build.gradle.kts
|
|
324
326
|
Java 21 - java.toolchain.languageVersion (line 14)
|
|
325
327
|
Spring Boot 3.2.1 - org.springframework.boot plugin (line 3)
|
|
326
328
|
Spring MVC - spring-boot-starter-web (line 20)
|
|
@@ -8,7 +8,7 @@ header, the backend never verified it, or one token was verified twice.
|
|
|
8
8
|
|
|
9
9
|
```console
|
|
10
10
|
requestshield auth status --json
|
|
11
|
-
requestshield
|
|
11
|
+
requestshield app get <app-key>
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Session status is local only. Application status is configuration state, not
|
|
@@ -105,7 +105,7 @@ If the API Secret appears in a repo, a log, a build artifact, or a chat transcri
|
|
|
105
105
|
rotate it — deleting the line does not un-expose it:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
|
-
requestshield
|
|
108
|
+
requestshield secret rotate <app-key>
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
The App Key is unchanged, so no browser edit is needed. Deploy the new secret to backend
|
package/src/api-client.mjs
CHANGED
|
@@ -107,7 +107,7 @@ export class ManagementApiClient {
|
|
|
107
107
|
if (!response.ok) {
|
|
108
108
|
const providerCode = objectString(body?.error, "code");
|
|
109
109
|
const safeCode = providerCode && API_ERRORS.has(providerCode) ? providerCode : undefined;
|
|
110
|
-
const guidance = response.status === 401 ? "; run `requestshield
|
|
110
|
+
const guidance = response.status === 401 ? "; run `requestshield login` again"
|
|
111
111
|
: safeCode === "insufficient_scope" ? "; this session lacks the required permissions" : "";
|
|
112
112
|
throw new CliError(`RequestShield API error: HTTP ${response.status}${safeCode ? ` (${safeCode})` : ""}${guidance}`, {
|
|
113
113
|
code: response.status === 401 ? "UNAUTHENTICATED" : "API_ERROR",
|
package/src/args.mjs
CHANGED
|
@@ -1,145 +1,137 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { CliError } from "./errors.mjs";
|
|
3
|
+
import { COMMAND_REGISTRY, commandUsage, renderHelp } from "./command-registry.mjs";
|
|
3
4
|
|
|
4
|
-
const HELP =
|
|
5
|
-
|
|
6
|
-
Usage:
|
|
7
|
-
requestshield signin [--no-open]
|
|
8
|
-
requestshield auth status [--json]
|
|
9
|
-
requestshield signout
|
|
10
|
-
requestshield keys create --app-name <name> [--idempotency-key <key>]
|
|
11
|
-
requestshield keys rotate <app-key> [--idempotency-key <key>] [--yes]
|
|
12
|
-
requestshield keys reveal <app-key> [--yes]
|
|
13
|
-
requestshield keys revoke <app-key> [--idempotency-key <key>] [--yes]
|
|
14
|
-
requestshield apps list [--json] [--limit <1-100>] [--cursor <cursor> | --all]
|
|
15
|
-
requestshield apps get <app-key>
|
|
16
|
-
requestshield apps rename <app-key> --name <name> [--idempotency-key <key>]
|
|
17
|
-
requestshield apps enable <app-key> [--idempotency-key <key>]
|
|
18
|
-
requestshield apps disable <app-key> [--idempotency-key <key>] [--yes]
|
|
19
|
-
requestshield agent setup [--force]
|
|
20
|
-
requestshield agent setup --codex [--force]
|
|
21
|
-
requestshield agent setup --claude [--force]
|
|
22
|
-
requestshield update check
|
|
23
|
-
requestshield --help
|
|
24
|
-
requestshield --version
|
|
25
|
-
|
|
26
|
-
List defaults to one page. Use --all for a bounded complete listing.
|
|
27
|
-
contract, challenge volume and get billing are not available yet.`;
|
|
5
|
+
export const HELP = renderHelp();
|
|
28
6
|
|
|
29
7
|
/**
|
|
30
|
-
* @typedef {{command:"help",help:string} | {command:"version"
|
|
31
|
-
* | {command:"
|
|
32
|
-
* | {command:"
|
|
33
|
-
* | {command:"
|
|
34
|
-
* | {command:"
|
|
35
|
-
* | {command:"
|
|
36
|
-
* | {command:"
|
|
37
|
-
* | {command:"
|
|
38
|
-
* | {command:"
|
|
8
|
+
* @typedef {{command:"help",help:string} | {command:"version"}
|
|
9
|
+
* | {command:"update-check"|"logout"|"auth-status"|"contract",json:boolean}
|
|
10
|
+
* | {command:"update-apply",yes:boolean,json:boolean}
|
|
11
|
+
* | {command:"login",noOpen:boolean,json:boolean}
|
|
12
|
+
* | {command:"app-create",appName:string,yes:boolean,json:boolean,idempotencyKey?:string}
|
|
13
|
+
* | {command:"secret-rotate"|"secret-reveal"|"secret-revoke",appKey:string,yes:boolean,json:boolean,idempotencyKey?:string}
|
|
14
|
+
* | {command:"app-list",json:boolean,all:boolean,limit?:number,cursor?:string}
|
|
15
|
+
* | {command:"app-get",appKey:string,json:boolean}
|
|
16
|
+
* | {command:"app-rename",appKey:string,name:string,yes:boolean,json:boolean,idempotencyKey?:string}
|
|
17
|
+
* | {command:"app-enable"|"app-disable",appKey:string,enabled:boolean,yes:boolean,json:boolean,idempotencyKey?:string}
|
|
18
|
+
* | {command:"agent-setup",agent?:"codex"|"claude",force:boolean,json:boolean}
|
|
19
|
+
* | {command:"agent-status",agent?:"codex"|"claude",json:boolean}} ParsedArgs
|
|
39
20
|
*/
|
|
40
21
|
|
|
41
22
|
/** @param {string[]} argv @returns {ParsedArgs} */
|
|
42
23
|
export function parseArgs(argv) {
|
|
43
|
-
if (!argv.length
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (group === "signout") { parseFlags(argv.slice(1), []); return {command:"signout"}; }
|
|
54
|
-
if (group === "auth" && action === "status") {
|
|
55
|
-
const flags = parseFlags(argv.slice(2), ["--json"]);
|
|
56
|
-
return {command:"auth-status",json:flags.has("--json")};
|
|
24
|
+
if (!argv.length) return {command:"help",help:HELP};
|
|
25
|
+
const definition = COMMAND_REGISTRY.find((command) => command.path.every((part,index) => argv[index] === part)
|
|
26
|
+
|| command.shortcuts?.includes(argv[0]) && command.path.length === 1);
|
|
27
|
+
if (!definition) {
|
|
28
|
+
const group = argv[0];
|
|
29
|
+
const isGroup = COMMAND_REGISTRY.some((command) => command.path.length > 1 && command.path[0] === group);
|
|
30
|
+
if (isGroup && (argv.length === 1 || argv.length === 2 && ["--help","-h"].includes(argv[1]))) {
|
|
31
|
+
return {command:"help",help:renderHelp(group)};
|
|
32
|
+
}
|
|
33
|
+
throw invalid("Unknown command. Use requestshield --help to list commands.");
|
|
57
34
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (typeof name !== "string") usage("keys create --app-name <name> [--idempotency-key <key>]");
|
|
63
|
-
return {command:"keys-create",appName:applicationName(name),...mutationFlags(flags)};
|
|
35
|
+
const args = argv.slice(definition.path.length);
|
|
36
|
+
const endOfOptions = args.indexOf("--");
|
|
37
|
+
if (args.slice(0,endOfOptions === -1 ? args.length : endOfOptions).some((value) => ["--help","-h"].includes(value))) {
|
|
38
|
+
return {command:"help",help:renderHelp(definition)};
|
|
64
39
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
40
|
+
const {flags,positionals} = parseOptions(args,definition.flags);
|
|
41
|
+
const requiredCount = definition.arguments.filter((argument) => argument.required).length;
|
|
42
|
+
if (positionals.length < requiredCount || positionals.length > definition.arguments.length) {
|
|
43
|
+
throw invalid(`Usage: requestshield ${commandUsage(definition)}`);
|
|
69
44
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
throw invalid("--limit must be an integer from 1 to 100");
|
|
75
|
-
}
|
|
76
|
-
const cursor = flags.get("--cursor");
|
|
77
|
-
if (cursor !== undefined && (typeof cursor !== "string" || !/^[A-Za-z0-9_-]{1,1024}$/.test(cursor))) throw invalid("Invalid --cursor");
|
|
78
|
-
if (cursor !== undefined && flags.has("--all")) throw invalid("--cursor and --all cannot be combined");
|
|
79
|
-
return {command:"apps-list",json:flags.has("--json"),all:flags.has("--all"),
|
|
80
|
-
...(limit !== undefined ? {limit:Number(limit)} : {}),...(typeof cursor === "string" ? {cursor} : {})};
|
|
45
|
+
/** @type {Record<string,string>} */
|
|
46
|
+
const values = {};
|
|
47
|
+
for (const [index,argument] of definition.arguments.entries()) {
|
|
48
|
+
if (positionals[index] !== undefined) values[argument.field] = argument.kind === "app-name" ? applicationName(positionals[index]) : validateAppKey(positionals[index]);
|
|
81
49
|
}
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
parseFlags(argv.slice(3), []);
|
|
85
|
-
return {command:"apps-get",appKey};
|
|
50
|
+
if (definition.availability === "placeholder") {
|
|
51
|
+
throw new CliError("This command is not available yet.",{code:"COMMAND_UNAVAILABLE",exitCode:2});
|
|
86
52
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
53
|
+
const json = flags.has("--json");
|
|
54
|
+
switch (definition.id) {
|
|
55
|
+
case "help": return {command:"help",help:HELP};
|
|
56
|
+
case "version": return {command:"version"};
|
|
57
|
+
case "login": return {command:"login",noOpen:flags.has("--no-open"),json};
|
|
58
|
+
case "logout": case "auth-status": case "update-check": case "contract": return {command:definition.id,json};
|
|
59
|
+
case "update-apply": return {command:"update-apply",yes:flags.has("--yes"),json};
|
|
60
|
+
case "app-create": return {command:"app-create",appName:values.appName,json,...mutationFlags(flags)};
|
|
61
|
+
case "app-get": return {command:"app-get",appKey:values.appKey,json};
|
|
62
|
+
case "app-rename": return {command:"app-rename",appKey:values.appKey,name:values.name,json,...mutationFlags(flags)};
|
|
63
|
+
case "app-enable": case "app-disable": return {command:definition.id,appKey:values.appKey,enabled:definition.id === "app-enable",json,...mutationFlags(flags)};
|
|
64
|
+
case "secret-rotate": case "secret-reveal": case "secret-revoke": return {command:definition.id,appKey:values.appKey,json,...mutationFlags(flags)};
|
|
65
|
+
case "app-list": {
|
|
66
|
+
const limit = flags.get("--limit");
|
|
67
|
+
if (limit !== undefined && (typeof limit !== "string" || !/^[1-9][0-9]{0,2}$/.test(limit) || Number(limit) > 100)) throw invalid("--limit must be an integer from 1 to 100");
|
|
68
|
+
const cursor = flags.get("--cursor");
|
|
69
|
+
if (cursor !== undefined && (typeof cursor !== "string" || !/^[A-Za-z0-9_-]{1,1024}$/.test(cursor))) throw invalid("Invalid --cursor");
|
|
70
|
+
if (cursor !== undefined && flags.has("--all")) throw invalid("--cursor and --all cannot be combined");
|
|
71
|
+
return {command:"app-list",json,all:flags.has("--all"),...(limit !== undefined ? {limit:Number(limit)} : {}),...(typeof cursor === "string" ? {cursor} : {})};
|
|
94
72
|
}
|
|
95
|
-
|
|
73
|
+
case "agent-setup": return {command:"agent-setup",force:flags.has("--force"),json,...agentOption(flags)};
|
|
74
|
+
case "agent-status": return {command:"agent-status",json,...agentOption(flags)};
|
|
75
|
+
default: throw new Error("Implemented command has no parser result mapping");
|
|
96
76
|
}
|
|
97
|
-
if (group === "agent" && action === "setup") {
|
|
98
|
-
const flags = parseFlags(argv.slice(2), ["--codex","--claude","--force"]);
|
|
99
|
-
if (flags.has("--codex") && flags.has("--claude")) throw invalid("Only one of --codex or --claude may be provided");
|
|
100
|
-
return {command:"agent-setup",force:flags.has("--force"),
|
|
101
|
-
...(flags.has("--codex") ? {agent:/** @type {const} */ ("codex")} : flags.has("--claude") ? {agent:/** @type {const} */ ("claude")} : {})};
|
|
102
|
-
}
|
|
103
|
-
throw invalid(`Unknown command.\n\n${HELP}`);
|
|
104
77
|
}
|
|
105
78
|
|
|
106
|
-
/** @param {string[]} args @param {
|
|
107
|
-
function
|
|
79
|
+
/** @param {string[]} args @param {import('./command-registry.mjs').FlagDefinition[]} options */
|
|
80
|
+
function parseOptions(args,options) {
|
|
108
81
|
/** @type {Map<string,string|true>} */
|
|
109
82
|
const flags = new Map();
|
|
83
|
+
/** @type {string[]} */
|
|
84
|
+
const positionals = [];
|
|
85
|
+
let positionalOnly = false;
|
|
110
86
|
for (let i = 0; i < args.length; i++) {
|
|
111
87
|
const arg = args[i];
|
|
112
|
-
if (
|
|
113
|
-
if (
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
88
|
+
if (positionalOnly) { positionals.push(arg); continue; }
|
|
89
|
+
if (arg === "--") { positionalOnly = true; continue; }
|
|
90
|
+
if (!arg.startsWith("-")) { positionals.push(arg); continue; }
|
|
91
|
+
const equals = arg.indexOf("=");
|
|
92
|
+
const name = equals === -1 ? arg : arg.slice(0,equals);
|
|
93
|
+
const option = options.find((item) => item.name === name);
|
|
94
|
+
if (!option) throw invalid(`Unknown option: ${name}`);
|
|
95
|
+
if (flags.has(name)) throw invalid(`${name} may only be provided once`);
|
|
96
|
+
if (option.kind === "boolean") {
|
|
97
|
+
if (equals !== -1) throw invalid(`${name} does not accept a value`);
|
|
98
|
+
flags.set(name,true);
|
|
99
|
+
} else {
|
|
100
|
+
const value = equals === -1 ? args[++i] : arg.slice(equals + 1);
|
|
101
|
+
if (!value || equals === -1 && value.startsWith("-")) throw invalid(`${name} requires a value`);
|
|
102
|
+
if (option.choices && !option.choices.includes(value)) throw invalid(`${name} must be one of: ${option.choices.join(", ")}`);
|
|
103
|
+
flags.set(name,value);
|
|
119
104
|
}
|
|
120
105
|
}
|
|
121
|
-
return flags;
|
|
106
|
+
return {flags,positionals};
|
|
122
107
|
}
|
|
108
|
+
|
|
109
|
+
/** @param {Map<string,string|true>} flags */
|
|
110
|
+
function agentOption(flags) {
|
|
111
|
+
const selections = ["--codex","--claude"];
|
|
112
|
+
if (selections.filter((name) => flags.has(name)).length > 1) throw invalid(`Only one of ${selections.join(", ")} may be provided`);
|
|
113
|
+
return flags.has("--codex") ? {agent:/** @type {const} */ ("codex")} : flags.has("--claude") ? {agent:/** @type {const} */ ("claude")} : {};
|
|
114
|
+
}
|
|
115
|
+
|
|
123
116
|
/** @param {Map<string,string|true>} flags */
|
|
124
117
|
function mutationFlags(flags) {
|
|
125
118
|
const key = flags.get("--idempotency-key");
|
|
126
119
|
if (key !== undefined && (typeof key !== "string" || !/^[A-Za-z0-9._~-]{1,128}$/.test(key))) throw invalid("Invalid --idempotency-key");
|
|
127
120
|
return {yes:flags.has("--yes"),...(typeof key === "string" ? {idempotencyKey:key} : {})};
|
|
128
121
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (!/^[A-Za-z0-9._~-]{1,128}$/.test(
|
|
133
|
-
return
|
|
122
|
+
|
|
123
|
+
/** @param {string} value */
|
|
124
|
+
export function validateAppKey(value) {
|
|
125
|
+
if (!/^[A-Za-z0-9._~-]{1,128}$/.test(value)) throw invalid("App Key must be 1-128 letters, numbers, '.', '_', '~' or '-'");
|
|
126
|
+
return value;
|
|
134
127
|
}
|
|
128
|
+
|
|
135
129
|
/** @param {string} value */
|
|
136
|
-
function applicationName(value) {
|
|
130
|
+
export function applicationName(value) {
|
|
137
131
|
const name = value.trim();
|
|
138
132
|
if (!name || [...name].length > 100 || /[\p{Cc}\p{Cs}]/u.test(name)) throw invalid("Application name must contain 1-100 characters without control characters");
|
|
139
133
|
return name;
|
|
140
134
|
}
|
|
141
|
-
|
|
142
|
-
function usage(command) { throw invalid(`Usage: requestshield ${command}`); }
|
|
135
|
+
|
|
143
136
|
/** @param {string} message */
|
|
144
137
|
function invalid(message) { return new CliError(message,{code:"INVALID_ARGUMENT",exitCode:2}); }
|
|
145
|
-
export { HELP };
|