spaps 0.7.6 → 0.7.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.
- package/AI_TOOLS.json +5566 -38
- package/README.md +65 -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/cli-dispatcher.js +365 -91
- 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 +471 -4
- package/src/home-view.js +200 -0
- package/src/local-runtime.js +19 -4
- package/src/local-server.js +30 -1
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,
|