spaps 0.7.6 → 0.7.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_TOOLS.json +5566 -38
- package/README.md +67 -13
- package/assets/local-runtime/Dockerfile +13 -0
- package/assets/local-runtime/docker-compose.yml +2 -1
- package/assets/local-runtime/manifest.json +3 -1
- package/bin/spaps.js +34 -8
- package/package.json +3 -4
- package/src/ai-helper.js +44 -10
- package/src/ai-tool-spec.js +19 -4
- package/src/auth/env.js +5 -0
- package/src/cli-dispatcher.js +365 -91
- package/src/docs-quick.js +37 -0
- package/src/docs-system.js +1 -31
- package/src/doctor.js +58 -1
- package/src/domain-cli.js +79 -0
- package/src/domains.js +193 -0
- package/src/fixture-kernel.js +898 -29
- package/src/handlers.js +535 -29
- package/src/help-quick.js +42 -0
- package/src/help-system.js +1 -36
- package/src/home-view.js +200 -0
- package/src/local-runtime.js +19 -4
- package/src/local-server.js +30 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Test SPAPS auth in a new app before you build auth backend infrastructure.
|
|
|
4
4
|
|
|
5
5
|
`spaps` is the shortest path from "I have a new frontend" to "this app can hit authenticated SPAPS routes locally." It wraps the local SPAPS server, tells you what auth mode the server is actually in, scaffolds starter code, and provisions a real local application when the server requires one.
|
|
6
6
|
|
|
7
|
+
Run `spaps` with no arguments to get the current operator session, app context, runtime mode, and the next recommended command.
|
|
8
|
+
|
|
7
9
|
## TL;DR
|
|
8
10
|
|
|
9
11
|
### The problem
|
|
@@ -28,6 +30,7 @@ Run SPAPS locally, inspect the current auth mode, then either use local mode dir
|
|
|
28
30
|
## No Backend Yet? Start Here
|
|
29
31
|
|
|
30
32
|
```bash
|
|
33
|
+
npx spaps
|
|
31
34
|
npx spaps local
|
|
32
35
|
npx spaps quickstart --json | jq '.auth'
|
|
33
36
|
SELF_SERVICE_PASSWORD=your-password npx spaps create demo-app --template react
|
|
@@ -74,9 +77,11 @@ Restore order is always: restore base dump first, then let the API container run
|
|
|
74
77
|
Run it without installing:
|
|
75
78
|
|
|
76
79
|
```bash
|
|
77
|
-
npx spaps
|
|
80
|
+
npx --yes spaps
|
|
78
81
|
```
|
|
79
82
|
|
|
83
|
+
For CI, SSH automation, or the first run on a fresh box, prefer `npx --yes spaps ...` so npm does not block on its install confirmation prompt.
|
|
84
|
+
|
|
80
85
|
Install globally:
|
|
81
86
|
|
|
82
87
|
```bash
|
|
@@ -93,49 +98,94 @@ npm install spaps
|
|
|
93
98
|
|
|
94
99
|
| Step | Command | Why |
|
|
95
100
|
| --- | --- |
|
|
96
|
-
| 1 | `npx spaps
|
|
97
|
-
|
|
|
98
|
-
|
|
|
99
|
-
| 3 | `npx spaps
|
|
100
|
-
| 4 | `npx spaps
|
|
101
|
-
| 5 | `npx spaps
|
|
102
|
-
| 6 | `npx spaps
|
|
101
|
+
| 1 | `npx spaps` | Inspect operator session, detected app context, runtime mode, and the next command |
|
|
102
|
+
| 2 | `npx spaps local` | Start the local SPAPS stack when the runtime is not up yet |
|
|
103
|
+
| 2b | `npx spaps local --data-source prod-cache` | Start the local stack with a cached prod-backed base DB |
|
|
104
|
+
| 3 | `npx spaps connect` | Authenticate the CLI with the active SPAPS server |
|
|
105
|
+
| 4 | `npx spaps verify --json` | Confirm the runtime and auth path end to end |
|
|
106
|
+
| 5 | `npx spaps create my-app --template react` | Scaffold a starter and, when possible, provision a real local app |
|
|
107
|
+
| 6 | `npx spaps tools --json` | Export the current AI tool contract for agents or tests |
|
|
108
|
+
| 7 | `npx spaps fixtures apply` | Materialize repo-local personas into Playwright/browser artifacts |
|
|
103
109
|
|
|
104
110
|
## CLI Surface
|
|
105
111
|
|
|
106
112
|
| Command | Purpose | Common flags |
|
|
107
113
|
| --- | --- | --- |
|
|
114
|
+
| `spaps` / `spaps home` | Show operator session, app context, runtime mode, and the next recommended action | `--port`, `--server-url`, `--json` |
|
|
108
115
|
| `spaps local [stop]` | Start or stop the local server workflow | `--port`, `--runtime-dir`, `--runtime-source`, `--data-source`, `--detach`, `--fresh`, `--from-backup`, `--open`, `--json` |
|
|
109
116
|
| `spaps status` | Check whether the local server is running | `--port`, `--json` |
|
|
117
|
+
| `spaps verify` | Run a compact runtime and auth verification pass | `--port`, `--server-url`, `--json` |
|
|
110
118
|
| `spaps quickstart` | Print quick-start instructions | `--port`, `--json` |
|
|
111
119
|
| `spaps init` | Create a starter `.env.local` | `--json` |
|
|
112
120
|
| `spaps create <name>` | Scaffold a SPAPS starter project directory and try local provisioning | `--template`, `--dir`, `--port`, `--force`, `--json` |
|
|
113
|
-
| `spaps fixtures <subcommand>` | Manage repo-local `.spaps` auth fixtures | `--dir`, `--port`, `--base-url`, `--persona`, `--format`, `--force`, `--json` |
|
|
121
|
+
| `spaps fixtures <subcommand>` | Manage repo-local `.spaps` auth fixtures | `--dir`, `--port`, `--base-url`, `--persona`, `--seed`, `--format`, `--force`, `--json` |
|
|
114
122
|
| `spaps docs` | Browse or search bundled docs | `--interactive`, `--search`, `--json` |
|
|
115
|
-
| `spaps tools` | Emit the AI tool spec | `--port`, `--format`, `--json` |
|
|
116
|
-
| `spaps doctor` | Diagnose local environment problems | `--port`, `--stripe`, `--json` |
|
|
123
|
+
| `spaps tools` | Emit the AI tool spec (covers auth, stripe, dayrate, email, webhooks, policies, issue-reporting) | `--port`, `--format`, `--json` |
|
|
124
|
+
| `spaps doctor` | Diagnose local environment problems and probe that every domain router is mounted | `--port`, `--stripe`, `--json` |
|
|
125
|
+
| `spaps dayrate config` | Fetch the active dayrate admin config (requires admin JWT) | `--server-url`, `--port`, `--json` |
|
|
126
|
+
| `spaps email <verb>` | Thin email control-plane commands over the existing SPAPS email API | `--server-url`, `--port`, `--json`; run `spaps email --help` for verb-specific flags |
|
|
127
|
+
| `spaps policy list\|create\|delete` | List, create, or delete authorization policies (admin) | `--name`, `--effect`, `--conditions`, `--id`, `--is-active`, `--limit`, `--json` |
|
|
128
|
+
| `spaps webhook list\|register` | List registered webhooks or register a new outbound webhook | `--url`, `--events`, `--json` |
|
|
129
|
+
| `spaps issue-reports list-mine` | List issue reports created by the authenticated caller | `--status`, `--limit`, `--offset`, `--json` |
|
|
130
|
+
|
|
131
|
+
### Email Commands
|
|
132
|
+
|
|
133
|
+
Use nested help for the exact flag surface:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
spaps email --help
|
|
137
|
+
spaps email send --help
|
|
138
|
+
spaps email preview --help
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
| Verb | Auth | Purpose |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| `send` | API key | Send a transactional email by template key |
|
|
144
|
+
| `get-template` | API key + JWT | Fetch one template definition |
|
|
145
|
+
| `preview` | API key + JWT | Render a preview with sample or custom context |
|
|
146
|
+
| `logs` | API key + JWT | List email logs for the caller's scope |
|
|
147
|
+
| `list-templates` | API key + JWT + admin | List all templates for the application |
|
|
148
|
+
| `create-template` | API key + JWT + admin | Create a template |
|
|
149
|
+
| `update-template` | API key + JWT + admin | Update a template |
|
|
150
|
+
| `get-override` | API key + JWT | Read the current template override |
|
|
151
|
+
| `set-override` | API key + JWT + admin | Create or update an override |
|
|
152
|
+
| `clear-override` | API key + JWT + admin | Delete an override |
|
|
117
153
|
|
|
118
154
|
Example command usage:
|
|
119
155
|
|
|
120
156
|
```bash
|
|
157
|
+
spaps --json
|
|
121
158
|
spaps local --port 3400 --detach
|
|
122
159
|
spaps local --runtime-source bundle --runtime-dir ./.skillbox/spaps-local
|
|
123
160
|
spaps local --runtime-source repo --data-source prod-cache
|
|
124
161
|
spaps local --runtime-source repo --fresh --data-source prod-fresh
|
|
125
162
|
spaps local --from-backup ~/.cache/spaps/db/prod.sql.gz
|
|
126
163
|
spaps status --json
|
|
164
|
+
spaps verify --json
|
|
127
165
|
spaps create my-app --template react
|
|
128
|
-
spaps
|
|
166
|
+
spaps connect --json
|
|
129
167
|
spaps fixtures apply --base-url http://localhost:5173
|
|
168
|
+
spaps fixtures apply --seed --persona dayrate-entitled --base-url http://localhost:5173
|
|
130
169
|
spaps fixtures storage-state --persona admin
|
|
131
170
|
spaps docs --search secure-messages
|
|
132
171
|
spaps doctor --stripe mock
|
|
133
172
|
spaps local stop
|
|
173
|
+
spaps dayrate config --json
|
|
174
|
+
spaps email --help
|
|
175
|
+
spaps email send --help
|
|
176
|
+
spaps email send --template-key welcome --to user@example.com --context '{"user_name":"Jane"}' --json
|
|
177
|
+
spaps email preview --template-key welcome
|
|
178
|
+
spaps email logs --owner-id owner-123 --limit 25 --json
|
|
179
|
+
spaps email set-override --template-key welcome --subject-override "New subject" --json
|
|
180
|
+
spaps policy list --json
|
|
181
|
+
spaps policy create --name allow_admin --effect allow --conditions '{"role":"admin"}' --json
|
|
182
|
+
spaps webhook register --url https://example.com/hook --events user.created,user.deleted --json
|
|
183
|
+
spaps issue-reports list-mine --status open --json
|
|
134
184
|
```
|
|
135
185
|
|
|
136
186
|
## CLI Auth
|
|
137
187
|
|
|
138
|
-
`spaps
|
|
188
|
+
`spaps connect` is an alias for `spaps login`, and both use the active FastAPI device-flow contract:
|
|
139
189
|
|
|
140
190
|
- device authorization at `/api/cli/device/*`
|
|
141
191
|
- authenticated follow-up calls at `/api/auth/*`
|
|
@@ -160,6 +210,7 @@ Authenticated follow-up calls such as `whoami`, `logout`, and token refresh stil
|
|
|
160
210
|
Examples:
|
|
161
211
|
|
|
162
212
|
```bash
|
|
213
|
+
npx spaps connect --client-id my-app
|
|
163
214
|
npx spaps login --client-id my-app
|
|
164
215
|
SPAPS_CLI_CLIENT_ID=my-app npx spaps login
|
|
165
216
|
VITE_SPAPS_API_KEY=spaps_pub_demo npx spaps whoami --json
|
|
@@ -217,9 +268,12 @@ Then run:
|
|
|
217
268
|
|
|
218
269
|
```bash
|
|
219
270
|
npx spaps fixtures apply --base-url http://localhost:5173
|
|
271
|
+
npx spaps fixtures apply --seed --persona issue-reporter-blocked --base-url http://localhost:5173
|
|
220
272
|
npx spaps fixtures storage-state --persona admin
|
|
221
273
|
```
|
|
222
274
|
|
|
275
|
+
Use `--seed` when a persona declares backend seed steps in `.spaps/users.json`. Seeding is opt-in, only runs against a reachable local-mode SPAPS server, and reuses the persona selectors already defined in the fixture kernel instead of adding fixture-only backend routes.
|
|
276
|
+
|
|
223
277
|
What `apply` emits:
|
|
224
278
|
|
|
225
279
|
- `browser/<persona>.storage-state.json` for Playwright `storageState`
|
|
@@ -14,6 +14,19 @@ RUN apt-get update && \
|
|
|
14
14
|
|
|
15
15
|
ARG SPAPS_SERVER_QUICKSTART_VERSION=0.5.0
|
|
16
16
|
RUN pip install --no-cache-dir "spaps-server-quickstart==${SPAPS_SERVER_QUICKSTART_VERSION}"
|
|
17
|
+
RUN python - <<'PY'
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from spaps_server_quickstart.domains.auth import schemas
|
|
21
|
+
|
|
22
|
+
schema_path = Path(schemas.__file__)
|
|
23
|
+
text = schema_path.read_text(encoding="utf-8")
|
|
24
|
+
text = text.replace(
|
|
25
|
+
" refresh_token: str\n",
|
|
26
|
+
" refresh_token: str | None = None\n",
|
|
27
|
+
)
|
|
28
|
+
schema_path.write_text(text, encoding="utf-8")
|
|
29
|
+
PY
|
|
17
30
|
|
|
18
31
|
COPY alembic.ini /app/alembic.ini
|
|
19
32
|
COPY alembic/ /app/alembic/
|
|
@@ -20,7 +20,8 @@ services:
|
|
|
20
20
|
LOG_LEVEL: DEBUG
|
|
21
21
|
LOG_FORMAT: console
|
|
22
22
|
AUTH_BASE_URL: "${SPAPS_AUTH_BASE_URL:-http://localhost:5173}"
|
|
23
|
-
CORS_ALLOW_ORIGINS: "${CORS_ALLOW_ORIGINS
|
|
23
|
+
CORS_ALLOW_ORIGINS: "${CORS_ALLOW_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000,http://localhost:3001,http://127.0.0.1:3001,http://localhost:5173,http://127.0.0.1:5173,http://localhost:30000,http://127.0.0.1:30000}"
|
|
24
|
+
CORS_ALLOW_HEADERS: "${CORS_ALLOW_HEADERS:-Accept,Accept-Language,Authorization,Content-Language,Content-Type,X-API-Key,X-Request-ID,X-SPAPS-Signature,X-SPAPS-Use-Refresh-Cookie,X-Test-User}"
|
|
24
25
|
LEGACY_API_KEY_AUTH_ENABLED: "${LEGACY_API_KEY_AUTH_ENABLED:-true}"
|
|
25
26
|
depends_on:
|
|
26
27
|
spaps-dev-db:
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
"portable_runtime": {
|
|
4
4
|
"default_local_mode": true,
|
|
5
5
|
"default_auth_base_url": "http://localhost:5173",
|
|
6
|
-
"default_self_service_password": "spaps_local_self_service_password"
|
|
6
|
+
"default_self_service_password": "spaps_local_self_service_password",
|
|
7
|
+
"default_cors_allow_origins": "http://localhost:3000,http://127.0.0.1:3000,http://localhost:3001,http://127.0.0.1:3001,http://localhost:5173,http://127.0.0.1:5173,http://localhost:30000,http://127.0.0.1:30000",
|
|
8
|
+
"default_cors_allow_headers": "Accept,Accept-Language,Authorization,Content-Language,Content-Type,X-API-Key,X-Request-ID,X-SPAPS-Signature,X-SPAPS-Use-Refresh-Cookie,X-Test-User"
|
|
7
9
|
}
|
|
8
10
|
}
|
package/bin/spaps.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const chalk = require('chalk');
|
|
8
|
-
const fs = require('fs');
|
|
9
8
|
const { buildProgram } = require('../src/cli-dispatcher');
|
|
10
9
|
const { createHandlers } = require('../src/handlers');
|
|
11
10
|
|
|
@@ -18,13 +17,40 @@ ${chalk.yellow('🍠 SPAPS')} - Sweet Potato Authentication & Payment Service
|
|
|
18
17
|
|
|
19
18
|
const handlers = createHandlers(version, logo);
|
|
20
19
|
const program = buildProgram({ handlers, dryRun: false, version, logo });
|
|
20
|
+
const argv = process.argv.slice(2);
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
if (!
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
function shouldRouteToHome(args) {
|
|
23
|
+
if (!args.length) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const flagsWithValues = new Set(['-p', '--port', '--server-url']);
|
|
28
|
+
const bareFlags = new Set(['--json']);
|
|
29
|
+
|
|
30
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
31
|
+
const arg = args[index];
|
|
32
|
+
|
|
33
|
+
if (arg === '--help' || arg === '-h' || arg === '--version' || arg === '-V') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (bareFlags.has(arg)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (flagsWithValues.has(arg)) {
|
|
42
|
+
index += 1;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true;
|
|
28
50
|
}
|
|
29
51
|
|
|
30
|
-
|
|
52
|
+
if (shouldRouteToHome(argv)) {
|
|
53
|
+
program.parse([process.argv[0], process.argv[1], 'home', ...argv]);
|
|
54
|
+
} else {
|
|
55
|
+
program.parse(process.argv);
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spaps",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
4
4
|
"description": "Sweet Potato Authentication & Payment Service CLI - Docker Compose orchestrator for local Python/FastAPI SPAPS server with built-in admin middleware",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -39,11 +39,10 @@
|
|
|
39
39
|
},
|
|
40
40
|
"homepage": "https://www.buildooor.com/services",
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"axios": "^1.
|
|
42
|
+
"axios": "^1.15.1",
|
|
43
43
|
"chalk": "^4.1.2",
|
|
44
44
|
"commander": "^11.1.0",
|
|
45
|
-
"js-yaml": "^4.1.
|
|
46
|
-
"ora": "^5.4.1",
|
|
45
|
+
"js-yaml": "^4.1.1",
|
|
47
46
|
"prompts": "^2.4.2"
|
|
48
47
|
},
|
|
49
48
|
"engines": {
|
package/src/ai-helper.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
const { DEFAULT_PORT } = require('./config');
|
|
2
2
|
const { getServerRuntime, readSelfServicePassword } = require('./local-runtime');
|
|
3
|
+
const { resolveAuthApiKey } = require('./auth/api-key');
|
|
4
|
+
|
|
5
|
+
function normalizeRuntimeArgs(input = DEFAULT_PORT) {
|
|
6
|
+
if (typeof input === 'number') {
|
|
7
|
+
return {
|
|
8
|
+
port: input,
|
|
9
|
+
serverUrl: null,
|
|
10
|
+
cwd: process.cwd(),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (input && typeof input === 'object') {
|
|
15
|
+
return {
|
|
16
|
+
port: Number(input.port) || DEFAULT_PORT,
|
|
17
|
+
serverUrl: input.serverUrl || resolveEnvServerUrl(),
|
|
18
|
+
cwd: input.cwd || process.cwd(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
port: DEFAULT_PORT,
|
|
24
|
+
serverUrl: null,
|
|
25
|
+
cwd: process.cwd(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveEnvServerUrl() {
|
|
30
|
+
const raw = String(process.env.SPAPS_API_URL || '').trim();
|
|
31
|
+
return raw ? raw : null;
|
|
32
|
+
}
|
|
3
33
|
|
|
4
34
|
function buildProvisioningCommand(template, port) {
|
|
5
35
|
return `SELF_SERVICE_PASSWORD=your-password npx spaps create demo-${template === 'node' ? 'api' : 'app'} --template ${template} --port ${port}`;
|
|
@@ -127,8 +157,9 @@ main().catch(console.error);
|
|
|
127
157
|
};
|
|
128
158
|
}
|
|
129
159
|
|
|
130
|
-
async function getQuickStartInstructions(
|
|
131
|
-
const
|
|
160
|
+
async function getQuickStartInstructions(input = DEFAULT_PORT) {
|
|
161
|
+
const { port, serverUrl } = normalizeRuntimeArgs(input);
|
|
162
|
+
const runtime = await getServerRuntime({ port, serverUrl });
|
|
132
163
|
|
|
133
164
|
if (!runtime.running) {
|
|
134
165
|
return {
|
|
@@ -154,12 +185,14 @@ async function getQuickStartInstructions(port = DEFAULT_PORT) {
|
|
|
154
185
|
return buildProvisionedModeInstructions(runtime, port);
|
|
155
186
|
}
|
|
156
187
|
|
|
157
|
-
async function getServerStatus(
|
|
158
|
-
|
|
188
|
+
async function getServerStatus(input = DEFAULT_PORT) {
|
|
189
|
+
const { port, serverUrl } = normalizeRuntimeArgs(input);
|
|
190
|
+
return getServerRuntime({ port, serverUrl });
|
|
159
191
|
}
|
|
160
192
|
|
|
161
|
-
async function runQuickTest(
|
|
162
|
-
const
|
|
193
|
+
async function runQuickTest(input = DEFAULT_PORT) {
|
|
194
|
+
const { port, serverUrl, cwd } = normalizeRuntimeArgs(input);
|
|
195
|
+
const runtime = await getServerRuntime({ port, serverUrl });
|
|
163
196
|
const results = [
|
|
164
197
|
{
|
|
165
198
|
test: 'server_status',
|
|
@@ -173,7 +206,7 @@ async function runQuickTest(port = DEFAULT_PORT) {
|
|
|
173
206
|
success: false,
|
|
174
207
|
summary: '0/1 tests passed',
|
|
175
208
|
results,
|
|
176
|
-
next_steps: [`
|
|
209
|
+
next_steps: [runtime.start_command || `Check the server at ${runtime.url} and rerun spaps verify.`],
|
|
177
210
|
};
|
|
178
211
|
}
|
|
179
212
|
|
|
@@ -185,12 +218,13 @@ async function runQuickTest(port = DEFAULT_PORT) {
|
|
|
185
218
|
});
|
|
186
219
|
|
|
187
220
|
if (!runtime.local_mode.active) {
|
|
188
|
-
const
|
|
221
|
+
const apiKey = resolveAuthApiKey({ cwd });
|
|
222
|
+
const hasKey = Boolean(apiKey.apiKey);
|
|
189
223
|
results.push({
|
|
190
224
|
test: 'api_key_wiring',
|
|
191
225
|
success: hasKey,
|
|
192
226
|
message: hasKey
|
|
193
|
-
?
|
|
227
|
+
? `API key is configured via ${apiKey.source}`
|
|
194
228
|
: 'SPAPS_API_KEY is required when local mode is disabled',
|
|
195
229
|
fix: hasKey ? null : buildProvisioningCommand('node', port),
|
|
196
230
|
});
|
|
@@ -204,7 +238,7 @@ async function runQuickTest(port = DEFAULT_PORT) {
|
|
|
204
238
|
results,
|
|
205
239
|
next_steps: allSuccess
|
|
206
240
|
? [`See docs at ${runtime.docs}`]
|
|
207
|
-
: ['Fix the failing checks above and re-run:
|
|
241
|
+
: ['Fix the failing checks above and re-run: spaps verify --json'],
|
|
208
242
|
};
|
|
209
243
|
}
|
|
210
244
|
|
package/src/ai-tool-spec.js
CHANGED
|
@@ -8,6 +8,7 @@ const { DEFAULT_PORT } = require('./config');
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const { getServerRuntime } = require('./local-runtime');
|
|
11
|
+
const { buildDomainTools } = require('./domains');
|
|
11
12
|
|
|
12
13
|
function tryLoadManifest() {
|
|
13
14
|
const candidates = [
|
|
@@ -74,7 +75,7 @@ function buildAuthSection(runtime) {
|
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
async function buildOpenAIToolSpec({ port = DEFAULT_PORT, version = '0.0.0' } = {}) {
|
|
78
|
+
async function buildOpenAIToolSpec({ port = DEFAULT_PORT, version = '0.0.0', include_non_agent = false } = {}) {
|
|
78
79
|
const baseUrl = `http://localhost:${port}`;
|
|
79
80
|
const runtime = await getServerRuntime({ port });
|
|
80
81
|
const auth = buildAuthSection(runtime);
|
|
@@ -190,6 +191,14 @@ async function buildOpenAIToolSpec({ port = DEFAULT_PORT, version = '0.0.0' } =
|
|
|
190
191
|
]
|
|
191
192
|
};
|
|
192
193
|
|
|
194
|
+
// Merge domain registry tools (dayrate, email, webhooks, policies, issue_reporting).
|
|
195
|
+
// Admin routes carry admin_required:true; non-agent routes (e.g. mailgun inbound)
|
|
196
|
+
// are excluded unless include_non_agent is set.
|
|
197
|
+
const domainTools = buildDomainTools({ includeNonAgent: include_non_agent });
|
|
198
|
+
for (const dt of domainTools) {
|
|
199
|
+
spec.tools.push(dt);
|
|
200
|
+
}
|
|
201
|
+
|
|
193
202
|
// Default error shapes used for enrichment/merging
|
|
194
203
|
const defaultErrors = {
|
|
195
204
|
'400': {
|
|
@@ -305,7 +314,13 @@ async function buildOpenAIToolSpec({ port = DEFAULT_PORT, version = '0.0.0' } =
|
|
|
305
314
|
setResponses('list_products', 'GET', '/api/stripe/products');
|
|
306
315
|
setResponses('request_magic_link', 'POST', '/api/auth/magic-link');
|
|
307
316
|
setResponses('get_wallet_nonce', 'POST', '/api/auth/nonce');
|
|
308
|
-
//
|
|
317
|
+
// Enrich every domain-registry tool opportunistically. Skips silently
|
|
318
|
+
// if the operation isn\u2019t in the OpenAPI doc.
|
|
319
|
+
for (const t of spec.tools) {
|
|
320
|
+
if (!t.domain) continue;
|
|
321
|
+
setBodySchema(t.name, t.method, t.path);
|
|
322
|
+
setResponses(t.name, t.method, t.path);
|
|
323
|
+
}
|
|
309
324
|
}
|
|
310
325
|
} catch {
|
|
311
326
|
// Ignore enrichment errors
|
|
@@ -319,11 +334,11 @@ async function buildOpenAIToolSpec({ port = DEFAULT_PORT, version = '0.0.0' } =
|
|
|
319
334
|
return spec;
|
|
320
335
|
}
|
|
321
336
|
|
|
322
|
-
async function buildToolSpec({ format = 'openai', port = DEFAULT_PORT, version = '0.0.0' } = {}) {
|
|
337
|
+
async function buildToolSpec({ format = 'openai', port = DEFAULT_PORT, version = '0.0.0', include_non_agent = false } = {}) {
|
|
323
338
|
switch (format) {
|
|
324
339
|
case 'openai':
|
|
325
340
|
default:
|
|
326
|
-
return buildOpenAIToolSpec({ port, version });
|
|
341
|
+
return buildOpenAIToolSpec({ port, version, include_non_agent });
|
|
327
342
|
}
|
|
328
343
|
}
|
|
329
344
|
|
package/src/auth/env.js
CHANGED
|
@@ -23,6 +23,10 @@ function isHeadless(env = process.env, platform = process.platform) {
|
|
|
23
23
|
return isSsh(env) || !hasGui(env, platform);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function hasInteractiveTerminal(stdin = process.stdin, stdout = process.stdout) {
|
|
27
|
+
return Boolean(stdin && stdin.isTTY && stdout && stdout.isTTY);
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
function tryOpenBrowser(url, { env = process.env, platform = process.platform } = {}) {
|
|
27
31
|
if (isHeadless(env, platform)) return false;
|
|
28
32
|
try {
|
|
@@ -53,5 +57,6 @@ module.exports = {
|
|
|
53
57
|
isSsh,
|
|
54
58
|
hasGui,
|
|
55
59
|
isHeadless,
|
|
60
|
+
hasInteractiveTerminal,
|
|
56
61
|
tryOpenBrowser,
|
|
57
62
|
};
|