requestshield 0.1.4 → 0.1.6

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 (37) hide show
  1. package/README.md +421 -85
  2. package/config/.env.prod +7 -0
  3. package/package.json +21 -12
  4. package/skills/requestshield/SKILL.md +299 -307
  5. package/skills/requestshield/assets/AGENTS.codex.md +62 -62
  6. package/skills/requestshield/references/backend-java-core.md +128 -128
  7. package/skills/requestshield/references/backend-spring-boot.md +145 -145
  8. package/skills/requestshield/references/browser-manual.md +210 -210
  9. package/skills/requestshield/references/browser-seamless.md +156 -164
  10. package/skills/requestshield/references/cli.md +107 -182
  11. package/skills/requestshield/references/integration-planning.md +362 -389
  12. package/skills/requestshield/references/troubleshooting.md +114 -118
  13. package/src/agent-detector.mjs +102 -74
  14. package/src/api-client.mjs +115 -79
  15. package/src/args.mjs +140 -80
  16. package/src/browser-opener.mjs +32 -0
  17. package/src/cli.mjs +277 -51
  18. package/src/commands/agent-setup.mjs +182 -185
  19. package/src/commands/application-mutations.mjs +33 -0
  20. package/src/commands/application-response.mjs +55 -0
  21. package/src/commands/apps-get.mjs +20 -0
  22. package/src/commands/apps-list.mjs +94 -0
  23. package/src/commands/auth-status.mjs +37 -0
  24. package/src/commands/keys-create.mjs +7 -38
  25. package/src/commands/mutation-support.mjs +110 -0
  26. package/src/commands/secret-commands.mjs +45 -0
  27. package/src/commands/signin.mjs +70 -57
  28. package/src/commands/signout.mjs +9 -0
  29. package/src/commands/update-check.mjs +12 -4
  30. package/src/config.mjs +150 -0
  31. package/src/entrypoint.mjs +24 -0
  32. package/src/errors.mjs +3 -1
  33. package/src/main.mjs +5 -24
  34. package/src/oauth-client.mjs +153 -0
  35. package/src/oauth-loopback.mjs +120 -0
  36. package/src/session-files.mjs +213 -0
  37. package/src/session-store.mjs +177 -64
package/README.md CHANGED
@@ -1,10 +1,10 @@
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
- Current package version: `0.1.4`.
7
+ Current package version: `0.1.6`.
8
8
 
9
9
  ## Requirements
10
10
 
@@ -12,50 +12,211 @@ Current package version: `0.1.4`.
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 agent setup [--force]
24
- requestshield agent setup --codex [--force]
25
- requestshield agent setup --claude [--force]
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]
61
+ requestshield apps get <app-key>
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]
26
66
  requestshield update check
27
67
  requestshield --help
28
68
  requestshield --version
29
69
  ```
30
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
+
31
76
  ### Sign in
32
77
 
33
78
  ```console
34
79
  requestshield signin
35
80
  ```
36
81
 
37
- Starts the device authorization flow. The CLI prints the verification URL and
38
- user code without opening the browser automatically. After authorization, the
39
- session is stored for the current operating-system user.
40
-
41
- For CI or automation, provide `REQUESTSHIELD_ACCESS_TOKEN`. A token supplied by
42
- the environment is used directly and is not persisted by the CLI.
43
-
44
- ### 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
45
144
 
46
145
  ```console
47
- requestshield keys create
146
+ requestshield auth status --json
147
+ requestshield signout
48
148
  ```
49
149
 
50
- Asks the customer to type `ROTATE`, then calls the management API to deactivate
51
- the previous credentials and create a new `appKey` and `apiSecret`.
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.
156
+
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.
52
159
 
53
- - `appKey` is the public application identifier.
54
- - `apiSecret` is a backend-only secret displayed once.
55
- - The CLI does not save the returned `apiSecret`.
56
- - `--yes` skips the confirmation prompt for non-interactive automation.
160
+ ### Applications and credentials
161
+
162
+ ```console
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>
172
+ ```
57
173
 
58
- Store the Secret Key in a backend secret manager immediately after creation.
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.
59
220
 
60
221
  ### Install the Agent Skill
61
222
 
@@ -87,7 +248,10 @@ Existing Skill content is preserved. Add `--force` to replace it:
87
248
  requestshield agent setup --codex --force
88
249
  ```
89
250
 
90
- 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.
91
255
 
92
256
  ### Check for updates
93
257
 
@@ -95,11 +259,12 @@ Restart the selected agent or start a new task after installation.
95
259
  requestshield update check
96
260
  ```
97
261
 
98
- Checks the npm registry for the latest `requestshield` version. When a newer
99
- version exists, the CLI displays the current and latest versions and asks:
262
+ Checks the npm registry for the latest `requestshield` version.
263
+ When a newer version exists, the CLI displays the current and latest versions
264
+ and asks:
100
265
 
101
266
  ```text
102
- Update to RequestShield <version>? (y/N):
267
+ Install RequestShield <version> globally with npm? (y/N):
103
268
  ```
104
269
 
105
270
  Entering `y` runs the equivalent of:
@@ -109,7 +274,9 @@ npm install --global requestshield@<latest-version>
109
274
  ```
110
275
 
111
276
  This command requires an interactive terminal, Node.js, and npm. It updates a
112
- 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.
113
280
 
114
281
  ## Install with npm
115
282
 
@@ -121,17 +288,22 @@ requestshield --help
121
288
  requestshield --version
122
289
  ```
123
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
+
124
296
  Run a specific version without installing globally:
125
297
 
126
298
  ```console
127
- npx --yes requestshield@0.1.4 --help
128
- npx --yes requestshield@0.1.4 signin
299
+ npx --yes --package=requestshield@0.1.6 requestshield --help
300
+ npx --yes --package=requestshield@0.1.6 requestshield signin
129
301
  ```
130
302
 
131
303
  To invoke the update command through npx, include the package name:
132
304
 
133
305
  ```console
134
- npx --yes requestshield@0.1.4 update check
306
+ npx --yes --package=requestshield@0.1.6 requestshield update check
135
307
  ```
136
308
 
137
309
  Do not run `npx update check`; npm interprets `update` as the name of a different
@@ -139,66 +311,231 @@ npm package.
139
311
 
140
312
  ## Local development
141
313
 
142
- 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.
143
317
 
144
318
  ```console
145
- npm install
146
- npm run lint
147
- npm run typecheck
148
- npm test
149
- npm run build
319
+ corepack pnpm install
320
+ cd requestshield-cli
321
+ npm run check
150
322
  node src/main.mjs --help
151
323
  node src/main.mjs --version
324
+ npm run requestshield-qat -- --help
325
+ npm run requestshield-stg -- --help
326
+ ```
327
+
328
+ From `requestshield-cli/`, select the private runner explicitly:
329
+
330
+ ```console
331
+ npx --offline --prefix ./dev requestshield-qat --help
332
+ npx --offline --prefix ./dev requestshield-stg --help
333
+ ```
334
+
335
+ For plain `npx` commands, change to `requestshield-cli/dev/`:
336
+
337
+ ```console
338
+ cd dev
339
+ npx requestshield-qat --help
340
+ npx requestshield-stg --help
341
+ ```
342
+
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.
368
+
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.
372
+
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.
377
+
378
+ ### Source CLI + Stytch Test + Management QAT
379
+
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:
392
+
393
+ ```console
394
+ cd requestshield-cli
395
+ npx --offline --prefix ./dev requestshield-qat signin
396
+ npx --offline --prefix ./dev requestshield-qat apps list --json
152
397
  ```
153
398
 
154
- The current `build` script validates the syntax of the `.mjs` source files. It
155
- does not generate a standalone executable.
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.
156
450
 
157
451
  ### Test the update flow locally
158
452
 
159
453
  The manual update test mocks the registry and install operation, so it does not
160
454
  publish a package or change the installed version:
161
455
 
162
- ```powershell
163
- $env:REQUESTSHIELD_MOCK_LATEST_VERSION = "0.1.5"
164
- npm run test:update-check:manual
165
- Remove-Item Env:REQUESTSHIELD_MOCK_LATEST_VERSION
456
+ ```console
457
+ npm run test:update-check:manual -- 0.1.6
166
458
  ```
167
459
 
168
460
  After entering `y`, the test prints `[MOCK] Would install ...`.
169
461
 
170
- ## Packaging the RequestShield Skill
462
+ ## Build and inspect the package
463
+
464
+ From `requestshield-cli/`, create and inspect the runtime package:
465
+
466
+ ```console
467
+ npm run build
468
+ node build/src/main.mjs --help
469
+ ```
470
+
471
+ The generated, gitignored `build/` directory contains:
472
+
473
+ ```text
474
+ build/
475
+ src/
476
+ config/.env.prod
477
+ skills/requestshield/
478
+ README.md
479
+ package.json
480
+ ```
171
481
 
172
- The canonical Skill is stored at `../skills/requestshield`. Do not maintain a
173
- second source copy inside this package.
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.
174
487
 
175
- Before npm creates the package, the `prepack` hook temporarily copies the
176
- canonical Skill to `skills/requestshield`. The `postpack` hook removes that
177
- generated copy after packaging.
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.
178
491
 
179
- Inspect the files that npm will publish:
492
+ Optionally inspect the npm file list or create a local distribution archive:
180
493
 
181
494
  ```console
182
- npm pack --dry-run
495
+ npm pack ./build --dry-run
496
+ npm pack ./build --pack-destination ./build
183
497
  ```
184
498
 
185
- Create a local tarball:
499
+ The latter writes `build/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.
504
+
505
+ ### Skill source and direct packing
506
+
507
+ The canonical Skill is stored at `../skills/requestshield`. The build copies it
508
+ to `build/skills/requestshield`; maintain only the canonical source.
509
+
510
+ Direct packing from `requestshield-cli/` remains supported:
186
511
 
187
512
  ```console
513
+ npm pack --dry-run
188
514
  npm pack
189
515
  ```
190
516
 
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.
520
+
191
521
  ## Publish to npm
192
522
 
193
523
  npm does not allow an existing package version to be overwritten. Check the
194
524
  published version, merge the latest team changes, then choose a new version in
195
525
  `package.json` before publishing.
196
526
 
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.
530
+
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.
535
+
197
536
  ```console
198
537
  npm view requestshield version
199
- npm run lint
200
- npm run typecheck
201
- npm test
538
+ npm run check
202
539
  npm pack --dry-run
203
540
  npm publish --access public
204
541
  ```
@@ -208,39 +545,38 @@ Verify the published package:
208
545
  ```console
209
546
  npm view requestshield version
210
547
  npm dist-tag ls requestshield
211
- npx --yes requestshield@0.1.4 --version
548
+ npx --yes --package=requestshield@0.1.6 requestshield --version
212
549
  ```
213
550
 
214
551
  ## Management API contract
215
552
 
216
- ### Start sign-in
217
-
218
- `POST /v1/cli/signin`
219
-
220
- ```json
221
- {
222
- "client": "requestshield-cli"
223
- }
224
- ```
225
-
226
- The response contains `deviceCode`, `userCode`, `verificationUri`, `expiresIn`,
227
- and `interval`.
228
-
229
- ### Poll sign-in
230
-
231
- `POST /v1/cli/signin/token` with `{ "deviceCode": "..." }`.
232
-
233
- Pending responses use HTTP 400 with `code` set to `authorization_pending` or
234
- `slow_down`. A successful response contains `accessToken` and optional account
235
- metadata.
236
-
237
- ### Rotate keys
238
-
239
- `POST /v1/cli/keys/rotate` with Bearer authentication and an empty JSON body.
240
- The response contains `appKey` and the one-time `apiSecret`.
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.
241
575
 
242
576
  ## Security notes
243
577
 
244
578
  - Never place an access token or Secret Key in URLs, command arguments, logs,
245
579
  test fixtures, or committed files.
246
- - 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.
@@ -0,0 +1,7 @@
1
+ # Public production settings shipped in the npm package. Never add secrets.
2
+ API_URL=https://api.intellifend.ai
3
+ # Fill in the production Connected App values before releasing sign-in.
4
+ OAUTH_ISSUER=
5
+ # Optional callback iss pin; blank uses OAUTH_ISSUER.
6
+ OAUTH_AUTHORIZATION_ISSUER=
7
+ OAUTH_CLIENT_ID=