requestshield 0.1.5 → 0.1.7

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.
Files changed (39) hide show
  1. package/README.md +407 -269
  2. package/config/.env.prod +5 -0
  3. package/package.json +8 -5
  4. package/skills/requestshield/SKILL.md +55 -63
  5. package/skills/requestshield/assets/AGENTS.codex.md +17 -17
  6. package/skills/requestshield/references/backend-java-core.md +3 -3
  7. package/skills/requestshield/references/backend-spring-boot.md +3 -3
  8. package/skills/requestshield/references/browser-manual.md +4 -4
  9. package/skills/requestshield/references/browser-seamless.md +7 -15
  10. package/skills/requestshield/references/cli.md +93 -169
  11. package/skills/requestshield/references/integration-planning.md +19 -46
  12. package/skills/requestshield/references/troubleshooting.md +26 -30
  13. package/src/api-client.mjs +106 -165
  14. package/src/args.mjs +108 -151
  15. package/src/browser-opener.mjs +32 -0
  16. package/src/cli.mjs +50 -28
  17. package/src/commands/agent-setup.mjs +34 -37
  18. package/src/commands/application-mutations.mjs +33 -0
  19. package/src/commands/application-response.mjs +55 -0
  20. package/src/commands/apps-get.mjs +3 -47
  21. package/src/commands/apps-list.mjs +40 -36
  22. package/src/commands/auth-status.mjs +37 -0
  23. package/src/commands/keys-create.mjs +7 -38
  24. package/src/commands/mutation-support.mjs +110 -0
  25. package/src/commands/secret-commands.mjs +45 -0
  26. package/src/commands/signin.mjs +70 -57
  27. package/src/commands/signout.mjs +9 -0
  28. package/src/commands/update-check.mjs +12 -4
  29. package/src/config.mjs +145 -3
  30. package/src/entrypoint.mjs +24 -0
  31. package/src/errors.mjs +3 -1
  32. package/src/main.mjs +2 -21
  33. package/src/oauth-client.mjs +153 -0
  34. package/src/oauth-loopback.mjs +120 -0
  35. package/src/session-files.mjs +213 -0
  36. package/src/session-store.mjs +177 -64
  37. package/src/commands/billing-get.mjs +0 -110
  38. package/src/commands/challenge-volume.mjs +0 -81
  39. package/src/commands/contract.mjs +0 -106
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # RequestShield CLI
2
2
 
3
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.
4
+ in, manage applications and their credentials, install the RequestShield Skill
5
+ for Codex or Claude, and update an npm-installed CLI.
6
6
 
7
7
  Current package version: `0.1.5`.
8
8
 
@@ -12,124 +12,211 @@ Current package version: `0.1.5`.
12
12
  - A RequestShield customer management API.
13
13
  - npm for local development and publishing.
14
14
 
15
- The management API defaults to `https://api.intellifend.ai`. Set
16
- `REQUESTSHIELD_API_URL` to use a different management API during development.
15
+ The public npm package installs only `requestshield`, using production
16
+ configuration. QAT and STG runners are available only in this repository:
17
+
18
+ | Command | Environment | Configuration file |
19
+ | --- | --- | --- |
20
+ | `requestshield` | PROD | `config/.env.prod`; API URL is `https://api.intellifend.ai`, while production OAuth values remain unconfigured. |
21
+ | `npx requestshield-qat <args>` from `requestshield-cli/dev/` | QAT | `config/.env.qat`; includes the public Stytch Test issuer/client ID and Management API QAT URL. |
22
+ | `npx requestshield-stg <args>` from `requestshield-cli/dev/` | STG | `config/.env.stg`; requires the actual STG API and OAuth values before use. |
23
+
24
+ The private `dev/package.json` resolves these `npx` commands to this checkout's
25
+ wrappers without a separate install, link or publish step. They are local source
26
+ runners, not packages fetched from the registry. From the parent
27
+ `requestshield-cli/` directory, use `npx --offline --prefix ./dev requestshield-qat <args>`
28
+ (or `requestshield-stg`). The matching `npm run requestshield-qat -- <args>` and
29
+ `npm run requestshield-stg -- <args>` scripts are alternatives. A bare QAT/STG
30
+ `npx` command from the parent directory does not select the private child package
31
+ and may search dependencies or the registry instead.
32
+
33
+ Each runner reads its package-relative file directly with Node.js `parseEnv`.
34
+ It does not load a `.env` from the current working directory or read OAuth/API
35
+ settings from `process.env`. Shell variables cannot switch the selected profile
36
+ or override its configuration. Missing STG/PROD values fail explicitly and never
37
+ fall back to QAT.
38
+
39
+ These files contain only public product settings: `OAUTH_ISSUER`,
40
+ `OAUTH_CLIENT_ID`, `API_URL` and optional `OAUTH_AUTHORIZATION_ISSUER`. They must never contain
41
+ access tokens, refresh tokens, client secrets or application API secrets. Release
42
+ maintainers must populate the production public settings before publishing a
43
+ working production sign-in flow. The npm package includes only `.env.prod`;
44
+ QAT/STG files and the private development package are excluded.
17
45
 
18
46
  ## Implemented commands
19
47
 
48
+ Examples use the production command. From `requestshield-cli/`, substitute
49
+ `npx --offline --prefix ./dev requestshield-qat` or `requestshield-stg` for local
50
+ QAT/STG runs. Plain `npx requestshield-qat` requires `requestshield-cli/dev/`.
51
+
20
52
  ```console
21
- requestshield signin
22
- requestshield keys create [--yes]
23
- requestshield contract
24
- requestshield apps list [--json]
53
+ requestshield signin [--no-open]
54
+ requestshield auth status [--json]
55
+ requestshield signout
56
+ requestshield keys create --app-name <name> [--idempotency-key <key>]
57
+ requestshield keys rotate <app-key> [--idempotency-key <key>] [--yes]
58
+ requestshield keys reveal <app-key> [--yes]
59
+ requestshield keys revoke <app-key> [--idempotency-key <key>] [--yes]
60
+ requestshield apps list [--json] [--limit <1-100>] [--cursor <cursor> | --all]
25
61
  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]
62
+ requestshield apps rename <app-key> --name <name> [--idempotency-key <key>]
63
+ requestshield apps enable <app-key> [--idempotency-key <key>]
64
+ requestshield apps disable <app-key> [--idempotency-key <key>] [--yes]
65
+ requestshield agent setup [--codex | --claude] [--force]
31
66
  requestshield update check
32
67
  requestshield --help
33
68
  requestshield --version
34
69
  ```
35
70
 
71
+ `contract`, `challenge volume` and `get billing` return `COMMAND_UNAVAILABLE`
72
+ before configuration, authentication or network access. Their Management API
73
+ endpoints are not implemented. `server` and `credentials status` are also absent
74
+ from the supported command surface.
75
+
36
76
  ### Sign in
37
77
 
38
78
  ```console
39
79
  requestshield signin
40
80
  ```
41
81
 
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
82
+ Opens the configured login portal in your browser using Authorization Code with
83
+ S256 PKCE. Sign in, verify your email if needed, then approve the displayed
84
+ permissions. The browser must run on the same machine as the CLI. Use
85
+ `requestshield signin --no-open` to open the printed authorization URL yourself.
86
+ If automatic browser opening fails, the CLI also prints that URL.
87
+
88
+ The CLI listens only on `127.0.0.1` at a dynamically allocated port and waits up
89
+ to ten minutes. The browser callback carries an authorization code, state and
90
+ optional issuer metadata, not access or refresh tokens. The CLI exchanges that
91
+ code and its PKCE verifier directly with the configured identity provider,
92
+ without a client secret. With `offline_access` granted, the exchange returns
93
+ access and refresh tokens; the refresh token stays in the protected local session
94
+ file. Sign-in succeeds only after saving that session. Closing the browser does
95
+ not cancel the command; press Ctrl+C to stop.
96
+
97
+ The selected runner reads only its environment's configuration file. QAT's public
98
+ settings are included in the repository. Production and STG sign-in still require
99
+ their actual public settings in the corresponding file.
100
+ Discovery must match `OAUTH_ISSUER` and use
101
+ an HTTPS token endpoint on that issuer's origin; an HTTP loopback authorization page is allowed for local
102
+ development. The requested permissions are `offline_access`,
103
+ `theair:applications:read`, `theair:applications:write`, `theair:secrets:read`, and
104
+ `theair:secrets:write`. The CLI does not request `full_access`.
105
+
106
+ If the provider includes `iss` in its authorization response, the CLI compares
107
+ it exactly with `OAUTH_AUTHORIZATION_ISSUER`, falling back to `OAUTH_ISSUER` when
108
+ the optional setting is absent or blank. This is an opaque identifier, not a URL
109
+ to fetch or normalize. Stytch's QAT response uses its project identifier, which
110
+ differs from the HTTPS discovery issuer; the QAT file pins that value explicitly.
111
+ This setting does not change discovery, token transport or the version-1 session
112
+ format and its API URL/discovery issuer/client ID binding.
113
+
114
+ Access and rotating refresh tokens are stored in an owner-only session file,
115
+ bound to the API URL, issuer and client ID. Commands refresh an access token
116
+ when it has less than 60 seconds remaining. Refresh is serialized between CLI
117
+ processes and credentials are replaced atomically. Older device-flow records
118
+ and records for another environment require a new sign-in. An uncertain refresh
119
+ result requires a new sign-in rather than retrying a potentially consumed token;
120
+ the previous credential file is retained if writing or replacing the new file
121
+ fails. If a filesystem durability check fails after the atomic replacement, the
122
+ CLI reports uncertain persistence: the new record may already be stored. It does
123
+ not roll back to a potentially consumed refresh token or repeat the exchange.
124
+
125
+ Each profile keeps a separate session. On Windows, the base directory is
126
+ `%LOCALAPPDATA%/IntelliFend/RequestShield`; on macOS/Linux it is
127
+ `$XDG_STATE_HOME/intellifend/requestshield` (default
128
+ `~/.local/state/intellifend/requestshield`). Production retains `session.json`,
129
+ while QAT uses `qat/session.json` and STG uses `stg/session.json` beneath that base.
130
+ Existing sessions are not moved or deleted. A session remains usable only when
131
+ its saved issuer, client ID and API URL exactly match the selected file; changing
132
+ those values requires a new sign-in. A command
133
+ that crashes while holding the refresh lock can leave `session.json.lock` beside
134
+ it. The CLI deliberately does not steal that lock from a possibly suspended
135
+ process. If it remains locked, confirm every RequestShield command has exited
136
+ before removing only the lock file, then sign in again if requested.
137
+
138
+ Authenticated commands use the private saved session. The CLI does not accept
139
+ `REQUESTSHIELD_ACCESS_TOKEN` or other credential overrides from the shell or
140
+ configuration files. A separate non-interactive authentication mechanism is not
141
+ implemented.
142
+
143
+ ### Local session status and sign-out
50
144
 
51
145
  ```console
52
- requestshield keys create
146
+ requestshield auth status --json
147
+ requestshield signout
53
148
  ```
54
149
 
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.
150
+ Status reads only the selected profile's saved session. It never refreshes,
151
+ contacts the provider or prints credentials. JSON includes `profile`, `apiUrl`,
152
+ `issuer`, `clientId`, `state` and `localOnly: true`; applicable records also include
153
+ `expiresAt` and `scopes`. States are `signed_out`, `valid`, `expired`,
154
+ `refresh_uncertain`, `config_mismatch`, `invalid` and `configuration_error`.
155
+ `valid` describes local expiry and binding checks, not current provider acceptance.
62
156
 
63
- Store the Secret Key in a backend secret manager immediately after creation.
157
+ Sign-out removes only the selected local session under its lock. It does not
158
+ revoke the provider grant, sign out the browser or remove another profile's session.
64
159
 
65
- ### Get the integration contract
160
+ ### Applications and credentials
66
161
 
67
162
  ```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
163
+ requestshield keys create --app-name "Checkout"
164
+ requestshield apps list --json --all
165
+ requestshield apps get <app-key>
166
+ requestshield apps rename <app-key> --name "Checkout API"
167
+ requestshield apps disable <app-key>
168
+ requestshield apps enable <app-key>
169
+ requestshield keys rotate <app-key>
170
+ requestshield keys reveal <app-key>
171
+ requestshield keys revoke <app-key>
129
172
  ```
130
173
 
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.
174
+ Create makes a new application and initial secret. Rotate replaces the current
175
+ secret while preserving the App Key. Reveal retrieves the current active secret;
176
+ revoke invalidates that secret. Disable changes the application's administrative
177
+ state while retaining its secret; enable requires an active secret.
178
+
179
+ Create, rename and enable run without a confirmation prompt. Disable, rotate,
180
+ revoke and reveal require confirmation; `--yes` explicitly skips that prompt.
181
+ App names are not unique; commands targeting an existing app require its exact
182
+ App Key. The App Key is public. Create, rotate and reveal print a backend-only
183
+ API secret when supplied by the service; store it directly in a backend secret
184
+ manager. The CLI never saves that secret.
185
+
186
+ Mutations except reveal accept `--idempotency-key`. The CLI generates a fresh
187
+ key for each invocation when omitted and prints it to stderr before dispatch,
188
+ leaving JSON stdout intact. Retain it for interruption or crash recovery. The CLI
189
+ never automatically retries a mutation.
190
+ For an uncertain result, repeat the **identical command and arguments with the
191
+ same key printed in the error**, within seven days of the original attempt.
192
+ A new key starts a new operation. After seven days, inspect the application before
193
+ acting. Create/rotate replay returns `apiSecret: null`; the CLI suggests explicit
194
+ `keys reveal` and never reveals or rotates automatically.
195
+
196
+ Enable, disable and revoke return HTTP `202` acceptance. Publication is
197
+ asynchronous; acceptance and an `enabled` status do not prove global propagation.
198
+
199
+ ### List and inspect applications
200
+
201
+ List defaults to one page. `--limit` accepts 1–100; `--cursor` continues a previous
202
+ page. `--all` traverses at most 100 pages and rejects repeated cursors or an
203
+ unfinished traversal at that bound. It cannot be combined with `--cursor`.
204
+
205
+ `apps list --json` returns `{ "data": [...], "nextCursor": null }` (or a cursor
206
+ for an incomplete single-page listing). `apps get` and rename return
207
+ `{ "data": { ... } }`. Application objects have exactly `appKey`, `name`,
208
+ `status`, `createdAt` and `updatedAt`; unexpected fields and secrets are not printed.
209
+
210
+ | Status | Meaning |
211
+ | --- | --- |
212
+ | `attention_required` | Publication is blocked or uncertain. |
213
+ | `pending` | The latest configuration is awaiting publication. |
214
+ | `disabled` | The saved administrative state disables the application. |
215
+ | `revoked` | The enabled application has a revoked secret. |
216
+ | `enabled` | The saved application and active secret are enabled. |
217
+
218
+ These aggregate states describe configuration, not traffic, backend enforcement
219
+ or exact secret state while pending, blocked or disabled.
133
220
 
134
221
  ### Install the Agent Skill
135
222
 
@@ -161,7 +248,10 @@ Existing Skill content is preserved. Add `--force` to replace it:
161
248
  requestshield agent setup --codex --force
162
249
  ```
163
250
 
164
- Restart the selected agent or start a new task after installation.
251
+ Restart the selected agent or start a new task after installation. Published
252
+ packages use their bundled skill. Repository runners resolve the canonical
253
+ `../skills/requestshield` using source-checkout markers; they do not depend on
254
+ the working directory or require a staged packaging copy.
165
255
 
166
256
  ### Check for updates
167
257
 
@@ -174,7 +264,7 @@ When a newer version exists, the CLI displays the current and latest versions
174
264
  and asks:
175
265
 
176
266
  ```text
177
- Update to RequestShield <version>? (y/N):
267
+ Install RequestShield <version> globally with npm? (y/N):
178
268
  ```
179
269
 
180
270
  Entering `y` runs the equivalent of:
@@ -184,7 +274,9 @@ npm install --global @intellifend/requestshield@<latest-version>
184
274
  ```
185
275
 
186
276
  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.
277
+ global npm installation. Repository checkouts and pinned `npx` invocations are
278
+ managed separately. QAT/STG `update check` prints local Git/source guidance and
279
+ returns without querying the registry, prompting or installing anything.
188
280
 
189
281
  ## Install with npm
190
282
 
@@ -196,6 +288,11 @@ requestshield --help
196
288
  requestshield --version
197
289
  ```
198
290
 
291
+ The public package exposes only the `requestshield` executable and production
292
+ configuration. Internal QAT/STG runners require this repository's private `dev/`
293
+ package and are not installed by the public package. Source changes do not alter
294
+ versions already published.
295
+
199
296
  Run a specific version without installing globally:
200
297
 
201
298
  ```console
@@ -214,231 +311,272 @@ npm package.
214
311
 
215
312
  ## Local development
216
313
 
217
- From the `requestshield-cli` directory:
314
+ Use Node.js >=22.21 for repository development. Install workspace dependencies
315
+ from the repository root; the CLI check includes contract tests that require the
316
+ sibling Management API package's dependencies.
218
317
 
219
318
  ```console
220
- npm install
221
- npm run lint
222
- npm run typecheck
223
- npm test
224
- npm run build
319
+ corepack pnpm install
320
+ cd requestshield-cli
321
+ npm run check
225
322
  node src/main.mjs --help
226
323
  node src/main.mjs --version
324
+ npm run requestshield-qat -- --help
325
+ npm run requestshield-stg -- --help
227
326
  ```
228
327
 
229
- The current `build` script validates the syntax of the `.mjs` source files. It
230
- does not generate a standalone executable.
328
+ From `requestshield-cli/`, select the private runner explicitly:
231
329
 
232
- ### Test the update flow locally
330
+ ```console
331
+ npx --offline --prefix ./dev requestshield-qat --help
332
+ npx --offline --prefix ./dev requestshield-stg --help
333
+ ```
233
334
 
234
- The manual update test mocks the registry and install operation, so it does not
235
- publish a package or change the installed version:
335
+ For plain `npx` commands, change to `requestshield-cli/dev/`:
236
336
 
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
337
+ ```console
338
+ cd dev
339
+ npx requestshield-qat --help
340
+ npx requestshield-stg --help
241
341
  ```
242
342
 
243
- After entering `y`, the test prints `[MOCK] Would install ...`.
343
+ `dev/package.json` is private and exposes only local QAT/STG wrappers. No
344
+ additional installation or linking is needed. Keep this working directory for
345
+ the plain `npx` commands; they resolve the current checkout rather than a
346
+ published QAT/STG artifact. To keep package resolution offline, use:
347
+
348
+ ```console
349
+ npx --offline requestshield-qat --help
350
+ ```
351
+
352
+ Both invocation forms run `dev/main-qat.mjs` or `dev/main-stg.mjs` and read the
353
+ parent package's fixed `config/.env.qat` or `config/.env.stg` file
354
+ (`../config/.env.qat` or `../config/.env.stg` from `dev/`). To change an internal
355
+ endpoint or public client, edit that file; do not set shell overrides. All three
356
+ profile files use the same public keys:
357
+
358
+ ```dotenv
359
+ OAUTH_ISSUER=https://identity.example.com
360
+ # Optional exact callback iss; blank uses OAUTH_ISSUER.
361
+ OAUTH_AUTHORIZATION_ISSUER=
362
+ OAUTH_CLIENT_ID=public-client-id
363
+ API_URL=https://management.example.com
364
+ ```
365
+
366
+ Do not put credentials in these files. Session storage remains private and
367
+ separate from package configuration.
244
368
 
245
- ## Packaging the RequestShield Skill
369
+ `npm run test:contract` exercises the real Management router in
370
+ `management-api/test/core/cli-contract.spec.mjs`; `npm run check` includes it.
371
+ These isolated tests use synthetic credentials and do not prove live acceptance.
246
372
 
247
- The canonical Skill is stored at `../skills/requestshield`. Do not maintain a
248
- second source copy inside this package.
373
+ `npm run build` validates source syntax and creates the standalone runtime
374
+ package in `build/`. The package gate runs this build after its other checks.
375
+ See [Build and inspect the package](#build-and-inspect-the-package) for the output
376
+ layout and tarball commands.
249
377
 
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.
378
+ ### Source CLI + Stytch Test + Management QAT
253
379
 
254
- Inspect the files that npm will publish:
380
+ Use the current source build for this workflow; the source package version does
381
+ not establish that these changes have been published to npm. `config/.env.qat`
382
+ contains the public configuration:
383
+
384
+ | Setting | QAT file value |
385
+ | --- | --- |
386
+ | Discovery issuer (`OAUTH_ISSUER`) | `https://incongruous-cherry-0283.customers.stytch.dev` |
387
+ | Authorization response issuer (`OAUTH_AUTHORIZATION_ISSUER`) | `stytch.com/project-test-968146e2-6f34-4936-9d1c-a7e966813c70` |
388
+ | Public client ID | `connected-app-test-5ff62ca4-c0eb-48c3-84e5-06ae8f669998` |
389
+ | API URL | `https://management-api-qat.intellifend.ai` |
390
+
391
+ From the repository root:
255
392
 
256
393
  ```console
257
- npm pack --dry-run
394
+ cd requestshield-cli
395
+ npx --offline --prefix ./dev requestshield-qat signin
396
+ npx --offline --prefix ./dev requestshield-qat apps list --json
258
397
  ```
259
398
 
260
- Create a local tarball:
399
+ The prefix selects the local private package; do not omit it from this directory.
400
+ Alternatively, from `requestshield-cli/`, run
401
+ `npm run requestshield-qat -- signin` and
402
+ `npm run requestshield-qat -- apps list --json`.
403
+
404
+ The private developer package and its runners are excluded from the public npm
405
+ package. Old `REQUESTSHIELD_OAUTH_*`, `REQUESTSHIELD_API_URL` and
406
+ `REQUESTSHIELD_ACCESS_TOKEN` shell variables have no effect on profile selection,
407
+ configuration or credentials.
408
+
409
+ The current QAT provider opens the deployed portal at
410
+ `https://requestshield-qat.intellifend.ai/oauth/authorize`; this CLI smoke test
411
+ does not require a local portal server.
412
+ If verification or password reset opens another tab, complete it and return to
413
+ the original authorization tab to continue. Email tokens are not shared between
414
+ tabs by the portal.
415
+
416
+ Provider settings to verify for QAT acceptance:
417
+
418
+ - Authorization URL `https://requestshield-qat.intellifend.ai/oauth/authorize`.
419
+ - A First Party Public Connected App with redirect `http://127.0.0.1/callback`
420
+ registered without a port, enabling the CLI's dynamic loopback port.
421
+ - The four custom scopes above plus `offline_access`, with user role permissions
422
+ and explicit consent; keep full-access and offline-consent bypass disabled.
423
+ - Access-token lifetime at most 900 seconds, audience
424
+ `urn:theair:management-api:dev`, and access-only template markers
425
+ `theair_token_use=management_access` and `theair_client_id` matching the client ID.
426
+ - SDK allowed origin and email callbacks matching
427
+ `https://requestshield-qat.intellifend.ai`.
428
+
429
+ Public discovery rechecked on 2026-09-16 advertises
430
+ `https://requestshield-qat.intellifend.ai/oauth/authorize`. A read-only QAT run on
431
+ the same date successfully listed all applications (an empty list). Local auth
432
+ status was expired before the command and valid with a later expiry afterward,
433
+ confirming automatic refresh and API acceptance for that session. This does not
434
+ audit every provider setting or verify live mutations, publication or production.
435
+ Source changes do not configure the provider or deploy the portal.
436
+ See the [Stytch CLI guide](https://stytch.com/docs/connected-apps/guides/cli-agents)
437
+ and [refresh contract](https://stytch.com/docs/api-reference/consumer/api/connected-apps/exchange-refresh-token).
438
+
439
+ Acceptance requires a real browser sign-in, then `apps list --json` accepted by
440
+ QAT. Wait until the access token expires and repeat the command to verify refresh.
441
+ An account with no apps should return an empty list; do not create an app just
442
+ for this test. Local mock checks do not establish provider or deployed API acceptance.
443
+
444
+ To test local portal changes separately, start the
445
+ [portal](../theair-customer-portal-frontend/README.md) on `http://localhost:5173`
446
+ and coordinate with the provider owner to point the authorization URL to
447
+ `http://localhost:5173/oauth/authorize`, with matching SDK allowed origin and email
448
+ callbacks. That optional provider configuration change is not a prerequisite for
449
+ the default QAT CLI smoke test above.
450
+
451
+ ### Test the update flow locally
452
+
453
+ The manual update test mocks the registry and install operation, so it does not
454
+ publish a package or change the installed version:
261
455
 
262
456
  ```console
263
- npm pack
457
+ npm run test:update-check:manual -- 0.1.6
264
458
  ```
265
459
 
266
- ## Publish to npm
460
+ After entering `y`, the test prints `[MOCK] Would install ...`.
267
461
 
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.
462
+ ## Build and inspect the package
463
+
464
+ From `requestshield-cli/`, create and inspect the runtime package:
271
465
 
272
466
  ```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
467
+ npm run build
468
+ node build/src/main.mjs --help
279
469
  ```
280
470
 
281
- Verify the published package:
471
+ The generated, gitignored `build/` directory contains:
282
472
 
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
473
+ ```text
474
+ build/
475
+ src/
476
+ config/.env.prod
477
+ skills/requestshield/
478
+ README.md
479
+ package.json
287
480
  ```
288
481
 
289
- ## Management API contract
482
+ The CLI remains Node.js ESM; the build copies the runtime files without
483
+ compilation or bundling. The generated `package.json` preserves package metadata,
484
+ the production executable and the file allowlist, while omitting development
485
+ dependencies and source lifecycle scripts. The directory can therefore run and
486
+ be packed independently of the repository's source tooling.
290
487
 
291
- ### Start sign-in
488
+ The build creates a directory only, not a `.tgz` archive. Rebuild after changing
489
+ source, configuration, package metadata or Skill content; `build/` is generated
490
+ output and should not be edited directly.
292
491
 
293
- `POST /v1/cli/signin`
492
+ Optionally inspect the npm file list or create a local distribution archive:
294
493
 
295
- ```json
296
- {
297
- "client": "requestshield-cli"
298
- }
494
+ ```console
495
+ npm pack ./build --dry-run
496
+ npm pack ./build --pack-destination ./build
299
497
  ```
300
498
 
301
- The response contains `deviceCode`, `userCode`, `verificationUri`, `expiresIn`,
302
- and `interval`.
499
+ The latter writes `build/intellifend-requestshield-<version>.tgz`. Archives are
500
+ generated artifacts, ignored by Git, and are not required in source control or
501
+ before publishing. The package listing should contain `src/main.mjs`,
502
+ `config/.env.prod` and the complete Skill, with no QAT/STG configuration or
503
+ private `dev/` package.
303
504
 
304
- ### Poll sign-in
505
+ ### Skill source and direct packing
305
506
 
306
- `POST /v1/cli/signin/token` with `{ "deviceCode": "..." }`.
507
+ The canonical Skill is stored at `../skills/requestshield`. The build copies it
508
+ to `build/skills/requestshield`; maintain only the canonical source.
307
509
 
308
- Pending responses use HTTP 400 with `code` set to `authorization_pending` or
309
- `slow_down`. A successful response contains `accessToken` and optional account
310
- metadata.
510
+ Direct packing from `requestshield-cli/` remains supported:
311
511
 
312
- ### Rotate keys
512
+ ```console
513
+ npm pack --dry-run
514
+ npm pack
515
+ ```
313
516
 
314
- `POST /v1/cli/keys/rotate` with Bearer authentication and an empty JSON body.
315
- The response contains `appKey` and the one-time `apiSecret`.
517
+ For this source-package path, `prepack` temporarily copies the canonical Skill to
518
+ `skills/requestshield`, and `postpack` removes that generated copy. The build
519
+ directory already includes its Skill and needs no staging hook.
316
520
 
317
- ### List applications
521
+ ## Publish to npm
318
522
 
319
- `GET /v1/applications` with Bearer authentication and no request body. The API must
320
- scope results to the authenticated user.
523
+ npm does not allow an existing package version to be overwritten. Check the
524
+ published version, merge the latest team changes, then choose a new version in
525
+ `package.json` before publishing.
321
526
 
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
- }
335
- ```
527
+ Run the release commands from the source `requestshield-cli/` directory. Its
528
+ `prepublishOnly` hook runs the complete package check before publication;
529
+ `build/` is the inspection and packaging output.
336
530
 
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.
341
-
342
- ### Get an application
343
-
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
- }
356
- ```
531
+ `npm publish` packages and uploads the module itself; a separate `npm pack`
532
+ command is optional. CI can install workspace dependencies from the repository
533
+ root and publish from this source package. Store a `.tgz` as a CI artifact only
534
+ when the release workflow needs a separately retained archive.
357
535
 
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.
361
-
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
- }
536
+ ```console
537
+ npm view @intellifend/requestshield version
538
+ npm run check
539
+ npm pack --dry-run
540
+ npm publish --access public
378
541
  ```
379
542
 
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.
383
-
384
- ### Get billing information
385
-
386
- `GET /v1/applications/{appKey}/billing` with Bearer authentication and no
387
- request body.
388
-
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
- }
409
- ```
543
+ Verify the published package:
410
544
 
411
- The CLI validates all documented fields and rebuilds the nested result so
412
- unexpected response fields cannot be printed.
413
-
414
- ### Get the integration contract
415
-
416
- `GET /v1/integration-contract` with Bearer authentication and no request body.
417
-
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
- }
545
+ ```console
546
+ npm view @intellifend/requestshield version
547
+ npm dist-tag ls @intellifend/requestshield
548
+ npx --yes --package=@intellifend/requestshield@0.1.5 requestshield --version
435
549
  ```
436
550
 
437
- The CLI requires these core fields and preserves additional authoritative
438
- contract fields returned by the API. Credential-shaped fields are rejected.
551
+ ## Management API contract
552
+
553
+ The authoritative HTTP definitions are [OpenAPI](../management-api/openapi.json)
554
+ and the [CLI mapping](../management-api/CLI-CONTRACT.md). All nine app/secret
555
+ operations are covered by the CLI-to-router contract gate.
556
+
557
+ | CLI command | Method and path |
558
+ | --- | --- |
559
+ | `keys create --app-name <name>` | `POST /v1/applications` with `{name}` |
560
+ | `apps list` | `GET /v1/applications` with optional `limit` and `cursor` |
561
+ | `apps get <app-key>` | `GET /v1/applications/{appKey}` |
562
+ | `apps rename <app-key> --name <name>` | `PATCH /v1/applications/{appKey}` with `{name}` |
563
+ | `apps enable` / `apps disable` | Bodyless `POST /v1/applications/{appKey}/enable` or `/disable` |
564
+ | `keys rotate` / `keys revoke` / `keys reveal` | Bodyless `POST /v1/applications/{appKey}/secret/rotate`, `/revoke` or `/reveal` |
565
+
566
+ Create and rotate return `{appKey,status,apiSecret}`; a matching replay has a
567
+ null secret. Reveal returns `{apiSecret}` and is not replayed. Application detail
568
+ and rename return `{data:Application}`; list returns `{data:Application[],nextCursor}`.
569
+ Lifecycle acknowledgements are `{status:"accepted"}`. Mutations other than reveal
570
+ send a caller-scoped `Idempotency-Key`.
571
+
572
+ Only OAuth access tokens authenticate these requests. Sign-in and refresh call
573
+ the identity provider directly; browser sessions, refresh tokens and application
574
+ API secrets cannot authenticate Management. No `/v1/cli/*` route is used.
439
575
 
440
576
  ## Security notes
441
577
 
442
578
  - Never place an access token or Secret Key in URLs, command arguments, logs,
443
579
  test fixtures, or committed files.
444
- - The CLI does not persist the Secret Key returned by `keys create`.
580
+ - The three environment files contain public configuration only. OAuth
581
+ credentials belong exclusively in the protected saved session.
582
+ - The CLI does not persist API secrets returned by create, rotate or reveal.