@switchboard.spot/cli 0.2.3 → 0.2.5
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/README.md +20 -9
- package/bin/switchboard.js +1 -17
- package/lib/client.js +0 -3
- package/lib/commands/account.js +0 -9
- package/lib/commands/auth.js +16 -124
- package/lib/commands/billing.js +7 -69
- package/lib/commands/doctor.js +10 -6
- package/lib/commands/env.js +31 -245
- package/lib/commands/projects.js +83 -63
- package/lib/commands/setup.js +90 -85
- package/lib/commands/usage.js +0 -23
- package/lib/commands/verify.js +4 -0
- package/lib/config.js +2 -3
- package/lib/credentialStore.js +0 -134
- package/lib/docsClient.js +1 -51
- package/lib/mcpServer.js +0 -21
- package/lib/verify/index.js +62 -10
- package/package.json +2 -2
- package/lib/commands/endUsers.js +0 -51
- package/lib/commands/init.js +0 -78
- package/lib/commands/integration.js +0 -38
- package/lib/commands/keys.js +0 -106
- package/lib/commands/launch.js +0 -213
- package/lib/commands/org.js +0 -55
- package/lib/commands/workspaces.js +0 -92
package/lib/commands/env.js
CHANGED
|
@@ -4,22 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import path from "path";
|
|
7
|
-
import { spawn } from "node:child_process";
|
|
8
7
|
import { accountRequest } from "../client.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
getProjectSecretKey,
|
|
12
|
-
setProjectSecretKey,
|
|
13
|
-
} from "../credentialStore.js";
|
|
8
|
+
import { resolveConfig } from "../config.js";
|
|
14
9
|
import { emit, fail, globalFlags } from "../output.js";
|
|
15
10
|
|
|
16
11
|
const DEFAULT_ENV_FILE = ".env.local";
|
|
17
|
-
const SECRET_ENV_NAMES = ["SWITCHBOARD_API_KEY"];
|
|
18
|
-
const SECRET_PATTERNS = [
|
|
19
|
-
/sb_test_[A-Za-z0-9_-]+/g,
|
|
20
|
-
/sb_live_[A-Za-z0-9_-]+/g,
|
|
21
|
-
/sb_sess_[A-Za-z0-9_-]+/g,
|
|
22
|
-
];
|
|
23
12
|
|
|
24
13
|
export function registerEnvCommands(program) {
|
|
25
14
|
const env = program.command("env").description("Agent-safe environment setup");
|
|
@@ -27,10 +16,8 @@ export function registerEnvCommands(program) {
|
|
|
27
16
|
env
|
|
28
17
|
.command("configure")
|
|
29
18
|
.description("Configure Switchboard environment values without printing secrets")
|
|
30
|
-
.option("--mode <mode>", "client
|
|
19
|
+
.option("--mode <mode>", "client", "client")
|
|
31
20
|
.option("--file <path>", "Local env file to update", DEFAULT_ENV_FILE)
|
|
32
|
-
.option("--target <target>", "local or exec", "local")
|
|
33
|
-
.option("--secret-command <command>", "Command that stores secrets from stdin JSON")
|
|
34
21
|
.option("--project-id <id>", "Override selected project")
|
|
35
22
|
.option("--force", "Replace existing Switchboard-managed values")
|
|
36
23
|
.action(async (opts, cmd) => {
|
|
@@ -41,12 +28,12 @@ export function registerEnvCommands(program) {
|
|
|
41
28
|
|
|
42
29
|
env
|
|
43
30
|
.command("run")
|
|
44
|
-
.description("
|
|
31
|
+
.description("Deprecated: server secret injection has been removed")
|
|
45
32
|
.allowUnknownOption(true)
|
|
46
33
|
.argument("[command...]", "Command to run")
|
|
47
34
|
.action(async (command, _opts, cmd) => {
|
|
48
35
|
const flags = globalFlags(cmd);
|
|
49
|
-
|
|
36
|
+
runWithEnvironment(command, { json: flags.json });
|
|
50
37
|
});
|
|
51
38
|
}
|
|
52
39
|
|
|
@@ -54,162 +41,81 @@ export async function configureEnvironment(
|
|
|
54
41
|
opts,
|
|
55
42
|
{
|
|
56
43
|
request = accountRequest,
|
|
57
|
-
getSecret = getProjectSecretKey,
|
|
58
|
-
setSecret = setProjectSecretKey,
|
|
59
44
|
cwd = process.cwd(),
|
|
60
45
|
json = false,
|
|
61
|
-
runSecretCommand = runSecretSinkCommand,
|
|
62
46
|
} = {},
|
|
63
47
|
) {
|
|
64
48
|
const mode = normalizeMode(opts.mode);
|
|
65
|
-
const target = normalizeTarget(opts.target);
|
|
66
49
|
const projectId = opts.projectId;
|
|
67
50
|
const force = Boolean(opts.force);
|
|
68
51
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const { data: kit } = await request("GET", "/integration_kit", {
|
|
74
|
-
json,
|
|
75
|
-
projectId,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const selectedProjectId = String(projectId || kit.project_id || resolveConfig().projectId || "");
|
|
52
|
+
const project = await resolveProjectContext({ projectId, request, json });
|
|
53
|
+
const selectedProjectId = String(project.id || resolveConfig().projectId || "");
|
|
79
54
|
if (!selectedProjectId) {
|
|
80
55
|
fail("Could not determine the selected Switchboard project id", 1, json);
|
|
81
56
|
}
|
|
82
57
|
|
|
83
58
|
const changed = [];
|
|
84
59
|
const skipped = [];
|
|
85
|
-
const secrets = [];
|
|
86
60
|
const envFile = path.resolve(cwd, opts.file || DEFAULT_ENV_FILE);
|
|
87
61
|
|
|
88
62
|
const envUpdates = {};
|
|
89
63
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
envUpdates.VITE_SWITCHBOARD_CLIENT_URL = clientUrl;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (mode === "server" || mode === "both") {
|
|
97
|
-
envUpdates.SWITCHBOARD_BASE_URL = kit.server_base_url || kit.base_url;
|
|
98
|
-
}
|
|
64
|
+
const clientUrl = project.client_gateway_url;
|
|
65
|
+
envUpdates.SWITCHBOARD_CLIENT_URL = clientUrl;
|
|
66
|
+
envUpdates.VITE_SWITCHBOARD_CLIENT_URL = clientUrl;
|
|
99
67
|
|
|
100
68
|
const fileResult = updateEnvFile(envFile, envUpdates, { force });
|
|
101
69
|
changed.push(...fileResult.changed);
|
|
102
70
|
skipped.push(...fileResult.skipped);
|
|
103
71
|
|
|
104
|
-
if (mode === "server" || mode === "both") {
|
|
105
|
-
const secretResult = await ensureServerSecret({
|
|
106
|
-
projectId: selectedProjectId,
|
|
107
|
-
force,
|
|
108
|
-
request,
|
|
109
|
-
getSecret,
|
|
110
|
-
setSecret,
|
|
111
|
-
target,
|
|
112
|
-
secretCommand: opts.secretCommand,
|
|
113
|
-
runSecretCommand,
|
|
114
|
-
json,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
changed.push(...secretResult.changed);
|
|
118
|
-
skipped.push(...secretResult.skipped);
|
|
119
|
-
secrets.push(secretResult.secret);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
72
|
return {
|
|
123
73
|
ok: true,
|
|
124
74
|
project_id: selectedProjectId,
|
|
125
75
|
mode,
|
|
126
|
-
target,
|
|
127
76
|
env_file: envFile,
|
|
128
77
|
changed,
|
|
129
78
|
skipped,
|
|
130
|
-
secrets,
|
|
131
79
|
};
|
|
132
80
|
}
|
|
133
81
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
getSecret = getProjectSecretKey,
|
|
139
|
-
env = process.env,
|
|
140
|
-
spawnProcess = spawn,
|
|
141
|
-
exitProcess = process.exit,
|
|
142
|
-
killProcess = process.kill,
|
|
143
|
-
json = false,
|
|
144
|
-
} = {},
|
|
145
|
-
) {
|
|
146
|
-
if (!command || command.length === 0) {
|
|
147
|
-
fail("Usage: switchboard env run -- <command>", 1, json);
|
|
82
|
+
async function resolveProjectContext({ projectId, request, json }) {
|
|
83
|
+
if (projectId) {
|
|
84
|
+
const { data } = await request("GET", `/projects/${projectId}`, { json });
|
|
85
|
+
return data;
|
|
148
86
|
}
|
|
149
87
|
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
1,
|
|
155
|
-
json,
|
|
156
|
-
"project_required",
|
|
157
|
-
);
|
|
88
|
+
const configProjectId = resolveConfig().projectId;
|
|
89
|
+
if (configProjectId) {
|
|
90
|
+
const { data } = await request("GET", `/projects/${configProjectId}`, { json });
|
|
91
|
+
return data;
|
|
158
92
|
}
|
|
159
93
|
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
1,
|
|
165
|
-
json,
|
|
166
|
-
"secret_required",
|
|
167
|
-
);
|
|
94
|
+
const { data } = await request("GET", "/me", { json });
|
|
95
|
+
const project = data.project || data.projects?.[0];
|
|
96
|
+
if (!project) {
|
|
97
|
+
fail("No Switchboard project is available. Run switchboard setup project first.", 1, json);
|
|
168
98
|
}
|
|
99
|
+
return project;
|
|
100
|
+
}
|
|
169
101
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
SWITCHBOARD_PROJECT_ID: cfg.projectId,
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
return new Promise((resolve, reject) => {
|
|
180
|
-
child.on("exit", (code, signal) => {
|
|
181
|
-
try {
|
|
182
|
-
if (signal) {
|
|
183
|
-
killProcess(process.pid, signal);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
resolve(exitProcess(code ?? 0));
|
|
188
|
-
} catch (error) {
|
|
189
|
-
reject(error);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
});
|
|
102
|
+
export async function runWithEnvironment(
|
|
103
|
+
_command,
|
|
104
|
+
{ json = false } = {},
|
|
105
|
+
) {
|
|
106
|
+
fail("switchboard env run was removed with project secret injection", 1, json);
|
|
193
107
|
}
|
|
194
108
|
|
|
195
109
|
function normalizeMode(mode) {
|
|
196
|
-
if (mode === "client" || mode ===
|
|
197
|
-
return
|
|
110
|
+
if (mode === "client" || mode === undefined) {
|
|
111
|
+
return "client";
|
|
198
112
|
}
|
|
199
113
|
|
|
200
114
|
if (mode === "virtual") {
|
|
201
115
|
return "client";
|
|
202
116
|
}
|
|
203
117
|
|
|
204
|
-
fail("--mode must be client
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function normalizeTarget(target) {
|
|
208
|
-
if (target === "local" || target === "exec") {
|
|
209
|
-
return target;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
fail("--target must be local or exec");
|
|
118
|
+
fail("--mode must be client");
|
|
213
119
|
}
|
|
214
120
|
|
|
215
121
|
function updateEnvFile(file, updates, { force }) {
|
|
@@ -268,126 +174,6 @@ function trimTrailingBlankLines(lines) {
|
|
|
268
174
|
return next;
|
|
269
175
|
}
|
|
270
176
|
|
|
271
|
-
async function ensureServerSecret({
|
|
272
|
-
projectId,
|
|
273
|
-
force,
|
|
274
|
-
request,
|
|
275
|
-
getSecret,
|
|
276
|
-
setSecret,
|
|
277
|
-
target,
|
|
278
|
-
secretCommand,
|
|
279
|
-
runSecretCommand,
|
|
280
|
-
json,
|
|
281
|
-
}) {
|
|
282
|
-
const existing = await getSecret(projectId, "sandbox");
|
|
283
|
-
const changed = [];
|
|
284
|
-
const skipped = [];
|
|
285
|
-
let plaintext = existing;
|
|
286
|
-
let metadata = null;
|
|
287
|
-
|
|
288
|
-
if (!plaintext || force) {
|
|
289
|
-
const { data } = await request("POST", "/keys", {
|
|
290
|
-
body: {
|
|
291
|
-
mode: "sandbox",
|
|
292
|
-
name: "Managed server secret",
|
|
293
|
-
key_type: "secret",
|
|
294
|
-
},
|
|
295
|
-
json,
|
|
296
|
-
projectId,
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
plaintext = data.plaintext;
|
|
300
|
-
metadata = redactedKeyMetadata(data);
|
|
301
|
-
changed.push("SWITCHBOARD_API_KEY");
|
|
302
|
-
} else {
|
|
303
|
-
skipped.push("SWITCHBOARD_API_KEY");
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (!plaintext) {
|
|
307
|
-
fail("Switchboard did not return a one-time project secret key", 1, json);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (target === "local") {
|
|
311
|
-
await setSecret(projectId, "sandbox", plaintext);
|
|
312
|
-
} else {
|
|
313
|
-
await runSecretCommand(secretCommand, {
|
|
314
|
-
secrets: SECRET_ENV_NAMES.map((name) => ({
|
|
315
|
-
name,
|
|
316
|
-
value: plaintext,
|
|
317
|
-
metadata: metadata || { project_id: projectId, mode: "sandbox" },
|
|
318
|
-
})),
|
|
319
|
-
redactValues: [plaintext],
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return {
|
|
324
|
-
changed,
|
|
325
|
-
skipped,
|
|
326
|
-
secret: {
|
|
327
|
-
name: "SWITCHBOARD_API_KEY",
|
|
328
|
-
stored: target,
|
|
329
|
-
...metadata,
|
|
330
|
-
redacted: true,
|
|
331
|
-
},
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function redactedKeyMetadata(data) {
|
|
336
|
-
return {
|
|
337
|
-
key_id: data.id,
|
|
338
|
-
project_id: data.project_id != null ? String(data.project_id) : undefined,
|
|
339
|
-
mode: data.mode,
|
|
340
|
-
key_type: data.key_type,
|
|
341
|
-
last_four: data.last_four,
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async function runSecretSinkCommand(command, { secrets, redactValues }) {
|
|
346
|
-
const redactions = [...SECRET_PATTERNS, ...redactValues.map((value) => new RegExp(escapeRegExp(value), "g"))];
|
|
347
|
-
const payload = JSON.stringify({ secrets });
|
|
348
|
-
|
|
349
|
-
await new Promise((resolve, reject) => {
|
|
350
|
-
const child = spawn(command, {
|
|
351
|
-
shell: true,
|
|
352
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
let stdout = "";
|
|
356
|
-
let stderr = "";
|
|
357
|
-
|
|
358
|
-
child.stdout.on("data", (chunk) => {
|
|
359
|
-
stdout += redact(String(chunk), redactions);
|
|
360
|
-
});
|
|
361
|
-
child.stderr.on("data", (chunk) => {
|
|
362
|
-
stderr += redact(String(chunk), redactions);
|
|
363
|
-
});
|
|
364
|
-
child.on("error", reject);
|
|
365
|
-
child.on("close", (code) => {
|
|
366
|
-
if (code === 0) {
|
|
367
|
-
resolve();
|
|
368
|
-
} else {
|
|
369
|
-
const error = new Error(stderr || stdout || `Secret command exited with ${code}`);
|
|
370
|
-
error.code = code;
|
|
371
|
-
reject(error);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
child.stdin.end(payload);
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function redact(text, redactions) {
|
|
380
|
-
return redactions.reduce((acc, pattern) => acc.replace(pattern, "[REDACTED]"), text);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function escapeRegExp(value) {
|
|
384
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function appOriginFromApiBase(baseUrl) {
|
|
388
|
-
return String(baseUrl || "").replace(/\/v1\/?$/, "");
|
|
389
|
-
}
|
|
390
|
-
|
|
391
177
|
function humanConfigureMessage(result) {
|
|
392
178
|
const changed = result.changed.length > 0 ? result.changed.join(", ") : "none";
|
|
393
179
|
const skipped = result.skipped.length > 0 ? ` Skipped existing: ${result.skipped.join(", ")}.` : "";
|
package/lib/commands/projects.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Project management commands.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
5
6
|
import { accountRequest } from "../client.js";
|
|
6
7
|
import { saveConfig } from "../config.js";
|
|
7
8
|
import { emit, globalFlags, printList } from "../output.js";
|
|
@@ -38,8 +39,7 @@ export function registerProjectsCommands(program) {
|
|
|
38
39
|
});
|
|
39
40
|
saveConfig({
|
|
40
41
|
projectId: String(data.id),
|
|
41
|
-
|
|
42
|
-
virtualMicroserviceUrl: data.virtual_microservice_url || null,
|
|
42
|
+
clientGatewayUrl: data.client_gateway_url || null,
|
|
43
43
|
endUserSession: null,
|
|
44
44
|
});
|
|
45
45
|
emit(flags.json ? data : `Created project ${data.name} (${data.id})`, flags);
|
|
@@ -70,15 +70,22 @@ export function registerProjectsCommands(program) {
|
|
|
70
70
|
"--allowed-origins <urls>",
|
|
71
71
|
"Comma-separated browser origins for Client Gateway requests",
|
|
72
72
|
)
|
|
73
|
-
.option(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
.option(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
.option("--
|
|
73
|
+
.option("--client-gateway-enabled <boolean>")
|
|
74
|
+
.option("--project-chat-limit <number>")
|
|
75
|
+
.option("--project-chat-window-seconds <number>")
|
|
76
|
+
.option("--end-user-chat-limit <number>")
|
|
77
|
+
.option("--end-user-chat-window-seconds <number>")
|
|
78
|
+
.option("--ip-chat-limit <number>")
|
|
79
|
+
.option("--ip-chat-window-seconds <number>")
|
|
80
|
+
.option("--anonymous-sessions-per-ip-limit <number>")
|
|
81
|
+
.option("--anonymous-sessions-per-ip-window-seconds <number>")
|
|
82
|
+
.option("--session-refresh-per-end-user-limit <number>")
|
|
83
|
+
.option("--session-refresh-per-end-user-window-seconds <number>")
|
|
84
|
+
.option("--allowed-models <slugs>", "Comma-separated Switchboard catalog model slugs")
|
|
85
|
+
.option("--clear-allowed-models", "Clear the project model allowlist")
|
|
86
|
+
.option("--system-prompt <text>", "Configure a private service-side system prompt")
|
|
87
|
+
.option("--system-prompt-file <path>", "Read private service-side system prompt from a file")
|
|
88
|
+
.option("--clear-system-prompt", "Clear the private service-side system prompt")
|
|
82
89
|
.action(async (id, opts, cmd) => {
|
|
83
90
|
const flags = globalFlags(cmd);
|
|
84
91
|
const body = buildProjectUpdateBody(opts);
|
|
@@ -122,23 +129,6 @@ export function registerProjectsCommands(program) {
|
|
|
122
129
|
emit(flags.json ? data : `Provisioned managed Turnstile for project ${id}`, flags);
|
|
123
130
|
});
|
|
124
131
|
|
|
125
|
-
projects
|
|
126
|
-
.command("request-production-access <id>")
|
|
127
|
-
.description("Submit a production access request")
|
|
128
|
-
.requiredOption("--contact-email <email>")
|
|
129
|
-
.requiredOption("--use-case <text>")
|
|
130
|
-
.option("--expected-monthly-volume <volume>")
|
|
131
|
-
.option("--needed-billing-mode <mode>", "Billing mode needed for production", "developer_paid")
|
|
132
|
-
.option("--notes <text>")
|
|
133
|
-
.action(async (id, opts, cmd) => {
|
|
134
|
-
const flags = globalFlags(cmd);
|
|
135
|
-
const { data } = await accountRequest("POST", `/projects/${id}/production_access_request`, {
|
|
136
|
-
body: buildProductionAccessRequestBody(opts),
|
|
137
|
-
json: flags.json,
|
|
138
|
-
});
|
|
139
|
-
emit(flags.json ? data : `Submitted production access request for project ${id}`, flags);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
132
|
projects
|
|
143
133
|
.command("use <id>")
|
|
144
134
|
.description("Set default project for subsequent commands")
|
|
@@ -147,8 +137,7 @@ export function registerProjectsCommands(program) {
|
|
|
147
137
|
const { data } = await accountRequest("GET", `/projects/${id}`, { json: flags.json });
|
|
148
138
|
saveConfig({
|
|
149
139
|
projectId: String(data.id),
|
|
150
|
-
|
|
151
|
-
virtualMicroserviceUrl: data.virtual_microservice_url || null,
|
|
140
|
+
clientGatewayUrl: data.client_gateway_url || null,
|
|
152
141
|
endUserSession: null,
|
|
153
142
|
});
|
|
154
143
|
emit(flags.json ? data : `Using project ${data.name} (${data.id})`, flags);
|
|
@@ -165,17 +154,6 @@ export function registerProjectsCommands(program) {
|
|
|
165
154
|
});
|
|
166
155
|
emit(flags.json ? data : `Deleted project ${data.name}`, flags);
|
|
167
156
|
});
|
|
168
|
-
|
|
169
|
-
projects
|
|
170
|
-
.command("restore <id>")
|
|
171
|
-
.description("Restore a deleted project")
|
|
172
|
-
.action(async (id, _opts, cmd) => {
|
|
173
|
-
const flags = globalFlags(cmd);
|
|
174
|
-
const { data } = await accountRequest("POST", `/projects/${id}/restore`, {
|
|
175
|
-
json: flags.json,
|
|
176
|
-
});
|
|
177
|
-
emit(flags.json ? data : `Restored project ${data.name}`, flags);
|
|
178
|
-
});
|
|
179
157
|
}
|
|
180
158
|
|
|
181
159
|
/**
|
|
@@ -203,16 +181,65 @@ export function buildProjectUpdateBody(opts) {
|
|
|
203
181
|
if (opts.allowedOrigins != null) {
|
|
204
182
|
body.allowed_origins = splitList(opts.allowedOrigins);
|
|
205
183
|
}
|
|
206
|
-
if (opts.
|
|
207
|
-
body.
|
|
184
|
+
if (opts.clientGatewayEnabled != null) {
|
|
185
|
+
body.client_gateway_enabled = parseBoolean(opts.clientGatewayEnabled);
|
|
208
186
|
}
|
|
209
|
-
|
|
210
|
-
|
|
187
|
+
const abuseProtection = buildAbuseProtectionBody(opts);
|
|
188
|
+
if (abuseProtection) body.abuse_protection = abuseProtection;
|
|
189
|
+
const aiPolicy = buildAiPolicyBody(opts);
|
|
190
|
+
if (aiPolicy) body.ai_policy = aiPolicy;
|
|
191
|
+
return body;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function buildAiPolicyBody(opts) {
|
|
195
|
+
const aiPolicy = {};
|
|
196
|
+
|
|
197
|
+
if (opts.clearAllowedModels) {
|
|
198
|
+
aiPolicy.allowed_model_slugs = [];
|
|
199
|
+
} else if (opts.allowedModels != null) {
|
|
200
|
+
aiPolicy.allowed_model_slugs = splitList(opts.allowedModels);
|
|
211
201
|
}
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
|
|
203
|
+
if (opts.clearSystemPrompt) {
|
|
204
|
+
aiPolicy.system_prompt = "";
|
|
205
|
+
} else if (opts.systemPromptFile) {
|
|
206
|
+
aiPolicy.system_prompt = readFileSync(opts.systemPromptFile, "utf8");
|
|
207
|
+
} else if (opts.systemPrompt != null) {
|
|
208
|
+
aiPolicy.system_prompt = opts.systemPrompt;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return Object.keys(aiPolicy).length ? aiPolicy : null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function buildAbuseProtectionBody(opts) {
|
|
215
|
+
const limits = {};
|
|
216
|
+
addLimit(limits, "project_chat", opts.projectChatLimit, opts.projectChatWindowSeconds);
|
|
217
|
+
addLimit(limits, "end_user_chat", opts.endUserChatLimit, opts.endUserChatWindowSeconds);
|
|
218
|
+
addLimit(limits, "ip_chat", opts.ipChatLimit, opts.ipChatWindowSeconds);
|
|
219
|
+
addLimit(
|
|
220
|
+
limits,
|
|
221
|
+
"anonymous_sessions_per_ip",
|
|
222
|
+
opts.anonymousSessionsPerIpLimit,
|
|
223
|
+
opts.anonymousSessionsPerIpWindowSeconds,
|
|
224
|
+
);
|
|
225
|
+
addLimit(
|
|
226
|
+
limits,
|
|
227
|
+
"session_refresh_per_end_user",
|
|
228
|
+
opts.sessionRefreshPerEndUserLimit,
|
|
229
|
+
opts.sessionRefreshPerEndUserWindowSeconds,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return Object.keys(limits).length ? { limits } : null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function addLimit(limits, scope, limit, windowSeconds) {
|
|
236
|
+
if (limit == null && windowSeconds == null) return;
|
|
237
|
+
|
|
238
|
+
limits[scope] = {};
|
|
239
|
+
if (limit != null) limits[scope].limit = parsePositiveInteger(limit, `${scope} limit`);
|
|
240
|
+
if (windowSeconds != null) {
|
|
241
|
+
limits[scope].window_seconds = parsePositiveInteger(windowSeconds, `${scope} window seconds`);
|
|
214
242
|
}
|
|
215
|
-
return body;
|
|
216
243
|
}
|
|
217
244
|
|
|
218
245
|
export function localhostCorsOriginWarning(project) {
|
|
@@ -238,27 +265,20 @@ export function buildTurnstileBody(opts) {
|
|
|
238
265
|
return body;
|
|
239
266
|
}
|
|
240
267
|
|
|
241
|
-
export function buildProductionAccessRequestBody(opts) {
|
|
242
|
-
const body = {
|
|
243
|
-
contact_email: opts.contactEmail,
|
|
244
|
-
use_case: opts.useCase,
|
|
245
|
-
needed_billing_mode: opts.neededBillingMode || "developer_paid",
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
if (opts.expectedMonthlyVolume) {
|
|
249
|
-
body.expected_monthly_volume = opts.expectedMonthlyVolume;
|
|
250
|
-
}
|
|
251
|
-
if (opts.notes) body.notes = opts.notes;
|
|
252
|
-
|
|
253
|
-
return body;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
268
|
function parseBoolean(value) {
|
|
257
269
|
if (value === true || value === "true") return true;
|
|
258
270
|
if (value === false || value === "false") return false;
|
|
259
271
|
throw new Error(`Expected boolean value true or false, got ${value}`);
|
|
260
272
|
}
|
|
261
273
|
|
|
274
|
+
function parsePositiveInteger(value, label) {
|
|
275
|
+
const parsed = Number(value);
|
|
276
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
277
|
+
throw new Error(`Expected ${label} to be an integer greater than or equal to 1`);
|
|
278
|
+
}
|
|
279
|
+
return parsed;
|
|
280
|
+
}
|
|
281
|
+
|
|
262
282
|
function warnForLocalhostCorsOrigins(projectOrProjects, flags) {
|
|
263
283
|
if (flags.json || flags.quiet) return;
|
|
264
284
|
|