@girardmedia/bootspring 2.0.21 → 2.0.23
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 +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- package/generators/index.js +0 -18
|
@@ -0,0 +1,1120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring API Client
|
|
3
|
+
*
|
|
4
|
+
* Handles all communication with api.bootspring.com
|
|
5
|
+
* This is the thin-client version that requires API for all operations.
|
|
6
|
+
*
|
|
7
|
+
* @package bootspring
|
|
8
|
+
* @module core/api-client
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as https from 'https';
|
|
12
|
+
import * as http from 'http';
|
|
13
|
+
import * as crypto from 'crypto';
|
|
14
|
+
import * as auth from './auth';
|
|
15
|
+
import type { LoginResponse as AuthLoginResponse } from './auth';
|
|
16
|
+
import * as session from './session';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export interface ApiRequestOptions {
|
|
23
|
+
headers?: Record<string, string> | undefined;
|
|
24
|
+
timeout?: number | undefined;
|
|
25
|
+
noCache?: boolean | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ApiError extends Error {
|
|
29
|
+
status?: number | undefined;
|
|
30
|
+
code?: string | undefined;
|
|
31
|
+
details?: unknown | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface HealthCheckResult {
|
|
35
|
+
connected: boolean;
|
|
36
|
+
version?: string | undefined;
|
|
37
|
+
error?: string | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DeviceCodeResponse {
|
|
41
|
+
device_code: string;
|
|
42
|
+
user_code: string;
|
|
43
|
+
verification_uri: string;
|
|
44
|
+
expires_in: number;
|
|
45
|
+
interval: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface DeviceTokenResponse {
|
|
49
|
+
access_token: string;
|
|
50
|
+
refresh_token: string;
|
|
51
|
+
token_type: string;
|
|
52
|
+
expires_in: number;
|
|
53
|
+
user: UserData;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface UserData {
|
|
57
|
+
id: string;
|
|
58
|
+
email: string;
|
|
59
|
+
name?: string | undefined;
|
|
60
|
+
tier: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Use AuthLoginResponse from auth.ts module
|
|
65
|
+
|
|
66
|
+
export interface ApiKeyValidationResponse {
|
|
67
|
+
valid: boolean;
|
|
68
|
+
tier: string;
|
|
69
|
+
scopes: string[];
|
|
70
|
+
project?: ProjectData | undefined;
|
|
71
|
+
device?: unknown | undefined;
|
|
72
|
+
usage?: unknown | undefined;
|
|
73
|
+
error?: string | undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ProjectData {
|
|
77
|
+
id: string;
|
|
78
|
+
name: string;
|
|
79
|
+
[key: string]: unknown;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface Agent {
|
|
83
|
+
id: string;
|
|
84
|
+
name: string;
|
|
85
|
+
description?: string | undefined;
|
|
86
|
+
tier?: string | undefined;
|
|
87
|
+
[key: string]: unknown;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface AgentContext {
|
|
91
|
+
id: string;
|
|
92
|
+
name: string;
|
|
93
|
+
description: string;
|
|
94
|
+
context: string;
|
|
95
|
+
tier: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface Skill {
|
|
99
|
+
id: string;
|
|
100
|
+
name: string;
|
|
101
|
+
category: string;
|
|
102
|
+
tier?: string | undefined;
|
|
103
|
+
[key: string]: unknown;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SkillListResponse {
|
|
107
|
+
skills: Skill[];
|
|
108
|
+
categories: string[];
|
|
109
|
+
userTier: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface SkillContent {
|
|
113
|
+
id: string;
|
|
114
|
+
name: string;
|
|
115
|
+
content: string;
|
|
116
|
+
tier: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface Template {
|
|
120
|
+
name: string;
|
|
121
|
+
description?: string | undefined;
|
|
122
|
+
[key: string]: unknown;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface TemplateContent {
|
|
126
|
+
name: string;
|
|
127
|
+
content: string;
|
|
128
|
+
variables?: string[] | undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface Workflow {
|
|
132
|
+
id: string;
|
|
133
|
+
name: string;
|
|
134
|
+
description?: string | undefined;
|
|
135
|
+
[key: string]: unknown;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface AnalyzeContextRequest {
|
|
139
|
+
projectContext: unknown;
|
|
140
|
+
currentTask?: string | undefined;
|
|
141
|
+
recentActions?: unknown[] | undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface McpTool {
|
|
145
|
+
name: string;
|
|
146
|
+
description?: string | undefined;
|
|
147
|
+
[key: string]: unknown;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface McpResource {
|
|
151
|
+
uri: string;
|
|
152
|
+
name?: string | undefined;
|
|
153
|
+
[key: string]: unknown;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface Subscription {
|
|
157
|
+
plan: string;
|
|
158
|
+
status: string;
|
|
159
|
+
[key: string]: unknown;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface Entitlements {
|
|
163
|
+
tier: string;
|
|
164
|
+
features: string[];
|
|
165
|
+
limits: Record<string, number>;
|
|
166
|
+
[key: string]: unknown;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface ProjectMember {
|
|
170
|
+
userId: string;
|
|
171
|
+
email: string;
|
|
172
|
+
role: string;
|
|
173
|
+
[key: string]: unknown;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface PreseedDocument {
|
|
177
|
+
name: string;
|
|
178
|
+
content?: string | undefined;
|
|
179
|
+
format?: string | undefined;
|
|
180
|
+
[key: string]: unknown;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface PreseedDocumentsResponse {
|
|
184
|
+
documents: PreseedDocument[];
|
|
185
|
+
count: number;
|
|
186
|
+
storage?: unknown | undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface PreseedWizardResponse {
|
|
190
|
+
wizardData: unknown;
|
|
191
|
+
progress: unknown;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface SkillSearchOptions {
|
|
195
|
+
category?: string | undefined;
|
|
196
|
+
search?: string | undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface TelemetryBatchInfo {
|
|
200
|
+
id?: string | undefined;
|
|
201
|
+
index?: number | undefined;
|
|
202
|
+
total?: number | undefined;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface CacheEntry {
|
|
206
|
+
data: unknown;
|
|
207
|
+
time: number;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// Constants & Cache
|
|
212
|
+
// ============================================================================
|
|
213
|
+
|
|
214
|
+
export const API_BASE = process.env['BOOTSPRING_API_URL'] || 'https://api.bootspring.com';
|
|
215
|
+
export const API_VERSION = 'v1'; // Note: auth routes don't use version prefix
|
|
216
|
+
|
|
217
|
+
// Cache for API responses
|
|
218
|
+
const cache = new Map<string, CacheEntry>();
|
|
219
|
+
const CACHE_TTL = 60000; // 1 minute
|
|
220
|
+
|
|
221
|
+
// Lazy-loaded package.json
|
|
222
|
+
let packageVersion: string | null = null;
|
|
223
|
+
|
|
224
|
+
function getPackageVersion(): string {
|
|
225
|
+
if (packageVersion === null) {
|
|
226
|
+
try {
|
|
227
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
228
|
+
const pkg = require('../../package.json') as { version: string };
|
|
229
|
+
packageVersion = pkg.version;
|
|
230
|
+
} catch {
|
|
231
|
+
packageVersion = 'unknown';
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return packageVersion;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ============================================================================
|
|
238
|
+
// Core Request Functions
|
|
239
|
+
// ============================================================================
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Make an API request (with version prefix)
|
|
243
|
+
*/
|
|
244
|
+
export async function request<T = unknown>(
|
|
245
|
+
method: string,
|
|
246
|
+
path: string,
|
|
247
|
+
data: unknown = null,
|
|
248
|
+
options: ApiRequestOptions = {}
|
|
249
|
+
): Promise<T> {
|
|
250
|
+
const token = auth.getToken();
|
|
251
|
+
const apiKey = auth.getApiKey();
|
|
252
|
+
const url = new URL(`/api/${API_VERSION}${path}`, API_BASE);
|
|
253
|
+
const isHttps = url.protocol === 'https:';
|
|
254
|
+
const httpModule = isHttps ? https : http;
|
|
255
|
+
|
|
256
|
+
// Get device ID for request tracking
|
|
257
|
+
const deviceId = auth.getDeviceId();
|
|
258
|
+
|
|
259
|
+
// Get project context for tracking
|
|
260
|
+
const project = session.getEffectiveProject();
|
|
261
|
+
const projectId = project?.id || null;
|
|
262
|
+
|
|
263
|
+
const headers: Record<string, string> = {
|
|
264
|
+
'Content-Type': 'application/json',
|
|
265
|
+
'User-Agent': `bootspring-cli/${getPackageVersion()}`,
|
|
266
|
+
'X-Device-Id': deviceId,
|
|
267
|
+
...(projectId && { 'X-Project-Id': projectId }),
|
|
268
|
+
...options.headers
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Use API key if available, otherwise use JWT token
|
|
272
|
+
if (apiKey) {
|
|
273
|
+
headers['X-API-Key'] = apiKey;
|
|
274
|
+
} else if (token) {
|
|
275
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Check cache for GET requests
|
|
279
|
+
if (method === 'GET' && !options.noCache) {
|
|
280
|
+
const cacheKey = `${method}:${path}`;
|
|
281
|
+
const cached = cache.get(cacheKey);
|
|
282
|
+
if (cached && Date.now() - cached.time < CACHE_TTL) {
|
|
283
|
+
return cached.data as T;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return new Promise<T>((resolve, reject) => {
|
|
288
|
+
const req = httpModule.request(url, {
|
|
289
|
+
method,
|
|
290
|
+
headers,
|
|
291
|
+
timeout: options.timeout || 30000
|
|
292
|
+
}, (res) => {
|
|
293
|
+
let body = '';
|
|
294
|
+
res.on('data', (chunk: Buffer | string) => { body += chunk; });
|
|
295
|
+
res.on('end', () => {
|
|
296
|
+
try {
|
|
297
|
+
const json = JSON.parse(body) as T & { message?: string; error?: string; code?: string; details?: unknown };
|
|
298
|
+
|
|
299
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
300
|
+
const error: ApiError = new Error(json.message || json.error || 'API Error');
|
|
301
|
+
error.status = res.statusCode;
|
|
302
|
+
error.code = json.error || json.code;
|
|
303
|
+
error.details = json.details;
|
|
304
|
+
reject(error);
|
|
305
|
+
} else {
|
|
306
|
+
// Cache successful GET responses
|
|
307
|
+
if (method === 'GET' && !options.noCache) {
|
|
308
|
+
const cacheKey = `${method}:${path}`;
|
|
309
|
+
cache.set(cacheKey, { data: json, time: Date.now() });
|
|
310
|
+
}
|
|
311
|
+
resolve(json);
|
|
312
|
+
}
|
|
313
|
+
} catch {
|
|
314
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
315
|
+
const error: ApiError = new Error(body || 'API Error');
|
|
316
|
+
error.status = res.statusCode;
|
|
317
|
+
reject(error);
|
|
318
|
+
} else {
|
|
319
|
+
resolve(body as unknown as T);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
req.on('error', (err: NodeJS.ErrnoException) => {
|
|
326
|
+
if (err.code === 'ECONNREFUSED') {
|
|
327
|
+
reject(new Error('Cannot connect to Bootspring API. Please check your internet connection.'));
|
|
328
|
+
} else {
|
|
329
|
+
reject(err);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
req.on('timeout', () => {
|
|
334
|
+
req.destroy();
|
|
335
|
+
reject(new Error('Request timeout'));
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
if (data) {
|
|
339
|
+
req.write(JSON.stringify(data));
|
|
340
|
+
}
|
|
341
|
+
req.end();
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Make a direct API request (without version prefix, for /api/projects etc.)
|
|
347
|
+
*/
|
|
348
|
+
export async function directRequest<T = unknown>(
|
|
349
|
+
method: string,
|
|
350
|
+
path: string,
|
|
351
|
+
data: unknown = null,
|
|
352
|
+
options: ApiRequestOptions = {}
|
|
353
|
+
): Promise<T> {
|
|
354
|
+
const token = auth.getToken();
|
|
355
|
+
const apiKey = auth.getApiKey();
|
|
356
|
+
const url = new URL(`/api${path}`, API_BASE);
|
|
357
|
+
const isHttps = url.protocol === 'https:';
|
|
358
|
+
const httpModule = isHttps ? https : http;
|
|
359
|
+
|
|
360
|
+
const deviceId = auth.getDeviceId();
|
|
361
|
+
const project = session.getEffectiveProject();
|
|
362
|
+
const projectId = project?.id || null;
|
|
363
|
+
|
|
364
|
+
const headers: Record<string, string> = {
|
|
365
|
+
'Content-Type': 'application/json',
|
|
366
|
+
'User-Agent': `bootspring-cli/${getPackageVersion()}`,
|
|
367
|
+
'X-Device-Id': deviceId,
|
|
368
|
+
...(projectId && { 'X-Project-Id': projectId }),
|
|
369
|
+
...options.headers
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
if (apiKey) {
|
|
373
|
+
headers['X-API-Key'] = apiKey;
|
|
374
|
+
} else if (token) {
|
|
375
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return new Promise<T>((resolve, reject) => {
|
|
379
|
+
const req = httpModule.request(url, {
|
|
380
|
+
method,
|
|
381
|
+
headers,
|
|
382
|
+
timeout: options.timeout || 30000
|
|
383
|
+
}, (res) => {
|
|
384
|
+
let body = '';
|
|
385
|
+
res.on('data', (chunk: Buffer | string) => { body += chunk; });
|
|
386
|
+
res.on('end', () => {
|
|
387
|
+
try {
|
|
388
|
+
const json = JSON.parse(body) as T & { message?: string; error?: string; code?: string; details?: unknown };
|
|
389
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
390
|
+
const error: ApiError = new Error(json.message || json.error || 'API Error');
|
|
391
|
+
error.status = res.statusCode;
|
|
392
|
+
error.code = json.error || json.code;
|
|
393
|
+
error.details = json.details;
|
|
394
|
+
reject(error);
|
|
395
|
+
} else {
|
|
396
|
+
resolve(json);
|
|
397
|
+
}
|
|
398
|
+
} catch {
|
|
399
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
400
|
+
const error: ApiError = new Error(body || 'API Error');
|
|
401
|
+
error.status = res.statusCode;
|
|
402
|
+
reject(error);
|
|
403
|
+
} else {
|
|
404
|
+
resolve(body as unknown as T);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
req.on('error', (err: NodeJS.ErrnoException) => {
|
|
411
|
+
if (err.code === 'ECONNREFUSED') {
|
|
412
|
+
reject(new Error('Cannot connect to Bootspring API. Please check your internet connection.'));
|
|
413
|
+
} else {
|
|
414
|
+
reject(err);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
req.on('timeout', () => {
|
|
419
|
+
req.destroy();
|
|
420
|
+
reject(new Error('Request timeout'));
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
if (data) {
|
|
424
|
+
req.write(JSON.stringify(data));
|
|
425
|
+
}
|
|
426
|
+
req.end();
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Clear the cache
|
|
432
|
+
*/
|
|
433
|
+
export function clearCache(): void {
|
|
434
|
+
cache.clear();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Check if API is reachable
|
|
439
|
+
*/
|
|
440
|
+
export async function healthCheck(): Promise<HealthCheckResult> {
|
|
441
|
+
try {
|
|
442
|
+
// Health endpoint is at /health, not under /api/v1
|
|
443
|
+
const url = new URL('/health', API_BASE);
|
|
444
|
+
const isHttps = url.protocol === 'https:';
|
|
445
|
+
const httpModule = isHttps ? https : http;
|
|
446
|
+
|
|
447
|
+
return new Promise<HealthCheckResult>((resolve) => {
|
|
448
|
+
const req = httpModule.request(url, {
|
|
449
|
+
method: 'GET',
|
|
450
|
+
timeout: 5000
|
|
451
|
+
}, (res) => {
|
|
452
|
+
let body = '';
|
|
453
|
+
res.on('data', (chunk: Buffer | string) => { body += chunk; });
|
|
454
|
+
res.on('end', () => {
|
|
455
|
+
try {
|
|
456
|
+
const json = JSON.parse(body) as { version?: string };
|
|
457
|
+
resolve({ connected: true, version: json.version });
|
|
458
|
+
} catch {
|
|
459
|
+
resolve({ connected: false, error: 'Invalid response' });
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
req.on('error', (err: Error) => {
|
|
465
|
+
resolve({ connected: false, error: err.message });
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
req.on('timeout', () => {
|
|
469
|
+
req.destroy();
|
|
470
|
+
resolve({ connected: false, error: 'Timeout' });
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
req.end();
|
|
474
|
+
});
|
|
475
|
+
} catch (error) {
|
|
476
|
+
const err = error as Error;
|
|
477
|
+
return { connected: false, error: err.message };
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Require authentication - throws if not logged in
|
|
483
|
+
*/
|
|
484
|
+
export function requireAuth(): void {
|
|
485
|
+
if (!auth.isAuthenticated()) {
|
|
486
|
+
const error: ApiError = new Error('Authentication required. Run: bootspring auth login');
|
|
487
|
+
error.code = 'AUTH_REQUIRED';
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ============================================================================
|
|
493
|
+
// API Methods
|
|
494
|
+
// ============================================================================
|
|
495
|
+
|
|
496
|
+
// Device Authorization Flow
|
|
497
|
+
export async function requestDeviceCode(): Promise<DeviceCodeResponse> {
|
|
498
|
+
const deviceContext = auth.getDeviceContext();
|
|
499
|
+
const url = new URL('/api/v1/auth/device', API_BASE);
|
|
500
|
+
const isHttps = url.protocol === 'https:';
|
|
501
|
+
const httpModule = isHttps ? https : http;
|
|
502
|
+
|
|
503
|
+
return new Promise<DeviceCodeResponse>((resolve, reject) => {
|
|
504
|
+
const req = httpModule.request(url, {
|
|
505
|
+
method: 'POST',
|
|
506
|
+
headers: {
|
|
507
|
+
'Content-Type': 'application/json',
|
|
508
|
+
'User-Agent': `bootspring-cli/${getPackageVersion()}`,
|
|
509
|
+
},
|
|
510
|
+
timeout: 10000
|
|
511
|
+
}, (res) => {
|
|
512
|
+
let body = '';
|
|
513
|
+
res.on('data', (chunk: Buffer | string) => { body += chunk; });
|
|
514
|
+
res.on('end', () => {
|
|
515
|
+
try {
|
|
516
|
+
const json = JSON.parse(body) as DeviceCodeResponse & { message?: string; error?: string };
|
|
517
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
518
|
+
const error: ApiError = new Error(json.message || json.error || 'Failed to get device code');
|
|
519
|
+
error.status = res.statusCode;
|
|
520
|
+
error.code = json.error;
|
|
521
|
+
reject(error);
|
|
522
|
+
} else {
|
|
523
|
+
resolve(json);
|
|
524
|
+
}
|
|
525
|
+
} catch {
|
|
526
|
+
reject(new Error('Invalid response from API'));
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
req.on('error', reject);
|
|
531
|
+
req.on('timeout', () => {
|
|
532
|
+
req.destroy();
|
|
533
|
+
reject(new Error('Request timeout'));
|
|
534
|
+
});
|
|
535
|
+
req.write(JSON.stringify({ device: deviceContext }));
|
|
536
|
+
req.end();
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export async function pollDeviceToken(deviceCode: string): Promise<DeviceTokenResponse> {
|
|
541
|
+
const url = new URL('/api/v1/auth/device/token', API_BASE);
|
|
542
|
+
const isHttps = url.protocol === 'https:';
|
|
543
|
+
const httpModule = isHttps ? https : http;
|
|
544
|
+
|
|
545
|
+
return new Promise<DeviceTokenResponse>((resolve, reject) => {
|
|
546
|
+
const req = httpModule.request(url, {
|
|
547
|
+
method: 'POST',
|
|
548
|
+
headers: {
|
|
549
|
+
'Content-Type': 'application/json',
|
|
550
|
+
'User-Agent': `bootspring-cli/${getPackageVersion()}`,
|
|
551
|
+
},
|
|
552
|
+
timeout: 10000
|
|
553
|
+
}, (res) => {
|
|
554
|
+
let body = '';
|
|
555
|
+
res.on('data', (chunk: Buffer | string) => { body += chunk; });
|
|
556
|
+
res.on('end', () => {
|
|
557
|
+
try {
|
|
558
|
+
const json = JSON.parse(body) as DeviceTokenResponse & { message?: string; error?: string };
|
|
559
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
560
|
+
const error: ApiError = new Error(json.message || json.error || 'Authorization pending');
|
|
561
|
+
error.status = res.statusCode;
|
|
562
|
+
error.code = json.error;
|
|
563
|
+
error.details = json;
|
|
564
|
+
reject(error);
|
|
565
|
+
} else {
|
|
566
|
+
resolve(json);
|
|
567
|
+
}
|
|
568
|
+
} catch {
|
|
569
|
+
reject(new Error('Invalid response from API'));
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
req.on('error', reject);
|
|
574
|
+
req.on('timeout', () => {
|
|
575
|
+
req.destroy();
|
|
576
|
+
reject(new Error('Request timeout'));
|
|
577
|
+
});
|
|
578
|
+
req.write(JSON.stringify({ device_code: deviceCode }));
|
|
579
|
+
req.end();
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Auth
|
|
584
|
+
export async function login(email: string, password: string): Promise<AuthLoginResponse> {
|
|
585
|
+
const deviceContext = auth.getDeviceContext();
|
|
586
|
+
const response = await request<AuthLoginResponse>('POST', '/auth/login', {
|
|
587
|
+
email,
|
|
588
|
+
password,
|
|
589
|
+
device: deviceContext
|
|
590
|
+
});
|
|
591
|
+
auth.login(response);
|
|
592
|
+
return response;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export async function loginWithApiKey(apiKey: string): Promise<ApiKeyValidationResponse> {
|
|
596
|
+
// Validate API key by calling /api/keys/validate
|
|
597
|
+
const url = new URL('/api/keys/validate', API_BASE);
|
|
598
|
+
const isHttps = url.protocol === 'https:';
|
|
599
|
+
const httpModule = isHttps ? https : http;
|
|
600
|
+
const deviceContext = auth.getDeviceContext();
|
|
601
|
+
|
|
602
|
+
return new Promise<ApiKeyValidationResponse>((resolve, reject) => {
|
|
603
|
+
const requestBody = JSON.stringify({
|
|
604
|
+
apiKey,
|
|
605
|
+
deviceFingerprint: deviceContext.deviceId,
|
|
606
|
+
deviceName: `CLI - ${deviceContext.hostname}`
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const req = httpModule.request(url, {
|
|
610
|
+
method: 'POST',
|
|
611
|
+
headers: {
|
|
612
|
+
'Content-Type': 'application/json',
|
|
613
|
+
'User-Agent': `bootspring-cli/${getPackageVersion()}`,
|
|
614
|
+
'Content-Length': Buffer.byteLength(requestBody).toString()
|
|
615
|
+
},
|
|
616
|
+
timeout: 10000
|
|
617
|
+
}, (res) => {
|
|
618
|
+
let body = '';
|
|
619
|
+
res.on('data', (chunk: Buffer | string) => { body += chunk; });
|
|
620
|
+
res.on('end', () => {
|
|
621
|
+
try {
|
|
622
|
+
const json = JSON.parse(body) as ApiKeyValidationResponse;
|
|
623
|
+
if ((res.statusCode && res.statusCode >= 400) || !json.valid) {
|
|
624
|
+
const error: ApiError = new Error(json.error || 'Invalid API key');
|
|
625
|
+
error.status = res.statusCode;
|
|
626
|
+
error.code = json.error;
|
|
627
|
+
reject(error);
|
|
628
|
+
} else {
|
|
629
|
+
// Build user info from response
|
|
630
|
+
const user = {
|
|
631
|
+
tier: json.tier,
|
|
632
|
+
scopes: json.scopes
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// Save API key and user info
|
|
636
|
+
auth.loginWithApiKey(apiKey, user);
|
|
637
|
+
|
|
638
|
+
// If key has a project, auto-set project context
|
|
639
|
+
if (json.project) {
|
|
640
|
+
session.setCurrentProject(json.project);
|
|
641
|
+
session.addRecentProject(json.project);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
resolve(json);
|
|
645
|
+
}
|
|
646
|
+
} catch {
|
|
647
|
+
reject(new Error('Invalid response from API'));
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
req.on('error', reject);
|
|
653
|
+
req.on('timeout', () => {
|
|
654
|
+
req.destroy();
|
|
655
|
+
reject(new Error('Request timeout'));
|
|
656
|
+
});
|
|
657
|
+
req.write(requestBody);
|
|
658
|
+
req.end();
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export async function register(email: string, password: string, name: string): Promise<AuthLoginResponse> {
|
|
663
|
+
const deviceContext = auth.getDeviceContext();
|
|
664
|
+
const response = await request<AuthLoginResponse>('POST', '/auth/register', {
|
|
665
|
+
email,
|
|
666
|
+
password,
|
|
667
|
+
name,
|
|
668
|
+
device: deviceContext
|
|
669
|
+
});
|
|
670
|
+
auth.login(response);
|
|
671
|
+
return response;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export async function me(): Promise<UserData> {
|
|
675
|
+
requireAuth();
|
|
676
|
+
return request<UserData>('GET', '/auth/me');
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export async function validateApiKey(apiKey: string): Promise<ApiKeyValidationResponse> {
|
|
680
|
+
const url = new URL('/api/keys/validate', API_BASE);
|
|
681
|
+
const isHttps = url.protocol === 'https:';
|
|
682
|
+
const httpModule = isHttps ? https : http;
|
|
683
|
+
const deviceContext = auth.getDeviceContext();
|
|
684
|
+
|
|
685
|
+
return new Promise<ApiKeyValidationResponse>((resolve, reject) => {
|
|
686
|
+
const requestBody = JSON.stringify({
|
|
687
|
+
apiKey,
|
|
688
|
+
deviceFingerprint: deviceContext.deviceId,
|
|
689
|
+
deviceName: `CLI - ${deviceContext.hostname}`
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const req = httpModule.request(url, {
|
|
693
|
+
method: 'POST',
|
|
694
|
+
headers: {
|
|
695
|
+
'Content-Type': 'application/json',
|
|
696
|
+
'User-Agent': `bootspring-cli/${getPackageVersion()}`,
|
|
697
|
+
'Content-Length': Buffer.byteLength(requestBody).toString()
|
|
698
|
+
},
|
|
699
|
+
timeout: 10000
|
|
700
|
+
}, (res) => {
|
|
701
|
+
let body = '';
|
|
702
|
+
res.on('data', (chunk: Buffer | string) => { body += chunk; });
|
|
703
|
+
res.on('end', () => {
|
|
704
|
+
try {
|
|
705
|
+
const json = JSON.parse(body) as ApiKeyValidationResponse;
|
|
706
|
+
if ((res.statusCode && res.statusCode >= 400) || !json.valid) {
|
|
707
|
+
const error: ApiError = new Error(json.error || 'Invalid API key');
|
|
708
|
+
error.status = res.statusCode;
|
|
709
|
+
reject(error);
|
|
710
|
+
} else {
|
|
711
|
+
resolve(json);
|
|
712
|
+
}
|
|
713
|
+
} catch {
|
|
714
|
+
reject(new Error('Invalid response from API'));
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
req.on('error', reject);
|
|
720
|
+
req.on('timeout', () => {
|
|
721
|
+
req.destroy();
|
|
722
|
+
reject(new Error('Request timeout'));
|
|
723
|
+
});
|
|
724
|
+
req.write(requestBody);
|
|
725
|
+
req.end();
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
export async function refreshToken(): Promise<AuthLoginResponse> {
|
|
730
|
+
const refreshTokenValue = auth.getRefreshToken();
|
|
731
|
+
if (!refreshTokenValue) {
|
|
732
|
+
throw new Error('No refresh token available');
|
|
733
|
+
}
|
|
734
|
+
const deviceContext = auth.getDeviceContext();
|
|
735
|
+
const response = await request<AuthLoginResponse>('POST', '/auth/refresh', {
|
|
736
|
+
refreshToken: refreshTokenValue,
|
|
737
|
+
device: deviceContext
|
|
738
|
+
});
|
|
739
|
+
auth.updateTokens(response);
|
|
740
|
+
return response;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export async function logout(): Promise<void> {
|
|
744
|
+
if (auth.isAuthenticated()) {
|
|
745
|
+
try {
|
|
746
|
+
const refreshTokenValue = auth.getRefreshToken();
|
|
747
|
+
await request('POST', '/auth/logout', { refreshToken: refreshTokenValue });
|
|
748
|
+
} catch {
|
|
749
|
+
// Ignore logout API errors
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
auth.logout();
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Agents
|
|
756
|
+
export async function listAgents(): Promise<{ agents: Agent[] }> {
|
|
757
|
+
requireAuth();
|
|
758
|
+
return request<{ agents: Agent[] }>('GET', '/agents');
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export async function getAgent(id: string): Promise<Agent> {
|
|
762
|
+
requireAuth();
|
|
763
|
+
return request<Agent>('GET', `/agents/${encodeURIComponent(id)}`);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
export async function getAgentContext(id: string): Promise<AgentContext> {
|
|
767
|
+
requireAuth();
|
|
768
|
+
return request<AgentContext>('GET', `/agents/${encodeURIComponent(id)}/context`);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export async function invokeAgent(id: string, projectContext: unknown, task: string): Promise<unknown> {
|
|
772
|
+
requireAuth();
|
|
773
|
+
return request('POST', `/agents/${encodeURIComponent(id)}/invoke`, {
|
|
774
|
+
projectContext,
|
|
775
|
+
task
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export async function getAgentCapabilities(id: string): Promise<unknown> {
|
|
780
|
+
requireAuth();
|
|
781
|
+
return request('GET', `/agents/${encodeURIComponent(id)}/capabilities`);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Skills
|
|
785
|
+
export async function listSkills(options: SkillSearchOptions = {}): Promise<SkillListResponse> {
|
|
786
|
+
requireAuth();
|
|
787
|
+
const params = new URLSearchParams();
|
|
788
|
+
if (options.category) params.append('category', options.category);
|
|
789
|
+
if (options.search) params.append('search', options.search);
|
|
790
|
+
|
|
791
|
+
const query = params.toString();
|
|
792
|
+
return request<SkillListResponse>('GET', `/skills${query ? '?' + query : ''}`);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
export async function getSkillContent(skillId: string): Promise<SkillContent> {
|
|
796
|
+
requireAuth();
|
|
797
|
+
return request<SkillContent>('GET', `/skills/${skillId}`);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* @deprecated Use getSkillContent instead
|
|
802
|
+
*/
|
|
803
|
+
export async function getSkill(category: string, name: string): Promise<SkillContent> {
|
|
804
|
+
requireAuth();
|
|
805
|
+
return request<SkillContent>('GET', `/skills/${encodeURIComponent(category)}/${encodeURIComponent(name)}`);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
export async function searchSkills(query: string, options: SkillSearchOptions = {}): Promise<SkillListResponse> {
|
|
809
|
+
requireAuth();
|
|
810
|
+
const params = new URLSearchParams();
|
|
811
|
+
params.append('search', query);
|
|
812
|
+
if (options.category) params.append('category', options.category);
|
|
813
|
+
return request<SkillListResponse>('GET', `/skills?${params.toString()}`);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Templates
|
|
817
|
+
export async function listTemplates(category: string): Promise<{ templates: Template[] }> {
|
|
818
|
+
requireAuth();
|
|
819
|
+
return request<{ templates: Template[] }>('GET', `/templates/${encodeURIComponent(category)}`);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
export async function getTemplate(category: string, name: string): Promise<TemplateContent> {
|
|
823
|
+
requireAuth();
|
|
824
|
+
return request<TemplateContent>('GET', `/templates/${encodeURIComponent(category)}/${encodeURIComponent(name)}`);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Orchestrator
|
|
828
|
+
export async function listWorkflows(): Promise<{ workflows: Workflow[] }> {
|
|
829
|
+
requireAuth();
|
|
830
|
+
return request<{ workflows: Workflow[] }>('GET', '/orchestrator/workflows');
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
export async function getWorkflow(id: string): Promise<Workflow> {
|
|
834
|
+
requireAuth();
|
|
835
|
+
return request<Workflow>('GET', `/orchestrator/workflows/${encodeURIComponent(id)}`);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export async function analyzeContext(projectContext: unknown, currentTask?: string, recentActions?: unknown[]): Promise<unknown> {
|
|
839
|
+
requireAuth();
|
|
840
|
+
return request('POST', '/orchestrator/analyze', {
|
|
841
|
+
projectContext,
|
|
842
|
+
currentTask,
|
|
843
|
+
recentActions
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
export async function startWorkflow(workflowId: string, projectContext: unknown, parameters?: unknown): Promise<unknown> {
|
|
848
|
+
requireAuth();
|
|
849
|
+
return request('POST', '/orchestrator/start', {
|
|
850
|
+
workflow: workflowId,
|
|
851
|
+
projectContext,
|
|
852
|
+
parameters
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
export async function getSuggestions(context: unknown, action?: string): Promise<unknown> {
|
|
857
|
+
requireAuth();
|
|
858
|
+
return request('POST', '/orchestrator/suggest', { context, action });
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Quality
|
|
862
|
+
export async function listQualityGates(): Promise<unknown> {
|
|
863
|
+
requireAuth();
|
|
864
|
+
return request('GET', '/quality/gates');
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
export async function runQualityGate(gateId: string, projectContext: unknown, options?: unknown): Promise<unknown> {
|
|
868
|
+
requireAuth();
|
|
869
|
+
return request('POST', '/quality/run', {
|
|
870
|
+
gate: gateId,
|
|
871
|
+
projectContext,
|
|
872
|
+
options
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export async function getLintBudgets(): Promise<unknown> {
|
|
877
|
+
requireAuth();
|
|
878
|
+
return request('GET', '/quality/lint-budgets');
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// MCP
|
|
882
|
+
export async function listMcpTools(): Promise<{ tools: McpTool[] }> {
|
|
883
|
+
requireAuth();
|
|
884
|
+
return request<{ tools: McpTool[] }>('GET', '/mcp/tools');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
export async function callMcpTool(tool: string, args: unknown): Promise<unknown> {
|
|
888
|
+
requireAuth();
|
|
889
|
+
return request('POST', '/mcp/tool', { tool, arguments: args });
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export async function listMcpResources(): Promise<{ resources: McpResource[] }> {
|
|
893
|
+
requireAuth();
|
|
894
|
+
return request<{ resources: McpResource[] }>('GET', '/mcp/resources');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
export async function getMcpResource(uri: string): Promise<McpResource> {
|
|
898
|
+
requireAuth();
|
|
899
|
+
return request<McpResource>('GET', `/mcp/resources/${encodeURIComponent(uri)}`);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Billing
|
|
903
|
+
export async function getSubscription(): Promise<Subscription> {
|
|
904
|
+
requireAuth();
|
|
905
|
+
return request<Subscription>('GET', '/billing/subscription');
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
export async function createCheckout(plan: string): Promise<{ url: string }> {
|
|
909
|
+
requireAuth();
|
|
910
|
+
return request<{ url: string }>('POST', '/billing/create-checkout', { plan });
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
export async function getPortalUrl(): Promise<{ url: string }> {
|
|
914
|
+
requireAuth();
|
|
915
|
+
return request<{ url: string }>('POST', '/billing/portal');
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
export async function getUsage(): Promise<unknown> {
|
|
919
|
+
requireAuth();
|
|
920
|
+
return request('GET', '/billing/usage');
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
export async function getInvoices(): Promise<unknown> {
|
|
924
|
+
requireAuth();
|
|
925
|
+
return request('GET', '/billing/invoices');
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Entitlements (v1)
|
|
929
|
+
export async function resolveEntitlements(): Promise<Entitlements> {
|
|
930
|
+
requireAuth();
|
|
931
|
+
return request<Entitlements>('GET', '/entitlements/resolve', null, { noCache: false });
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Usage Tracking (v1)
|
|
935
|
+
export async function trackUsage(type: string, metadata: Record<string, unknown> = {}): Promise<unknown> {
|
|
936
|
+
requireAuth();
|
|
937
|
+
return request('POST', '/track', { type, metadata });
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Telemetry Batch Upload (v1)
|
|
941
|
+
export async function uploadTelemetryBatch(events: unknown[], batchInfo: TelemetryBatchInfo = {}): Promise<unknown> {
|
|
942
|
+
requireAuth();
|
|
943
|
+
const batchId = batchInfo.id || crypto.randomUUID();
|
|
944
|
+
return request('POST', '/events/batch', {
|
|
945
|
+
source: 'bootspring',
|
|
946
|
+
batch: {
|
|
947
|
+
index: batchInfo.index || 0,
|
|
948
|
+
total: batchInfo.total || 1,
|
|
949
|
+
id: batchId
|
|
950
|
+
},
|
|
951
|
+
events
|
|
952
|
+
}, {
|
|
953
|
+
headers: {
|
|
954
|
+
'X-Bootspring-Batch-Id': batchId
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Projects
|
|
960
|
+
export async function listProjects(): Promise<{ projects: ProjectData[] }> {
|
|
961
|
+
requireAuth();
|
|
962
|
+
const url = new URL('/api/auth/device/projects', API_BASE);
|
|
963
|
+
const isHttps = url.protocol === 'https:';
|
|
964
|
+
const httpModule = isHttps ? https : http;
|
|
965
|
+
const apiKey = auth.getApiKey();
|
|
966
|
+
const token = auth.getToken();
|
|
967
|
+
|
|
968
|
+
return new Promise<{ projects: ProjectData[] }>((resolve, reject) => {
|
|
969
|
+
const headers: Record<string, string> = {
|
|
970
|
+
'Content-Type': 'application/json',
|
|
971
|
+
'User-Agent': `bootspring-cli/${getPackageVersion()}`,
|
|
972
|
+
};
|
|
973
|
+
if (apiKey) {
|
|
974
|
+
headers['X-API-Key'] = apiKey;
|
|
975
|
+
} else if (token) {
|
|
976
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const req = httpModule.request(url, {
|
|
980
|
+
method: 'GET',
|
|
981
|
+
headers,
|
|
982
|
+
timeout: 10000
|
|
983
|
+
}, (res) => {
|
|
984
|
+
let body = '';
|
|
985
|
+
res.on('data', (chunk: Buffer | string) => { body += chunk; });
|
|
986
|
+
res.on('end', () => {
|
|
987
|
+
try {
|
|
988
|
+
const json = JSON.parse(body) as { projects: ProjectData[]; message?: string; error?: string };
|
|
989
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
990
|
+
const error: ApiError = new Error(json.message || json.error || 'Failed to list projects');
|
|
991
|
+
error.status = res.statusCode;
|
|
992
|
+
reject(error);
|
|
993
|
+
} else {
|
|
994
|
+
resolve(json);
|
|
995
|
+
}
|
|
996
|
+
} catch {
|
|
997
|
+
reject(new Error('Invalid response from API'));
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
});
|
|
1001
|
+
req.on('error', reject);
|
|
1002
|
+
req.on('timeout', () => {
|
|
1003
|
+
req.destroy();
|
|
1004
|
+
reject(new Error('Request timeout'));
|
|
1005
|
+
});
|
|
1006
|
+
req.end();
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
// Project Members Management
|
|
1011
|
+
export async function getProjectMembers(projectId: string): Promise<{ members: ProjectMember[] }> {
|
|
1012
|
+
requireAuth();
|
|
1013
|
+
return directRequest<{ members: ProjectMember[] }>('GET', `/projects/${encodeURIComponent(projectId)}/members`);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
export async function addProjectMember(projectId: string, email: string, role: string = 'member'): Promise<ProjectMember> {
|
|
1017
|
+
requireAuth();
|
|
1018
|
+
return directRequest<ProjectMember>('POST', `/projects/${encodeURIComponent(projectId)}/members`, {
|
|
1019
|
+
email,
|
|
1020
|
+
role
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
export async function updateProjectMember(projectId: string, userId: string, role: string): Promise<ProjectMember> {
|
|
1025
|
+
requireAuth();
|
|
1026
|
+
return directRequest<ProjectMember>('PATCH', `/projects/${encodeURIComponent(projectId)}/members/${encodeURIComponent(userId)}`, {
|
|
1027
|
+
role
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
export async function removeProjectMember(projectId: string, userId: string): Promise<void> {
|
|
1032
|
+
requireAuth();
|
|
1033
|
+
await directRequest('DELETE', `/projects/${encodeURIComponent(projectId)}/members/${encodeURIComponent(userId)}`);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
export async function transferProjectOwnership(projectId: string, newOwnerId: string): Promise<unknown> {
|
|
1037
|
+
requireAuth();
|
|
1038
|
+
return directRequest('POST', `/projects/${encodeURIComponent(projectId)}/transfer`, {
|
|
1039
|
+
newOwnerId
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
export async function findSimilarProjects(name?: string, repoUrl?: string): Promise<{ projects: ProjectData[] }> {
|
|
1044
|
+
requireAuth();
|
|
1045
|
+
const params = new URLSearchParams();
|
|
1046
|
+
if (name) params.set('name', name);
|
|
1047
|
+
if (repoUrl) params.set('repo', repoUrl);
|
|
1048
|
+
return directRequest<{ projects: ProjectData[] }>('GET', `/projects/similar?${params.toString()}`);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
// Preseed Documents
|
|
1052
|
+
export async function listPreseedDocuments(projectId: string): Promise<PreseedDocumentsResponse> {
|
|
1053
|
+
requireAuth();
|
|
1054
|
+
return directRequest<PreseedDocumentsResponse>('GET', `/projects/${encodeURIComponent(projectId)}/preseed/documents`);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
export async function getPreseedDocument(projectId: string, documentName: string): Promise<PreseedDocument> {
|
|
1058
|
+
requireAuth();
|
|
1059
|
+
return directRequest<PreseedDocument>('GET', `/projects/${encodeURIComponent(projectId)}/preseed/documents?name=${encodeURIComponent(documentName)}`);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
export async function downloadPreseedZip(projectId: string): Promise<Buffer> {
|
|
1063
|
+
requireAuth();
|
|
1064
|
+
const apiKey = auth.getApiKey();
|
|
1065
|
+
const token = auth.getToken();
|
|
1066
|
+
const url = new URL(`/api/projects/${encodeURIComponent(projectId)}/preseed/documents?format=zip`, API_BASE);
|
|
1067
|
+
const isHttps = url.protocol === 'https:';
|
|
1068
|
+
const httpModule = isHttps ? https : http;
|
|
1069
|
+
|
|
1070
|
+
return new Promise<Buffer>((resolve, reject) => {
|
|
1071
|
+
const headers: Record<string, string> = {
|
|
1072
|
+
'User-Agent': `bootspring-cli/${getPackageVersion()}`,
|
|
1073
|
+
};
|
|
1074
|
+
if (apiKey) {
|
|
1075
|
+
headers['X-API-Key'] = apiKey;
|
|
1076
|
+
} else if (token) {
|
|
1077
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const req = httpModule.request(url, {
|
|
1081
|
+
method: 'GET',
|
|
1082
|
+
headers,
|
|
1083
|
+
timeout: 60000
|
|
1084
|
+
}, (res) => {
|
|
1085
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
1086
|
+
let body = '';
|
|
1087
|
+
res.on('data', (chunk: Buffer | string) => { body += chunk; });
|
|
1088
|
+
res.on('end', () => {
|
|
1089
|
+
try {
|
|
1090
|
+
const json = JSON.parse(body) as { message?: string; error?: string };
|
|
1091
|
+
const error: ApiError = new Error(json.message || json.error || 'Failed to download preseed documents');
|
|
1092
|
+
error.status = res.statusCode;
|
|
1093
|
+
reject(error);
|
|
1094
|
+
} catch {
|
|
1095
|
+
reject(new Error(`HTTP ${res.statusCode}: ${body}`));
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const chunks: Buffer[] = [];
|
|
1102
|
+
res.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
1103
|
+
res.on('end', () => {
|
|
1104
|
+
resolve(Buffer.concat(chunks));
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
req.on('error', reject);
|
|
1109
|
+
req.on('timeout', () => {
|
|
1110
|
+
req.destroy();
|
|
1111
|
+
reject(new Error('Request timeout'));
|
|
1112
|
+
});
|
|
1113
|
+
req.end();
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
export async function getPreseedWizard(projectId: string): Promise<PreseedWizardResponse> {
|
|
1118
|
+
requireAuth();
|
|
1119
|
+
return directRequest<PreseedWizardResponse>('GET', `/projects/${encodeURIComponent(projectId)}/preseed/wizard`);
|
|
1120
|
+
}
|