@switchboard.spot/cli 0.2.4 → 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 +18 -7
- package/bin/switchboard.js +1 -17
- package/lib/client.js +0 -3
- package/lib/commands/account.js +0 -9
- package/lib/commands/auth.js +0 -2
- package/lib/commands/billing.js +7 -69
- package/lib/commands/doctor.js +6 -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/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 +47 -7
- 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/setup.js
CHANGED
|
@@ -5,16 +5,8 @@
|
|
|
5
5
|
import { configureEnvironment } from "./env.js";
|
|
6
6
|
import { accountRequest } from "../client.js";
|
|
7
7
|
import { emit, fail, globalFlags, redactSecrets } from "../output.js";
|
|
8
|
-
import { resolveConfig } from "../config.js";
|
|
8
|
+
import { resolveConfig, saveConfig } from "../config.js";
|
|
9
9
|
import { runDoctorChecks } from "./doctor.js";
|
|
10
|
-
import {
|
|
11
|
-
ensureProject,
|
|
12
|
-
fetchProject,
|
|
13
|
-
provisionManagedTurnstile,
|
|
14
|
-
requestAccessIfNeeded,
|
|
15
|
-
saveProjectConfig,
|
|
16
|
-
validateLaunchOptions,
|
|
17
|
-
} from "./launch.js";
|
|
18
10
|
|
|
19
11
|
export function registerSetupCommand(program) {
|
|
20
12
|
const setup = program
|
|
@@ -28,24 +20,14 @@ export function registerSetupCommand(program) {
|
|
|
28
20
|
.option("--project-name <name>", "Create a project when no project id is provided")
|
|
29
21
|
.option("--project-slug <slug>", "Slug for a project created by this command")
|
|
30
22
|
.requiredOption("--origin <origin>", "Exact local, preview, or sandbox browser origin")
|
|
31
|
-
.option("--target <target>", "client
|
|
23
|
+
.option("--target <target>", "client", "client")
|
|
32
24
|
.option("--file <path>", "Local env file to update", ".env.local")
|
|
33
|
-
.option("--secret-target <target>", "local or exec", "local")
|
|
34
|
-
.option("--secret-command <command>", "Command that stores secrets from stdin JSON")
|
|
35
25
|
.option("--force", "Replace existing Switchboard-managed env values and project origins")
|
|
36
|
-
.option(
|
|
37
|
-
"--production-origin <origin>",
|
|
38
|
-
"Exact HTTPS production origin, for example https://app.example.com",
|
|
39
|
-
)
|
|
26
|
+
.option("--production-origin <origin>", "Exact HTTPS production origin to add as an allowed origin")
|
|
40
27
|
.option("--end-user-terms-url <url>")
|
|
41
28
|
.option("--end-user-privacy-url <url>")
|
|
42
29
|
.option("--support-url <url>")
|
|
43
30
|
.option("--support-email <email>")
|
|
44
|
-
.option("--contact-email <email>")
|
|
45
|
-
.option("--use-case <text>")
|
|
46
|
-
.option("--expected-monthly-volume <volume>")
|
|
47
|
-
.option("--needed-billing-mode <mode>", "Billing mode needed for production")
|
|
48
|
-
.option("--notes <text>")
|
|
49
31
|
.action(async (opts, cmd) => {
|
|
50
32
|
const flags = globalFlags(cmd);
|
|
51
33
|
const result = await setupProject(opts, { json: flags.json });
|
|
@@ -59,8 +41,6 @@ export async function setupSwitchboard(opts, dependencies = {}) {
|
|
|
59
41
|
{
|
|
60
42
|
mode: target,
|
|
61
43
|
file: opts.file,
|
|
62
|
-
target: opts.secretTarget,
|
|
63
|
-
secretCommand: opts.secretCommand,
|
|
64
44
|
projectId: opts.projectId,
|
|
65
45
|
force: opts.force,
|
|
66
46
|
},
|
|
@@ -76,11 +56,11 @@ export async function setupSwitchboard(opts, dependencies = {}) {
|
|
|
76
56
|
}
|
|
77
57
|
|
|
78
58
|
function normalizeSetupTarget(target, json) {
|
|
79
|
-
if (target === "client" || target ===
|
|
59
|
+
if (target === "client" || target === undefined) {
|
|
80
60
|
return target;
|
|
81
61
|
}
|
|
82
62
|
|
|
83
|
-
fail("--target must be client
|
|
63
|
+
fail("--target must be client", 1, json);
|
|
84
64
|
}
|
|
85
65
|
|
|
86
66
|
function smokeCheck(result) {
|
|
@@ -94,11 +74,6 @@ function smokeCheck(result) {
|
|
|
94
74
|
ok: true,
|
|
95
75
|
checks: [
|
|
96
76
|
clientEnvConfigured ? "client_env" : null,
|
|
97
|
-
result.changed.includes("SWITCHBOARD_BASE_URL") ||
|
|
98
|
-
result.skipped.includes("SWITCHBOARD_BASE_URL")
|
|
99
|
-
? "server_env"
|
|
100
|
-
: null,
|
|
101
|
-
result.secrets.length > 0 ? "server_secret" : null,
|
|
102
77
|
].filter(Boolean),
|
|
103
78
|
};
|
|
104
79
|
}
|
|
@@ -119,9 +94,6 @@ export async function setupProject(
|
|
|
119
94
|
config = resolveConfig(),
|
|
120
95
|
cwd,
|
|
121
96
|
json = false,
|
|
122
|
-
getSecret,
|
|
123
|
-
setSecret,
|
|
124
|
-
runSecretCommand,
|
|
125
97
|
} = {},
|
|
126
98
|
) {
|
|
127
99
|
validateProjectSetupOptions(opts, { json });
|
|
@@ -140,12 +112,10 @@ export async function setupProject(
|
|
|
140
112
|
{
|
|
141
113
|
mode: normalizeSetupTarget(opts.target, json),
|
|
142
114
|
file: opts.file,
|
|
143
|
-
target: opts.secretTarget,
|
|
144
|
-
secretCommand: opts.secretCommand,
|
|
145
115
|
projectId: String(configured.id),
|
|
146
116
|
force: opts.force,
|
|
147
117
|
},
|
|
148
|
-
{ request, cwd, json
|
|
118
|
+
{ request, cwd, json },
|
|
149
119
|
);
|
|
150
120
|
|
|
151
121
|
const idempotencyKey = `setup-project-${configured.id}`;
|
|
@@ -153,18 +123,13 @@ export async function setupProject(
|
|
|
153
123
|
request,
|
|
154
124
|
});
|
|
155
125
|
|
|
156
|
-
let access = null;
|
|
157
|
-
if (hasProductionOptions(opts)) {
|
|
158
|
-
access = await requestAccessIfNeeded(configured.id, configured, opts, flags, { request });
|
|
159
|
-
}
|
|
160
|
-
|
|
161
126
|
const latest = await fetchProject(configured.id, flags, { request });
|
|
162
127
|
saveProject(latest);
|
|
163
128
|
|
|
164
129
|
const doctorConfig = {
|
|
165
130
|
...config,
|
|
166
131
|
projectId: String(latest.id),
|
|
167
|
-
|
|
132
|
+
clientGatewayUrl: latest.client_gateway_url || config.clientGatewayUrl,
|
|
168
133
|
};
|
|
169
134
|
const readiness = await doctor({ config: doctorConfig, request });
|
|
170
135
|
|
|
@@ -173,7 +138,6 @@ export async function setupProject(
|
|
|
173
138
|
env,
|
|
174
139
|
challenge,
|
|
175
140
|
readiness,
|
|
176
|
-
access,
|
|
177
141
|
origin: opts.origin,
|
|
178
142
|
origins,
|
|
179
143
|
});
|
|
@@ -188,19 +152,15 @@ function validateProjectSetupOptions(opts, flags) {
|
|
|
188
152
|
fail("--origin must be an exact browser origin such as http://127.0.0.1:4173", 1, flags.json);
|
|
189
153
|
}
|
|
190
154
|
|
|
191
|
-
if (!
|
|
155
|
+
if (!opts.productionOrigin) return;
|
|
192
156
|
|
|
193
|
-
|
|
157
|
+
if (!productionHttpsOrigin(opts.productionOrigin)) {
|
|
158
|
+
fail("--production-origin must be an exact HTTPS production origin", 1, flags.json);
|
|
159
|
+
}
|
|
194
160
|
|
|
195
|
-
for (const name of [
|
|
196
|
-
"endUserTermsUrl",
|
|
197
|
-
"endUserPrivacyUrl",
|
|
198
|
-
"supportEmail",
|
|
199
|
-
"contactEmail",
|
|
200
|
-
"useCase",
|
|
201
|
-
]) {
|
|
161
|
+
for (const name of ["endUserTermsUrl", "endUserPrivacyUrl", "supportEmail"]) {
|
|
202
162
|
if (!opts[name]) {
|
|
203
|
-
fail(`--${dasherize(name)} is required when production
|
|
163
|
+
fail(`--${dasherize(name)} is required when --production-origin is provided`, 1, flags.json);
|
|
204
164
|
}
|
|
205
165
|
}
|
|
206
166
|
}
|
|
@@ -212,11 +172,6 @@ function hasProductionOptions(opts) {
|
|
|
212
172
|
"endUserPrivacyUrl",
|
|
213
173
|
"supportUrl",
|
|
214
174
|
"supportEmail",
|
|
215
|
-
"contactEmail",
|
|
216
|
-
"useCase",
|
|
217
|
-
"expectedMonthlyVolume",
|
|
218
|
-
"neededBillingMode",
|
|
219
|
-
"notes",
|
|
220
175
|
].some((key) => opts[key] != null);
|
|
221
176
|
}
|
|
222
177
|
|
|
@@ -233,7 +188,7 @@ function configuredOrigins(project, opts) {
|
|
|
233
188
|
async function updateSetupProject(projectId, opts, origins, flags, { request, save }) {
|
|
234
189
|
const body = {
|
|
235
190
|
allowed_origins: origins,
|
|
236
|
-
|
|
191
|
+
client_gateway_enabled: true,
|
|
237
192
|
};
|
|
238
193
|
|
|
239
194
|
if (hasProductionOptions(opts)) {
|
|
@@ -251,25 +206,20 @@ async function updateSetupProject(projectId, opts, origins, flags, { request, sa
|
|
|
251
206
|
return data;
|
|
252
207
|
}
|
|
253
208
|
|
|
254
|
-
function projectSetupSummary({ project, env, challenge, readiness,
|
|
209
|
+
function projectSetupSummary({ project, env, challenge, readiness, origin, origins }) {
|
|
255
210
|
const hardFailures = readiness.checks.filter((check) => !check.ok).map((check) => check.name);
|
|
256
211
|
const configured = hardFailures.length === 0;
|
|
257
|
-
const productionRequested = Boolean(access);
|
|
258
212
|
|
|
259
213
|
return {
|
|
260
214
|
object: "setup_project_result",
|
|
261
215
|
ok: configured,
|
|
262
|
-
status:
|
|
263
|
-
? "failed"
|
|
264
|
-
: productionRequested
|
|
265
|
-
? "production_requested"
|
|
266
|
-
: "ready_for_browser_manual_check",
|
|
216
|
+
status: configured ? "ready_for_browser_manual_check" : "failed",
|
|
267
217
|
configured,
|
|
268
218
|
ready_for_browser_manual_check: configured,
|
|
269
|
-
production_requested:
|
|
219
|
+
production_requested: false,
|
|
270
220
|
project_id: project.id,
|
|
271
221
|
project_slug: project.slug,
|
|
272
|
-
client_gateway_url: project.
|
|
222
|
+
client_gateway_url: project.client_gateway_url,
|
|
273
223
|
allowed_origins: origins,
|
|
274
224
|
local_origin: origin,
|
|
275
225
|
turnstile: challenge,
|
|
@@ -277,13 +227,11 @@ function projectSetupSummary({ project, env, challenge, readiness, access, origi
|
|
|
277
227
|
env,
|
|
278
228
|
doctor: readiness,
|
|
279
229
|
hard_failures: hardFailures,
|
|
280
|
-
production_access:
|
|
230
|
+
production_access: null,
|
|
281
231
|
next_steps: [
|
|
282
232
|
"Use the SDK-managed browser challenge in your app to confirm browser chat manually.",
|
|
283
233
|
"Run switchboard verify setup after the app integration is wired.",
|
|
284
|
-
|
|
285
|
-
? "Run switchboard verify publish after approval and billing readiness are complete."
|
|
286
|
-
: "Use switchboard launch prepare when you are ready for production approval.",
|
|
234
|
+
"Run switchboard verify publish after production billing readiness is complete.",
|
|
287
235
|
],
|
|
288
236
|
};
|
|
289
237
|
}
|
|
@@ -298,16 +246,6 @@ export function humanProjectSetupMessage(result) {
|
|
|
298
246
|
`Readiness: ${result.configured ? "ready for browser manual check" : "failed"}`,
|
|
299
247
|
];
|
|
300
248
|
|
|
301
|
-
if (result.production_requested) {
|
|
302
|
-
lines.push(
|
|
303
|
-
`Production access: ${
|
|
304
|
-
result.production_access?.project_production_access_status ||
|
|
305
|
-
result.production_access?.status ||
|
|
306
|
-
"requested"
|
|
307
|
-
}`,
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
249
|
if (result.hard_failures.length > 0) {
|
|
312
250
|
lines.push(`Hard failures: ${result.hard_failures.join(", ")}`);
|
|
313
251
|
}
|
|
@@ -315,12 +253,79 @@ export function humanProjectSetupMessage(result) {
|
|
|
315
253
|
lines.push(
|
|
316
254
|
"Next: integrate the SDK browser challenge in your app, then run switchboard verify setup.",
|
|
317
255
|
);
|
|
256
|
+
return lines.join("\n");
|
|
257
|
+
}
|
|
318
258
|
|
|
319
|
-
|
|
320
|
-
|
|
259
|
+
export async function ensureProject(opts, flags, { request = accountRequest, save = saveProjectConfig } = {}) {
|
|
260
|
+
if (opts.projectId) {
|
|
261
|
+
const project = await fetchProject(opts.projectId, flags, { request });
|
|
262
|
+
save(project);
|
|
263
|
+
return project;
|
|
321
264
|
}
|
|
322
265
|
|
|
323
|
-
|
|
266
|
+
if (opts.projectName) {
|
|
267
|
+
const { data } = await request("POST", "/projects", {
|
|
268
|
+
body: { name: opts.projectName, slug: opts.projectSlug },
|
|
269
|
+
json: flags.json,
|
|
270
|
+
});
|
|
271
|
+
save(data);
|
|
272
|
+
return data;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const { data } = await request("GET", "/me", { json: flags.json });
|
|
276
|
+
if (!data.project?.id) {
|
|
277
|
+
fail(
|
|
278
|
+
"No default project is selected. Use --project-id or --project-name with --project-slug.",
|
|
279
|
+
1,
|
|
280
|
+
flags.json,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
save(data.project);
|
|
284
|
+
return data.project;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export async function fetchProject(projectId, flags, { request = accountRequest } = {}) {
|
|
288
|
+
const { data } = await request("GET", `/projects/${projectId}`, { json: flags.json });
|
|
289
|
+
return data;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function provisionManagedTurnstile(
|
|
293
|
+
projectId,
|
|
294
|
+
idempotencyKey,
|
|
295
|
+
flags,
|
|
296
|
+
{ request = accountRequest } = {},
|
|
297
|
+
) {
|
|
298
|
+
const { data } = await request("POST", `/projects/${projectId}/turnstile/provision`, {
|
|
299
|
+
body: { idempotency_key: `${idempotencyKey}:turnstile` },
|
|
300
|
+
json: flags.json,
|
|
301
|
+
});
|
|
302
|
+
return data;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function saveProjectConfig(project) {
|
|
306
|
+
saveConfig({
|
|
307
|
+
projectId: String(project.id),
|
|
308
|
+
clientGatewayUrl: project.client_gateway_url || null,
|
|
309
|
+
endUserSession: null,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function productionHttpsOrigin(origin) {
|
|
314
|
+
try {
|
|
315
|
+
const url = new URL(origin);
|
|
316
|
+
return (
|
|
317
|
+
url.protocol === "https:" &&
|
|
318
|
+
url.username === "" &&
|
|
319
|
+
url.password === "" &&
|
|
320
|
+
url.pathname === "/" &&
|
|
321
|
+
url.search === "" &&
|
|
322
|
+
url.hash === "" &&
|
|
323
|
+
!["localhost", "127.0.0.1", "::1"].includes(url.hostname) &&
|
|
324
|
+
!url.hostname.includes("*")
|
|
325
|
+
);
|
|
326
|
+
} catch {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
324
329
|
}
|
|
325
330
|
|
|
326
331
|
function turnstileStatus(challenge) {
|
package/lib/commands/usage.js
CHANGED
|
@@ -26,27 +26,4 @@ export function registerUsageCommands(program) {
|
|
|
26
26
|
console.log(`Dev margin (micros): ${data.dev_margin_micros}`);
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
|
-
|
|
30
|
-
usage
|
|
31
|
-
.command("requests")
|
|
32
|
-
.description("List recent gateway requests")
|
|
33
|
-
.option("--mode <mode>", "all, sandbox, or live", "all")
|
|
34
|
-
.option("--range <range>", "mtd, 7d, 30d, or all", "mtd")
|
|
35
|
-
.option("--status <status>")
|
|
36
|
-
.option("--q <query>")
|
|
37
|
-
.option("--limit <limit>", "Maximum rows", parseInt)
|
|
38
|
-
.action(async (opts, cmd) => {
|
|
39
|
-
const flags = globalFlags(cmd);
|
|
40
|
-
const params = new URLSearchParams();
|
|
41
|
-
params.set("mode", opts.mode);
|
|
42
|
-
params.set("range", opts.range);
|
|
43
|
-
if (opts.status) params.set("status", opts.status);
|
|
44
|
-
if (opts.q) params.set("q", opts.q);
|
|
45
|
-
if (opts.limit != null) params.set("limit", String(opts.limit));
|
|
46
|
-
|
|
47
|
-
const { data } = await accountRequest("GET", `/usage/requests?${params}`, {
|
|
48
|
-
json: flags.json,
|
|
49
|
-
});
|
|
50
|
-
emit(flags.json ? data : JSON.stringify(data, null, 2), flags);
|
|
51
|
-
});
|
|
52
29
|
}
|
package/lib/config.js
CHANGED
|
@@ -73,14 +73,13 @@ export function resolveConfig() {
|
|
|
73
73
|
const file = loadConfig();
|
|
74
74
|
return {
|
|
75
75
|
baseUrl: process.env.SWITCHBOARD_BASE_URL || file.baseUrl || DEFAULT_BASE_URL,
|
|
76
|
-
|
|
76
|
+
clientGatewayUrl:
|
|
77
77
|
process.env.SWITCHBOARD_CLIENT_URL ||
|
|
78
78
|
process.env.VITE_SWITCHBOARD_CLIENT_URL ||
|
|
79
|
-
file.
|
|
79
|
+
file.clientGatewayUrl ||
|
|
80
80
|
null,
|
|
81
81
|
accountToken: null,
|
|
82
82
|
accountTokenSource: null,
|
|
83
|
-
apiKey: process.env.SWITCHBOARD_API_KEY || null,
|
|
84
83
|
endUserSession:
|
|
85
84
|
process.env.SWITCHBOARD_END_USER_SESSION || file.endUserSession || null,
|
|
86
85
|
projectId: process.env.SWITCHBOARD_PROJECT_ID || file.projectId || null,
|
package/lib/credentialStore.js
CHANGED
|
@@ -17,7 +17,6 @@ import { promisify } from "node:util";
|
|
|
17
17
|
const execFileAsync = promisify(execFile);
|
|
18
18
|
const SERVICE = "Switchboard CLI";
|
|
19
19
|
const ACCOUNT = "account-session";
|
|
20
|
-
const PROJECT_SECRET_PREFIX = "project-secret";
|
|
21
20
|
const ACCOUNT_SESSION_FILE = "account-session.json";
|
|
22
21
|
|
|
23
22
|
/**
|
|
@@ -197,139 +196,6 @@ function chmodAccountSessionFile(configDir) {
|
|
|
197
196
|
}
|
|
198
197
|
}
|
|
199
198
|
|
|
200
|
-
/**
|
|
201
|
-
* Reads a stored project secret key from the OS keychain.
|
|
202
|
-
*/
|
|
203
|
-
export async function getProjectSecretKey(projectId, mode = "sandbox") {
|
|
204
|
-
return getCredential(projectSecretAccount(projectId, mode));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Stores a project secret key in the OS keychain.
|
|
209
|
-
*/
|
|
210
|
-
export async function setProjectSecretKey(projectId, mode, token) {
|
|
211
|
-
if (!token) {
|
|
212
|
-
throw new Error("Cannot store an empty Switchboard project secret key");
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return setCredential(projectSecretAccount(projectId, mode), token);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Deletes a stored project secret key from the OS keychain.
|
|
220
|
-
*/
|
|
221
|
-
export async function deleteProjectSecretKey(projectId, mode = "sandbox") {
|
|
222
|
-
return deleteCredential(projectSecretAccount(projectId, mode));
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function projectSecretAccount(projectId, mode) {
|
|
226
|
-
if (!projectId) {
|
|
227
|
-
throw new Error("Project id is required for Switchboard project secret storage");
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return `${PROJECT_SECRET_PREFIX}:${projectId}:${mode}`;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async function getCredential(account) {
|
|
234
|
-
try {
|
|
235
|
-
if (process.platform === "darwin") {
|
|
236
|
-
const { stdout } = await execFileAsync("security", [
|
|
237
|
-
"find-generic-password",
|
|
238
|
-
"-a",
|
|
239
|
-
account,
|
|
240
|
-
"-s",
|
|
241
|
-
SERVICE,
|
|
242
|
-
"-w",
|
|
243
|
-
]);
|
|
244
|
-
return stdout.trim() || null;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (process.platform === "linux") {
|
|
248
|
-
const { stdout } = await execFileAsync("secret-tool", [
|
|
249
|
-
"lookup",
|
|
250
|
-
"service",
|
|
251
|
-
SERVICE,
|
|
252
|
-
"account",
|
|
253
|
-
account,
|
|
254
|
-
]);
|
|
255
|
-
return stdout.trim() || null;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
throw unsupportedPlatformError();
|
|
259
|
-
} catch (error) {
|
|
260
|
-
if (credentialMissing(error)) {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
throw normalizeKeychainError(error);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async function setCredential(account, token) {
|
|
269
|
-
try {
|
|
270
|
-
if (process.platform === "darwin") {
|
|
271
|
-
await execFileAsync("security", [
|
|
272
|
-
"add-generic-password",
|
|
273
|
-
"-a",
|
|
274
|
-
account,
|
|
275
|
-
"-s",
|
|
276
|
-
SERVICE,
|
|
277
|
-
"-w",
|
|
278
|
-
token,
|
|
279
|
-
"-U",
|
|
280
|
-
]);
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (process.platform === "linux") {
|
|
285
|
-
await execFileWithInput(
|
|
286
|
-
"secret-tool",
|
|
287
|
-
["store", "--label", SERVICE, "service", SERVICE, "account", account],
|
|
288
|
-
token,
|
|
289
|
-
);
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
throw unsupportedPlatformError();
|
|
294
|
-
} catch (error) {
|
|
295
|
-
throw normalizeKeychainError(error);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async function deleteCredential(account) {
|
|
300
|
-
try {
|
|
301
|
-
if (process.platform === "darwin") {
|
|
302
|
-
await execFileAsync("security", [
|
|
303
|
-
"delete-generic-password",
|
|
304
|
-
"-a",
|
|
305
|
-
account,
|
|
306
|
-
"-s",
|
|
307
|
-
SERVICE,
|
|
308
|
-
]);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (process.platform === "linux") {
|
|
313
|
-
await execFileAsync("secret-tool", [
|
|
314
|
-
"clear",
|
|
315
|
-
"service",
|
|
316
|
-
SERVICE,
|
|
317
|
-
"account",
|
|
318
|
-
account,
|
|
319
|
-
]);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
throw unsupportedPlatformError();
|
|
324
|
-
} catch (error) {
|
|
325
|
-
if (credentialMissing(error)) {
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
throw normalizeKeychainError(error);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
199
|
function credentialMissing(error) {
|
|
334
200
|
return error?.code === 44 || error?.code === 1;
|
|
335
201
|
}
|
package/lib/docsClient.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Public docs client used by CLI docs commands and the embedded MCP server.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { gatewayApiUrl,
|
|
5
|
+
import { gatewayApiUrl, resolveConfig } from "./config.js";
|
|
6
6
|
import { redactSecrets } from "./output.js";
|
|
7
7
|
|
|
8
8
|
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
@@ -43,54 +43,6 @@ export async function models({ config } = {}) {
|
|
|
43
43
|
return redactSecrets(data);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export async function integrationKit({ stack, config } = {}) {
|
|
47
|
-
let cfg;
|
|
48
|
-
try {
|
|
49
|
-
cfg = await resolveAccountConfig(config || resolveConfig());
|
|
50
|
-
} catch (error) {
|
|
51
|
-
return {
|
|
52
|
-
ok: false,
|
|
53
|
-
error: {
|
|
54
|
-
type: "keychain_unavailable",
|
|
55
|
-
message: error.message || "Could not read Switchboard account session from the OS keychain.",
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!cfg.accountToken) {
|
|
61
|
-
return {
|
|
62
|
-
ok: false,
|
|
63
|
-
error: {
|
|
64
|
-
type: "authentication_required",
|
|
65
|
-
message: "Run `switchboard auth login`, then select a project with `switchboard projects use <id>`.",
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (!cfg.projectId) {
|
|
71
|
-
return {
|
|
72
|
-
ok: false,
|
|
73
|
-
error: {
|
|
74
|
-
type: "project_required",
|
|
75
|
-
message: "Select a project with `switchboard projects use <id>` or set SWITCHBOARD_PROJECT_ID.",
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const params = new URLSearchParams({ project_id: cfg.projectId });
|
|
81
|
-
if (stack) params.set("stack", stack);
|
|
82
|
-
|
|
83
|
-
const url = `${accountApiUrl(cfg)}/integration_kit?${params}`;
|
|
84
|
-
const data = await fetchJson(url, {
|
|
85
|
-
headers: {
|
|
86
|
-
Authorization: `Bearer ${cfg.accountToken}`,
|
|
87
|
-
Accept: "application/json",
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
return redactSecrets(data);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
46
|
export async function publicResource(name, options = {}) {
|
|
95
47
|
switch (name) {
|
|
96
48
|
case "docs":
|
|
@@ -101,8 +53,6 @@ export async function publicResource(name, options = {}) {
|
|
|
101
53
|
return readDoc("knowledge", options);
|
|
102
54
|
case "openapi":
|
|
103
55
|
return openApi(options);
|
|
104
|
-
case "integration-kit":
|
|
105
|
-
return integrationKit(options);
|
|
106
56
|
case "capabilities":
|
|
107
57
|
return docsCapabilities(options);
|
|
108
58
|
default:
|
package/lib/mcpServer.js
CHANGED
|
@@ -7,7 +7,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import {
|
|
9
9
|
docsCapabilities,
|
|
10
|
-
integrationKit,
|
|
11
10
|
listDocs,
|
|
12
11
|
models,
|
|
13
12
|
openApi,
|
|
@@ -53,13 +52,6 @@ function registerResources(server) {
|
|
|
53
52
|
registerJsonResource(server, "switchboard_openapi", "switchboard://openapi", "Switchboard OpenAPI schema", () =>
|
|
54
53
|
openApi(),
|
|
55
54
|
);
|
|
56
|
-
registerJsonResource(
|
|
57
|
-
server,
|
|
58
|
-
"switchboard_integration_kit",
|
|
59
|
-
"switchboard://integration-kit",
|
|
60
|
-
"Project Integration Kit for the selected CLI project",
|
|
61
|
-
() => integrationKit(),
|
|
62
|
-
);
|
|
63
55
|
registerJsonResource(
|
|
64
56
|
server,
|
|
65
57
|
"switchboard_capabilities",
|
|
@@ -132,19 +124,6 @@ function registerTools(server) {
|
|
|
132
124
|
async ({ id }) => jsonTool(await readDoc(id)),
|
|
133
125
|
);
|
|
134
126
|
|
|
135
|
-
server.registerTool(
|
|
136
|
-
"switchboard_integration_kit",
|
|
137
|
-
{
|
|
138
|
-
title: "Switchboard Integration Kit",
|
|
139
|
-
description: "Return Integration Kit data for the logged-in selected CLI project.",
|
|
140
|
-
inputSchema: {
|
|
141
|
-
stack: z.string().optional(),
|
|
142
|
-
},
|
|
143
|
-
annotations: { readOnlyHint: true },
|
|
144
|
-
},
|
|
145
|
-
async ({ stack }) => jsonTool(await integrationKit({ stack })),
|
|
146
|
-
);
|
|
147
|
-
|
|
148
127
|
server.registerTool(
|
|
149
128
|
"switchboard_models",
|
|
150
129
|
{
|