requestshield 0.1.3 → 0.1.5

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 CHANGED
@@ -1,43 +1,290 @@
1
1
  # RequestShield CLI
2
2
 
3
- Customer-facing CLI for signing in, rotating RequestShield application keys,
4
- and installing the RequestShield skill for Codex or Claude.
3
+ Customer-facing CLI for IntelliFend RequestShield. It allows customers to sign
4
+ in, rotate application credentials, install the RequestShield Skill for Codex
5
+ or Claude, and update an npm-installed CLI.
6
+
7
+ Current package version: `0.1.5`.
5
8
 
6
9
  ## Requirements
7
10
 
8
11
  - Node.js 22.13 or newer.
9
- - A customer management API implementing the contract below.
12
+ - A RequestShield customer management API.
13
+ - npm for local development and publishing.
14
+
15
+ The management API defaults to `https://api.intellifend.ai`. Set
16
+ `REQUESTSHIELD_API_URL` to use a different management API during development.
17
+
18
+ ## Implemented commands
10
19
 
11
- The API defaults to `https://api.intellifend.ai`. For development, set
12
- `REQUESTSHIELD_API_URL` to the management API origin.
20
+ ```console
21
+ requestshield signin
22
+ requestshield keys create [--yes]
23
+ requestshield contract
24
+ requestshield apps list [--json]
25
+ requestshield apps get <app-key>
26
+ requestshield challenge volume <app-key> [--from <time>] [--to <time>] [--granularity <value>]
27
+ requestshield get billing <app-key>
28
+ requestshield agent setup [--force]
29
+ requestshield agent setup --codex [--force]
30
+ requestshield agent setup --claude [--force]
31
+ requestshield update check
32
+ requestshield --help
33
+ requestshield --version
34
+ ```
13
35
 
14
- ## Commands
36
+ ### Sign in
15
37
 
16
38
  ```console
17
39
  requestshield signin
40
+ ```
41
+
42
+ Starts the device authorization flow. The CLI prints the verification URL and
43
+ user code without opening the browser automatically. After authorization, the
44
+ session is stored for the current operating-system user.
45
+
46
+ For CI or automation, provide `REQUESTSHIELD_ACCESS_TOKEN`. A token supplied by
47
+ the environment is used directly and is not persisted by the CLI.
48
+
49
+ ### Create new keys
50
+
51
+ ```console
18
52
  requestshield keys create
53
+ ```
54
+
55
+ Asks the customer to type `ROTATE`, then calls the management API to deactivate
56
+ the previous credentials and create a new `appKey` and `apiSecret`.
57
+
58
+ - `appKey` is the public application identifier.
59
+ - `apiSecret` is a backend-only secret displayed once.
60
+ - The CLI does not save the returned `apiSecret`.
61
+ - `--yes` skips the confirmation prompt for non-interactive automation.
62
+
63
+ Store the Secret Key in a backend secret manager immediately after creation.
64
+
65
+ ### Get the integration contract
66
+
67
+ ```console
68
+ requestshield contract
69
+ ```
70
+
71
+ Returns the current authoritative integration contract as JSON, including any
72
+ additional SDK-version or CSP fields supplied by the API. The command requires a
73
+ saved sign-in session and validates the core browser, backend, and release fields
74
+ before printing the response.
75
+
76
+ ### List applications
77
+
78
+ ```console
79
+ requestshield apps list
80
+ ```
81
+
82
+ Lists the applications the signed-in user is allowed to access:
83
+
84
+ ```text
85
+ APP KEY NAME STATUS
86
+ app_123 Production Website pending
87
+ app_456 Staging Website active
88
+ ```
89
+
90
+ Use `--json` to print the stable `{ "data": [...], "nextCursor": null }`
91
+ response shape. The CLI allowlists `appKey`, `name`, `status`, `createdAt`, and
92
+ `updatedAt`; it never prints an API Secret returned accidentally by the service.
93
+
94
+ ### Get an application
95
+
96
+ ```console
97
+ requestshield apps get app_123
98
+ ```
99
+
100
+ Returns `app_key`, `name`, and the application's current status as JSON.
101
+ Valid statuses are:
102
+
103
+ - `ready` — the application key was just created and no traffic has been received.
104
+ - `active` — the application is receiving traffic.
105
+ - `deactivated` — the application has been deactivated.
106
+
107
+ The requested App Key must exactly match the key in the API response.
108
+
109
+ ### Get challenge volume
110
+
111
+ ```console
112
+ requestshield challenge volume app_123 \
113
+ --from 2026-08-01T00:00:00Z \
114
+ --to 2026-08-27T23:59:59Z \
115
+ --granularity day
116
+ ```
117
+
118
+ Returns challenge/request volume as JSON. Times must be ISO-8601 timestamps, and
119
+ `--from` cannot be later than `--to`. Granularity values such as `hour` and `day`
120
+ are passed to the API, which owns the supported set.
121
+
122
+ Challenge volume proves that traffic reached the platform; it does not prove
123
+ that the protected backend rejects requests without a valid token.
124
+
125
+ ### Get billing information
126
+
127
+ ```console
128
+ requestshield get billing app_123
129
+ ```
130
+
131
+ Returns the selected application's current plan, billing cycle, charges, quota,
132
+ and usage as JSON. The command is read-only and requires a saved sign-in session.
133
+
134
+ ### Install the Agent Skill
135
+
136
+ Let the CLI detect the installed coding agent:
137
+
138
+ ```console
19
139
  requestshield agent setup
140
+ ```
141
+
142
+ Or select an agent explicitly:
143
+
144
+ ```console
20
145
  requestshield agent setup --codex
21
146
  requestshield agent setup --claude
22
147
  ```
23
148
 
24
- `signin` starts a device authorization flow and prints the verification URL and
25
- code for the customer to open manually. The returned session is stored under the user's standard
26
- application-data directory with permissions restricted to that user. CI may
27
- instead provide `REQUESTSHIELD_ACCESS_TOKEN`; environment tokens are never
28
- persisted.
149
+ The CLI installs the Skill into:
150
+
151
+ - Codex: `~/.agents/skills/requestshield`
152
+ - Claude: `~/.claude/skills/requestshield`
153
+
154
+ If exactly one supported agent is detected, it is selected automatically. If
155
+ both are detected in an interactive terminal, the CLI asks which one to use.
156
+ If neither is detected, select one with `--codex` or `--claude`.
157
+
158
+ Existing Skill content is preserved. Add `--force` to replace it:
159
+
160
+ ```console
161
+ requestshield agent setup --codex --force
162
+ ```
163
+
164
+ Restart the selected agent or start a new task after installation.
165
+
166
+ ### Check for updates
167
+
168
+ ```console
169
+ requestshield update check
170
+ ```
171
+
172
+ Checks the npm registry for the latest `@intellifend/requestshield` version.
173
+ When a newer version exists, the CLI displays the current and latest versions
174
+ and asks:
175
+
176
+ ```text
177
+ Update to RequestShield <version>? (y/N):
178
+ ```
179
+
180
+ Entering `y` runs the equivalent of:
181
+
182
+ ```console
183
+ npm install --global @intellifend/requestshield@<latest-version>
184
+ ```
185
+
186
+ This command requires an interactive terminal, Node.js, and npm. It updates a
187
+ global npm installation; it does not replace a standalone Windows executable.
188
+
189
+ ## Install with npm
190
+
191
+ Install globally:
192
+
193
+ ```console
194
+ npm install --global @intellifend/requestshield
195
+ requestshield --help
196
+ requestshield --version
197
+ ```
198
+
199
+ Run a specific version without installing globally:
29
200
 
30
- `keys create` asks for confirmation, then atomically deactivates the previous
31
- application credentials and creates a new `appKey` + `apiSecret`. The public
32
- app key and the backend-only secret are printed after success. The secret is
33
- not stored by the CLI and cannot be retrieved again.
201
+ ```console
202
+ npx --yes --package=@intellifend/requestshield@0.1.5 requestshield --help
203
+ npx --yes --package=@intellifend/requestshield@0.1.5 requestshield signin
204
+ ```
205
+
206
+ To invoke the update command through npx, include the package name:
207
+
208
+ ```console
209
+ npx --yes --package=@intellifend/requestshield@0.1.5 requestshield update check
210
+ ```
211
+
212
+ Do not run `npx update check`; npm interprets `update` as the name of a different
213
+ npm package.
214
+
215
+ ## Local development
216
+
217
+ From the `requestshield-cli` directory:
218
+
219
+ ```console
220
+ npm install
221
+ npm run lint
222
+ npm run typecheck
223
+ npm test
224
+ npm run build
225
+ node src/main.mjs --help
226
+ node src/main.mjs --version
227
+ ```
228
+
229
+ The current `build` script validates the syntax of the `.mjs` source files. It
230
+ does not generate a standalone executable.
231
+
232
+ ### Test the update flow locally
233
+
234
+ The manual update test mocks the registry and install operation, so it does not
235
+ publish a package or change the installed version:
236
+
237
+ ```powershell
238
+ $env:REQUESTSHIELD_MOCK_LATEST_VERSION = "0.1.6"
239
+ npm run test:update-check:manual
240
+ Remove-Item Env:REQUESTSHIELD_MOCK_LATEST_VERSION
241
+ ```
242
+
243
+ After entering `y`, the test prints `[MOCK] Would install ...`.
244
+
245
+ ## Packaging the RequestShield Skill
246
+
247
+ The canonical Skill is stored at `../skills/requestshield`. Do not maintain a
248
+ second source copy inside this package.
249
+
250
+ Before npm creates the package, the `prepack` hook temporarily copies the
251
+ canonical Skill to `skills/requestshield`. The `postpack` hook removes that
252
+ generated copy after packaging.
253
+
254
+ Inspect the files that npm will publish:
255
+
256
+ ```console
257
+ npm pack --dry-run
258
+ ```
259
+
260
+ Create a local tarball:
261
+
262
+ ```console
263
+ npm pack
264
+ ```
265
+
266
+ ## Publish to npm
267
+
268
+ npm does not allow an existing package version to be overwritten. Check the
269
+ published version, merge the latest team changes, then choose a new version in
270
+ `package.json` before publishing.
271
+
272
+ ```console
273
+ npm view @intellifend/requestshield version
274
+ npm run lint
275
+ npm run typecheck
276
+ npm test
277
+ npm pack --dry-run
278
+ npm publish --access public
279
+ ```
34
280
 
35
- `agent setup` detects Codex or Claude and installs the bundled `requestshield`
36
- skill into `~/.agents/skills` for Codex or `~/.claude/skills` for Claude. Use
37
- `--codex` or `--claude` to select one directly. When both agents are detected,
38
- an interactive terminal asks which one to use; non-interactive callers must
39
- provide one of the flags. Existing content is preserved unless `--force` is
40
- supplied.
281
+ Verify the published package:
282
+
283
+ ```console
284
+ npm view @intellifend/requestshield version
285
+ npm dist-tag ls @intellifend/requestshield
286
+ npx --yes --package=@intellifend/requestshield@0.1.5 requestshield --version
287
+ ```
41
288
 
42
289
  ## Management API contract
43
290
 
@@ -51,8 +298,8 @@ supplied.
51
298
  }
52
299
  ```
53
300
 
54
- The response contains `deviceCode`, `userCode`, `verificationUri`,
55
- `expiresIn`, and `interval`.
301
+ The response contains `deviceCode`, `userCode`, `verificationUri`, `expiresIn`,
302
+ and `interval`.
56
303
 
57
304
  ### Poll sign-in
58
305
 
@@ -65,63 +312,133 @@ metadata.
65
312
  ### Rotate keys
66
313
 
67
314
  `POST /v1/cli/keys/rotate` with Bearer authentication and an empty JSON body.
68
- The response contains `appKey` and one-time `apiSecret`.
315
+ The response contains `appKey` and the one-time `apiSecret`.
69
316
 
70
- ## Development
317
+ ### List applications
71
318
 
72
- ```console
73
- pnpm --filter requestshield lint
74
- pnpm --filter requestshield typecheck
75
- pnpm --filter requestshield test
76
- pnpm --filter requestshield build
319
+ `GET /v1/applications` with Bearer authentication and no request body. The API must
320
+ scope results to the authenticated user.
321
+
322
+ ```json
323
+ {
324
+ "data": [
325
+ {
326
+ "appKey": "pk_AAAAAAAAAAAAAAAAAAAAAAAA",
327
+ "name": "Checkout",
328
+ "status": "pending",
329
+ "createdAt": "2026-09-11T00:00:00.000Z",
330
+ "updatedAt": "2026-09-11T00:00:00.000Z"
331
+ }
332
+ ],
333
+ "nextCursor": null
334
+ }
77
335
  ```
78
- ## Quick start
79
336
 
80
- RequestShield requires Node.js 22.13 or newer.
337
+ Every application requires `appKey`, `name`, `status`, `createdAt`, and `updatedAt`.
338
+ `nextCursor` must be a non-empty string or `null`. An account with no applications
339
+ returns `200` with an empty `data` array. A missing or rejected session returns
340
+ `401`. The response must never contain an API Secret.
81
341
 
82
- Run without installing globally:
342
+ ### Get an application
83
343
 
84
- ```console
85
- npx requestshield --help
86
- npx requestshield signin
87
- npx requestshield keys create
88
- npx requestshield agent setup
89
- npx requestshield agent setup --codex
90
- npx requestshield agent setup --claude
344
+ `GET /v1/applications/{appKey}` with Bearer authentication and no request body.
345
+ The path segment is URL-encoded by the CLI.
346
+
347
+ ```json
348
+ {
349
+ "ok": true,
350
+ "data": {
351
+ "app_key": "app_123",
352
+ "name": "Website",
353
+ "status": "active"
354
+ }
355
+ }
91
356
  ```
92
357
 
93
- Pin a specific version for reproducible usage:
358
+ `status` must be `ready`, `active`, or `deactivated`. The response App Key must
359
+ match the requested key. The CLI rebuilds the output from these three fields so
360
+ unexpected response fields cannot be printed.
94
361
 
95
- ```console
96
- npx requestshield@0.1.0 --help
362
+ ### Get challenge volume
363
+
364
+ `GET /v1/applications/{appKey}/challenge-volume` with Bearer authentication and
365
+ optional `from`, `to`, and `granularity` query parameters.
366
+
367
+ ```json
368
+ {
369
+ "ok": true,
370
+ "data": {
371
+ "app_key": "app_123",
372
+ "from": "2026-08-01T00:00:00Z",
373
+ "to": "2026-08-27T23:59:59Z",
374
+ "granularity": "day",
375
+ "challenge_count": 125430
376
+ }
377
+ }
97
378
  ```
98
379
 
99
- ## Packaging the bundled skill
380
+ The CLI requires a matching App Key, valid ordered timestamps, a non-empty
381
+ granularity, and a non-negative safe-integer `challenge_count`. It rebuilds the
382
+ output from these fields so unexpected API fields cannot be printed.
100
383
 
101
- The canonical RequestShield skill lives at `../skills/requestshield`, outside
102
- this npm package directory. The `prepack` hook temporarily copies the complete
103
- skill into `skills/requestshield`, and `postpack` removes that generated copy
104
- after npm creates the tarball. Do not edit the generated copy.
384
+ ### Get billing information
105
385
 
106
- Build and inspect a local package from this directory:
386
+ `GET /v1/applications/{appKey}/billing` with Bearer authentication and no
387
+ request body.
107
388
 
108
- ```console
109
- npm test
110
- npm run typecheck
111
- npm run lint
112
- npm pack --dry-run
389
+ ```json
390
+ {
391
+ "ok": true,
392
+ "data": {
393
+ "plan": {
394
+ "tier": "pro",
395
+ "current_cycle": [
396
+ "2026-07-07T07:07:07",
397
+ "2027-07-07T07:07:07"
398
+ ],
399
+ "billing": 1080,
400
+ "next_charge": "2027-07-07T07:07:07"
401
+ },
402
+ "usage": {
403
+ "monthly_quota": 100000,
404
+ "current_usage": 110000,
405
+ "overage_charge": 10
406
+ }
407
+ }
408
+ }
113
409
  ```
114
410
 
115
- To exercise the package entry point and bundled skill, create a tarball and run
116
- the command through that tarball:
411
+ The CLI validates all documented fields and rebuilds the nested result so
412
+ unexpected response fields cannot be printed.
117
413
 
118
- ```console
119
- npm pack
120
- npx --yes --package=./requestshield-0.1.0.tgz requestshield agent setup --codex
121
- ```
414
+ ### Get the integration contract
122
415
 
123
- To automatically accept npm's installation prompt:
416
+ `GET /v1/integration-contract` with Bearer authentication and no request body.
124
417
 
125
- ```console
126
- npx --yes requestshield@0.1.0 --help
418
+ ```json
419
+ {
420
+ "ok": true,
421
+ "data": {
422
+ "contract_version": "2026-08-27",
423
+ "browser": {
424
+ "script_url": "https://intellifend.ai/intellifend.js",
425
+ "token_header": "X-IntelliFend-Token",
426
+ "available_modes": ["manual"]
427
+ },
428
+ "backend": {
429
+ "supported_languages": ["java"],
430
+ "min_jdk": 17
431
+ },
432
+ "release_state": "released"
433
+ }
434
+ }
127
435
  ```
436
+
437
+ The CLI requires these core fields and preserves additional authoritative
438
+ contract fields returned by the API. Credential-shaped fields are rejected.
439
+
440
+ ## Security notes
441
+
442
+ - Never place an access token or Secret Key in URLs, command arguments, logs,
443
+ test fixtures, or committed files.
444
+ - The CLI does not persist the Secret Key returned by `keys create`.
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "requestshield",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Customer CLI for IntelliFend RequestShield.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "requestshield": "src/main.mjs"
8
8
  },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
9
12
  "files": [
10
13
  "src/",
11
14
  "skills/",
@@ -18,6 +21,7 @@
18
21
  "build": "node scripts/check-package.mjs",
19
22
  "lint": "node scripts/check-package.mjs",
20
23
  "test": "node --test",
24
+ "test:update-check:manual": "node scripts/manual-update-check.mjs",
21
25
  "typecheck": "tsc -p jsconfig.json --noEmit",
22
26
  "prepack": "node scripts/package-skill.mjs stage",
23
27
  "postpack": "node scripts/package-skill.mjs clean",
@@ -57,7 +57,7 @@ single CLI call or a short answer. Match on what the user wants, not on exact wo
57
57
  | **Uninstall** | "remove it", "roll it back" | `references/integration-planning.md` (Detect existing integration only) → **Uninstalling**. |
58
58
  | **Verify / troubleshoot** | "is it set up right?", "why is it not blocking?", a reason code | `references/integration-planning.md` (Detect existing integration only) → **Verifying**. For a reason code, also read `references/troubleshooting.md`. |
59
59
  | **Challenge volume** | "how much traffic", "how many challenges", "show me the volume" | Ask for the time range first. `challenge volume` takes `--from`/`--to`/`--granularity`; a guessed window produces a confident wrong answer. Then run it with the App Key. |
60
- | **Billing** | "am I being charged", "what plan", "what are my limits" | `requestshield get billing`. |
60
+ | **Billing** | "am I being charged", "what plan", "what are my limits" | Resolve the application, then run `requestshield get billing <app-key>`. |
61
61
  | **Service health** | "is the service up", "is it degraded" | `requestshield server`. |
62
62
  | **Agent setup** | "set this up for Codex too", "install the skill for Claude" | Run `requestshield agent setup` for auto-detection, or pass the requested agent: `--agent codex` or `--agent claude`. This installs agent guidance; it does not mint keys or edit customer application code. |
63
63
  | **Anything else** | | Look the command up in `references/cli.md`. If it is not there, run `requestshield --help` rather than guessing at a flag. |
@@ -120,10 +120,11 @@ requestshield apps get <app-key>
120
120
  ```
121
121
 
122
122
  **auth.** `apps list` returns the applications the current user can access, each with
123
- `app_key`, `name`, and `status`. Use it to resolve a human-supplied app name to a key
124
- rather than asking the user to retype one.
123
+ `appKey`, `name`, `status`, `createdAt`, and `updatedAt`; JSON output also includes
124
+ `nextCursor`. Use it to resolve a human-supplied app name to a key rather than asking
125
+ the user to retype one.
125
126
 
126
- `apps get <app-key>` returns `app_id`, `app_name`, and `status`. Status distinguishes a
127
+ `apps get <app-key>` returns `app_key`, `name`, and `status`. Status distinguishes a
127
128
  key that has never seen traffic (`ready`) from one in normal service (`active`) — which
128
129
  is exactly the difference between "the integration is not wired up yet" and "it is
129
130
  working", so check it before spending time debugging code.
@@ -161,11 +162,11 @@ mode's reference file.
161
162
  ## Billing
162
163
 
163
164
  ```bash
164
- requestshield get billing
165
+ requestshield get billing <app-key>
165
166
  ```
166
167
 
167
- **auth.** Current plan and usage for the application. Read-only; surface it when the
168
- user asks about cost or limits.
168
+ **auth.** Current plan and usage for the explicitly selected application. Read-only;
169
+ surface it when the user asks about cost or limits.
169
170
 
170
171
  ## Agent integration
171
172
 
@@ -12,9 +12,21 @@ import path from "node:path";
12
12
  * @returns {Promise<Array<"codex" | "claude">>}
13
13
  */
14
14
  export async function detectAgents(options = {}) {
15
- const env = options.env ?? process.env;
16
- const homeDir = options.homeDir ?? os.homedir();
17
- const codexHome = env.CODEX_HOME ?? path.join(homeDir, ".codex");
15
+
16
+ const env = options.env ??
17
+ process.env;
18
+
19
+ const homeDir = options.homeDir ??
20
+ os.homedir();
21
+
22
+ const codexHome = env.CODEX_HOME ??
23
+ path.join(homeDir, ".codex");
24
+
25
+ const codexConfigPath =
26
+ path.join(codexHome, "config.toml");
27
+
28
+ const claudeConfigPath =
29
+ path.join(homeDir, ".claude.json");
18
30
 
19
31
  // Executables prove the CLI is on PATH. Configuration files cover desktop
20
32
  // installs and agents that were previously configured but are not on PATH.
@@ -29,8 +41,14 @@ export async function detectAgents(options = {}) {
29
41
 
30
42
  /** @type {Array<"codex" | "claude">} */
31
43
  const detected = [];
32
- if (codexCommand || codexConfig) detected.push("codex");
33
- if (claudeCommand || claudeConfig || claudeState) detected.push("claude");
44
+
45
+ if (codexCommand || codexConfig) {
46
+ detected.push("codex")
47
+ };
48
+
49
+ if (claudeCommand || claudeConfig || claudeState) {
50
+ detected.push("claude")
51
+ };
34
52
  return detected;
35
53
  }
36
54
 
@@ -41,19 +59,29 @@ export async function detectAgents(options = {}) {
41
59
  * @param {NodeJS.ProcessEnv} env
42
60
  */
43
61
  async function commandExists(command, env) {
44
- const searchPath = env.PATH ?? env.Path ?? "";
45
- const extensions = process.platform === "win32"
46
- ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
62
+
63
+ const searchPath = env.PATH ??
64
+ env.Path ?? "";
65
+
66
+ const extensions =
67
+ process.platform === "win32"
68
+ ? (env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM")
69
+ .split(";")
70
+ .filter(Boolean)
47
71
  : [""];
48
72
 
49
73
  for (const directory of searchPath.split(path.delimiter)) {
50
- if (!directory) continue;
74
+ if (!directory) {
75
+ continue
76
+ };
77
+
51
78
  for (const extension of extensions) {
52
79
  try {
53
80
  await access(
54
81
  path.join(directory, `${command}${extension}`),
55
82
  process.platform === "win32" ? constants.F_OK : constants.X_OK,
56
83
  );
84
+
57
85
  return true;
58
86
  } catch {
59
87
  // Continue through the remaining executable candidates.