@girardmedia/bootspring 2.1.3 → 2.2.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/bin/bootspring.js +157 -83
- package/claude-commands/agent.md +34 -0
- package/claude-commands/bs.md +31 -0
- package/claude-commands/build.md +25 -0
- package/claude-commands/skill.md +31 -0
- package/claude-commands/todo.md +25 -0
- package/dist/core/index.d.ts +5814 -0
- package/dist/core.js +5779 -0
- package/dist/index.js +93883 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp-server.js +2298 -0
- package/generators/api-docs.js +3 -3
- package/generators/decisions.js +14 -14
- package/generators/health.js +6 -6
- package/generators/sprint.js +4 -4
- package/generators/templates/build-planning.template.js +2 -2
- package/generators/visual-doc-generator.js +1 -1
- package/package.json +22 -68
- package/cli/agent.js +0 -799
- package/cli/auth.js +0 -896
- package/cli/billing.js +0 -320
- package/cli/build.js +0 -1442
- package/cli/dashboard.js +0 -123
- package/cli/init.js +0 -669
- package/cli/mcp.js +0 -240
- package/cli/orchestrator.js +0 -240
- package/cli/project.js +0 -825
- package/cli/quality.js +0 -281
- package/cli/skill.js +0 -503
- package/cli/switch.js +0 -453
- package/cli/todo.js +0 -629
- package/cli/update.js +0 -132
- package/core/api-client.d.ts +0 -69
- package/core/api-client.js +0 -1482
- package/core/auth.d.ts +0 -98
- package/core/auth.js +0 -737
- package/core/build-orchestrator.js +0 -508
- package/core/build-state.js +0 -612
- package/core/config.d.ts +0 -106
- package/core/config.js +0 -1328
- package/core/context-loader.js +0 -580
- package/core/context.d.ts +0 -61
- package/core/context.js +0 -327
- package/core/entitlements.d.ts +0 -70
- package/core/entitlements.js +0 -322
- package/core/index.d.ts +0 -53
- package/core/index.js +0 -62
- package/core/mcp-config.js +0 -115
- package/core/policies.d.ts +0 -43
- package/core/policies.js +0 -113
- package/core/policy-matrix.js +0 -303
- package/core/project-activity.js +0 -175
- package/core/redaction.d.ts +0 -5
- package/core/redaction.js +0 -63
- package/core/self-update.js +0 -259
- package/core/session.js +0 -353
- package/core/task-extractor.js +0 -1098
- package/core/telemetry.d.ts +0 -55
- package/core/telemetry.js +0 -617
- package/core/tier-enforcement.js +0 -928
- package/core/utils.d.ts +0 -90
- package/core/utils.js +0 -455
- package/core/validation.js +0 -572
- package/mcp/server.d.ts +0 -57
- package/mcp/server.js +0 -264
|
@@ -0,0 +1,2298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
12
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
27
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
28
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
29
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
30
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
31
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
32
|
+
mod
|
|
33
|
+
));
|
|
34
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
|
+
|
|
36
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
37
|
+
var init_cjs_shims = __esm({
|
|
38
|
+
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// package.json
|
|
44
|
+
var require_package = __commonJS({
|
|
45
|
+
"package.json"(exports2, module2) {
|
|
46
|
+
module2.exports = {
|
|
47
|
+
name: "@girardmedia/bootspring",
|
|
48
|
+
version: "2.2.1",
|
|
49
|
+
description: "Thin client for Bootspring cloud MCP, hosted agents, and paywalled workflow intelligence",
|
|
50
|
+
keywords: [
|
|
51
|
+
"ai",
|
|
52
|
+
"development",
|
|
53
|
+
"scaffolding",
|
|
54
|
+
"mcp",
|
|
55
|
+
"claude",
|
|
56
|
+
"agents",
|
|
57
|
+
"context",
|
|
58
|
+
"workflow",
|
|
59
|
+
"devtools"
|
|
60
|
+
],
|
|
61
|
+
author: "Bootspring",
|
|
62
|
+
license: "SEE LICENSE IN LICENSE",
|
|
63
|
+
repository: {
|
|
64
|
+
type: "git",
|
|
65
|
+
url: "https://github.com/bootspring/bootspring.git"
|
|
66
|
+
},
|
|
67
|
+
homepage: "https://bootspring.com",
|
|
68
|
+
bugs: {
|
|
69
|
+
url: "https://github.com/bootspring/bootspring/issues"
|
|
70
|
+
},
|
|
71
|
+
bin: {
|
|
72
|
+
bootspring: "./bin/bootspring.js"
|
|
73
|
+
},
|
|
74
|
+
main: "./dist/core.js",
|
|
75
|
+
types: "./dist/core/index.d.ts",
|
|
76
|
+
exports: {
|
|
77
|
+
".": {
|
|
78
|
+
types: "./dist/core/index.d.ts",
|
|
79
|
+
default: "./dist/core.js"
|
|
80
|
+
},
|
|
81
|
+
"./mcp": {
|
|
82
|
+
types: "./dist/mcp/index.d.ts",
|
|
83
|
+
default: "./dist/mcp-server.js"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
files: [
|
|
87
|
+
"bin/bootspring.js",
|
|
88
|
+
"dist/index.js",
|
|
89
|
+
"dist/core.js",
|
|
90
|
+
"dist/mcp-server.js",
|
|
91
|
+
"dist/core/index.d.ts",
|
|
92
|
+
"dist/mcp/index.d.ts",
|
|
93
|
+
"generators/",
|
|
94
|
+
"claude-commands/",
|
|
95
|
+
"scripts/postinstall.js"
|
|
96
|
+
],
|
|
97
|
+
scripts: {
|
|
98
|
+
postinstall: "node scripts/postinstall.js",
|
|
99
|
+
start: "node bin/bootspring.js",
|
|
100
|
+
dashboard: "node bin/bootspring.js dashboard",
|
|
101
|
+
mcp: "node dist/mcp-server.js",
|
|
102
|
+
pretest: "npm run build",
|
|
103
|
+
test: "vitest run",
|
|
104
|
+
"test:launch-smoke": "vitest run __tests__/unit/cli-first-run-smoke.test.js __tests__/unit/health-fresh-start.test.ts __tests__/unit/session-project-scope.test.ts __tests__/unit/auth-cli-mixed-states.test.ts __tests__/unit/api-client-project-auth-fallback.test.js __tests__/unit/cli-help-surface.test.js __tests__/unit/cli-command-manifest.test.js",
|
|
105
|
+
"test:seed-ingestion": "vitest run __tests__/unit/seed-ingestion-regression.test.js",
|
|
106
|
+
"test:watch": "vitest",
|
|
107
|
+
"test:coverage": "vitest run --coverage",
|
|
108
|
+
lint: "eslint .",
|
|
109
|
+
"lint:fix": "eslint . --fix",
|
|
110
|
+
typecheck: "tsc --noEmit",
|
|
111
|
+
"typecheck:active": "tsc --noEmit -p tsconfig.active.json",
|
|
112
|
+
build: "tsup",
|
|
113
|
+
"build:watch": "tsup --watch",
|
|
114
|
+
dev: "tsx watch src/index.ts",
|
|
115
|
+
"verify:lint-budget": "node scripts/check-lint-budgets.js",
|
|
116
|
+
"verify:release-gates": "node scripts/release-gate-check.js",
|
|
117
|
+
"verify:shared-contracts": "node scripts/verify-shared-contracts.js",
|
|
118
|
+
"verify:thin-client-contract": "node scripts/verify-thin-client-contract.js",
|
|
119
|
+
"build:mcp-contract": "node scripts/export-mcp-contract.js",
|
|
120
|
+
"verify:mcp-contract": "node scripts/export-mcp-contract.js --check",
|
|
121
|
+
"planning:sync": "node scripts/sync-planning-queue.js",
|
|
122
|
+
"planning:sync:check": "node scripts/sync-planning-queue.js --check",
|
|
123
|
+
"planning:realign": "node scripts/sync-planning-queue.js --sync-runtime",
|
|
124
|
+
"verify:package": "node scripts/check-package-boundaries.js",
|
|
125
|
+
"db:sync": "node shared/db/sync.js",
|
|
126
|
+
"db:sync:check": "node shared/db/sync.js --check",
|
|
127
|
+
prepublishOnly: "npm run build && npm test && npm run lint --if-present && npm run verify:package && npm run verify:mcp-contract"
|
|
128
|
+
},
|
|
129
|
+
devDependencies: {
|
|
130
|
+
"@eslint/js": "^9.39.2",
|
|
131
|
+
"@swc/core": "^1.15.13",
|
|
132
|
+
"@types/node": "^25.3.1",
|
|
133
|
+
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
|
134
|
+
"@typescript-eslint/parser": "^8.57.0",
|
|
135
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
136
|
+
eslint: "^9.39.2",
|
|
137
|
+
globals: "^17.3.0",
|
|
138
|
+
tsup: "^8.5.1",
|
|
139
|
+
tsx: "^4.21.0",
|
|
140
|
+
typescript: "^5.9.3",
|
|
141
|
+
vitest: "^4.0.18"
|
|
142
|
+
},
|
|
143
|
+
dependencies: {
|
|
144
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
145
|
+
jsonwebtoken: "^9.0.3",
|
|
146
|
+
ws: "^8.18.0",
|
|
147
|
+
yaml: "^2.8.0",
|
|
148
|
+
zod: "^4.3.6"
|
|
149
|
+
},
|
|
150
|
+
engines: {
|
|
151
|
+
node: ">=18.0.0"
|
|
152
|
+
},
|
|
153
|
+
overrides: {
|
|
154
|
+
table: {
|
|
155
|
+
ajv: "^8.12.0"
|
|
156
|
+
},
|
|
157
|
+
minimatch: "^10.2.1",
|
|
158
|
+
hono: "4.12.4",
|
|
159
|
+
"@hono/node-server": "1.19.10",
|
|
160
|
+
"express-rate-limit": "^8.2.2"
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// src/core/auth.ts
|
|
167
|
+
var auth_exports = {};
|
|
168
|
+
__export(auth_exports, {
|
|
169
|
+
BOOTSPRING_DIR: () => BOOTSPRING_DIR,
|
|
170
|
+
CONFIG_FILE: () => CONFIG_FILE,
|
|
171
|
+
CREDENTIALS_FILE: () => CREDENTIALS_FILE,
|
|
172
|
+
DEVICE_FILE: () => DEVICE_FILE,
|
|
173
|
+
clearCredentials: () => clearCredentials,
|
|
174
|
+
clearDeviceInfo: () => clearDeviceInfo,
|
|
175
|
+
clearProjectApiKey: () => clearProjectApiKey,
|
|
176
|
+
clearProjectScopedSession: () => clearProjectScopedSession,
|
|
177
|
+
ensureDir: () => ensureDir,
|
|
178
|
+
generateDeviceFingerprint: () => generateDeviceFingerprint,
|
|
179
|
+
getApiKey: () => getApiKey,
|
|
180
|
+
getConfig: () => getConfig,
|
|
181
|
+
getCredentials: () => getCredentials,
|
|
182
|
+
getCredentialsPath: () => getCredentialsPath,
|
|
183
|
+
getDeviceContext: () => getDeviceContext,
|
|
184
|
+
getDeviceId: () => getDeviceId,
|
|
185
|
+
getDeviceInfo: () => getDeviceInfo,
|
|
186
|
+
getLegacyProjectApiKey: () => getLegacyProjectApiKey,
|
|
187
|
+
getProjectScopedToken: () => getProjectScopedToken,
|
|
188
|
+
getRefreshToken: () => getRefreshToken,
|
|
189
|
+
getStoredApiKey: () => getStoredApiKey,
|
|
190
|
+
getTier: () => getTier,
|
|
191
|
+
getToken: () => getToken,
|
|
192
|
+
getUser: () => getUser,
|
|
193
|
+
isApiKeyAuth: () => isApiKeyAuth,
|
|
194
|
+
isAuthenticated: () => isAuthenticated,
|
|
195
|
+
login: () => login,
|
|
196
|
+
loginWithApiKey: () => loginWithApiKey,
|
|
197
|
+
logout: () => logout,
|
|
198
|
+
saveApiKeyToProject: () => saveApiKeyToProject,
|
|
199
|
+
saveConfig: () => saveConfig,
|
|
200
|
+
saveCredentials: () => saveCredentials,
|
|
201
|
+
saveProjectScopedSession: () => saveProjectScopedSession,
|
|
202
|
+
updateTokens: () => updateTokens
|
|
203
|
+
});
|
|
204
|
+
function getEncryptionKey() {
|
|
205
|
+
const machineId = os.hostname() + os.userInfo().username;
|
|
206
|
+
return crypto.createHash("sha256").update(machineId).digest();
|
|
207
|
+
}
|
|
208
|
+
function ensureDir() {
|
|
209
|
+
if (!fs.existsSync(BOOTSPRING_DIR)) {
|
|
210
|
+
fs.mkdirSync(BOOTSPRING_DIR, { recursive: true, mode: 448 });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function encrypt(data) {
|
|
214
|
+
try {
|
|
215
|
+
const key = getEncryptionKey();
|
|
216
|
+
const iv = crypto.randomBytes(16);
|
|
217
|
+
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
|
|
218
|
+
let encrypted = cipher.update(JSON.stringify(data), "utf8", "hex");
|
|
219
|
+
encrypted += cipher.final("hex");
|
|
220
|
+
return { iv: iv.toString("hex"), data: encrypted };
|
|
221
|
+
} catch {
|
|
222
|
+
return data;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function decrypt(encrypted) {
|
|
226
|
+
try {
|
|
227
|
+
if ("iv" in encrypted && "data" in encrypted && typeof encrypted.iv === "string") {
|
|
228
|
+
const key = getEncryptionKey();
|
|
229
|
+
const iv = Buffer.from(encrypted.iv, "hex");
|
|
230
|
+
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
|
|
231
|
+
let decrypted = decipher.update(encrypted.data, "hex", "utf8");
|
|
232
|
+
decrypted += decipher.final("utf8");
|
|
233
|
+
return JSON.parse(decrypted);
|
|
234
|
+
}
|
|
235
|
+
return encrypted;
|
|
236
|
+
} catch {
|
|
237
|
+
return encrypted;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function getCredentials() {
|
|
241
|
+
try {
|
|
242
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
243
|
+
const raw = JSON.parse(fs.readFileSync(CREDENTIALS_FILE, "utf-8"));
|
|
244
|
+
return decrypt(raw);
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
function saveCredentials(credentials) {
|
|
251
|
+
ensureDir();
|
|
252
|
+
const encrypted = encrypt(credentials);
|
|
253
|
+
fs.writeFileSync(
|
|
254
|
+
CREDENTIALS_FILE,
|
|
255
|
+
JSON.stringify(encrypted, null, 2),
|
|
256
|
+
{ mode: 384 }
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
function clearCredentials() {
|
|
260
|
+
try {
|
|
261
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
262
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function getToken() {
|
|
268
|
+
const creds = getCredentials();
|
|
269
|
+
if (!creds) return null;
|
|
270
|
+
if (creds.expiresAt && new Date(creds.expiresAt) < /* @__PURE__ */ new Date()) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
return creds.token || null;
|
|
274
|
+
}
|
|
275
|
+
function findNearestProjectConfigPath() {
|
|
276
|
+
let dir = process.cwd();
|
|
277
|
+
for (let i = 0; i < 10; i++) {
|
|
278
|
+
const configPath = path.join(dir, ".bootspring.json");
|
|
279
|
+
if (fs.existsSync(configPath)) {
|
|
280
|
+
return configPath;
|
|
281
|
+
}
|
|
282
|
+
const parent = path.dirname(dir);
|
|
283
|
+
if (parent === dir) break;
|
|
284
|
+
dir = parent;
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
function readNearestProjectConfig() {
|
|
289
|
+
try {
|
|
290
|
+
const configPath = findNearestProjectConfigPath();
|
|
291
|
+
if (!configPath) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
295
|
+
return { path: configPath, config };
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
function writeNearestProjectConfig(pathname, config) {
|
|
301
|
+
try {
|
|
302
|
+
fs.writeFileSync(pathname, JSON.stringify(config, null, 2));
|
|
303
|
+
return true;
|
|
304
|
+
} catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
function getProjectScopedSessionState() {
|
|
309
|
+
const projectConfig = readNearestProjectConfig();
|
|
310
|
+
if (!projectConfig || !projectConfig.config.projectAuth) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const projectAuth = projectConfig.config.projectAuth;
|
|
314
|
+
if (typeof projectAuth.token !== "string" || projectAuth.token.length === 0 || typeof projectAuth.expiresAt !== "string" || projectAuth.expiresAt.length === 0) {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
token: projectAuth.token,
|
|
319
|
+
expiresAt: projectAuth.expiresAt,
|
|
320
|
+
issuedAt: typeof projectAuth.issuedAt === "string" ? projectAuth.issuedAt : (/* @__PURE__ */ new Date()).toISOString(),
|
|
321
|
+
source: typeof projectAuth.source === "string" ? projectAuth.source : void 0,
|
|
322
|
+
migratedFromLegacyApiKey: Boolean(projectAuth.migratedFromLegacyApiKey)
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function getLegacyProjectApiKey() {
|
|
326
|
+
const projectConfig = readNearestProjectConfig();
|
|
327
|
+
if (!projectConfig) {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
const legacyApiKey = projectConfig.config.apiKey;
|
|
331
|
+
if (typeof legacyApiKey === "string" && legacyApiKey.length > 0) {
|
|
332
|
+
return legacyApiKey;
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
function getProjectScopedToken() {
|
|
337
|
+
const projectAuth = getProjectScopedSessionState();
|
|
338
|
+
if (!projectAuth) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const expiresAt = new Date(projectAuth.expiresAt).getTime();
|
|
342
|
+
if (!Number.isFinite(expiresAt)) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
if (Date.now() >= expiresAt - 6e4) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
return projectAuth.token;
|
|
349
|
+
}
|
|
350
|
+
function getStoredApiKey() {
|
|
351
|
+
const creds = getCredentials();
|
|
352
|
+
return creds?.apiKey || null;
|
|
353
|
+
}
|
|
354
|
+
function getApiKey() {
|
|
355
|
+
const envApiKey = process.env.BOOTSPRING_API_KEY;
|
|
356
|
+
if (envApiKey) {
|
|
357
|
+
return envApiKey;
|
|
358
|
+
}
|
|
359
|
+
if (getToken()) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
const projectScopedToken = getProjectScopedToken();
|
|
363
|
+
if (projectScopedToken) {
|
|
364
|
+
return projectScopedToken;
|
|
365
|
+
}
|
|
366
|
+
const storedApiKey = getStoredApiKey();
|
|
367
|
+
if (storedApiKey) {
|
|
368
|
+
return storedApiKey;
|
|
369
|
+
}
|
|
370
|
+
const legacyApiKey = getLegacyProjectApiKey();
|
|
371
|
+
if (legacyApiKey) {
|
|
372
|
+
if (!legacyProjectApiKeyWarned) {
|
|
373
|
+
legacyProjectApiKeyWarned = true;
|
|
374
|
+
console.warn(
|
|
375
|
+
"[bootspring] Using legacy .bootspring.json apiKey fallback. Run `bootspring auth login --api-key <key>` to migrate to short-lived project tokens."
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return legacyApiKey;
|
|
379
|
+
}
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
function isApiKeyAuth() {
|
|
383
|
+
if (process.env.BOOTSPRING_API_KEY) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
if (getToken()) {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
return Boolean(
|
|
390
|
+
getProjectScopedToken() || getStoredApiKey() || getLegacyProjectApiKey()
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
function getRefreshToken() {
|
|
394
|
+
const creds = getCredentials();
|
|
395
|
+
return creds?.refreshToken || null;
|
|
396
|
+
}
|
|
397
|
+
function isAuthenticated() {
|
|
398
|
+
return !!getToken() || !!getApiKey();
|
|
399
|
+
}
|
|
400
|
+
function getUser() {
|
|
401
|
+
const creds = getCredentials();
|
|
402
|
+
return creds?.user || null;
|
|
403
|
+
}
|
|
404
|
+
function getTier() {
|
|
405
|
+
const envTier = process.env.BOOTSPRING_USER_TIER;
|
|
406
|
+
if (envTier) {
|
|
407
|
+
return envTier.toLowerCase();
|
|
408
|
+
}
|
|
409
|
+
const user = getUser();
|
|
410
|
+
return user?.tier || "free";
|
|
411
|
+
}
|
|
412
|
+
function parseExpiry(expiry) {
|
|
413
|
+
const match = expiry.match(/^(\d+)([mhd])$/);
|
|
414
|
+
if (!match || !match[1] || !match[2]) return 15 * 60 * 1e3;
|
|
415
|
+
const value = parseInt(match[1]);
|
|
416
|
+
const unit = match[2];
|
|
417
|
+
switch (unit) {
|
|
418
|
+
case "m":
|
|
419
|
+
return value * 60 * 1e3;
|
|
420
|
+
case "h":
|
|
421
|
+
return value * 60 * 60 * 1e3;
|
|
422
|
+
case "d":
|
|
423
|
+
return value * 24 * 60 * 60 * 1e3;
|
|
424
|
+
default:
|
|
425
|
+
return 15 * 60 * 1e3;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function login(response) {
|
|
429
|
+
const expiresIn = response.expiresIn ?? "15m";
|
|
430
|
+
const expiresMs = parseExpiry(expiresIn);
|
|
431
|
+
const expiresAt = new Date(Date.now() + expiresMs).toISOString();
|
|
432
|
+
const credentials = {
|
|
433
|
+
token: response.token,
|
|
434
|
+
expiresAt
|
|
435
|
+
};
|
|
436
|
+
if (response.refreshToken !== void 0) {
|
|
437
|
+
credentials.refreshToken = response.refreshToken;
|
|
438
|
+
}
|
|
439
|
+
if (response.user !== void 0) {
|
|
440
|
+
credentials.user = response.user;
|
|
441
|
+
}
|
|
442
|
+
saveCredentials(credentials);
|
|
443
|
+
}
|
|
444
|
+
function saveProjectScopedSession(token, options = {}) {
|
|
445
|
+
try {
|
|
446
|
+
const projectConfig = readNearestProjectConfig();
|
|
447
|
+
if (!projectConfig || !token) {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
const resolvedExpiresAt = (() => {
|
|
451
|
+
if (options.expiresAt) {
|
|
452
|
+
return options.expiresAt;
|
|
453
|
+
}
|
|
454
|
+
if (typeof options.expiresIn === "number" && Number.isFinite(options.expiresIn)) {
|
|
455
|
+
return new Date(Date.now() + options.expiresIn * 1e3).toISOString();
|
|
456
|
+
}
|
|
457
|
+
if (typeof options.expiresIn === "string") {
|
|
458
|
+
return new Date(Date.now() + parseExpiry(options.expiresIn)).toISOString();
|
|
459
|
+
}
|
|
460
|
+
return new Date(Date.now() + 15 * 60 * 1e3).toISOString();
|
|
461
|
+
})();
|
|
462
|
+
const nextConfig = {
|
|
463
|
+
...projectConfig.config,
|
|
464
|
+
projectAuth: {
|
|
465
|
+
token,
|
|
466
|
+
expiresAt: resolvedExpiresAt,
|
|
467
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
468
|
+
source: options.source || "api-key-exchange",
|
|
469
|
+
migratedFromLegacyApiKey: Boolean(options.migratedFromLegacyApiKey)
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
if (options.migratedFromLegacyApiKey && Object.prototype.hasOwnProperty.call(nextConfig, "apiKey")) {
|
|
473
|
+
delete nextConfig.apiKey;
|
|
474
|
+
}
|
|
475
|
+
return writeNearestProjectConfig(projectConfig.path, nextConfig);
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
function clearProjectScopedSession() {
|
|
481
|
+
try {
|
|
482
|
+
const projectConfig = readNearestProjectConfig();
|
|
483
|
+
if (!projectConfig || !projectConfig.config.projectAuth) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
const nextConfig = { ...projectConfig.config };
|
|
487
|
+
delete nextConfig.projectAuth;
|
|
488
|
+
return writeNearestProjectConfig(projectConfig.path, nextConfig);
|
|
489
|
+
} catch {
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
function clearProjectApiKey() {
|
|
494
|
+
try {
|
|
495
|
+
const projectConfig = readNearestProjectConfig();
|
|
496
|
+
if (!projectConfig) {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
const config = projectConfig.config;
|
|
500
|
+
if (!Object.prototype.hasOwnProperty.call(config, "apiKey")) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
delete config.apiKey;
|
|
504
|
+
return writeNearestProjectConfig(projectConfig.path, config);
|
|
505
|
+
} catch {
|
|
506
|
+
}
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
function saveApiKeyToProject(apiKey) {
|
|
510
|
+
try {
|
|
511
|
+
const projectConfig = readNearestProjectConfig();
|
|
512
|
+
if (!projectConfig) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
const config = { ...projectConfig.config, apiKey };
|
|
516
|
+
return writeNearestProjectConfig(projectConfig.path, config);
|
|
517
|
+
} catch {
|
|
518
|
+
}
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
function loginWithApiKey(apiKey, user) {
|
|
522
|
+
const credentials = { apiKey };
|
|
523
|
+
if (user !== void 0) {
|
|
524
|
+
credentials.user = user;
|
|
525
|
+
}
|
|
526
|
+
saveCredentials(credentials);
|
|
527
|
+
clearProjectApiKey();
|
|
528
|
+
}
|
|
529
|
+
function updateTokens(response) {
|
|
530
|
+
const creds = getCredentials() || {};
|
|
531
|
+
const expiresIn = response.expiresIn ?? "15m";
|
|
532
|
+
const expiresMs = parseExpiry(expiresIn);
|
|
533
|
+
const expiresAt = new Date(Date.now() + expiresMs).toISOString();
|
|
534
|
+
const credentials = {
|
|
535
|
+
...creds,
|
|
536
|
+
token: response.token,
|
|
537
|
+
expiresAt
|
|
538
|
+
};
|
|
539
|
+
if (response.refreshToken !== void 0) {
|
|
540
|
+
credentials.refreshToken = response.refreshToken;
|
|
541
|
+
}
|
|
542
|
+
saveCredentials(credentials);
|
|
543
|
+
}
|
|
544
|
+
function logout() {
|
|
545
|
+
clearCredentials();
|
|
546
|
+
clearProjectScopedSession();
|
|
547
|
+
clearProjectApiKey();
|
|
548
|
+
}
|
|
549
|
+
function getConfig() {
|
|
550
|
+
try {
|
|
551
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
552
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
553
|
+
}
|
|
554
|
+
} catch {
|
|
555
|
+
}
|
|
556
|
+
return {};
|
|
557
|
+
}
|
|
558
|
+
function saveConfig(config) {
|
|
559
|
+
ensureDir();
|
|
560
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
561
|
+
}
|
|
562
|
+
function getCredentialsPath() {
|
|
563
|
+
return CREDENTIALS_FILE;
|
|
564
|
+
}
|
|
565
|
+
function generateDeviceFingerprint() {
|
|
566
|
+
const networkInterfaces2 = os.networkInterfaces();
|
|
567
|
+
const macAddresses = Object.values(networkInterfaces2).flat().filter(
|
|
568
|
+
(iface) => iface !== void 0 && !iface.internal && iface.mac !== "00:00:00:00:00:00"
|
|
569
|
+
).map((iface) => iface.mac).sort().join(",");
|
|
570
|
+
const components = [
|
|
571
|
+
os.hostname(),
|
|
572
|
+
os.userInfo().username,
|
|
573
|
+
os.platform(),
|
|
574
|
+
os.arch(),
|
|
575
|
+
os.cpus()[0]?.model || "unknown-cpu",
|
|
576
|
+
os.homedir(),
|
|
577
|
+
macAddresses
|
|
578
|
+
];
|
|
579
|
+
return crypto.createHash("sha256").update(components.join("|")).digest("hex");
|
|
580
|
+
}
|
|
581
|
+
function getDeviceInfo() {
|
|
582
|
+
ensureDir();
|
|
583
|
+
try {
|
|
584
|
+
if (fs.existsSync(DEVICE_FILE)) {
|
|
585
|
+
const stored = JSON.parse(fs.readFileSync(DEVICE_FILE, "utf-8"));
|
|
586
|
+
const currentFingerprint = generateDeviceFingerprint();
|
|
587
|
+
if (stored.fingerprint === currentFingerprint) {
|
|
588
|
+
return stored;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} catch {
|
|
592
|
+
}
|
|
593
|
+
const deviceInfo = {
|
|
594
|
+
deviceId: crypto.randomUUID(),
|
|
595
|
+
fingerprint: generateDeviceFingerprint(),
|
|
596
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
597
|
+
platform: os.platform(),
|
|
598
|
+
arch: os.arch(),
|
|
599
|
+
hostname: os.hostname()
|
|
600
|
+
};
|
|
601
|
+
fs.writeFileSync(DEVICE_FILE, JSON.stringify(deviceInfo, null, 2), { mode: 384 });
|
|
602
|
+
return deviceInfo;
|
|
603
|
+
}
|
|
604
|
+
function getDeviceId() {
|
|
605
|
+
return getDeviceInfo().deviceId;
|
|
606
|
+
}
|
|
607
|
+
function getDeviceContext() {
|
|
608
|
+
const info = getDeviceInfo();
|
|
609
|
+
let cliVersion = "unknown";
|
|
610
|
+
try {
|
|
611
|
+
const pkg = require_package();
|
|
612
|
+
cliVersion = pkg.version;
|
|
613
|
+
} catch {
|
|
614
|
+
}
|
|
615
|
+
return {
|
|
616
|
+
deviceId: info.deviceId,
|
|
617
|
+
platform: info.platform,
|
|
618
|
+
arch: info.arch,
|
|
619
|
+
hostname: info.hostname,
|
|
620
|
+
cliVersion,
|
|
621
|
+
nodeVersion: process.version,
|
|
622
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function clearDeviceInfo() {
|
|
626
|
+
try {
|
|
627
|
+
if (fs.existsSync(DEVICE_FILE)) {
|
|
628
|
+
fs.unlinkSync(DEVICE_FILE);
|
|
629
|
+
}
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
var fs, path, os, crypto, BOOTSPRING_DIR, CREDENTIALS_FILE, CONFIG_FILE, DEVICE_FILE, legacyProjectApiKeyWarned;
|
|
634
|
+
var init_auth = __esm({
|
|
635
|
+
"src/core/auth.ts"() {
|
|
636
|
+
"use strict";
|
|
637
|
+
init_cjs_shims();
|
|
638
|
+
fs = __toESM(require("fs"));
|
|
639
|
+
path = __toESM(require("path"));
|
|
640
|
+
os = __toESM(require("os"));
|
|
641
|
+
crypto = __toESM(require("crypto"));
|
|
642
|
+
BOOTSPRING_DIR = path.join(os.homedir(), ".bootspring");
|
|
643
|
+
CREDENTIALS_FILE = path.join(BOOTSPRING_DIR, "credentials.json");
|
|
644
|
+
CONFIG_FILE = path.join(BOOTSPRING_DIR, "config.json");
|
|
645
|
+
DEVICE_FILE = path.join(BOOTSPRING_DIR, "device.json");
|
|
646
|
+
legacyProjectApiKeyWarned = false;
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// src/core/auth.js
|
|
651
|
+
var init_auth2 = __esm({
|
|
652
|
+
"src/core/auth.js"() {
|
|
653
|
+
"use strict";
|
|
654
|
+
init_cjs_shims();
|
|
655
|
+
init_auth();
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
// src/core/session.ts
|
|
660
|
+
function ensureDir2() {
|
|
661
|
+
if (!fs2.existsSync(BOOTSPRING_DIR2)) {
|
|
662
|
+
fs2.mkdirSync(BOOTSPRING_DIR2, { recursive: true, mode: 448 });
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function getSession() {
|
|
666
|
+
try {
|
|
667
|
+
if (fs2.existsSync(SESSION_FILE)) {
|
|
668
|
+
return JSON.parse(fs2.readFileSync(SESSION_FILE, "utf-8"));
|
|
669
|
+
}
|
|
670
|
+
} catch {
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
function saveSession(session) {
|
|
675
|
+
ensureDir2();
|
|
676
|
+
const data = {
|
|
677
|
+
...session,
|
|
678
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
679
|
+
};
|
|
680
|
+
fs2.writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2));
|
|
681
|
+
}
|
|
682
|
+
function hasScopeMarker(dir) {
|
|
683
|
+
for (const marker of PROJECT_SCOPE_MARKERS) {
|
|
684
|
+
if (fs2.existsSync(path2.join(dir, marker))) {
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
function normalizeDirPath(dir) {
|
|
691
|
+
const resolved = path2.resolve(dir);
|
|
692
|
+
try {
|
|
693
|
+
return fs2.realpathSync.native(resolved);
|
|
694
|
+
} catch {
|
|
695
|
+
try {
|
|
696
|
+
return fs2.realpathSync(resolved);
|
|
697
|
+
} catch {
|
|
698
|
+
return resolved;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function findNearestScopeRoot(startDir) {
|
|
703
|
+
let dir = normalizeDirPath(startDir);
|
|
704
|
+
for (let i = 0; i < 10; i++) {
|
|
705
|
+
if (hasScopeMarker(dir)) {
|
|
706
|
+
return dir;
|
|
707
|
+
}
|
|
708
|
+
const parent = path2.dirname(dir);
|
|
709
|
+
if (parent === dir) {
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
dir = parent;
|
|
713
|
+
}
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
function resolveProjectScope(scopeDir) {
|
|
717
|
+
const resolvedDir = normalizeDirPath(scopeDir);
|
|
718
|
+
const scopeRoot = findNearestScopeRoot(resolvedDir);
|
|
719
|
+
if (scopeRoot) {
|
|
720
|
+
return { dir: scopeRoot, mode: "tree" };
|
|
721
|
+
}
|
|
722
|
+
return { dir: resolvedDir, mode: "exact" };
|
|
723
|
+
}
|
|
724
|
+
function getScopeMode(sessionData, resolvedScopeDir) {
|
|
725
|
+
if (sessionData.projectScopeMode === "exact" || sessionData.projectScopeMode === "tree") {
|
|
726
|
+
return sessionData.projectScopeMode;
|
|
727
|
+
}
|
|
728
|
+
return hasScopeMarker(resolvedScopeDir) ? "tree" : "exact";
|
|
729
|
+
}
|
|
730
|
+
function setCurrentProject(project, scopeDir) {
|
|
731
|
+
const session = getSession() || {};
|
|
732
|
+
if (project) {
|
|
733
|
+
const scope = resolveProjectScope(scopeDir || process.cwd());
|
|
734
|
+
session.project = project;
|
|
735
|
+
session.projectScopeDir = scope.dir;
|
|
736
|
+
session.projectScopeMode = scope.mode;
|
|
737
|
+
} else {
|
|
738
|
+
delete session.project;
|
|
739
|
+
delete session.projectScopeDir;
|
|
740
|
+
delete session.projectScopeMode;
|
|
741
|
+
}
|
|
742
|
+
saveSession(session);
|
|
743
|
+
}
|
|
744
|
+
function addRecentProject(project) {
|
|
745
|
+
const session = getSession() || {};
|
|
746
|
+
const recent = session.recentProjects || [];
|
|
747
|
+
const filtered = recent.filter((p) => p.id !== project.id);
|
|
748
|
+
filtered.unshift({
|
|
749
|
+
id: project.id,
|
|
750
|
+
name: project.name,
|
|
751
|
+
slug: project.slug,
|
|
752
|
+
lastUsed: (/* @__PURE__ */ new Date()).toISOString()
|
|
753
|
+
});
|
|
754
|
+
session.recentProjects = filtered.slice(0, 10);
|
|
755
|
+
saveSession(session);
|
|
756
|
+
}
|
|
757
|
+
function findLocalConfig(startDir) {
|
|
758
|
+
let dir = startDir || process.cwd();
|
|
759
|
+
for (let i = 0; i < 10; i++) {
|
|
760
|
+
const jsonConfigPath = path2.join(dir, LOCAL_CONFIG_NAME);
|
|
761
|
+
if (fs2.existsSync(jsonConfigPath)) {
|
|
762
|
+
try {
|
|
763
|
+
const config = JSON.parse(fs2.readFileSync(jsonConfigPath, "utf-8"));
|
|
764
|
+
return {
|
|
765
|
+
...config,
|
|
766
|
+
_path: jsonConfigPath,
|
|
767
|
+
_dir: dir
|
|
768
|
+
};
|
|
769
|
+
} catch {
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
const jsConfigPath = path2.join(dir, "bootspring.config.js");
|
|
773
|
+
if (fs2.existsSync(jsConfigPath)) {
|
|
774
|
+
try {
|
|
775
|
+
delete require.cache[require.resolve(jsConfigPath)];
|
|
776
|
+
const config = require(jsConfigPath);
|
|
777
|
+
if (config.projectId) {
|
|
778
|
+
const project = config.project;
|
|
779
|
+
return {
|
|
780
|
+
projectId: config.projectId,
|
|
781
|
+
projectName: project?.name || config.name || "Unknown",
|
|
782
|
+
projectSlug: project?.slug,
|
|
783
|
+
_path: jsConfigPath,
|
|
784
|
+
_dir: dir,
|
|
785
|
+
_fromJsConfig: true
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const parent = path2.dirname(dir);
|
|
792
|
+
if (parent === dir) break;
|
|
793
|
+
dir = parent;
|
|
794
|
+
}
|
|
795
|
+
return null;
|
|
796
|
+
}
|
|
797
|
+
function getEffectiveProject(startDir) {
|
|
798
|
+
const local = findLocalConfig(startDir);
|
|
799
|
+
if (local?.projectId) {
|
|
800
|
+
return {
|
|
801
|
+
id: local.projectId,
|
|
802
|
+
name: local.projectName || "Unknown",
|
|
803
|
+
slug: local.projectSlug,
|
|
804
|
+
source: "local",
|
|
805
|
+
_localConfig: local
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const sessionData = getSession();
|
|
809
|
+
const sessionProject = sessionData?.project || null;
|
|
810
|
+
if (!sessionProject) {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
if (!sessionData?.projectScopeDir) {
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
const resolvedScopeDir = normalizeDirPath(sessionData.projectScopeDir);
|
|
817
|
+
const scopeMode = getScopeMode(sessionData, resolvedScopeDir);
|
|
818
|
+
const currentDir = normalizeDirPath(startDir || process.cwd());
|
|
819
|
+
const isInScope = (() => {
|
|
820
|
+
if (scopeMode === "exact") {
|
|
821
|
+
return currentDir === resolvedScopeDir;
|
|
822
|
+
}
|
|
823
|
+
const relativeToScope = path2.relative(resolvedScopeDir, currentDir);
|
|
824
|
+
return relativeToScope === "" || !relativeToScope.startsWith("..") && !path2.isAbsolute(relativeToScope);
|
|
825
|
+
})();
|
|
826
|
+
if (isInScope) {
|
|
827
|
+
return {
|
|
828
|
+
...sessionProject,
|
|
829
|
+
source: "session"
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
var fs2, path2, os2, BOOTSPRING_DIR2, SESSION_FILE, LOCAL_CONFIG_NAME, PROJECT_SCOPE_MARKERS;
|
|
835
|
+
var init_session = __esm({
|
|
836
|
+
"src/core/session.ts"() {
|
|
837
|
+
"use strict";
|
|
838
|
+
init_cjs_shims();
|
|
839
|
+
fs2 = __toESM(require("fs"));
|
|
840
|
+
path2 = __toESM(require("path"));
|
|
841
|
+
os2 = __toESM(require("os"));
|
|
842
|
+
BOOTSPRING_DIR2 = path2.join(os2.homedir(), ".bootspring");
|
|
843
|
+
SESSION_FILE = path2.join(BOOTSPRING_DIR2, "session.json");
|
|
844
|
+
LOCAL_CONFIG_NAME = ".bootspring.json";
|
|
845
|
+
PROJECT_SCOPE_MARKERS = [
|
|
846
|
+
LOCAL_CONFIG_NAME,
|
|
847
|
+
"bootspring.config.js",
|
|
848
|
+
".git"
|
|
849
|
+
];
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// src/core/session.js
|
|
854
|
+
var init_session2 = __esm({
|
|
855
|
+
"src/core/session.js"() {
|
|
856
|
+
"use strict";
|
|
857
|
+
init_cjs_shims();
|
|
858
|
+
init_session();
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// src/core/redaction.ts
|
|
863
|
+
function redactPatternMatches(value) {
|
|
864
|
+
return value.replace(/\b(?:bs|sk)_(?:live|test)_[A-Za-z0-9_-]{8,}\b/g, REDACTED).replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, REDACTED).replace(/\bBearer\s+[A-Za-z0-9._-]+\b/gi, `Bearer ${REDACTED}`).replace(/\bproj_[A-Za-z0-9_-]{6,}\b/g, `proj_${REDACTED}`).replace(/(["']?(?:authorization|x-api-key|apiKey|token|refreshToken|projectId)["']?\s*[:=]\s*["']?)([^"',\s}]+)/gi, `$1${REDACTED}`);
|
|
865
|
+
}
|
|
866
|
+
function redactSensitiveString(value) {
|
|
867
|
+
return redactPatternMatches(String(value || ""));
|
|
868
|
+
}
|
|
869
|
+
function redactSensitiveData(input, depth = 0) {
|
|
870
|
+
if (depth > 10) {
|
|
871
|
+
return input;
|
|
872
|
+
}
|
|
873
|
+
if (typeof input === "string") {
|
|
874
|
+
return redactSensitiveString(input);
|
|
875
|
+
}
|
|
876
|
+
if (Array.isArray(input)) {
|
|
877
|
+
return input.map((item) => redactSensitiveData(item, depth + 1));
|
|
878
|
+
}
|
|
879
|
+
if (!input || typeof input !== "object") {
|
|
880
|
+
return input;
|
|
881
|
+
}
|
|
882
|
+
const output = {};
|
|
883
|
+
for (const [key, value] of Object.entries(input)) {
|
|
884
|
+
if (SENSITIVE_KEY_PATTERN.test(key)) {
|
|
885
|
+
output[key] = REDACTED;
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
output[key] = redactSensitiveData(value, depth + 1);
|
|
889
|
+
}
|
|
890
|
+
return output;
|
|
891
|
+
}
|
|
892
|
+
var REDACTED, SENSITIVE_KEY_PATTERN;
|
|
893
|
+
var init_redaction = __esm({
|
|
894
|
+
"src/core/redaction.ts"() {
|
|
895
|
+
"use strict";
|
|
896
|
+
init_cjs_shims();
|
|
897
|
+
REDACTED = "[REDACTED]";
|
|
898
|
+
SENSITIVE_KEY_PATTERN = /(?:^|[_-])(api[_-]?key|token|refresh[_-]?token|authorization|x[_-]?api[_-]?key|project[_-]?id)$/i;
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// src/core/redaction.js
|
|
903
|
+
var init_redaction2 = __esm({
|
|
904
|
+
"src/core/redaction.js"() {
|
|
905
|
+
"use strict";
|
|
906
|
+
init_cjs_shims();
|
|
907
|
+
init_redaction();
|
|
908
|
+
}
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
// src/core/api-client.ts
|
|
912
|
+
var api_client_exports = {};
|
|
913
|
+
__export(api_client_exports, {
|
|
914
|
+
API_BASE: () => API_BASE,
|
|
915
|
+
API_VERSION: () => API_VERSION,
|
|
916
|
+
addProjectMember: () => addProjectMember,
|
|
917
|
+
analyzeContext: () => analyzeContext,
|
|
918
|
+
callMcpTool: () => callMcpTool,
|
|
919
|
+
clearCache: () => clearCache,
|
|
920
|
+
createCheckout: () => createCheckout,
|
|
921
|
+
directRequest: () => directRequest,
|
|
922
|
+
downloadPreseedZip: () => downloadPreseedZip,
|
|
923
|
+
enrichCodebasePreseed: () => enrichCodebasePreseed,
|
|
924
|
+
ensureProjectScopedToken: () => ensureProjectScopedToken,
|
|
925
|
+
exchangeProjectScopedToken: () => exchangeProjectScopedToken,
|
|
926
|
+
findSimilarProjects: () => findSimilarProjects,
|
|
927
|
+
getActiveMcpConnectorMap: () => getActiveMcpConnectorMap,
|
|
928
|
+
getAgent: () => getAgent,
|
|
929
|
+
getAgentCapabilities: () => getAgentCapabilities,
|
|
930
|
+
getAgentContext: () => getAgentContext,
|
|
931
|
+
getInvoices: () => getInvoices,
|
|
932
|
+
getLintBudgets: () => getLintBudgets,
|
|
933
|
+
getMcpResource: () => getMcpResource,
|
|
934
|
+
getOrganization: () => getOrganization,
|
|
935
|
+
getPortalUrl: () => getPortalUrl,
|
|
936
|
+
getPreseedDocument: () => getPreseedDocument,
|
|
937
|
+
getPreseedWizard: () => getPreseedWizard,
|
|
938
|
+
getProjectActivity: () => getProjectActivity,
|
|
939
|
+
getProjectMembers: () => getProjectMembers,
|
|
940
|
+
getSkill: () => getSkill,
|
|
941
|
+
getSkillContent: () => getSkillContent,
|
|
942
|
+
getSubscription: () => getSubscription,
|
|
943
|
+
getSuggestions: () => getSuggestions,
|
|
944
|
+
getTemplate: () => getTemplate,
|
|
945
|
+
getUsage: () => getUsage,
|
|
946
|
+
getWorkflow: () => getWorkflow,
|
|
947
|
+
healthCheck: () => healthCheck,
|
|
948
|
+
inviteProjectMember: () => inviteProjectMember,
|
|
949
|
+
invokeAgent: () => invokeAgent,
|
|
950
|
+
listAgents: () => listAgents,
|
|
951
|
+
listMcpConnectors: () => listMcpConnectors,
|
|
952
|
+
listMcpResources: () => listMcpResources,
|
|
953
|
+
listMcpTools: () => listMcpTools,
|
|
954
|
+
listPreseedDocuments: () => listPreseedDocuments,
|
|
955
|
+
listProjectInvitations: () => listProjectInvitations,
|
|
956
|
+
listProjects: () => listProjects,
|
|
957
|
+
listQualityGates: () => listQualityGates,
|
|
958
|
+
listSkills: () => listSkills,
|
|
959
|
+
listTemplates: () => listTemplates,
|
|
960
|
+
listWorkflows: () => listWorkflows,
|
|
961
|
+
login: () => login2,
|
|
962
|
+
loginWithApiKey: () => loginWithApiKey2,
|
|
963
|
+
logout: () => logout2,
|
|
964
|
+
me: () => me,
|
|
965
|
+
pollDeviceToken: () => pollDeviceToken,
|
|
966
|
+
refreshToken: () => refreshToken,
|
|
967
|
+
register: () => register,
|
|
968
|
+
removeProjectMember: () => removeProjectMember,
|
|
969
|
+
request: () => request,
|
|
970
|
+
requestDeviceCode: () => requestDeviceCode,
|
|
971
|
+
requireAuth: () => requireAuth,
|
|
972
|
+
resolveEntitlements: () => resolveEntitlements,
|
|
973
|
+
respondToProjectInvitation: () => respondToProjectInvitation,
|
|
974
|
+
runQualityGate: () => runQualityGate,
|
|
975
|
+
searchSkills: () => searchSkills,
|
|
976
|
+
setMcpConnectorEnabled: () => setMcpConnectorEnabled,
|
|
977
|
+
startWorkflow: () => startWorkflow,
|
|
978
|
+
trackUsage: () => trackUsage,
|
|
979
|
+
transferProjectOwnership: () => transferProjectOwnership,
|
|
980
|
+
updateProjectMember: () => updateProjectMember,
|
|
981
|
+
uploadTelemetryBatch: () => uploadTelemetryBatch,
|
|
982
|
+
validateApiKey: () => validateApiKey
|
|
983
|
+
});
|
|
984
|
+
function getPackageVersion() {
|
|
985
|
+
if (packageVersion === null) {
|
|
986
|
+
try {
|
|
987
|
+
const pkg = require_package();
|
|
988
|
+
packageVersion = pkg.version;
|
|
989
|
+
} catch {
|
|
990
|
+
packageVersion = "unknown";
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return packageVersion;
|
|
994
|
+
}
|
|
995
|
+
function formatHttpErrorBody(body, statusCode) {
|
|
996
|
+
const raw = String(body || "").trim();
|
|
997
|
+
if (!raw) {
|
|
998
|
+
return `API Error (${statusCode || "unknown"})`;
|
|
999
|
+
}
|
|
1000
|
+
if (/^\s*<!doctype html/i.test(raw) || /^\s*<html/i.test(raw)) {
|
|
1001
|
+
const titleMatch = raw.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
1002
|
+
const title = titleMatch && titleMatch[1] ? `: ${titleMatch[1].trim()}` : "";
|
|
1003
|
+
return `Bootspring API returned an HTML error page (HTTP ${statusCode || "unknown"}${title})`;
|
|
1004
|
+
}
|
|
1005
|
+
return redactSensitiveString(raw);
|
|
1006
|
+
}
|
|
1007
|
+
function parseExpiresInSeconds(raw) {
|
|
1008
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) {
|
|
1009
|
+
return Math.round(raw);
|
|
1010
|
+
}
|
|
1011
|
+
if (typeof raw === "string") {
|
|
1012
|
+
const trimmed = raw.trim().toLowerCase();
|
|
1013
|
+
if (!trimmed) return 15 * 60;
|
|
1014
|
+
if (/^\d+$/.test(trimmed)) {
|
|
1015
|
+
return parseInt(trimmed, 10);
|
|
1016
|
+
}
|
|
1017
|
+
const durationMatch = trimmed.match(/^(\d+)\s*([smhd])$/);
|
|
1018
|
+
if (durationMatch) {
|
|
1019
|
+
const value = parseInt(durationMatch[1] || "0", 10);
|
|
1020
|
+
const unit = durationMatch[2];
|
|
1021
|
+
if (unit === "s") return value;
|
|
1022
|
+
if (unit === "m") return value * 60;
|
|
1023
|
+
if (unit === "h") return value * 60 * 60;
|
|
1024
|
+
if (unit === "d") return value * 60 * 60 * 24;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
return 15 * 60;
|
|
1028
|
+
}
|
|
1029
|
+
function parseProjectScopedTokenPayload(raw) {
|
|
1030
|
+
const payload = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : null;
|
|
1031
|
+
if (!payload) {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
const nestedData = payload.data;
|
|
1035
|
+
const source = nestedData && typeof nestedData === "object" && !Array.isArray(nestedData) ? nestedData : payload;
|
|
1036
|
+
const tokenCandidates = [
|
|
1037
|
+
source.token,
|
|
1038
|
+
source.sessionToken,
|
|
1039
|
+
source.session_token,
|
|
1040
|
+
source.accessToken,
|
|
1041
|
+
source.access_token
|
|
1042
|
+
];
|
|
1043
|
+
const token = tokenCandidates.find((value) => typeof value === "string" && value.length > 0);
|
|
1044
|
+
if (!token) {
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
const explicitExpiresAt = [source.expiresAt, source.expires_at].find((value) => typeof value === "string" && value.length > 0);
|
|
1048
|
+
const expiresInSeconds = parseExpiresInSeconds(
|
|
1049
|
+
[source.expiresIn, source.expires_in, source.ttl, source.ttlSeconds].find((value) => value !== void 0)
|
|
1050
|
+
);
|
|
1051
|
+
const expiresAt = explicitExpiresAt || new Date(Date.now() + expiresInSeconds * 1e3).toISOString();
|
|
1052
|
+
return {
|
|
1053
|
+
token,
|
|
1054
|
+
expiresAt,
|
|
1055
|
+
expiresInSeconds
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
async function requestProjectScopedToken(path3, apiKey, projectId) {
|
|
1059
|
+
const url = new URL(path3, API_BASE);
|
|
1060
|
+
const isHttps = url.protocol === "https:";
|
|
1061
|
+
const httpModule = isHttps ? https : http;
|
|
1062
|
+
const deviceContext = getDeviceContext();
|
|
1063
|
+
return new Promise((resolve2, reject) => {
|
|
1064
|
+
const requestBody = JSON.stringify({
|
|
1065
|
+
apiKey,
|
|
1066
|
+
projectId,
|
|
1067
|
+
scope: "project",
|
|
1068
|
+
deviceFingerprint: deviceContext.deviceId,
|
|
1069
|
+
deviceName: `CLI - ${deviceContext.hostname}`
|
|
1070
|
+
});
|
|
1071
|
+
const req = httpModule.request(url, {
|
|
1072
|
+
method: "POST",
|
|
1073
|
+
headers: {
|
|
1074
|
+
"Content-Type": "application/json",
|
|
1075
|
+
"User-Agent": `bootspring-cli/${getPackageVersion()}`,
|
|
1076
|
+
"Content-Length": Buffer.byteLength(requestBody).toString()
|
|
1077
|
+
},
|
|
1078
|
+
timeout: 1e4
|
|
1079
|
+
}, (res) => {
|
|
1080
|
+
let body = "";
|
|
1081
|
+
res.on("data", (chunk) => {
|
|
1082
|
+
body += chunk;
|
|
1083
|
+
});
|
|
1084
|
+
res.on("end", () => {
|
|
1085
|
+
let parsed = null;
|
|
1086
|
+
try {
|
|
1087
|
+
parsed = body ? JSON.parse(body) : {};
|
|
1088
|
+
} catch {
|
|
1089
|
+
parsed = {};
|
|
1090
|
+
}
|
|
1091
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1092
|
+
const payload = parsed && typeof parsed === "object" ? parsed : {};
|
|
1093
|
+
const error = new Error(
|
|
1094
|
+
redactSensitiveString(String(payload.message || payload.error || `Token exchange failed (${res.statusCode})`))
|
|
1095
|
+
);
|
|
1096
|
+
error.status = res.statusCode;
|
|
1097
|
+
error.code = typeof payload.code === "string" ? payload.code : void 0;
|
|
1098
|
+
error.details = redactSensitiveData(payload.details);
|
|
1099
|
+
reject(error);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
const exchange = parseProjectScopedTokenPayload(parsed);
|
|
1103
|
+
if (!exchange) {
|
|
1104
|
+
const error = new Error("Token exchange response did not include a scoped token");
|
|
1105
|
+
error.status = res.statusCode;
|
|
1106
|
+
reject(error);
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
resolve2(exchange);
|
|
1110
|
+
});
|
|
1111
|
+
});
|
|
1112
|
+
req.on("error", reject);
|
|
1113
|
+
req.on("timeout", () => {
|
|
1114
|
+
req.destroy();
|
|
1115
|
+
reject(new Error("Token exchange request timeout"));
|
|
1116
|
+
});
|
|
1117
|
+
req.write(requestBody);
|
|
1118
|
+
req.end();
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
async function exchangeProjectScopedToken(apiKey, projectId = null) {
|
|
1122
|
+
const paths = [
|
|
1123
|
+
"/api/keys/scoped-token",
|
|
1124
|
+
"/api/keys/exchange",
|
|
1125
|
+
"/api/keys/session"
|
|
1126
|
+
];
|
|
1127
|
+
let lastError = null;
|
|
1128
|
+
for (const exchangePath of paths) {
|
|
1129
|
+
try {
|
|
1130
|
+
const exchange = await requestProjectScopedToken(exchangePath, apiKey, projectId);
|
|
1131
|
+
return {
|
|
1132
|
+
...exchange,
|
|
1133
|
+
sourcePath: exchangePath
|
|
1134
|
+
};
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
lastError = error;
|
|
1137
|
+
const apiError = error;
|
|
1138
|
+
if (apiError.status && apiError.status >= 400 && apiError.status < 500 && apiError.status !== 404 && apiError.status !== 405) {
|
|
1139
|
+
break;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
throw lastError || new Error("Failed to exchange scoped token");
|
|
1144
|
+
}
|
|
1145
|
+
async function ensureProjectScopedToken() {
|
|
1146
|
+
if (process.env.BOOTSPRING_API_KEY) {
|
|
1147
|
+
return process.env.BOOTSPRING_API_KEY;
|
|
1148
|
+
}
|
|
1149
|
+
const scopedToken = getProjectScopedToken();
|
|
1150
|
+
if (scopedToken) {
|
|
1151
|
+
return scopedToken;
|
|
1152
|
+
}
|
|
1153
|
+
const project = getEffectiveProject();
|
|
1154
|
+
const projectId = project?.id || null;
|
|
1155
|
+
const storedApiKey = getStoredApiKey();
|
|
1156
|
+
if (storedApiKey) {
|
|
1157
|
+
try {
|
|
1158
|
+
const exchanged = await exchangeProjectScopedToken(storedApiKey, projectId);
|
|
1159
|
+
saveProjectScopedSession(exchanged.token, {
|
|
1160
|
+
expiresAt: exchanged.expiresAt,
|
|
1161
|
+
source: exchanged.sourcePath
|
|
1162
|
+
});
|
|
1163
|
+
return exchanged.token;
|
|
1164
|
+
} catch {
|
|
1165
|
+
return storedApiKey;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const legacyProjectApiKey = getLegacyProjectApiKey();
|
|
1169
|
+
if (legacyProjectApiKey) {
|
|
1170
|
+
try {
|
|
1171
|
+
const exchanged = await exchangeProjectScopedToken(legacyProjectApiKey, projectId);
|
|
1172
|
+
saveProjectScopedSession(exchanged.token, {
|
|
1173
|
+
expiresAt: exchanged.expiresAt,
|
|
1174
|
+
source: exchanged.sourcePath,
|
|
1175
|
+
migratedFromLegacyApiKey: true
|
|
1176
|
+
});
|
|
1177
|
+
clearProjectApiKey();
|
|
1178
|
+
return exchanged.token;
|
|
1179
|
+
} catch {
|
|
1180
|
+
return legacyProjectApiKey;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
async function resolveAuthHeaders() {
|
|
1186
|
+
const token = getToken();
|
|
1187
|
+
if (token) {
|
|
1188
|
+
return { Authorization: `Bearer ${token}` };
|
|
1189
|
+
}
|
|
1190
|
+
const scopedOrApiKey = await ensureProjectScopedToken();
|
|
1191
|
+
if (scopedOrApiKey) {
|
|
1192
|
+
return { "X-API-Key": scopedOrApiKey };
|
|
1193
|
+
}
|
|
1194
|
+
const fallbackApiKey = getApiKey();
|
|
1195
|
+
if (fallbackApiKey) {
|
|
1196
|
+
return { "X-API-Key": fallbackApiKey };
|
|
1197
|
+
}
|
|
1198
|
+
return {};
|
|
1199
|
+
}
|
|
1200
|
+
async function request(method, path3, data = null, options = {}) {
|
|
1201
|
+
const authHeaders = await resolveAuthHeaders();
|
|
1202
|
+
const url = new URL(`/api/${API_VERSION}${path3}`, API_BASE);
|
|
1203
|
+
const isHttps = url.protocol === "https:";
|
|
1204
|
+
const httpModule = isHttps ? https : http;
|
|
1205
|
+
const deviceId = getDeviceId();
|
|
1206
|
+
const project = getEffectiveProject();
|
|
1207
|
+
const projectId = project?.id || null;
|
|
1208
|
+
const headers = {
|
|
1209
|
+
"Content-Type": "application/json",
|
|
1210
|
+
"User-Agent": `bootspring-cli/${getPackageVersion()}`,
|
|
1211
|
+
"X-Device-Id": deviceId,
|
|
1212
|
+
...projectId && { "X-Project-Id": projectId },
|
|
1213
|
+
...authHeaders,
|
|
1214
|
+
...options.headers
|
|
1215
|
+
};
|
|
1216
|
+
if (method === "GET" && !options.noCache) {
|
|
1217
|
+
const cacheKey = `${method}:${path3}`;
|
|
1218
|
+
const cached = cache.get(cacheKey);
|
|
1219
|
+
if (cached && Date.now() - cached.time < CACHE_TTL) {
|
|
1220
|
+
return cached.data;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return new Promise((resolve2, reject) => {
|
|
1224
|
+
const req = httpModule.request(url, {
|
|
1225
|
+
method,
|
|
1226
|
+
headers,
|
|
1227
|
+
timeout: options.timeout || 3e4
|
|
1228
|
+
}, (res) => {
|
|
1229
|
+
let body = "";
|
|
1230
|
+
res.on("data", (chunk) => {
|
|
1231
|
+
body += chunk;
|
|
1232
|
+
});
|
|
1233
|
+
res.on("end", () => {
|
|
1234
|
+
try {
|
|
1235
|
+
const json = JSON.parse(body);
|
|
1236
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1237
|
+
const error = new Error(
|
|
1238
|
+
redactSensitiveString(String(json.message || json.error || "API Error"))
|
|
1239
|
+
);
|
|
1240
|
+
error.status = res.statusCode;
|
|
1241
|
+
error.code = json.error || json.code;
|
|
1242
|
+
error.details = redactSensitiveData(json.details);
|
|
1243
|
+
reject(error);
|
|
1244
|
+
} else {
|
|
1245
|
+
if (method === "GET" && !options.noCache) {
|
|
1246
|
+
const cacheKey = `${method}:${path3}`;
|
|
1247
|
+
cache.set(cacheKey, { data: json, time: Date.now() });
|
|
1248
|
+
}
|
|
1249
|
+
resolve2(json);
|
|
1250
|
+
}
|
|
1251
|
+
} catch {
|
|
1252
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1253
|
+
const error = new Error(formatHttpErrorBody(body, res.statusCode));
|
|
1254
|
+
error.status = res.statusCode;
|
|
1255
|
+
reject(error);
|
|
1256
|
+
} else {
|
|
1257
|
+
resolve2(body);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
});
|
|
1262
|
+
req.on("error", (err) => {
|
|
1263
|
+
if (err.code === "ECONNREFUSED") {
|
|
1264
|
+
reject(new Error("Cannot connect to Bootspring API. Please check your internet connection."));
|
|
1265
|
+
} else {
|
|
1266
|
+
reject(new Error(redactSensitiveString(err.message || String(err))));
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
req.on("timeout", () => {
|
|
1270
|
+
req.destroy();
|
|
1271
|
+
reject(new Error("Request timeout"));
|
|
1272
|
+
});
|
|
1273
|
+
if (data) {
|
|
1274
|
+
req.write(JSON.stringify(data));
|
|
1275
|
+
}
|
|
1276
|
+
req.end();
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
async function directRequest(method, path3, data = null, options = {}) {
|
|
1280
|
+
const authHeaders = await resolveAuthHeaders();
|
|
1281
|
+
const url = new URL(`/api${path3}`, API_BASE);
|
|
1282
|
+
const isHttps = url.protocol === "https:";
|
|
1283
|
+
const httpModule = isHttps ? https : http;
|
|
1284
|
+
const deviceId = getDeviceId();
|
|
1285
|
+
const project = getEffectiveProject();
|
|
1286
|
+
const projectId = project?.id || null;
|
|
1287
|
+
const headers = {
|
|
1288
|
+
"Content-Type": "application/json",
|
|
1289
|
+
"User-Agent": `bootspring-cli/${getPackageVersion()}`,
|
|
1290
|
+
"X-Device-Id": deviceId,
|
|
1291
|
+
...projectId && { "X-Project-Id": projectId },
|
|
1292
|
+
...authHeaders,
|
|
1293
|
+
...options.headers
|
|
1294
|
+
};
|
|
1295
|
+
return new Promise((resolve2, reject) => {
|
|
1296
|
+
const req = httpModule.request(url, {
|
|
1297
|
+
method,
|
|
1298
|
+
headers,
|
|
1299
|
+
timeout: options.timeout || 3e4
|
|
1300
|
+
}, (res) => {
|
|
1301
|
+
let body = "";
|
|
1302
|
+
res.on("data", (chunk) => {
|
|
1303
|
+
body += chunk;
|
|
1304
|
+
});
|
|
1305
|
+
res.on("end", () => {
|
|
1306
|
+
try {
|
|
1307
|
+
const json = JSON.parse(body);
|
|
1308
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1309
|
+
const error = new Error(
|
|
1310
|
+
redactSensitiveString(String(json.message || json.error || "API Error"))
|
|
1311
|
+
);
|
|
1312
|
+
error.status = res.statusCode;
|
|
1313
|
+
error.code = json.error || json.code;
|
|
1314
|
+
error.details = redactSensitiveData(json.details);
|
|
1315
|
+
reject(error);
|
|
1316
|
+
} else {
|
|
1317
|
+
resolve2(json);
|
|
1318
|
+
}
|
|
1319
|
+
} catch {
|
|
1320
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1321
|
+
const error = new Error(formatHttpErrorBody(body, res.statusCode));
|
|
1322
|
+
error.status = res.statusCode;
|
|
1323
|
+
reject(error);
|
|
1324
|
+
} else {
|
|
1325
|
+
resolve2(body);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
});
|
|
1330
|
+
req.on("error", (err) => {
|
|
1331
|
+
if (err.code === "ECONNREFUSED") {
|
|
1332
|
+
reject(new Error("Cannot connect to Bootspring API. Please check your internet connection."));
|
|
1333
|
+
} else {
|
|
1334
|
+
reject(new Error(redactSensitiveString(err.message || String(err))));
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
req.on("timeout", () => {
|
|
1338
|
+
req.destroy();
|
|
1339
|
+
reject(new Error("Request timeout"));
|
|
1340
|
+
});
|
|
1341
|
+
if (data) {
|
|
1342
|
+
req.write(JSON.stringify(data));
|
|
1343
|
+
}
|
|
1344
|
+
req.end();
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
function clearCache() {
|
|
1348
|
+
cache.clear();
|
|
1349
|
+
}
|
|
1350
|
+
async function healthCheck() {
|
|
1351
|
+
try {
|
|
1352
|
+
const url = new URL("/health", API_BASE);
|
|
1353
|
+
const isHttps = url.protocol === "https:";
|
|
1354
|
+
const httpModule = isHttps ? https : http;
|
|
1355
|
+
return new Promise((resolve2) => {
|
|
1356
|
+
const req = httpModule.request(url, {
|
|
1357
|
+
method: "GET",
|
|
1358
|
+
timeout: 5e3
|
|
1359
|
+
}, (res) => {
|
|
1360
|
+
let body = "";
|
|
1361
|
+
res.on("data", (chunk) => {
|
|
1362
|
+
body += chunk;
|
|
1363
|
+
});
|
|
1364
|
+
res.on("end", () => {
|
|
1365
|
+
try {
|
|
1366
|
+
const json = JSON.parse(body);
|
|
1367
|
+
resolve2({ connected: true, version: json.version });
|
|
1368
|
+
} catch {
|
|
1369
|
+
resolve2({ connected: false, error: "Invalid response" });
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
});
|
|
1373
|
+
req.on("error", (err) => {
|
|
1374
|
+
resolve2({ connected: false, error: redactSensitiveString(err.message) });
|
|
1375
|
+
});
|
|
1376
|
+
req.on("timeout", () => {
|
|
1377
|
+
req.destroy();
|
|
1378
|
+
resolve2({ connected: false, error: "Timeout" });
|
|
1379
|
+
});
|
|
1380
|
+
req.end();
|
|
1381
|
+
});
|
|
1382
|
+
} catch (error) {
|
|
1383
|
+
const err = error;
|
|
1384
|
+
return { connected: false, error: redactSensitiveString(err.message) };
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function requireAuth() {
|
|
1388
|
+
if (!isAuthenticated()) {
|
|
1389
|
+
const error = new Error("Authentication required. Run: bootspring auth login");
|
|
1390
|
+
error.code = "AUTH_REQUIRED";
|
|
1391
|
+
throw error;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
async function requestDeviceCode() {
|
|
1395
|
+
const deviceContext = getDeviceContext();
|
|
1396
|
+
const url = new URL("/api/v1/auth/device", API_BASE);
|
|
1397
|
+
const isHttps = url.protocol === "https:";
|
|
1398
|
+
const httpModule = isHttps ? https : http;
|
|
1399
|
+
return new Promise((resolve2, reject) => {
|
|
1400
|
+
const req = httpModule.request(url, {
|
|
1401
|
+
method: "POST",
|
|
1402
|
+
headers: {
|
|
1403
|
+
"Content-Type": "application/json",
|
|
1404
|
+
"User-Agent": `bootspring-cli/${getPackageVersion()}`
|
|
1405
|
+
},
|
|
1406
|
+
timeout: 1e4
|
|
1407
|
+
}, (res) => {
|
|
1408
|
+
let body = "";
|
|
1409
|
+
res.on("data", (chunk) => {
|
|
1410
|
+
body += chunk;
|
|
1411
|
+
});
|
|
1412
|
+
res.on("end", () => {
|
|
1413
|
+
try {
|
|
1414
|
+
const json = JSON.parse(body);
|
|
1415
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1416
|
+
const error = new Error(
|
|
1417
|
+
redactSensitiveString(String(json.message || json.error || "Failed to get device code"))
|
|
1418
|
+
);
|
|
1419
|
+
error.status = res.statusCode;
|
|
1420
|
+
error.code = json.error;
|
|
1421
|
+
reject(error);
|
|
1422
|
+
} else {
|
|
1423
|
+
resolve2(json);
|
|
1424
|
+
}
|
|
1425
|
+
} catch {
|
|
1426
|
+
reject(new Error("Invalid response from API"));
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
});
|
|
1430
|
+
req.on("error", reject);
|
|
1431
|
+
req.on("timeout", () => {
|
|
1432
|
+
req.destroy();
|
|
1433
|
+
reject(new Error("Request timeout"));
|
|
1434
|
+
});
|
|
1435
|
+
req.write(JSON.stringify({ device: deviceContext }));
|
|
1436
|
+
req.end();
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
async function pollDeviceToken(deviceCode) {
|
|
1440
|
+
const url = new URL("/api/v1/auth/device/token", API_BASE);
|
|
1441
|
+
const isHttps = url.protocol === "https:";
|
|
1442
|
+
const httpModule = isHttps ? https : http;
|
|
1443
|
+
return new Promise((resolve2, reject) => {
|
|
1444
|
+
const req = httpModule.request(url, {
|
|
1445
|
+
method: "POST",
|
|
1446
|
+
headers: {
|
|
1447
|
+
"Content-Type": "application/json",
|
|
1448
|
+
"User-Agent": `bootspring-cli/${getPackageVersion()}`
|
|
1449
|
+
},
|
|
1450
|
+
timeout: 1e4
|
|
1451
|
+
}, (res) => {
|
|
1452
|
+
let body = "";
|
|
1453
|
+
res.on("data", (chunk) => {
|
|
1454
|
+
body += chunk;
|
|
1455
|
+
});
|
|
1456
|
+
res.on("end", () => {
|
|
1457
|
+
try {
|
|
1458
|
+
const json = JSON.parse(body);
|
|
1459
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1460
|
+
const error = new Error(
|
|
1461
|
+
redactSensitiveString(String(json.message || json.error || "Authorization pending"))
|
|
1462
|
+
);
|
|
1463
|
+
error.status = res.statusCode;
|
|
1464
|
+
error.code = json.error;
|
|
1465
|
+
error.details = redactSensitiveData(json);
|
|
1466
|
+
reject(error);
|
|
1467
|
+
} else {
|
|
1468
|
+
resolve2(json);
|
|
1469
|
+
}
|
|
1470
|
+
} catch {
|
|
1471
|
+
reject(new Error("Invalid response from API"));
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
});
|
|
1475
|
+
req.on("error", reject);
|
|
1476
|
+
req.on("timeout", () => {
|
|
1477
|
+
req.destroy();
|
|
1478
|
+
reject(new Error("Request timeout"));
|
|
1479
|
+
});
|
|
1480
|
+
req.write(JSON.stringify({ device_code: deviceCode }));
|
|
1481
|
+
req.end();
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
async function login2(email, password) {
|
|
1485
|
+
const deviceContext = getDeviceContext();
|
|
1486
|
+
const response = await request("POST", "/auth/login", {
|
|
1487
|
+
email,
|
|
1488
|
+
password,
|
|
1489
|
+
device: deviceContext
|
|
1490
|
+
});
|
|
1491
|
+
login(response);
|
|
1492
|
+
return response;
|
|
1493
|
+
}
|
|
1494
|
+
async function loginWithApiKey2(apiKey, options = {}) {
|
|
1495
|
+
const url = new URL("/api/keys/validate", API_BASE);
|
|
1496
|
+
const isHttps = url.protocol === "https:";
|
|
1497
|
+
const httpModule = isHttps ? https : http;
|
|
1498
|
+
const deviceContext = getDeviceContext();
|
|
1499
|
+
return new Promise((resolve2, reject) => {
|
|
1500
|
+
const requestBody = JSON.stringify({
|
|
1501
|
+
apiKey,
|
|
1502
|
+
deviceFingerprint: deviceContext.deviceId,
|
|
1503
|
+
deviceName: `CLI - ${deviceContext.hostname}`
|
|
1504
|
+
});
|
|
1505
|
+
const req = httpModule.request(url, {
|
|
1506
|
+
method: "POST",
|
|
1507
|
+
headers: {
|
|
1508
|
+
"Content-Type": "application/json",
|
|
1509
|
+
"User-Agent": `bootspring-cli/${getPackageVersion()}`,
|
|
1510
|
+
"Content-Length": Buffer.byteLength(requestBody).toString()
|
|
1511
|
+
},
|
|
1512
|
+
timeout: 1e4
|
|
1513
|
+
}, (res) => {
|
|
1514
|
+
let body = "";
|
|
1515
|
+
res.on("data", (chunk) => {
|
|
1516
|
+
body += chunk;
|
|
1517
|
+
});
|
|
1518
|
+
res.on("end", async () => {
|
|
1519
|
+
try {
|
|
1520
|
+
const json = JSON.parse(body);
|
|
1521
|
+
if (res.statusCode && res.statusCode >= 400 || !json.valid) {
|
|
1522
|
+
const error = new Error(redactSensitiveString(json.error || "Invalid API key"));
|
|
1523
|
+
error.status = res.statusCode;
|
|
1524
|
+
error.code = json.error;
|
|
1525
|
+
reject(error);
|
|
1526
|
+
} else {
|
|
1527
|
+
const projectId = options.projectId || json.project?.id || null;
|
|
1528
|
+
const user = {
|
|
1529
|
+
tier: json.tier,
|
|
1530
|
+
scopes: json.scopes
|
|
1531
|
+
};
|
|
1532
|
+
loginWithApiKey(apiKey, user);
|
|
1533
|
+
if (json.project) {
|
|
1534
|
+
setCurrentProject(json.project);
|
|
1535
|
+
addRecentProject(json.project);
|
|
1536
|
+
}
|
|
1537
|
+
if (projectId && saveApiKeyToProject) {
|
|
1538
|
+
saveApiKeyToProject(apiKey);
|
|
1539
|
+
}
|
|
1540
|
+
try {
|
|
1541
|
+
const exchanged = await exchangeProjectScopedToken(apiKey, projectId);
|
|
1542
|
+
saveProjectScopedSession(exchanged.token, {
|
|
1543
|
+
expiresAt: exchanged.expiresAt,
|
|
1544
|
+
source: exchanged.sourcePath,
|
|
1545
|
+
migratedFromLegacyApiKey: true
|
|
1546
|
+
});
|
|
1547
|
+
} catch {
|
|
1548
|
+
}
|
|
1549
|
+
resolve2({
|
|
1550
|
+
...json,
|
|
1551
|
+
user
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
} catch {
|
|
1555
|
+
reject(new Error("Invalid response from API"));
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
});
|
|
1559
|
+
req.on("error", reject);
|
|
1560
|
+
req.on("timeout", () => {
|
|
1561
|
+
req.destroy();
|
|
1562
|
+
reject(new Error("Request timeout"));
|
|
1563
|
+
});
|
|
1564
|
+
req.write(requestBody);
|
|
1565
|
+
req.end();
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
async function register(email, password, name) {
|
|
1569
|
+
const deviceContext = getDeviceContext();
|
|
1570
|
+
const response = await request("POST", "/auth/register", {
|
|
1571
|
+
email,
|
|
1572
|
+
password,
|
|
1573
|
+
name,
|
|
1574
|
+
device: deviceContext
|
|
1575
|
+
});
|
|
1576
|
+
login(response);
|
|
1577
|
+
return response;
|
|
1578
|
+
}
|
|
1579
|
+
async function me() {
|
|
1580
|
+
requireAuth();
|
|
1581
|
+
return request("GET", "/auth/me");
|
|
1582
|
+
}
|
|
1583
|
+
async function validateApiKey(apiKey) {
|
|
1584
|
+
const url = new URL("/api/keys/validate", API_BASE);
|
|
1585
|
+
const isHttps = url.protocol === "https:";
|
|
1586
|
+
const httpModule = isHttps ? https : http;
|
|
1587
|
+
const deviceContext = getDeviceContext();
|
|
1588
|
+
return new Promise((resolve2, reject) => {
|
|
1589
|
+
const requestBody = JSON.stringify({
|
|
1590
|
+
apiKey,
|
|
1591
|
+
deviceFingerprint: deviceContext.deviceId,
|
|
1592
|
+
deviceName: `CLI - ${deviceContext.hostname}`
|
|
1593
|
+
});
|
|
1594
|
+
const req = httpModule.request(url, {
|
|
1595
|
+
method: "POST",
|
|
1596
|
+
headers: {
|
|
1597
|
+
"Content-Type": "application/json",
|
|
1598
|
+
"User-Agent": `bootspring-cli/${getPackageVersion()}`,
|
|
1599
|
+
"Content-Length": Buffer.byteLength(requestBody).toString()
|
|
1600
|
+
},
|
|
1601
|
+
timeout: 1e4
|
|
1602
|
+
}, (res) => {
|
|
1603
|
+
let body = "";
|
|
1604
|
+
res.on("data", (chunk) => {
|
|
1605
|
+
body += chunk;
|
|
1606
|
+
});
|
|
1607
|
+
res.on("end", () => {
|
|
1608
|
+
try {
|
|
1609
|
+
const json = JSON.parse(body);
|
|
1610
|
+
if (res.statusCode && res.statusCode >= 400 || !json.valid) {
|
|
1611
|
+
const error = new Error(redactSensitiveString(json.error || "Invalid API key"));
|
|
1612
|
+
error.status = res.statusCode;
|
|
1613
|
+
reject(error);
|
|
1614
|
+
} else {
|
|
1615
|
+
resolve2(json);
|
|
1616
|
+
}
|
|
1617
|
+
} catch {
|
|
1618
|
+
reject(new Error("Invalid response from API"));
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
});
|
|
1622
|
+
req.on("error", reject);
|
|
1623
|
+
req.on("timeout", () => {
|
|
1624
|
+
req.destroy();
|
|
1625
|
+
reject(new Error("Request timeout"));
|
|
1626
|
+
});
|
|
1627
|
+
req.write(requestBody);
|
|
1628
|
+
req.end();
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
async function refreshToken() {
|
|
1632
|
+
const refreshTokenValue = getRefreshToken();
|
|
1633
|
+
if (!refreshTokenValue) {
|
|
1634
|
+
throw new Error("No refresh token available");
|
|
1635
|
+
}
|
|
1636
|
+
const deviceContext = getDeviceContext();
|
|
1637
|
+
const response = await request("POST", "/auth/refresh", {
|
|
1638
|
+
refreshToken: refreshTokenValue,
|
|
1639
|
+
device: deviceContext
|
|
1640
|
+
});
|
|
1641
|
+
updateTokens(response);
|
|
1642
|
+
return response;
|
|
1643
|
+
}
|
|
1644
|
+
async function logout2() {
|
|
1645
|
+
if (isAuthenticated()) {
|
|
1646
|
+
try {
|
|
1647
|
+
const refreshTokenValue = getRefreshToken();
|
|
1648
|
+
await request("POST", "/auth/logout", { refreshToken: refreshTokenValue });
|
|
1649
|
+
} catch {
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
logout();
|
|
1653
|
+
}
|
|
1654
|
+
async function listAgents() {
|
|
1655
|
+
requireAuth();
|
|
1656
|
+
return request("GET", "/agents");
|
|
1657
|
+
}
|
|
1658
|
+
async function getAgent(id) {
|
|
1659
|
+
requireAuth();
|
|
1660
|
+
return request("GET", `/agents/${encodeURIComponent(id)}`);
|
|
1661
|
+
}
|
|
1662
|
+
async function getAgentContext(id) {
|
|
1663
|
+
requireAuth();
|
|
1664
|
+
return request("GET", `/agents/${encodeURIComponent(id)}/context`);
|
|
1665
|
+
}
|
|
1666
|
+
async function invokeAgent(id, projectContext, task) {
|
|
1667
|
+
requireAuth();
|
|
1668
|
+
return request("POST", `/agents/${encodeURIComponent(id)}/invoke`, {
|
|
1669
|
+
projectContext,
|
|
1670
|
+
task
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
async function getAgentCapabilities(id) {
|
|
1674
|
+
requireAuth();
|
|
1675
|
+
return request("GET", `/agents/${encodeURIComponent(id)}/capabilities`);
|
|
1676
|
+
}
|
|
1677
|
+
async function listSkills(options = {}) {
|
|
1678
|
+
requireAuth();
|
|
1679
|
+
const params = new URLSearchParams();
|
|
1680
|
+
if (options.category) params.append("category", options.category);
|
|
1681
|
+
if (options.search) params.append("search", options.search);
|
|
1682
|
+
const query = params.toString();
|
|
1683
|
+
return request("GET", `/skills${query ? "?" + query : ""}`);
|
|
1684
|
+
}
|
|
1685
|
+
async function getSkillContent(skillId) {
|
|
1686
|
+
requireAuth();
|
|
1687
|
+
return request("GET", `/skills/${skillId}`);
|
|
1688
|
+
}
|
|
1689
|
+
async function getSkill(category, name) {
|
|
1690
|
+
requireAuth();
|
|
1691
|
+
return request("GET", `/skills/${encodeURIComponent(category)}/${encodeURIComponent(name)}`);
|
|
1692
|
+
}
|
|
1693
|
+
async function searchSkills(query, options = {}) {
|
|
1694
|
+
requireAuth();
|
|
1695
|
+
const params = new URLSearchParams();
|
|
1696
|
+
params.append("search", query);
|
|
1697
|
+
if (options.category) params.append("category", options.category);
|
|
1698
|
+
return request("GET", `/skills?${params.toString()}`);
|
|
1699
|
+
}
|
|
1700
|
+
async function listTemplates(category) {
|
|
1701
|
+
requireAuth();
|
|
1702
|
+
return request("GET", `/templates/${encodeURIComponent(category)}`);
|
|
1703
|
+
}
|
|
1704
|
+
async function getTemplate(category, name) {
|
|
1705
|
+
requireAuth();
|
|
1706
|
+
return request("GET", `/templates/${encodeURIComponent(category)}/${encodeURIComponent(name)}`);
|
|
1707
|
+
}
|
|
1708
|
+
async function listWorkflows() {
|
|
1709
|
+
requireAuth();
|
|
1710
|
+
return request("GET", "/orchestrator/workflows");
|
|
1711
|
+
}
|
|
1712
|
+
async function getWorkflow(id) {
|
|
1713
|
+
requireAuth();
|
|
1714
|
+
return request("GET", `/orchestrator/workflows/${encodeURIComponent(id)}`);
|
|
1715
|
+
}
|
|
1716
|
+
async function analyzeContext(projectContext, currentTask, recentActions) {
|
|
1717
|
+
requireAuth();
|
|
1718
|
+
return request("POST", "/orchestrator/analyze", {
|
|
1719
|
+
projectContext,
|
|
1720
|
+
currentTask,
|
|
1721
|
+
recentActions
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
async function startWorkflow(workflowId, projectContext, parameters) {
|
|
1725
|
+
requireAuth();
|
|
1726
|
+
return request("POST", "/orchestrator/start", {
|
|
1727
|
+
workflow: workflowId,
|
|
1728
|
+
projectContext,
|
|
1729
|
+
parameters
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
async function getSuggestions(context, action) {
|
|
1733
|
+
requireAuth();
|
|
1734
|
+
return request("POST", "/orchestrator/suggest", { context, action });
|
|
1735
|
+
}
|
|
1736
|
+
async function listQualityGates() {
|
|
1737
|
+
requireAuth();
|
|
1738
|
+
return request("GET", "/quality/gates");
|
|
1739
|
+
}
|
|
1740
|
+
async function runQualityGate(gateId, projectContext, options) {
|
|
1741
|
+
requireAuth();
|
|
1742
|
+
return request("POST", "/quality/run", {
|
|
1743
|
+
gate: gateId,
|
|
1744
|
+
projectContext,
|
|
1745
|
+
options
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
async function getLintBudgets() {
|
|
1749
|
+
requireAuth();
|
|
1750
|
+
return request("GET", "/quality/lint-budgets");
|
|
1751
|
+
}
|
|
1752
|
+
async function listMcpTools() {
|
|
1753
|
+
requireAuth();
|
|
1754
|
+
return request("GET", "/mcp/tools");
|
|
1755
|
+
}
|
|
1756
|
+
async function callMcpTool(tool, args) {
|
|
1757
|
+
requireAuth();
|
|
1758
|
+
return request("POST", "/mcp/tool", { tool, arguments: args });
|
|
1759
|
+
}
|
|
1760
|
+
async function listMcpResources() {
|
|
1761
|
+
requireAuth();
|
|
1762
|
+
return request("GET", "/mcp/resources");
|
|
1763
|
+
}
|
|
1764
|
+
async function getMcpResource(uri) {
|
|
1765
|
+
requireAuth();
|
|
1766
|
+
return request("GET", `/mcp/resources/${encodeURIComponent(uri)}`);
|
|
1767
|
+
}
|
|
1768
|
+
async function listMcpConnectors() {
|
|
1769
|
+
requireAuth();
|
|
1770
|
+
return request("GET", "/mcp/connectors", null, { noCache: true });
|
|
1771
|
+
}
|
|
1772
|
+
async function getActiveMcpConnectorMap() {
|
|
1773
|
+
requireAuth();
|
|
1774
|
+
return request("GET", "/mcp/connectors/active", null, { noCache: true });
|
|
1775
|
+
}
|
|
1776
|
+
async function setMcpConnectorEnabled(connectorId, enabled) {
|
|
1777
|
+
requireAuth();
|
|
1778
|
+
return request("PATCH", `/mcp/connectors/${encodeURIComponent(connectorId)}`, { enabled });
|
|
1779
|
+
}
|
|
1780
|
+
async function getSubscription() {
|
|
1781
|
+
requireAuth();
|
|
1782
|
+
return request("GET", "/billing/subscription");
|
|
1783
|
+
}
|
|
1784
|
+
async function createCheckout(plan) {
|
|
1785
|
+
requireAuth();
|
|
1786
|
+
return request("POST", "/billing/create-checkout", { plan });
|
|
1787
|
+
}
|
|
1788
|
+
async function getPortalUrl() {
|
|
1789
|
+
requireAuth();
|
|
1790
|
+
return request("POST", "/billing/portal");
|
|
1791
|
+
}
|
|
1792
|
+
async function getUsage() {
|
|
1793
|
+
requireAuth();
|
|
1794
|
+
return request("GET", "/billing/usage");
|
|
1795
|
+
}
|
|
1796
|
+
async function getInvoices() {
|
|
1797
|
+
requireAuth();
|
|
1798
|
+
return request("GET", "/billing/invoices");
|
|
1799
|
+
}
|
|
1800
|
+
async function resolveEntitlements() {
|
|
1801
|
+
requireAuth();
|
|
1802
|
+
return request("GET", "/entitlements/resolve", null, { noCache: false });
|
|
1803
|
+
}
|
|
1804
|
+
async function trackUsage(type, metadata = {}) {
|
|
1805
|
+
requireAuth();
|
|
1806
|
+
return request("POST", "/track", { type, metadata });
|
|
1807
|
+
}
|
|
1808
|
+
async function uploadTelemetryBatch(events, batchInfo = {}) {
|
|
1809
|
+
requireAuth();
|
|
1810
|
+
const batchId = batchInfo.id || crypto2.randomUUID();
|
|
1811
|
+
return request("POST", "/events/batch", {
|
|
1812
|
+
source: "bootspring",
|
|
1813
|
+
batch: {
|
|
1814
|
+
index: batchInfo.index || 0,
|
|
1815
|
+
total: batchInfo.total || 1,
|
|
1816
|
+
id: batchId
|
|
1817
|
+
},
|
|
1818
|
+
events
|
|
1819
|
+
}, {
|
|
1820
|
+
headers: {
|
|
1821
|
+
"X-Bootspring-Batch-Id": batchId
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
async function listProjects() {
|
|
1826
|
+
requireAuth();
|
|
1827
|
+
const url = new URL("/api/auth/device/projects", API_BASE);
|
|
1828
|
+
const isHttps = url.protocol === "https:";
|
|
1829
|
+
const httpModule = isHttps ? https : http;
|
|
1830
|
+
const authHeaders = await resolveAuthHeaders();
|
|
1831
|
+
return new Promise((resolve2, reject) => {
|
|
1832
|
+
const headers = {
|
|
1833
|
+
"Content-Type": "application/json",
|
|
1834
|
+
"User-Agent": `bootspring-cli/${getPackageVersion()}`,
|
|
1835
|
+
...authHeaders
|
|
1836
|
+
};
|
|
1837
|
+
const req = httpModule.request(url, {
|
|
1838
|
+
method: "GET",
|
|
1839
|
+
headers,
|
|
1840
|
+
timeout: 1e4
|
|
1841
|
+
}, (res) => {
|
|
1842
|
+
let body = "";
|
|
1843
|
+
res.on("data", (chunk) => {
|
|
1844
|
+
body += chunk;
|
|
1845
|
+
});
|
|
1846
|
+
res.on("end", () => {
|
|
1847
|
+
try {
|
|
1848
|
+
const json = JSON.parse(body);
|
|
1849
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1850
|
+
const error = new Error(
|
|
1851
|
+
redactSensitiveString(String(json.message || json.error || "Failed to list projects"))
|
|
1852
|
+
);
|
|
1853
|
+
error.status = res.statusCode;
|
|
1854
|
+
reject(error);
|
|
1855
|
+
} else {
|
|
1856
|
+
resolve2(json);
|
|
1857
|
+
}
|
|
1858
|
+
} catch {
|
|
1859
|
+
reject(new Error("Invalid response from API"));
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
});
|
|
1863
|
+
req.on("error", reject);
|
|
1864
|
+
req.on("timeout", () => {
|
|
1865
|
+
req.destroy();
|
|
1866
|
+
reject(new Error("Request timeout"));
|
|
1867
|
+
});
|
|
1868
|
+
req.end();
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
async function getProjectMembers(projectId) {
|
|
1872
|
+
requireAuth();
|
|
1873
|
+
return directRequest("GET", `/projects/${encodeURIComponent(projectId)}/members`);
|
|
1874
|
+
}
|
|
1875
|
+
async function addProjectMember(projectId, email, role = "member") {
|
|
1876
|
+
requireAuth();
|
|
1877
|
+
return directRequest("POST", `/projects/${encodeURIComponent(projectId)}/members`, {
|
|
1878
|
+
email,
|
|
1879
|
+
role
|
|
1880
|
+
});
|
|
1881
|
+
}
|
|
1882
|
+
async function updateProjectMember(projectId, userId, role) {
|
|
1883
|
+
requireAuth();
|
|
1884
|
+
return directRequest("PATCH", `/projects/${encodeURIComponent(projectId)}/members/${encodeURIComponent(userId)}`, {
|
|
1885
|
+
role
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
async function removeProjectMember(projectId, userId) {
|
|
1889
|
+
requireAuth();
|
|
1890
|
+
await directRequest("DELETE", `/projects/${encodeURIComponent(projectId)}/members/${encodeURIComponent(userId)}`);
|
|
1891
|
+
}
|
|
1892
|
+
async function transferProjectOwnership(projectId, newOwnerId) {
|
|
1893
|
+
requireAuth();
|
|
1894
|
+
return directRequest("POST", `/projects/${encodeURIComponent(projectId)}/transfer`, {
|
|
1895
|
+
newOwnerId
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
async function listProjectInvitations(projectId) {
|
|
1899
|
+
requireAuth();
|
|
1900
|
+
return directRequest(
|
|
1901
|
+
"GET",
|
|
1902
|
+
`/projects/${encodeURIComponent(projectId)}/invitations`
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1905
|
+
async function inviteProjectMember(projectId, email, role = "member") {
|
|
1906
|
+
requireAuth();
|
|
1907
|
+
return directRequest(
|
|
1908
|
+
"POST",
|
|
1909
|
+
`/projects/${encodeURIComponent(projectId)}/invitations`,
|
|
1910
|
+
{ email, role }
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
async function respondToProjectInvitation(invitationId, decision) {
|
|
1914
|
+
requireAuth();
|
|
1915
|
+
return directRequest(
|
|
1916
|
+
"POST",
|
|
1917
|
+
`/projects/invitations/${encodeURIComponent(invitationId)}/${decision}`
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
async function getProjectActivity(projectId, options = {}) {
|
|
1921
|
+
requireAuth();
|
|
1922
|
+
const limit = Number(options.limit);
|
|
1923
|
+
const effectiveLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, 200) : 50;
|
|
1924
|
+
return directRequest(
|
|
1925
|
+
"GET",
|
|
1926
|
+
`/projects/${encodeURIComponent(projectId)}/activity?limit=${effectiveLimit}`
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
async function findSimilarProjects(name, repoUrl) {
|
|
1930
|
+
requireAuth();
|
|
1931
|
+
const params = new URLSearchParams();
|
|
1932
|
+
if (name) params.set("name", name);
|
|
1933
|
+
if (repoUrl) params.set("repo", repoUrl);
|
|
1934
|
+
return directRequest("GET", `/projects/similar?${params.toString()}`);
|
|
1935
|
+
}
|
|
1936
|
+
async function listPreseedDocuments(projectId) {
|
|
1937
|
+
requireAuth();
|
|
1938
|
+
return directRequest("GET", `/projects/${encodeURIComponent(projectId)}/preseed/documents`);
|
|
1939
|
+
}
|
|
1940
|
+
async function getPreseedDocument(projectId, documentName) {
|
|
1941
|
+
requireAuth();
|
|
1942
|
+
return directRequest("GET", `/projects/${encodeURIComponent(projectId)}/preseed/documents?name=${encodeURIComponent(documentName)}`);
|
|
1943
|
+
}
|
|
1944
|
+
async function enrichCodebasePreseed(payload = {}, options = {}) {
|
|
1945
|
+
requireAuth();
|
|
1946
|
+
const contractVersion = typeof payload.contractVersion === "string" && payload.contractVersion.trim() ? payload.contractVersion.trim() : "preseed-enrichment.v1";
|
|
1947
|
+
const body = {
|
|
1948
|
+
...payload,
|
|
1949
|
+
contractVersion
|
|
1950
|
+
};
|
|
1951
|
+
const requestedPaths = Array.isArray(options.paths) ? options.paths.filter((candidate) => typeof candidate === "string" && candidate.trim().length > 0) : [];
|
|
1952
|
+
const candidatePaths = [.../* @__PURE__ */ new Set([...requestedPaths, ...PRESEED_CODEBASE_ENRICHMENT_PATHS])];
|
|
1953
|
+
const requestOptions = {
|
|
1954
|
+
timeout: Number(options.timeout) > 0 ? Number(options.timeout) : 6e4,
|
|
1955
|
+
noCache: true,
|
|
1956
|
+
headers: {
|
|
1957
|
+
...options.headers || {},
|
|
1958
|
+
"X-Bootspring-Contract": contractVersion
|
|
1959
|
+
}
|
|
1960
|
+
};
|
|
1961
|
+
let lastError = null;
|
|
1962
|
+
for (const endpoint of candidatePaths) {
|
|
1963
|
+
try {
|
|
1964
|
+
return await request("POST", endpoint, body, requestOptions);
|
|
1965
|
+
} catch (error) {
|
|
1966
|
+
lastError = error;
|
|
1967
|
+
const status = Number(error.status);
|
|
1968
|
+
if (status === 404 || status === 405 || status === 501) {
|
|
1969
|
+
continue;
|
|
1970
|
+
}
|
|
1971
|
+
throw error;
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
const unavailable = new Error("Remote enrichment endpoint unavailable");
|
|
1975
|
+
unavailable.code = "ENRICHMENT_ENDPOINT_UNAVAILABLE";
|
|
1976
|
+
unavailable.details = { attemptedPaths: candidatePaths };
|
|
1977
|
+
if (lastError && typeof lastError.status === "number") {
|
|
1978
|
+
unavailable.status = lastError.status;
|
|
1979
|
+
}
|
|
1980
|
+
throw unavailable;
|
|
1981
|
+
}
|
|
1982
|
+
async function downloadPreseedZip(projectId) {
|
|
1983
|
+
requireAuth();
|
|
1984
|
+
const authHeaders = await resolveAuthHeaders();
|
|
1985
|
+
const url = new URL(`/api/projects/${encodeURIComponent(projectId)}/preseed/documents?format=zip`, API_BASE);
|
|
1986
|
+
const isHttps = url.protocol === "https:";
|
|
1987
|
+
const httpModule = isHttps ? https : http;
|
|
1988
|
+
return new Promise((resolve2, reject) => {
|
|
1989
|
+
const headers = {
|
|
1990
|
+
"User-Agent": `bootspring-cli/${getPackageVersion()}`,
|
|
1991
|
+
...authHeaders
|
|
1992
|
+
};
|
|
1993
|
+
const req = httpModule.request(url, {
|
|
1994
|
+
method: "GET",
|
|
1995
|
+
headers,
|
|
1996
|
+
timeout: 6e4
|
|
1997
|
+
}, (res) => {
|
|
1998
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1999
|
+
let body = "";
|
|
2000
|
+
res.on("data", (chunk) => {
|
|
2001
|
+
body += chunk;
|
|
2002
|
+
});
|
|
2003
|
+
res.on("end", () => {
|
|
2004
|
+
try {
|
|
2005
|
+
const json = JSON.parse(body);
|
|
2006
|
+
const error = new Error(
|
|
2007
|
+
redactSensitiveString(String(json.message || json.error || "Failed to download preseed documents"))
|
|
2008
|
+
);
|
|
2009
|
+
error.status = res.statusCode;
|
|
2010
|
+
reject(error);
|
|
2011
|
+
} catch {
|
|
2012
|
+
reject(new Error(redactSensitiveString(`HTTP ${res.statusCode}: ${body}`)));
|
|
2013
|
+
}
|
|
2014
|
+
});
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
const chunks = [];
|
|
2018
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
2019
|
+
res.on("end", () => {
|
|
2020
|
+
resolve2(Buffer.concat(chunks));
|
|
2021
|
+
});
|
|
2022
|
+
});
|
|
2023
|
+
req.on("error", reject);
|
|
2024
|
+
req.on("timeout", () => {
|
|
2025
|
+
req.destroy();
|
|
2026
|
+
reject(new Error("Request timeout"));
|
|
2027
|
+
});
|
|
2028
|
+
req.end();
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
async function getPreseedWizard(projectId) {
|
|
2032
|
+
requireAuth();
|
|
2033
|
+
return directRequest("GET", `/projects/${encodeURIComponent(projectId)}/preseed/wizard`);
|
|
2034
|
+
}
|
|
2035
|
+
async function getOrganization(orgId, options = {}) {
|
|
2036
|
+
requireAuth();
|
|
2037
|
+
try {
|
|
2038
|
+
return await request("GET", `/organizations/${encodeURIComponent(orgId)}`, null, options);
|
|
2039
|
+
} catch {
|
|
2040
|
+
return null;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
var https, http, crypto2, API_BASE, API_VERSION, cache, CACHE_TTL, PRESEED_CODEBASE_ENRICHMENT_PATHS, packageVersion;
|
|
2044
|
+
var init_api_client = __esm({
|
|
2045
|
+
"src/core/api-client.ts"() {
|
|
2046
|
+
"use strict";
|
|
2047
|
+
init_cjs_shims();
|
|
2048
|
+
https = __toESM(require("https"));
|
|
2049
|
+
http = __toESM(require("http"));
|
|
2050
|
+
crypto2 = __toESM(require("crypto"));
|
|
2051
|
+
init_auth2();
|
|
2052
|
+
init_session2();
|
|
2053
|
+
init_redaction2();
|
|
2054
|
+
API_BASE = process.env["BOOTSPRING_API_URL"] || "https://api.bootspring.com";
|
|
2055
|
+
API_VERSION = "v1";
|
|
2056
|
+
cache = /* @__PURE__ */ new Map();
|
|
2057
|
+
CACHE_TTL = 6e4;
|
|
2058
|
+
PRESEED_CODEBASE_ENRICHMENT_PATHS = [
|
|
2059
|
+
"/preseed/enrich/from-codebase",
|
|
2060
|
+
"/preseed/from-codebase/enrich",
|
|
2061
|
+
"/projects/preseed/enrich/from-codebase"
|
|
2062
|
+
];
|
|
2063
|
+
packageVersion = null;
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
|
|
2067
|
+
// src/mcp-proxy.ts
|
|
2068
|
+
init_cjs_shims();
|
|
2069
|
+
var { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2070
|
+
var { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
2071
|
+
var {
|
|
2072
|
+
CallToolRequestSchema,
|
|
2073
|
+
ListResourcesRequestSchema,
|
|
2074
|
+
ListToolsRequestSchema,
|
|
2075
|
+
ReadResourceRequestSchema
|
|
2076
|
+
} = require("@modelcontextprotocol/sdk/types.js");
|
|
2077
|
+
var api = (init_api_client(), __toCommonJS(api_client_exports));
|
|
2078
|
+
var auth = (init_auth(), __toCommonJS(auth_exports));
|
|
2079
|
+
var VERSION = require_package().version;
|
|
2080
|
+
var FALLBACK_TOOLS = [
|
|
2081
|
+
{
|
|
2082
|
+
name: "bootspring_agent",
|
|
2083
|
+
description: "Invoke a hosted Bootspring agent.",
|
|
2084
|
+
inputSchema: {
|
|
2085
|
+
type: "object",
|
|
2086
|
+
properties: {
|
|
2087
|
+
agent: { type: "string" },
|
|
2088
|
+
task: { type: "string" }
|
|
2089
|
+
},
|
|
2090
|
+
required: ["agent"]
|
|
2091
|
+
}
|
|
2092
|
+
},
|
|
2093
|
+
{
|
|
2094
|
+
name: "bootspring_skill",
|
|
2095
|
+
description: "Fetch a hosted Bootspring skill.",
|
|
2096
|
+
inputSchema: {
|
|
2097
|
+
type: "object",
|
|
2098
|
+
properties: {
|
|
2099
|
+
skill: { type: "string" }
|
|
2100
|
+
},
|
|
2101
|
+
required: ["skill"]
|
|
2102
|
+
}
|
|
2103
|
+
},
|
|
2104
|
+
{
|
|
2105
|
+
name: "bootspring_context",
|
|
2106
|
+
description: "Get or analyze project context.",
|
|
2107
|
+
inputSchema: {
|
|
2108
|
+
type: "object",
|
|
2109
|
+
properties: {
|
|
2110
|
+
action: { type: "string", enum: ["get", "generate", "analyze"] }
|
|
2111
|
+
},
|
|
2112
|
+
required: ["action"]
|
|
2113
|
+
}
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
name: "bootspring_orchestrator",
|
|
2117
|
+
description: "Use the hosted orchestrator.",
|
|
2118
|
+
inputSchema: {
|
|
2119
|
+
type: "object",
|
|
2120
|
+
properties: {
|
|
2121
|
+
action: { type: "string", enum: ["analyze", "start", "suggest"] },
|
|
2122
|
+
workflow: { type: "string" }
|
|
2123
|
+
},
|
|
2124
|
+
required: ["action"]
|
|
2125
|
+
}
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
name: "bootspring_quality",
|
|
2129
|
+
description: "Fetch a hosted quality gate.",
|
|
2130
|
+
inputSchema: {
|
|
2131
|
+
type: "object",
|
|
2132
|
+
properties: {
|
|
2133
|
+
gate: { type: "string", enum: ["pre-commit", "pre-push", "pre-deploy"] }
|
|
2134
|
+
},
|
|
2135
|
+
required: ["gate"]
|
|
2136
|
+
}
|
|
2137
|
+
},
|
|
2138
|
+
{
|
|
2139
|
+
name: "bootspring_todo",
|
|
2140
|
+
description: "Manage local todo items.",
|
|
2141
|
+
inputSchema: {
|
|
2142
|
+
type: "object",
|
|
2143
|
+
properties: {
|
|
2144
|
+
action: { type: "string", enum: ["list", "add", "complete", "delete"] },
|
|
2145
|
+
text: { type: "string" },
|
|
2146
|
+
id: { type: "string" }
|
|
2147
|
+
},
|
|
2148
|
+
required: ["action"]
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
];
|
|
2152
|
+
var FALLBACK_RESOURCES = [
|
|
2153
|
+
{
|
|
2154
|
+
uri: "bootspring://context",
|
|
2155
|
+
name: "Project Context",
|
|
2156
|
+
description: "Current project context and configuration",
|
|
2157
|
+
mimeType: "application/json"
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
uri: "bootspring://agents",
|
|
2161
|
+
name: "Available Agents",
|
|
2162
|
+
description: "Hosted Bootspring agents for your account",
|
|
2163
|
+
mimeType: "application/json"
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
uri: "bootspring://skills",
|
|
2167
|
+
name: "Code Skills",
|
|
2168
|
+
description: "Hosted Bootspring skills and patterns",
|
|
2169
|
+
mimeType: "application/json"
|
|
2170
|
+
},
|
|
2171
|
+
{
|
|
2172
|
+
uri: "bootspring://workflows",
|
|
2173
|
+
name: "Workflows",
|
|
2174
|
+
description: "Hosted Bootspring workflows",
|
|
2175
|
+
mimeType: "application/json"
|
|
2176
|
+
}
|
|
2177
|
+
];
|
|
2178
|
+
var TOOLS = FALLBACK_TOOLS;
|
|
2179
|
+
var RESOURCES = FALLBACK_RESOURCES;
|
|
2180
|
+
function formatTextContent(value) {
|
|
2181
|
+
return typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
2182
|
+
}
|
|
2183
|
+
function createAuthError(message) {
|
|
2184
|
+
return {
|
|
2185
|
+
content: [{ type: "text", text: message }],
|
|
2186
|
+
isError: true
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
async function resolveTools() {
|
|
2190
|
+
if (!auth.isAuthenticated()) {
|
|
2191
|
+
return FALLBACK_TOOLS;
|
|
2192
|
+
}
|
|
2193
|
+
try {
|
|
2194
|
+
const response = await api.listMcpTools();
|
|
2195
|
+
return Array.isArray(response) ? response : response.tools || FALLBACK_TOOLS;
|
|
2196
|
+
} catch {
|
|
2197
|
+
return FALLBACK_TOOLS;
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
async function resolveResources() {
|
|
2201
|
+
if (!auth.isAuthenticated()) {
|
|
2202
|
+
return FALLBACK_RESOURCES;
|
|
2203
|
+
}
|
|
2204
|
+
try {
|
|
2205
|
+
const response = await api.listMcpResources();
|
|
2206
|
+
return Array.isArray(response) ? response : response.resources || FALLBACK_RESOURCES;
|
|
2207
|
+
} catch {
|
|
2208
|
+
return FALLBACK_RESOURCES;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
async function proxyToolCall(name, args) {
|
|
2212
|
+
if (!auth.isAuthenticated()) {
|
|
2213
|
+
return createAuthError("Authentication required. Run `bootspring auth login` first.");
|
|
2214
|
+
}
|
|
2215
|
+
try {
|
|
2216
|
+
const response = await api.callMcpTool(name, args || {});
|
|
2217
|
+
return {
|
|
2218
|
+
content: [{ type: "text", text: formatTextContent(response.result || response) }]
|
|
2219
|
+
};
|
|
2220
|
+
} catch (error) {
|
|
2221
|
+
const message = error.status === 403 ? "This MCP capability requires a paid Bootspring plan." : error.message;
|
|
2222
|
+
return {
|
|
2223
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
2224
|
+
isError: true
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
async function proxyResourceRead(uri) {
|
|
2229
|
+
if (!auth.isAuthenticated()) {
|
|
2230
|
+
return {
|
|
2231
|
+
contents: [{
|
|
2232
|
+
uri,
|
|
2233
|
+
mimeType: "text/plain",
|
|
2234
|
+
text: "Authentication required. Run `bootspring auth login` first."
|
|
2235
|
+
}]
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
try {
|
|
2239
|
+
const response = await api.getMcpResource(uri);
|
|
2240
|
+
return {
|
|
2241
|
+
contents: [{
|
|
2242
|
+
uri: response.uri || uri,
|
|
2243
|
+
mimeType: response.mimeType || "application/json",
|
|
2244
|
+
text: formatTextContent(response.content || response)
|
|
2245
|
+
}]
|
|
2246
|
+
};
|
|
2247
|
+
} catch (error) {
|
|
2248
|
+
return {
|
|
2249
|
+
contents: [{
|
|
2250
|
+
uri,
|
|
2251
|
+
mimeType: "text/plain",
|
|
2252
|
+
text: `Error: ${error.message}`
|
|
2253
|
+
}]
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
var toolHandlers = Object.fromEntries(
|
|
2258
|
+
TOOLS.map((tool) => [tool.name, async (args = {}) => proxyToolCall(tool.name, args)])
|
|
2259
|
+
);
|
|
2260
|
+
var resourceHandlers = Object.fromEntries(
|
|
2261
|
+
RESOURCES.map((resource) => [resource.uri, async () => proxyResourceRead(resource.uri)])
|
|
2262
|
+
);
|
|
2263
|
+
async function main() {
|
|
2264
|
+
const server = new Server(
|
|
2265
|
+
{ name: "bootspring", version: VERSION },
|
|
2266
|
+
{ capabilities: { tools: {}, resources: {} } }
|
|
2267
|
+
);
|
|
2268
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
2269
|
+
tools: await resolveTools()
|
|
2270
|
+
}));
|
|
2271
|
+
server.setRequestHandler(CallToolRequestSchema, async (request2) => {
|
|
2272
|
+
const { name, arguments: args } = request2.params;
|
|
2273
|
+
return proxyToolCall(name, args);
|
|
2274
|
+
});
|
|
2275
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
2276
|
+
resources: await resolveResources()
|
|
2277
|
+
}));
|
|
2278
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request2) => proxyResourceRead(request2.params.uri));
|
|
2279
|
+
const transport = new StdioServerTransport();
|
|
2280
|
+
await server.connect(transport);
|
|
2281
|
+
console.error(`Bootspring MCP proxy v${VERSION} started`);
|
|
2282
|
+
}
|
|
2283
|
+
if (require.main === module) {
|
|
2284
|
+
main().catch((error) => {
|
|
2285
|
+
console.error("Failed to start MCP proxy:", error.message);
|
|
2286
|
+
process.exit(1);
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
module.exports = {
|
|
2290
|
+
TOOLS,
|
|
2291
|
+
RESOURCES,
|
|
2292
|
+
toolHandlers,
|
|
2293
|
+
resourceHandlers,
|
|
2294
|
+
FALLBACK_TOOLS,
|
|
2295
|
+
FALLBACK_RESOURCES,
|
|
2296
|
+
main
|
|
2297
|
+
};
|
|
2298
|
+
//# sourceMappingURL=mcp-server.js.map
|