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
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
function showQuickHelp() {
|
|
4
|
+
const output = [
|
|
5
|
+
'',
|
|
6
|
+
chalk.yellow('š SPAPS Quick Reference'),
|
|
7
|
+
'',
|
|
8
|
+
chalk.green('Common Commands:'),
|
|
9
|
+
` npx spaps local ${chalk.gray('# Start local server')}`,
|
|
10
|
+
` npx spaps init ${chalk.gray('# Initialize in project')}`,
|
|
11
|
+
` npx spaps create <name> ${chalk.gray('# Scaffold a SPAPS starter project')}`,
|
|
12
|
+
` npx spaps fixtures apply ${chalk.gray('# Generate repo-local auth fixtures')}`,
|
|
13
|
+
` npx spaps fixtures storage-state ${chalk.gray('# Export one persona artifact')}`,
|
|
14
|
+
` npx spaps types ${chalk.gray('# Generate types (v0.4.0)')}`,
|
|
15
|
+
'',
|
|
16
|
+
chalk.green('Local Development:'),
|
|
17
|
+
` npx spaps local --port 3001 ${chalk.gray('# Custom port')}`,
|
|
18
|
+
` npx spaps local --open ${chalk.gray('# Open browser')}`,
|
|
19
|
+
` npx spaps local --json ${chalk.gray('# JSON output')}`,
|
|
20
|
+
'',
|
|
21
|
+
chalk.green('SDK Usage:'),
|
|
22
|
+
` npm install spaps-sdk ${chalk.gray('# Install SDK')}`,
|
|
23
|
+
'',
|
|
24
|
+
chalk.gray(' import { SPAPSClient } from "spaps-sdk"'),
|
|
25
|
+
chalk.gray(' const spaps = new SPAPSClient()'),
|
|
26
|
+
chalk.gray(' await spaps.login(email, password)'),
|
|
27
|
+
'',
|
|
28
|
+
chalk.green('Debugging:'),
|
|
29
|
+
` curl http://localhost:3301/health ${chalk.gray('# Check server')}`,
|
|
30
|
+
` npx spaps help --interactive ${chalk.gray('# Interactive help')}`,
|
|
31
|
+
'',
|
|
32
|
+
chalk.blue('š Docs: https://sweetpotato.dev'),
|
|
33
|
+
chalk.blue('š¬ Discord: https://discord.gg/sweetpotato'),
|
|
34
|
+
'',
|
|
35
|
+
].join('\n');
|
|
36
|
+
|
|
37
|
+
process.stdout.write(`${output}\n`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
showQuickHelp,
|
|
42
|
+
};
|
package/src/help-system.js
CHANGED
|
@@ -7,6 +7,7 @@ const chalk = require('chalk');
|
|
|
7
7
|
const prompts = require('prompts');
|
|
8
8
|
|
|
9
9
|
const { DEFAULT_PORT } = require('./config');
|
|
10
|
+
const { showQuickHelp } = require('./help-quick');
|
|
10
11
|
|
|
11
12
|
const HELP_TREE = {
|
|
12
13
|
root: {
|
|
@@ -448,42 +449,6 @@ async function showInteractiveHelp() {
|
|
|
448
449
|
}
|
|
449
450
|
}
|
|
450
451
|
|
|
451
|
-
function showQuickHelp() {
|
|
452
|
-
console.log(chalk.yellow('\nš SPAPS Quick Reference\n'));
|
|
453
|
-
|
|
454
|
-
console.log(chalk.green('Common Commands:'));
|
|
455
|
-
console.log(' npx spaps local ' + chalk.gray('# Start local server'));
|
|
456
|
-
console.log(' npx spaps init ' + chalk.gray('# Initialize in project'));
|
|
457
|
-
console.log(' npx spaps create <name> ' + chalk.gray('# Scaffold a SPAPS starter project'));
|
|
458
|
-
console.log(' npx spaps fixtures apply ' + chalk.gray('# Generate repo-local auth fixtures'));
|
|
459
|
-
console.log(' npx spaps fixtures storage-state ' + chalk.gray('# Export one persona artifact'));
|
|
460
|
-
console.log(' npx spaps types ' + chalk.gray('# Generate types (v0.4.0)'));
|
|
461
|
-
console.log();
|
|
462
|
-
|
|
463
|
-
console.log(chalk.green('Local Development:'));
|
|
464
|
-
console.log(' npx spaps local --port 3001 ' + chalk.gray('# Custom port'));
|
|
465
|
-
console.log(' npx spaps local --open ' + chalk.gray('# Open browser'));
|
|
466
|
-
console.log(' npx spaps local --json ' + chalk.gray('# JSON output'));
|
|
467
|
-
console.log();
|
|
468
|
-
|
|
469
|
-
console.log(chalk.green('SDK Usage:'));
|
|
470
|
-
console.log(' npm install spaps-sdk ' + chalk.gray('# Install SDK'));
|
|
471
|
-
console.log();
|
|
472
|
-
console.log(chalk.gray(' import { SPAPSClient } from "spaps-sdk"'));
|
|
473
|
-
console.log(chalk.gray(' const spaps = new SPAPSClient()'));
|
|
474
|
-
console.log(chalk.gray(' await spaps.login(email, password)'));
|
|
475
|
-
console.log();
|
|
476
|
-
|
|
477
|
-
console.log(chalk.green('Debugging:'));
|
|
478
|
-
console.log(' curl http://localhost:3301/health ' + chalk.gray('# Check server'));
|
|
479
|
-
console.log(' npx spaps help --interactive ' + chalk.gray('# Interactive help'));
|
|
480
|
-
console.log();
|
|
481
|
-
|
|
482
|
-
console.log(chalk.blue('š Docs: https://sweetpotato.dev'));
|
|
483
|
-
console.log(chalk.blue('š¬ Discord: https://discord.gg/sweetpotato'));
|
|
484
|
-
console.log();
|
|
485
|
-
}
|
|
486
|
-
|
|
487
452
|
module.exports = {
|
|
488
453
|
showInteractiveHelp,
|
|
489
454
|
showQuickHelp,
|
package/src/home-view.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
const { DEFAULT_PORT } = require('./config');
|
|
4
|
+
const { getServerRuntime } = require('./local-runtime');
|
|
5
|
+
const { resolveServerUrl } = require('./auth/client');
|
|
6
|
+
const { getCredentials, CREDENTIALS_PATH } = require('./auth/credentials');
|
|
7
|
+
const { findUpContract } = require('./auth/client-id');
|
|
8
|
+
const { resolveAuthApiKey } = require('./auth/api-key');
|
|
9
|
+
|
|
10
|
+
function resolveAppContext({ cwd, runtime }) {
|
|
11
|
+
if (typeof process.env.SPAPS_CLI_CLIENT_ID === 'string' && process.env.SPAPS_CLI_CLIENT_ID.trim()) {
|
|
12
|
+
return {
|
|
13
|
+
clientId: process.env.SPAPS_CLI_CLIENT_ID.trim(),
|
|
14
|
+
source: 'SPAPS_CLI_CLIENT_ID',
|
|
15
|
+
path: null,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const contractHit = findUpContract(cwd);
|
|
20
|
+
if (contractHit) {
|
|
21
|
+
return contractHit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const runtimeClientId = runtime?.local_mode?.test_application?.slug || null;
|
|
25
|
+
if (runtimeClientId) {
|
|
26
|
+
return {
|
|
27
|
+
clientId: runtimeClientId,
|
|
28
|
+
source: '/health/local-mode',
|
|
29
|
+
path: null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
clientId: null,
|
|
35
|
+
source: null,
|
|
36
|
+
path: null,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isLocalhostUrl(url) {
|
|
41
|
+
try {
|
|
42
|
+
const parsed = new URL(url);
|
|
43
|
+
return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function formatSource(source, filePath) {
|
|
50
|
+
if (!source) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return filePath ? `${source} (${filePath})` : source;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatMode(runtime) {
|
|
57
|
+
if (!runtime.running) {
|
|
58
|
+
return 'server unreachable';
|
|
59
|
+
}
|
|
60
|
+
if (!runtime.local_mode?.known) {
|
|
61
|
+
return 'mode unknown';
|
|
62
|
+
}
|
|
63
|
+
return runtime.local_mode.active ? 'local mode active' : 'application key required';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function buildRecommendedNext({ operator, runtime, port }) {
|
|
67
|
+
if (!operator.connected) {
|
|
68
|
+
return {
|
|
69
|
+
command: 'spaps connect',
|
|
70
|
+
reason: `No saved operator session for ${operator.server_url}.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!runtime.running) {
|
|
75
|
+
return {
|
|
76
|
+
command: isLocalhostUrl(runtime.url)
|
|
77
|
+
? `spaps local${port !== DEFAULT_PORT ? ` --port ${port}` : ''}`
|
|
78
|
+
: 'spaps verify',
|
|
79
|
+
reason: isLocalhostUrl(runtime.url)
|
|
80
|
+
? `No SPAPS runtime is reachable at ${runtime.url}.`
|
|
81
|
+
: `No SPAPS runtime is reachable at ${runtime.url}; verify the configured server URL.`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
command: 'spaps verify',
|
|
87
|
+
reason: runtime.local_mode?.active
|
|
88
|
+
? 'Confirm the local auth path and runtime hints end to end.'
|
|
89
|
+
: 'Confirm the provisioned auth path and API key wiring end to end.',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function buildHomeView({ port = DEFAULT_PORT, serverUrl = null, cwd = process.cwd() } = {}) {
|
|
94
|
+
const resolvedPort = Number(port) || DEFAULT_PORT;
|
|
95
|
+
const resolvedServerUrl = resolveServerUrl({ port: resolvedPort, serverUrl });
|
|
96
|
+
const runtime = await getServerRuntime({ port: resolvedPort, serverUrl: resolvedServerUrl });
|
|
97
|
+
const storedCreds = getCredentials(resolvedServerUrl);
|
|
98
|
+
const envAccessToken =
|
|
99
|
+
typeof process.env.SPAPS_ACCESS_TOKEN === 'string' && process.env.SPAPS_ACCESS_TOKEN.trim()
|
|
100
|
+
? process.env.SPAPS_ACCESS_TOKEN.trim()
|
|
101
|
+
: null;
|
|
102
|
+
const appContext = resolveAppContext({ cwd, runtime });
|
|
103
|
+
const apiKey = resolveAuthApiKey({ cwd });
|
|
104
|
+
|
|
105
|
+
const operator = {
|
|
106
|
+
server_url: resolvedServerUrl,
|
|
107
|
+
connected: Boolean(envAccessToken || storedCreds?.access_token),
|
|
108
|
+
source: envAccessToken ? 'SPAPS_ACCESS_TOKEN' : storedCreds?.access_token ? 'credentials' : null,
|
|
109
|
+
credentials_path: CREDENTIALS_PATH,
|
|
110
|
+
user_id: storedCreds?.user_id || null,
|
|
111
|
+
client_id: storedCreds?.client_id || null,
|
|
112
|
+
expires_at: storedCreds?.expires_at || null,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const app = {
|
|
116
|
+
client_id: appContext.clientId,
|
|
117
|
+
client_id_source: appContext.source,
|
|
118
|
+
client_id_path: appContext.path,
|
|
119
|
+
api_key_configured: Boolean(apiKey.apiKey),
|
|
120
|
+
api_key_source: apiKey.source,
|
|
121
|
+
api_key_path: apiKey.path,
|
|
122
|
+
api_key_env: apiKey.envVar || null,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
command: 'home',
|
|
128
|
+
operator,
|
|
129
|
+
app,
|
|
130
|
+
runtime,
|
|
131
|
+
recommended_next: buildRecommendedNext({
|
|
132
|
+
operator,
|
|
133
|
+
runtime,
|
|
134
|
+
port: resolvedPort,
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function renderHomeView(view, { logo = '' } = {}) {
|
|
140
|
+
if (logo) {
|
|
141
|
+
console.log(logo);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(chalk.bold('Operator'));
|
|
145
|
+
console.log(' Server:', chalk.cyan(view.operator.server_url));
|
|
146
|
+
console.log(
|
|
147
|
+
' Session:',
|
|
148
|
+
view.operator.connected
|
|
149
|
+
? chalk.green(`connected via ${view.operator.source || 'credentials'}`)
|
|
150
|
+
: chalk.yellow('not connected')
|
|
151
|
+
);
|
|
152
|
+
if (view.operator.client_id) {
|
|
153
|
+
console.log(' Session app:', chalk.cyan(view.operator.client_id));
|
|
154
|
+
}
|
|
155
|
+
if (view.operator.user_id) {
|
|
156
|
+
console.log(' User:', chalk.cyan(view.operator.user_id));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log();
|
|
160
|
+
console.log(chalk.bold('App'));
|
|
161
|
+
if (view.app.client_id) {
|
|
162
|
+
console.log(' Client id:', chalk.cyan(view.app.client_id));
|
|
163
|
+
if (view.app.client_id_source) {
|
|
164
|
+
console.log(chalk.gray(` source: ${formatSource(view.app.client_id_source, view.app.client_id_path)}`));
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
console.log(' Client id:', chalk.yellow('not detected'));
|
|
168
|
+
}
|
|
169
|
+
console.log(
|
|
170
|
+
' API key:',
|
|
171
|
+
view.app.api_key_configured
|
|
172
|
+
? chalk.green(`configured via ${formatSource(view.app.api_key_source, view.app.api_key_path)}`)
|
|
173
|
+
: chalk.yellow('not found')
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
console.log();
|
|
177
|
+
console.log(chalk.bold('Runtime'));
|
|
178
|
+
if (!view.runtime.running) {
|
|
179
|
+
console.log(' Status:', chalk.red('unreachable'));
|
|
180
|
+
console.log(' Target:', chalk.cyan(view.runtime.url));
|
|
181
|
+
if (view.runtime.error?.message) {
|
|
182
|
+
console.log(chalk.gray(` ${view.runtime.error.message}`));
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
console.log(' Status:', chalk.green('running'));
|
|
186
|
+
console.log(' URL:', chalk.cyan(view.runtime.url));
|
|
187
|
+
console.log(' Mode:', chalk.cyan(formatMode(view.runtime)));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.log();
|
|
191
|
+
console.log(chalk.bold('Next'));
|
|
192
|
+
console.log(' ' + chalk.cyan(view.recommended_next.command));
|
|
193
|
+
console.log(chalk.gray(` ${view.recommended_next.reason}`));
|
|
194
|
+
console.log();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
buildHomeView,
|
|
199
|
+
renderHomeView,
|
|
200
|
+
};
|
package/src/local-runtime.js
CHANGED
|
@@ -12,6 +12,19 @@ function buildDocsUrl(port = DEFAULT_PORT) {
|
|
|
12
12
|
return `${buildBaseUrl(port)}/docs`;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function normalizeServerUrl(url) {
|
|
16
|
+
return String(url || '').trim().replace(/\/+$/, '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isLocalhostUrl(url) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(url);
|
|
22
|
+
return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
15
28
|
function unwrapEnvelope(payload) {
|
|
16
29
|
if (
|
|
17
30
|
payload &&
|
|
@@ -82,9 +95,9 @@ async function requestJson({ method, url, data = null, headers = {}, timeoutMs =
|
|
|
82
95
|
}
|
|
83
96
|
}
|
|
84
97
|
|
|
85
|
-
async function getServerRuntime({ port = DEFAULT_PORT, timeoutMs = REQUEST_TIMEOUT_MS } = {}) {
|
|
86
|
-
const url = buildBaseUrl(port);
|
|
87
|
-
const docs =
|
|
98
|
+
async function getServerRuntime({ port = DEFAULT_PORT, serverUrl = null, timeoutMs = REQUEST_TIMEOUT_MS } = {}) {
|
|
99
|
+
const url = serverUrl ? normalizeServerUrl(serverUrl) : buildBaseUrl(port);
|
|
100
|
+
const docs = `${url}/docs`;
|
|
88
101
|
|
|
89
102
|
const health = await requestJson({
|
|
90
103
|
method: 'GET',
|
|
@@ -99,7 +112,7 @@ async function getServerRuntime({ port = DEFAULT_PORT, timeoutMs = REQUEST_TIMEO
|
|
|
99
112
|
url,
|
|
100
113
|
docs,
|
|
101
114
|
message: health.network_error ? 'Server not running' : 'Health endpoint returned an error',
|
|
102
|
-
start_command: `npx spaps local --port ${port}
|
|
115
|
+
start_command: isLocalhostUrl(url) ? `npx spaps local --port ${port}` : null,
|
|
103
116
|
error: health.error,
|
|
104
117
|
};
|
|
105
118
|
}
|
|
@@ -251,6 +264,8 @@ module.exports = {
|
|
|
251
264
|
buildBaseUrl,
|
|
252
265
|
buildDocsUrl,
|
|
253
266
|
getServerRuntime,
|
|
267
|
+
isLocalhostUrl,
|
|
268
|
+
normalizeServerUrl,
|
|
254
269
|
provisionStarterApplication,
|
|
255
270
|
readSelfServicePassword,
|
|
256
271
|
requestJson,
|
package/src/local-server.js
CHANGED
|
@@ -20,6 +20,28 @@ const DEFAULT_RUNTIME_SOURCE = 'auto';
|
|
|
20
20
|
const DEFAULT_AUTH_BASE_URL = 'http://localhost:5173';
|
|
21
21
|
const DEFAULT_DATA_SOURCE = 'empty';
|
|
22
22
|
const EXTERNAL_NETWORKS = ['reverse-proxy'];
|
|
23
|
+
const DEFAULT_CORS_ALLOW_ORIGINS = [
|
|
24
|
+
'http://localhost:3000',
|
|
25
|
+
'http://127.0.0.1:3000',
|
|
26
|
+
'http://localhost:3001',
|
|
27
|
+
'http://127.0.0.1:3001',
|
|
28
|
+
'http://localhost:5173',
|
|
29
|
+
'http://127.0.0.1:5173',
|
|
30
|
+
'http://localhost:30000',
|
|
31
|
+
'http://127.0.0.1:30000',
|
|
32
|
+
].join(',');
|
|
33
|
+
const DEFAULT_CORS_ALLOW_HEADERS = [
|
|
34
|
+
'Accept',
|
|
35
|
+
'Accept-Language',
|
|
36
|
+
'Authorization',
|
|
37
|
+
'Content-Language',
|
|
38
|
+
'Content-Type',
|
|
39
|
+
'X-API-Key',
|
|
40
|
+
'X-Request-ID',
|
|
41
|
+
'X-SPAPS-Signature',
|
|
42
|
+
'X-SPAPS-Use-Refresh-Cookie',
|
|
43
|
+
'X-Test-User',
|
|
44
|
+
].join(',');
|
|
23
45
|
|
|
24
46
|
function readBundledRuntimeManifest() {
|
|
25
47
|
if (!fs.existsSync(BUNDLED_RUNTIME_MANIFEST)) {
|
|
@@ -123,7 +145,14 @@ function buildComposeEnv({ port, authBaseUrl }) {
|
|
|
123
145
|
process.env.SELF_SERVICE_PASSWORD ||
|
|
124
146
|
portableRuntime.default_self_service_password ||
|
|
125
147
|
'spaps_local_self_service_password',
|
|
126
|
-
CORS_ALLOW_ORIGINS:
|
|
148
|
+
CORS_ALLOW_ORIGINS:
|
|
149
|
+
process.env.CORS_ALLOW_ORIGINS ||
|
|
150
|
+
portableRuntime.default_cors_allow_origins ||
|
|
151
|
+
DEFAULT_CORS_ALLOW_ORIGINS,
|
|
152
|
+
CORS_ALLOW_HEADERS:
|
|
153
|
+
process.env.CORS_ALLOW_HEADERS ||
|
|
154
|
+
portableRuntime.default_cors_allow_headers ||
|
|
155
|
+
DEFAULT_CORS_ALLOW_HEADERS,
|
|
127
156
|
LEGACY_API_KEY_AUTH_ENABLED: process.env.LEGACY_API_KEY_AUTH_ENABLED || 'true',
|
|
128
157
|
SPAPS_SERVER_QUICKSTART_VERSION:
|
|
129
158
|
process.env.SPAPS_SERVER_QUICKSTART_VERSION || manifest.spaps_server_quickstart_version,
|