@typeship-ax/cli 0.22.0 → 0.23.1
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/AGENTS.md +12 -8
- package/README.md +14 -27
- package/api.json +8898 -9026
- package/api.md +369 -327
- package/dist/arguments.d.ts +47 -0
- package/dist/arguments.d.ts.map +1 -0
- package/dist/arguments.js +254 -0
- package/dist/cli-agent.d.ts +31 -8
- package/dist/cli-agent.d.ts.map +1 -1
- package/dist/cli-agent.js +146 -28
- package/dist/cli.js +483 -237
- package/dist/core/http.d.ts +162 -19
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +381 -48
- package/dist/core/pagination.d.ts +42 -6
- package/dist/core/pagination.d.ts.map +1 -1
- package/dist/core/pagination.js +111 -17
- package/dist/credential-storage.d.ts +10 -3
- package/dist/credential-storage.d.ts.map +1 -1
- package/dist/credential-storage.js +15 -6
- package/dist/dates.d.ts +1 -1
- package/dist/dates.js +1 -1
- package/dist/errors.d.ts +20 -84
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -108
- package/dist/fields.d.ts +29 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +101 -0
- package/dist/index.d.ts +28 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -25
- package/dist/named-credentials.d.ts +19 -0
- package/dist/named-credentials.d.ts.map +1 -1
- package/dist/named-credentials.js +81 -1
- package/dist/oauth-login.d.ts +8 -2
- package/dist/oauth-login.d.ts.map +1 -1
- package/dist/oauth-login.js +31 -19
- package/dist/oauth-request.d.ts +7 -1
- package/dist/oauth-request.d.ts.map +1 -1
- package/dist/oauth-request.js +26 -4
- package/dist/oauth-session.d.ts +13 -1
- package/dist/oauth-session.d.ts.map +1 -1
- package/dist/oauth-session.js +34 -18
- package/dist/ops.d.ts +53 -5
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +49 -40
- package/dist/polling-login.d.ts +8 -2
- package/dist/polling-login.d.ts.map +1 -1
- package/dist/polling-login.js +25 -11
- package/dist/resources/api-keys.d.ts +10 -7
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +10 -31
- package/dist/resources/deliveries.d.ts +88 -4
- package/dist/resources/deliveries.d.ts.map +1 -1
- package/dist/resources/deliveries.js +95 -18
- package/dist/resources/drafts.d.ts +15 -15
- package/dist/resources/drafts.d.ts.map +1 -1
- package/dist/resources/drafts.js +11 -64
- package/dist/resources/files.d.ts +4 -4
- package/dist/resources/files.d.ts.map +1 -1
- package/dist/resources/files.js +3 -12
- package/dist/resources/generations.d.ts +14 -14
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +21 -45
- package/dist/resources/organization.d.ts +4 -4
- package/dist/resources/organization.d.ts.map +1 -1
- package/dist/resources/organization.js +3 -10
- package/dist/resources/{generate.d.ts → packages.d.ts} +16 -16
- package/dist/resources/packages.d.ts.map +1 -0
- package/dist/resources/{generate.js → packages.js} +13 -29
- package/dist/resources/projects.d.ts +50 -50
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +60 -116
- package/dist/resources/releases.d.ts +21 -16
- package/dist/resources/releases.d.ts.map +1 -1
- package/dist/resources/releases.js +18 -39
- package/dist/resources/spec-revisions.d.ts +15 -6
- package/dist/resources/spec-revisions.d.ts.map +1 -1
- package/dist/resources/spec-revisions.js +6 -28
- package/dist/resources/specs.d.ts +7 -7
- package/dist/resources/specs.d.ts.map +1 -1
- package/dist/resources/specs.js +6 -34
- package/dist/resources/targets.d.ts +48 -48
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +58 -114
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +83 -81
- package/dist/search.d.ts +54 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +421 -0
- package/dist/types.d.ts +499 -339
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +18 -18
- package/package.json +5 -2
- package/src/arguments.ts +242 -0
- package/src/cli-agent.ts +156 -30
- package/src/cli.ts +444 -211
- package/src/core/http.ts +457 -58
- package/src/core/pagination.ts +129 -18
- package/src/credential-storage.ts +16 -6
- package/src/dates.ts +1 -1
- package/src/errors.ts +46 -115
- package/src/fields.ts +91 -0
- package/src/index.ts +45 -28
- package/src/named-credentials.ts +66 -1
- package/src/oauth-login.ts +36 -21
- package/src/oauth-request.ts +32 -6
- package/src/oauth-session.ts +37 -19
- package/src/ops.ts +82 -44
- package/src/polling-login.ts +24 -11
- package/src/resources/api-keys.ts +34 -48
- package/src/resources/deliveries.ts +211 -30
- package/src/resources/drafts.ts +60 -107
- package/src/resources/files.ts +19 -20
- package/src/resources/generations.ts +57 -75
- package/src/resources/organization.ts +11 -16
- package/src/resources/{generate.ts → packages.ts} +43 -51
- package/src/resources/projects.ts +145 -200
- package/src/resources/releases.ts +48 -65
- package/src/resources/spec-revisions.ts +38 -47
- package/src/resources/specs.ts +39 -59
- package/src/resources/targets.ts +143 -193
- package/src/schemas.ts +83 -81
- package/src/search.ts +434 -0
- package/src/types.ts +538 -357
- package/dist/console-login-check.d.ts +0 -21
- package/dist/console-login-check.d.ts.map +0 -1
- package/dist/console-login-check.js +0 -107
- package/dist/console-login-contract.d.ts +0 -45
- package/dist/console-login-contract.d.ts.map +0 -1
- package/dist/console-login-contract.js +0 -40
- package/dist/resources/generate.d.ts.map +0 -1
- package/dist/resources/publications.d.ts +0 -47
- package/dist/resources/publications.d.ts.map +0 -1
- package/dist/resources/publications.js +0 -70
- package/src/console-login-check.ts +0 -88
- package/src/console-login-contract.ts +0 -65
- package/src/resources/publications.ts +0 -140
package/dist/cli-agent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The agent contract of a generated CLI. Generated by
|
|
2
|
+
* The agent contract of a generated CLI. Generated by Typeship — https://typeship.dev
|
|
3
3
|
*
|
|
4
4
|
* Everything a coding agent needs from a command-line tool and a human does
|
|
5
5
|
* not: a JSON error envelope with stable codes and next steps, agent-mode
|
|
@@ -27,6 +27,7 @@ export function exitCodeFor(code) {
|
|
|
27
27
|
case "UNKNOWN_COMMAND":
|
|
28
28
|
case "UNKNOWN_FLAG":
|
|
29
29
|
case "MISSING_ARGUMENT":
|
|
30
|
+
case "FIELDS_UNMATCHED":
|
|
30
31
|
case "TTY_REQUIRED":
|
|
31
32
|
case "CONFIRMATION_REQUIRED":
|
|
32
33
|
return 2;
|
|
@@ -38,9 +39,9 @@ export function exitCodeFor(code) {
|
|
|
38
39
|
export function classifyApiError(error, context) {
|
|
39
40
|
const e = (error ?? {});
|
|
40
41
|
// The message is the API's own words when it sent any, else the SDK's
|
|
41
|
-
// (
|
|
42
|
-
//
|
|
43
|
-
//
|
|
42
|
+
// ("HTTP 404"). The error class name rides in detail, not in front of the
|
|
43
|
+
// message: "NotFoundError: No such account (no such account)" said one
|
|
44
|
+
// thing three times.
|
|
44
45
|
const base = e.message ? e.message : String(error);
|
|
45
46
|
if (e.violations !== undefined) {
|
|
46
47
|
const nextStep = e.direction === "response"
|
|
@@ -50,12 +51,12 @@ export function classifyApiError(error, context) {
|
|
|
50
51
|
: "Correct the body fields named in detail.violations to match their constraints, then run the command again.";
|
|
51
52
|
return { code: "VALIDATION_FAILED", message: base, detail: { ...(e.name ? { error: e.name } : {}), violations: e.violations }, nextSteps: [nextStep] };
|
|
52
53
|
}
|
|
53
|
-
if (e.name === "TransportError" || (typeof e.status !== "number" && /fetch|ECONN|ENOTFOUND|timed out|TLS|abort/i.test(base))) {
|
|
54
|
+
if (e.name === "TransportError" || (e.name !== "PaginationError" && typeof e.status !== "number" && /fetch|ECONN|ENOTFOUND|timed out|TLS|abort/i.test(base))) {
|
|
54
55
|
return {
|
|
55
56
|
code: "NETWORK_ERROR",
|
|
56
57
|
message: base,
|
|
57
58
|
nextSteps: [
|
|
58
|
-
"Check the base URL (--base-url, the " + context.
|
|
59
|
+
"Check the base URL (--base-url, the " + context.envPrefix + "_BASE_URL variable, or '" + context.bin + " config base-url') and the network.",
|
|
59
60
|
"Retry once with backoff; do not loop.",
|
|
60
61
|
],
|
|
61
62
|
};
|
|
@@ -72,11 +73,33 @@ export function classifyApiError(error, context) {
|
|
|
72
73
|
const same = apiMessage !== undefined && (apiMessage.toLowerCase() === base.toLowerCase() || base.toLowerCase().includes(apiMessage.toLowerCase()) || apiMessage.toLowerCase().includes(base.toLowerCase()));
|
|
73
74
|
const message = apiMessage === undefined ? base : same ? apiMessage : apiMessage + " (" + base + ")";
|
|
74
75
|
const upgradeUrl = extractUrl(e.body, ["upgrade_url", "upgradeUrl", "signup_url", "claim_url"]);
|
|
76
|
+
// A rate limit can arrive as a 403 (GitHub); the SDK marks it either way.
|
|
77
|
+
if (status === 429 || e.rateLimit !== undefined) {
|
|
78
|
+
return { status: "action_required", code: "RATE_LIMITED", message, detail: { ...detail, ...(e.rateLimit?.retryAt ? { retry_at: isoSeconds(e.rateLimit.retryAt) } : {}) }, nextSteps: [rateLimitNextStep(e.rateLimit?.retryAt, "run the same command again")] };
|
|
79
|
+
}
|
|
80
|
+
// A failure the API reported inside a 2xx body: classify by its code.
|
|
81
|
+
const scopes = context.requiredScopes ?? [];
|
|
82
|
+
const scopeFailure = () => ({ code: "INSUFFICIENT_SCOPE", message: message + " This operation requires the OAuth scopes: " + scopes.join(", ") + ".", detail: { ...detail, required_scopes: scopes }, nextSteps: [
|
|
83
|
+
context.canLogin ? "Sign in again with those scopes: '" + context.bin + " login --scopes " + scopes.join(",") + "'." : "Use a token granted those scopes.",
|
|
84
|
+
"If the token already has them, the account may lack access to this resource.",
|
|
85
|
+
] });
|
|
86
|
+
if (e.name === "PayloadError") {
|
|
87
|
+
if (scopes.length && /^missing_scope$/i.test(String(e.code)))
|
|
88
|
+
return scopeFailure();
|
|
89
|
+
const reported = payloadFailureCode(e.code);
|
|
90
|
+
if (reported === "AUTH_INVALID")
|
|
91
|
+
return { code: "AUTH_INVALID", message, detail, nextSteps: ["The API rejected the credential (" + String(e.code) + "). Check it is current: '" + context.bin + " auth check'."] };
|
|
92
|
+
if (reported === "RATE_LIMITED")
|
|
93
|
+
return { status: "action_required", code: "RATE_LIMITED", message, detail, nextSteps: ["Wait, then run the same command again."] };
|
|
94
|
+
return { code: "CALL_FAILED", message, detail, nextSteps: ["The API reported a failure in a successful response; detail.body says why."] };
|
|
95
|
+
}
|
|
75
96
|
if (status === 401) {
|
|
76
97
|
return context.hadCredential
|
|
77
98
|
? { code: "AUTH_INVALID", message, detail, nextSteps: ["The credential was rejected. Check it is current: '" + context.bin + " auth check', then '" + context.bin + " login --help' to store a new one."] }
|
|
78
99
|
: { status: "action_required", code: "NO_AUTH", message, detail, nextSteps: ["No credential was sent. Set the auth env var, pass --token, or run '" + context.bin + " login'.", "'" + context.bin + " auth check' shows what the CLI would send."] };
|
|
79
100
|
}
|
|
101
|
+
if (status === 403 && scopes.length)
|
|
102
|
+
return scopeFailure();
|
|
80
103
|
if (status === 403)
|
|
81
104
|
return { code: "AUTH_INVALID", message, detail, nextSteps: ["The credential lacks access to this operation."] };
|
|
82
105
|
if (status === 402) {
|
|
@@ -84,11 +107,7 @@ export function classifyApiError(error, context) {
|
|
|
84
107
|
}
|
|
85
108
|
if (status === 404)
|
|
86
109
|
return { code: "NOT_FOUND", message, detail, nextSteps: notFoundNextSteps(message) };
|
|
87
|
-
if (status ===
|
|
88
|
-
const retryAfter = extractRetryAfter(e);
|
|
89
|
-
return { status: "action_required", code: "RATE_LIMITED", message, detail, nextSteps: [retryAfter ? "Wait " + retryAfter + " seconds, then run the same command again." : "Back off and retry once; the SDK already retried with the server's Retry-After."] };
|
|
90
|
-
}
|
|
91
|
-
if (status === 422 && apiCode === "spec_error")
|
|
110
|
+
if (status === 422 && (apiCode === "spec_invalid" || apiCode === "spec_error"))
|
|
92
111
|
return { code: "SPEC_INVALID", message, detail, nextSteps: ["The API rejected the spec it was given; the message says why.", context.docsUrl ? "Look the message up: '" + context.bin + " docs search \"" + (apiMessage ?? "").slice(0, 60).replace(/"/g, "'") + "\"'." : "Fix the spec and run again."] };
|
|
93
112
|
if (status === 400 || status === 422 || status === 409 || status === 413)
|
|
94
113
|
return { code: "INVALID_REQUEST", message, detail, nextSteps: ["Read detail.body for the field the API named; run the command with --help for its flags."] };
|
|
@@ -96,6 +115,43 @@ export function classifyApiError(error, context) {
|
|
|
96
115
|
return { code: "SERVER_ERROR", message, detail, nextSteps: ["Retry once with backoff. If it persists, report detail.request_id."] };
|
|
97
116
|
return { code: "CALL_FAILED", message, detail };
|
|
98
117
|
}
|
|
118
|
+
/** Login, saved-session and credential-store failures, recognised by error
|
|
119
|
+
* name (so this module needs no login runtime) on the error or its cause.
|
|
120
|
+
* Undefined when the error is none of them. */
|
|
121
|
+
export function classifyAuthFailure(error, context) {
|
|
122
|
+
const login = "'" + context.bin + " login'";
|
|
123
|
+
const environment = context.envVars.length ? ["Or supply credentials through the environment: " + context.envVars.join(", ") + "."] : [];
|
|
124
|
+
for (let node = error, depth = 0; node && typeof node === "object" && depth < 4; node = node.cause, depth++) {
|
|
125
|
+
const message = typeof node.message === "string" && node.message ? node.message : "Login failed.";
|
|
126
|
+
switch (node.name) {
|
|
127
|
+
case "DeviceFlowUnavailableError":
|
|
128
|
+
return { status: "action_required", code: "LOGIN_FAILED", message, nextSteps: ["Run " + login + " without --device to sign in through the browser.", ...environment] };
|
|
129
|
+
case "LoginPollingError":
|
|
130
|
+
if (node.code === "request_failed" && typeof node.providerError === "string")
|
|
131
|
+
return { code: "LOGIN_FAILED", message, nextSteps: ["The provider rejected the login request. Check the client ID and that the application is registered for this sign-in flow, then run " + login + " again.", ...environment] };
|
|
132
|
+
if (node.code === "request_failed")
|
|
133
|
+
return { code: "LOGIN_FAILED", message, nextSteps: ["Check the network and the login service, then run " + login + " again.", ...environment] };
|
|
134
|
+
if (node.code === "invalid_response")
|
|
135
|
+
return { code: "LOGIN_FAILED", message, nextSteps: ["The login service's response did not match its contract. Retry " + login + " once; if it persists, report it to the API owner.", ...environment] };
|
|
136
|
+
return { code: "LOGIN_FAILED", message, nextSteps: ["Run " + login + " again and approve the new request.", ...environment] };
|
|
137
|
+
case "OAuthResponseError":
|
|
138
|
+
if (node.code === "request_failed" || node.code === "timed_out")
|
|
139
|
+
return { code: "NETWORK_ERROR", message: "The OAuth provider could not be reached (" + String(node.code).replace("_", " ") + ").", nextSteps: ["Check the network and the provider, then run " + login + " again."] };
|
|
140
|
+
return { code: "LOGIN_FAILED", message: "The OAuth provider returned an unusable response (" + String(node.code).replace(/_/g, " ") + ").", nextSteps: ["Run " + login + " again. If it keeps failing, report it to the API owner.", ...environment] };
|
|
141
|
+
case "OAuthLoginError":
|
|
142
|
+
return { code: "LOGIN_FAILED", message, nextSteps: ["Run " + login + " again. If it keeps failing, the OAuth application settings may need attention from the API owner.", ...environment] };
|
|
143
|
+
case "OAuthSessionError":
|
|
144
|
+
return { status: "action_required", code: "LOGIN_REQUIRED", message, nextSteps: ["Run " + login + " to sign in again.", ...environment] };
|
|
145
|
+
case "CredentialStorageError":
|
|
146
|
+
return { status: "action_required", code: "CREDENTIAL_STORE_UNAVAILABLE", message, nextSteps: [
|
|
147
|
+
"Unlock or enable the OS credential store (macOS Keychain, Linux Secret Service or Windows DPAPI) and retry.",
|
|
148
|
+
...environment,
|
|
149
|
+
"To use a plaintext file store instead, set " + context.storeVariable + "=file and run " + login + ".",
|
|
150
|
+
] };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
99
155
|
/** A file lookup needs path guidance, not the generic advice for a missing
|
|
100
156
|
* resource id. Prefer the API's own index when its message names one. */
|
|
101
157
|
function notFoundNextSteps(message) {
|
|
@@ -122,13 +178,25 @@ function extractUrl(body, keys) {
|
|
|
122
178
|
}
|
|
123
179
|
return undefined;
|
|
124
180
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
181
|
+
function isoSeconds(at) {
|
|
182
|
+
return new Date(Math.ceil(at.getTime() / 1000) * 1000).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
183
|
+
}
|
|
184
|
+
/** "Wait until <time>" when the API said when the limit resets. */
|
|
185
|
+
export function rateLimitNextStep(retryAt, then) {
|
|
186
|
+
return retryAt instanceof Date && !Number.isNaN(retryAt.getTime())
|
|
187
|
+
? "Rate limited: wait until " + isoSeconds(retryAt) + ", then " + then + "."
|
|
188
|
+
: "Rate limited: back off, then " + then + "; the SDK already honored any Retry-After within its ceiling.";
|
|
189
|
+
}
|
|
190
|
+
/** Error codes APIs report inside a 2xx body (Slack's `error`), mapped to
|
|
191
|
+
* the stable codes when their meaning is unambiguous. */
|
|
192
|
+
export function payloadFailureCode(code) {
|
|
193
|
+
if (typeof code !== "string")
|
|
194
|
+
return undefined;
|
|
195
|
+
if (/^(not_authed|invalid_auth|token_revoked|token_expired|account_inactive|unauthorized|unauthenticated|forbidden|access_denied|missing_scope)$/i.test(code))
|
|
196
|
+
return "AUTH_INVALID";
|
|
197
|
+
if (/^(ratelimited|rate_limited|rate_limit_exceeded|too_many_requests)$/i.test(code))
|
|
198
|
+
return "RATE_LIMITED";
|
|
199
|
+
return undefined;
|
|
132
200
|
}
|
|
133
201
|
/**
|
|
134
202
|
* --mode agent beats <PREFIX>_MODE=agent beats "no terminal on either end".
|
|
@@ -166,6 +234,28 @@ export function detectHarness(env = process.env) {
|
|
|
166
234
|
return "devin";
|
|
167
235
|
return null;
|
|
168
236
|
}
|
|
237
|
+
const ENV_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
238
|
+
/** Rewrite `${VAR}` header references into the client's syntax, or drop the headers that hold one. */
|
|
239
|
+
function entryForClient(client, entry) {
|
|
240
|
+
const style = client.headerEnv ?? "dollar";
|
|
241
|
+
if (!entry.headers || style === "dollar")
|
|
242
|
+
return { entry };
|
|
243
|
+
const headers = {};
|
|
244
|
+
const dropped = [];
|
|
245
|
+
for (const [name, value] of Object.entries(entry.headers)) {
|
|
246
|
+
if (style === "none" && value.search(ENV_REF) !== -1)
|
|
247
|
+
dropped.push(name);
|
|
248
|
+
else
|
|
249
|
+
headers[name] = style === "env-colon" ? value.replace(ENV_REF, "${env:$1}") : style === "brace-env" ? value.replace(ENV_REF, "{env:$1}") : value;
|
|
250
|
+
}
|
|
251
|
+
const next = { ...entry };
|
|
252
|
+
delete next.headers;
|
|
253
|
+
if (Object.keys(headers).length)
|
|
254
|
+
next.headers = headers;
|
|
255
|
+
return dropped.length
|
|
256
|
+
? { entry: next, note: client.label + " does not expand environment variables in headers, so the entry omits " + dropped.join(", ") + "; the client signs in with MCP authorization when the server supports it." }
|
|
257
|
+
: { entry: next };
|
|
258
|
+
}
|
|
169
259
|
function readOr(file, fallback) {
|
|
170
260
|
try {
|
|
171
261
|
return readFileSync(file, "utf8");
|
|
@@ -201,10 +291,18 @@ function tomlMerge(existing, name, entry) {
|
|
|
201
291
|
const lines = [header];
|
|
202
292
|
if (entry.url) {
|
|
203
293
|
lines.push("url = " + JSON.stringify(entry.url));
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
294
|
+
const envHeaders = [];
|
|
295
|
+
for (const [header, value] of Object.entries(entry.headers ?? {})) {
|
|
296
|
+
const envRef = /\$\{([A-Z0-9_]+)\}/.exec(value)?.[1];
|
|
297
|
+
if (!envRef)
|
|
298
|
+
continue;
|
|
299
|
+
if (header.toLowerCase() === "authorization" && /^Bearer /.test(value))
|
|
300
|
+
lines.push("bearer_token_env_var = " + JSON.stringify(envRef));
|
|
301
|
+
else
|
|
302
|
+
envHeaders.push(JSON.stringify(header) + " = " + JSON.stringify(envRef));
|
|
303
|
+
}
|
|
304
|
+
if (envHeaders.length)
|
|
305
|
+
lines.push("env_http_headers = { " + envHeaders.join(", ") + " }");
|
|
208
306
|
}
|
|
209
307
|
else {
|
|
210
308
|
lines.push("command = " + JSON.stringify(entry.command ?? "node"));
|
|
@@ -238,6 +336,7 @@ export const MCP_CLIENTS = [
|
|
|
238
336
|
label: "VS Code",
|
|
239
337
|
file: (cwd) => join(cwd, ".vscode", "mcp.json"),
|
|
240
338
|
detect: (cwd) => existsSync(join(cwd, ".vscode")) || existsSync(join(home(), ".vscode")),
|
|
339
|
+
headerEnv: "env-colon",
|
|
241
340
|
write: (existing, name, entry) => jsonMerge(existing, ["servers"], name, standardEntry(entry)),
|
|
242
341
|
},
|
|
243
342
|
{
|
|
@@ -245,6 +344,7 @@ export const MCP_CLIENTS = [
|
|
|
245
344
|
label: "Windsurf",
|
|
246
345
|
file: () => join(home(), ".codeium", "windsurf", "mcp_config.json"),
|
|
247
346
|
detect: () => existsSync(join(home(), ".codeium", "windsurf")),
|
|
347
|
+
headerEnv: "env-colon",
|
|
248
348
|
write: (existing, name, entry) => jsonMerge(existing, ["mcpServers"], name, entry.url ? { serverUrl: entry.url, ...(entry.headers ? { headers: entry.headers } : {}) } : stdioEntry(entry)),
|
|
249
349
|
},
|
|
250
350
|
{
|
|
@@ -252,6 +352,7 @@ export const MCP_CLIENTS = [
|
|
|
252
352
|
label: "Gemini CLI",
|
|
253
353
|
file: () => join(home(), ".gemini", "settings.json"),
|
|
254
354
|
detect: () => existsSync(join(home(), ".gemini")),
|
|
355
|
+
headerEnv: "none",
|
|
255
356
|
write: (existing, name, entry) => jsonMerge(existing, ["mcpServers"], name, entry.url ? { httpUrl: entry.url, ...(entry.headers ? { headers: entry.headers } : {}) } : stdioEntry(entry)),
|
|
256
357
|
},
|
|
257
358
|
{
|
|
@@ -259,6 +360,7 @@ export const MCP_CLIENTS = [
|
|
|
259
360
|
label: "OpenCode",
|
|
260
361
|
file: () => join(xdg(), "opencode", "opencode.json"),
|
|
261
362
|
detect: () => existsSync(join(xdg(), "opencode")),
|
|
363
|
+
headerEnv: "brace-env",
|
|
262
364
|
write: (existing, name, entry) => jsonMerge(existing, ["mcp"], name, entry.url ? { type: "remote", url: entry.url, ...(entry.headers ? { headers: entry.headers } : {}) } : { type: "local", command: [entry.command ?? "node", ...(entry.args ?? [])] }),
|
|
263
365
|
},
|
|
264
366
|
{
|
|
@@ -266,6 +368,7 @@ export const MCP_CLIENTS = [
|
|
|
266
368
|
label: "Zed",
|
|
267
369
|
file: () => join(xdg(), "zed", "settings.json"),
|
|
268
370
|
detect: () => existsSync(join(xdg(), "zed")),
|
|
371
|
+
headerEnv: "none",
|
|
269
372
|
write: (existing, name, entry) => jsonMerge(existing, ["context_servers"], name, entry.url ? { source: "custom", url: entry.url, ...(entry.headers ? { headers: entry.headers } : {}) } : { source: "custom", command: entry.command ?? "node", args: entry.args ?? [] }),
|
|
270
373
|
},
|
|
271
374
|
{
|
|
@@ -284,23 +387,32 @@ export const MCP_CLIENTS = [
|
|
|
284
387
|
label: "Cursor",
|
|
285
388
|
file: (cwd) => join(cwd, ".cursor", "mcp.json"),
|
|
286
389
|
detect: () => existsSync(join(home(), ".cursor")),
|
|
390
|
+
headerEnv: "env-colon",
|
|
287
391
|
write: (existing, name, entry) => jsonMerge(existing, ["mcpServers"], name, standardEntry(entry)),
|
|
288
392
|
},
|
|
289
393
|
];
|
|
394
|
+
// Every client's write receives entries in its own env-reference syntax,
|
|
395
|
+
// whether it is called through writeMcpConfig or directly.
|
|
396
|
+
for (const client of MCP_CLIENTS) {
|
|
397
|
+
const write = client.write;
|
|
398
|
+
client.write = (existing, name, entry) => write(existing, name, entryForClient(client, entry).entry);
|
|
399
|
+
}
|
|
290
400
|
export function findMcpClient(id) {
|
|
291
401
|
return MCP_CLIENTS.find((c) => c.id === id);
|
|
292
402
|
}
|
|
293
|
-
/** Merge the entry into one client's config file. Never writes a literal secret: callers pass
|
|
403
|
+
/** Merge the entry into one client's config file. Never writes a literal secret: callers pass `${VAR}` references, which each client receives in its own syntax. */
|
|
294
404
|
export function writeMcpConfig(client, cwd, name, entry) {
|
|
295
405
|
const file = client.file(cwd);
|
|
296
406
|
if (!entry.url && client.id === "claude-desktop" && !entry.command) {
|
|
297
407
|
return { client: client.id, file, written: false, note: "Claude Desktop reads only stdio servers from its config file; add a remote server as a connector in the app." };
|
|
298
408
|
}
|
|
299
409
|
const existing = readOr(file, "");
|
|
410
|
+
const adapted = entryForClient(client, entry);
|
|
300
411
|
const next = client.write(existing, name, entry);
|
|
301
412
|
mkdirSync(dirname(file), { recursive: true });
|
|
302
413
|
writeFileSync(file, next);
|
|
303
|
-
|
|
414
|
+
const note = [client.incompatible, adapted.note].filter(Boolean).join(" ");
|
|
415
|
+
return { client: client.id, file, written: true, ...(note ? { note } : {}) };
|
|
304
416
|
}
|
|
305
417
|
/** Does a client's config already mention this server? For doctor. */
|
|
306
418
|
export function mcpConfigured(client, cwd, name) {
|
|
@@ -371,8 +483,10 @@ export function agentBlock(ctx, commands) {
|
|
|
371
483
|
"",
|
|
372
484
|
"API commands write JSON on stdout; discovery commands take --json. Errors are JSON on stderr ({status, issues[{code,message}], next_steps}), exit 0/1/2. Non-interactive under an agent: no prompts, no browsers.",
|
|
373
485
|
"",
|
|
374
|
-
...((ctx.
|
|
375
|
-
|
|
486
|
+
...((ctx.omittedOperationCount ?? 0) > 0 ? ["- Coverage: this build includes " + (ctx.generatedOperationCount ?? commands.length) + " of " + ((ctx.generatedOperationCount ?? commands.length) + ctx.omittedOperationCount) + " operations; api.json lists the rest, and calling one returns `PLAN_LIMIT`."] : []),
|
|
487
|
+
ctx.authNotDeclared
|
|
488
|
+
? "- Auth: not declared by the API Spec. If the API needs a token, set " + auth + " or run `" + ctx.bin + " login`; other headers go in `--header \"Name: value\"` or " + ctx.envPrefix + "_HEADERS. Never write a key into a file in this repo."
|
|
489
|
+
: "- Auth: " + auth + " in the environment, or `" + ctx.bin + " login`. Never write a key into a file in this repo.",
|
|
376
490
|
"- Discover: `" + ctx.bin + " --help`, `" + ctx.bin + " <resource> <command> --help`, `" + ctx.bin + " help --json` (machine-readable), `" + ctx.bin + " agent-guide --format json`.",
|
|
377
491
|
"- Docs: " + (index ? "`" + ctx.bin + " docs search <term> --json`; " + index : "`" + ctx.bin + " docs <resource> <command> --json` (a docs URL was not provided at generate time)") + ".",
|
|
378
492
|
"- Lists: `--all` streams every page as NDJSON. Destructive commands need `--force`.",
|
|
@@ -401,12 +515,12 @@ export function agentGuide(ctx, commands) {
|
|
|
401
515
|
package: ctx.pkg,
|
|
402
516
|
api: ctx.apiTitle,
|
|
403
517
|
version: ctx.version,
|
|
404
|
-
generated_by: "
|
|
518
|
+
generated_by: "Typeship",
|
|
405
519
|
guide: agentBlock(ctx, commands),
|
|
406
520
|
first_command: first ? ctx.bin + " " + first.resource + " " + first.command : ctx.bin + " --help",
|
|
407
521
|
docs_index_url: docsIndexUrl,
|
|
408
522
|
docs_full_url: docsFullUrl,
|
|
409
|
-
...((ctx.
|
|
523
|
+
...((ctx.omittedOperationCount ?? 0) > 0 ? { coverage: { generated_operations: ctx.generatedOperationCount ?? commands.length, total_operations: (ctx.generatedOperationCount ?? commands.length) + ctx.omittedOperationCount } } : {}),
|
|
410
524
|
hosted_mcp_url: ctx.mcpUrl,
|
|
411
525
|
local_mcp: ctx.hasMcp ? ctx.bin + "-mcp (stdio) or '" + ctx.bin + " mcp install --all'" : null,
|
|
412
526
|
skills_install: ctx.skillsRepo ? "npx skills add " + ctx.skillsRepo : null,
|
|
@@ -544,3 +658,7 @@ export function summarizeDoctor(checks) {
|
|
|
544
658
|
next_steps: failing.map((c) => c.fix).filter((f) => Boolean(f)),
|
|
545
659
|
};
|
|
546
660
|
}
|
|
661
|
+
/** Every OAuth scope an operation's security requirements name, in order. */
|
|
662
|
+
export function requiredScopes(security) {
|
|
663
|
+
return [...new Set((security ?? []).flatMap((requirement) => Object.values(requirement).flat()))];
|
|
664
|
+
}
|