@agentforge-ai/cli 0.5.0 → 0.5.2
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/dist/default/agentforge.config.ts +78 -0
- package/dist/default/convex/agents.ts +16 -16
- package/dist/default/convex/apiKeys.ts +7 -7
- package/dist/default/convex/cronJobs.ts +12 -12
- package/dist/default/convex/files.ts +7 -7
- package/dist/default/convex/folders.ts +11 -11
- package/dist/default/convex/heartbeat.ts +20 -20
- package/dist/default/convex/mastraIntegration.ts +3 -2
- package/dist/default/convex/mcpConnections.ts +5 -5
- package/dist/default/convex/messages.ts +4 -4
- package/dist/default/convex/projects.ts +10 -10
- package/dist/default/convex/schema.ts +150 -83
- package/dist/default/convex/sessions.ts +12 -12
- package/dist/default/convex/settings.ts +3 -3
- package/dist/default/convex/skills.ts +8 -8
- package/dist/default/convex/threads.ts +9 -9
- package/dist/default/convex/usage.ts +16 -16
- package/dist/default/convex/vault.ts +50 -33
- package/dist/default/package.json +1 -0
- package/dist/default/tsconfig.json +1 -0
- package/dist/index.js +691 -84
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/templates/default/agentforge.config.ts +78 -0
- package/templates/default/convex/agents.ts +16 -16
- package/templates/default/convex/apiKeys.ts +7 -7
- package/templates/default/convex/cronJobs.ts +12 -12
- package/templates/default/convex/files.ts +7 -7
- package/templates/default/convex/folders.ts +11 -11
- package/templates/default/convex/heartbeat.ts +20 -20
- package/templates/default/convex/mastraIntegration.ts +3 -2
- package/templates/default/convex/mcpConnections.ts +5 -5
- package/templates/default/convex/messages.ts +4 -4
- package/templates/default/convex/projects.ts +10 -10
- package/templates/default/convex/schema.ts +150 -83
- package/templates/default/convex/sessions.ts +12 -12
- package/templates/default/convex/settings.ts +3 -3
- package/templates/default/convex/skills.ts +8 -8
- package/templates/default/convex/threads.ts +9 -9
- package/templates/default/convex/usage.ts +16 -16
- package/templates/default/convex/vault.ts +50 -33
- package/templates/default/package.json +1 -0
- package/templates/default/tsconfig.json +1 -0
package/dist/index.js
CHANGED
|
@@ -181,11 +181,271 @@ async function runProject(options) {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// src/commands/deploy.ts
|
|
184
|
-
import
|
|
184
|
+
import path4 from "path";
|
|
185
185
|
import { execSync as execSync2 } from "child_process";
|
|
186
|
+
import fs4 from "fs-extra";
|
|
187
|
+
import chalk from "chalk";
|
|
188
|
+
import ora from "ora";
|
|
189
|
+
|
|
190
|
+
// src/lib/credentials.ts
|
|
186
191
|
import fs3 from "fs-extra";
|
|
192
|
+
import path3 from "path";
|
|
193
|
+
import os from "os";
|
|
194
|
+
var CREDENTIALS_DIR = path3.join(os.homedir(), ".agentforge");
|
|
195
|
+
var CREDENTIALS_FILE = path3.join(CREDENTIALS_DIR, "credentials.json");
|
|
196
|
+
var DEFAULT_CLOUD_URL = "https://cloud.agentforge.ai";
|
|
197
|
+
async function ensureCredentialsDir() {
|
|
198
|
+
await fs3.ensureDir(CREDENTIALS_DIR);
|
|
199
|
+
try {
|
|
200
|
+
await fs3.chmod(CREDENTIALS_DIR, 448);
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function readCredentials() {
|
|
205
|
+
try {
|
|
206
|
+
if (!await fs3.pathExists(CREDENTIALS_FILE)) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
const content = await fs3.readFile(CREDENTIALS_FILE, "utf-8");
|
|
210
|
+
const creds = JSON.parse(content);
|
|
211
|
+
if (!creds.cloudUrl) {
|
|
212
|
+
creds.cloudUrl = DEFAULT_CLOUD_URL;
|
|
213
|
+
}
|
|
214
|
+
return creds;
|
|
215
|
+
} catch (error2) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function writeCredentials(credentials) {
|
|
220
|
+
await ensureCredentialsDir();
|
|
221
|
+
const data = {
|
|
222
|
+
...credentials,
|
|
223
|
+
cloudUrl: credentials.cloudUrl || DEFAULT_CLOUD_URL,
|
|
224
|
+
refreshedAt: Date.now()
|
|
225
|
+
};
|
|
226
|
+
await fs3.writeFile(CREDENTIALS_FILE, JSON.stringify(data, null, 2), "utf-8");
|
|
227
|
+
try {
|
|
228
|
+
await fs3.chmod(CREDENTIALS_FILE, 384);
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
async function deleteCredentials() {
|
|
233
|
+
try {
|
|
234
|
+
if (await fs3.pathExists(CREDENTIALS_FILE)) {
|
|
235
|
+
await fs3.remove(CREDENTIALS_FILE);
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
} catch {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function isAuthenticated() {
|
|
244
|
+
const creds = await readCredentials();
|
|
245
|
+
return creds !== null && creds.apiKey !== void 0;
|
|
246
|
+
}
|
|
247
|
+
async function getCloudUrl() {
|
|
248
|
+
const creds = await readCredentials();
|
|
249
|
+
return creds?.cloudUrl || process.env.AGENTFORGE_CLOUD_URL || DEFAULT_CLOUD_URL;
|
|
250
|
+
}
|
|
251
|
+
async function getApiKey() {
|
|
252
|
+
const creds = await readCredentials();
|
|
253
|
+
return creds?.apiKey || null;
|
|
254
|
+
}
|
|
255
|
+
function getCredentialsPath() {
|
|
256
|
+
return CREDENTIALS_FILE;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/lib/cloud-client.ts
|
|
260
|
+
var CloudClientError = class extends Error {
|
|
261
|
+
constructor(message, code, status) {
|
|
262
|
+
super(message);
|
|
263
|
+
this.code = code;
|
|
264
|
+
this.status = status;
|
|
265
|
+
this.name = "CloudClientError";
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
var CloudClient = class {
|
|
269
|
+
baseUrl;
|
|
270
|
+
apiKey = null;
|
|
271
|
+
constructor(baseUrl, apiKey) {
|
|
272
|
+
this.baseUrl = baseUrl || "https://cloud.agentforge.ai";
|
|
273
|
+
this.apiKey = apiKey || null;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Initialize the client with stored credentials
|
|
277
|
+
*/
|
|
278
|
+
async initialize() {
|
|
279
|
+
this.baseUrl = await getCloudUrl();
|
|
280
|
+
this.apiKey = await getApiKey();
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Set the API key directly
|
|
284
|
+
*/
|
|
285
|
+
setApiKey(apiKey) {
|
|
286
|
+
this.apiKey = apiKey;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Set the base URL directly
|
|
290
|
+
*/
|
|
291
|
+
setBaseUrl(baseUrl) {
|
|
292
|
+
this.baseUrl = baseUrl;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Get the full API URL for an endpoint
|
|
296
|
+
*/
|
|
297
|
+
getUrl(endpoint) {
|
|
298
|
+
const base = this.baseUrl.replace(/\/$/, "");
|
|
299
|
+
const path10 = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
|
|
300
|
+
return `${base}${path10}`;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Get request headers with authentication
|
|
304
|
+
*/
|
|
305
|
+
getHeaders() {
|
|
306
|
+
const headers = {
|
|
307
|
+
"Content-Type": "application/json",
|
|
308
|
+
"Accept": "application/json",
|
|
309
|
+
"X-Client-Version": "0.5.1"
|
|
310
|
+
};
|
|
311
|
+
if (this.apiKey) {
|
|
312
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
313
|
+
}
|
|
314
|
+
return headers;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Make an HTTP request to the Cloud API
|
|
318
|
+
*/
|
|
319
|
+
async request(method, endpoint, body) {
|
|
320
|
+
const url = this.getUrl(endpoint);
|
|
321
|
+
const options = {
|
|
322
|
+
method,
|
|
323
|
+
headers: this.getHeaders()
|
|
324
|
+
};
|
|
325
|
+
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
326
|
+
options.body = JSON.stringify(body);
|
|
327
|
+
}
|
|
328
|
+
let response;
|
|
329
|
+
try {
|
|
330
|
+
response = await fetch(url, options);
|
|
331
|
+
} catch (error2) {
|
|
332
|
+
throw new CloudClientError(
|
|
333
|
+
`Network error: ${error2.message || "Failed to connect to AgentForge Cloud"}`,
|
|
334
|
+
"NETWORK_ERROR"
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
const contentType = response.headers.get("content-type");
|
|
338
|
+
const isJson = contentType?.includes("application/json");
|
|
339
|
+
if (!response.ok) {
|
|
340
|
+
const errorBody = isJson ? await response.json() : null;
|
|
341
|
+
const message = errorBody?.message || errorBody?.error || `HTTP ${response.status}: ${response.statusText}`;
|
|
342
|
+
throw new CloudClientError(
|
|
343
|
+
message,
|
|
344
|
+
errorBody?.code || `HTTP_${response.status}`,
|
|
345
|
+
response.status
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (response.status === 204) {
|
|
349
|
+
return {};
|
|
350
|
+
}
|
|
351
|
+
if (!isJson) {
|
|
352
|
+
const text = await response.text();
|
|
353
|
+
return { text };
|
|
354
|
+
}
|
|
355
|
+
return response.json();
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Check if credentials are valid
|
|
359
|
+
*/
|
|
360
|
+
async authenticate() {
|
|
361
|
+
if (!this.apiKey) {
|
|
362
|
+
throw new CloudClientError(
|
|
363
|
+
'No API key configured. Run "agentforge login" to authenticate.',
|
|
364
|
+
"NO_CREDENTIALS",
|
|
365
|
+
401
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const user = await this.request("GET", "/api/auth/me");
|
|
370
|
+
return user;
|
|
371
|
+
} catch (error2) {
|
|
372
|
+
if (error2.status === 401) {
|
|
373
|
+
throw new CloudClientError(
|
|
374
|
+
'Invalid API key. Run "agentforge login" to re-authenticate.',
|
|
375
|
+
"UNAUTHORIZED",
|
|
376
|
+
401
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
throw error2;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* List all projects for the authenticated user
|
|
384
|
+
*/
|
|
385
|
+
async listProjects() {
|
|
386
|
+
return this.request("GET", "/api/projects");
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Get a specific project by ID
|
|
390
|
+
*/
|
|
391
|
+
async getProject(projectId) {
|
|
392
|
+
return this.request("GET", `/api/projects/${projectId}`);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Upload agent configuration to Cloud
|
|
396
|
+
*/
|
|
397
|
+
async uploadAgentConfig(projectId, agentConfig) {
|
|
398
|
+
return this.request(
|
|
399
|
+
"POST",
|
|
400
|
+
`/api/projects/${projectId}/agents`,
|
|
401
|
+
agentConfig
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Create a new deployment
|
|
406
|
+
*/
|
|
407
|
+
async createDeployment(request) {
|
|
408
|
+
return this.request(
|
|
409
|
+
"POST",
|
|
410
|
+
"/api/deployments/create",
|
|
411
|
+
request
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Get deployment status
|
|
416
|
+
*/
|
|
417
|
+
async getDeploymentStatus(deploymentId) {
|
|
418
|
+
return this.request(
|
|
419
|
+
"GET",
|
|
420
|
+
`/api/deployments/${deploymentId}/status`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get deployment details
|
|
425
|
+
*/
|
|
426
|
+
async getDeployment(deploymentId) {
|
|
427
|
+
return this.request("GET", `/api/deployments/${deploymentId}`);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Rollback a deployment
|
|
431
|
+
*/
|
|
432
|
+
async rollbackDeployment(deploymentId) {
|
|
433
|
+
return this.request(
|
|
434
|
+
"POST",
|
|
435
|
+
`/api/deployments/${deploymentId}/rollback`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* List deployments for a project
|
|
440
|
+
*/
|
|
441
|
+
async listDeployments(projectId) {
|
|
442
|
+
return this.request("GET", `/api/projects/${projectId}/deployments`);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// src/commands/deploy.ts
|
|
187
447
|
function parseEnvFile(filePath) {
|
|
188
|
-
const content =
|
|
448
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
189
449
|
const vars = {};
|
|
190
450
|
for (const line of content.split("\n")) {
|
|
191
451
|
const trimmed = line.trim();
|
|
@@ -203,17 +463,217 @@ function parseEnvFile(filePath) {
|
|
|
203
463
|
}
|
|
204
464
|
return vars;
|
|
205
465
|
}
|
|
206
|
-
async function
|
|
466
|
+
async function readAgentForgeConfig(projectDir) {
|
|
467
|
+
const tsConfigPath = path4.join(projectDir, "agentforge.config.ts");
|
|
468
|
+
if (await fs4.pathExists(tsConfigPath)) {
|
|
469
|
+
try {
|
|
470
|
+
const content = await fs4.readFile(tsConfigPath, "utf-8");
|
|
471
|
+
return parseConfigFromContent(content, projectDir);
|
|
472
|
+
} catch {
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const jsonConfigPath = path4.join(projectDir, "agentforge.json");
|
|
476
|
+
if (await fs4.pathExists(jsonConfigPath)) {
|
|
477
|
+
try {
|
|
478
|
+
const content = await fs4.readFile(jsonConfigPath, "utf-8");
|
|
479
|
+
return JSON.parse(content);
|
|
480
|
+
} catch {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
function parseConfigFromContent(content, projectDir) {
|
|
487
|
+
const config = {};
|
|
488
|
+
const projectIdMatch = content.match(/projectId\s*:\s*["']([^"']+)["']/);
|
|
489
|
+
if (projectIdMatch) {
|
|
490
|
+
config.projectId = projectIdMatch[1];
|
|
491
|
+
}
|
|
492
|
+
const agentsPath = path4.join(projectDir, "convex", "agents.ts");
|
|
493
|
+
if (fs4.existsSync(agentsPath)) {
|
|
494
|
+
try {
|
|
495
|
+
const agentsContent = fs4.readFileSync(agentsPath, "utf-8");
|
|
496
|
+
config.agents = parseAgentsFromConvex(agentsContent);
|
|
497
|
+
} catch {
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
const cloudUrlMatch = content.match(/cloudUrl\s*:\s*["']([^"']+)["']/);
|
|
501
|
+
if (cloudUrlMatch) {
|
|
502
|
+
config.cloudUrl = cloudUrlMatch[1];
|
|
503
|
+
}
|
|
504
|
+
return Object.keys(config).length > 0 ? config : null;
|
|
505
|
+
}
|
|
506
|
+
function parseAgentsFromConvex(content) {
|
|
507
|
+
const agents = [];
|
|
508
|
+
const agentPattern = /\{\s*id:\s*["']([^"']+)["']\s*,\s*name:\s*["']([^"']+)["']([\s\S]*?)\}/g;
|
|
509
|
+
let match;
|
|
510
|
+
while ((match = agentPattern.exec(content)) !== null) {
|
|
511
|
+
const id = match[1];
|
|
512
|
+
const name = match[2];
|
|
513
|
+
const rest = match[3];
|
|
514
|
+
const agent = { id, name, instructions: "", model: "gpt-4o-mini" };
|
|
515
|
+
const instructionsMatch = rest.match(/instructions:\s*["']([^"']+)["']/);
|
|
516
|
+
if (instructionsMatch) agent.instructions = instructionsMatch[1];
|
|
517
|
+
const modelMatch = rest.match(/model:\s*["']([^"']+)["']/);
|
|
518
|
+
if (modelMatch) agent.model = modelMatch[1];
|
|
519
|
+
const providerMatch = rest.match(/provider:\s*["']([^"']+)["']/);
|
|
520
|
+
if (providerMatch) agent.provider = providerMatch[1];
|
|
521
|
+
const descriptionMatch = rest.match(/description:\s*["']([^"']+)["']/);
|
|
522
|
+
if (descriptionMatch) agent.description = descriptionMatch[1];
|
|
523
|
+
agents.push(agent);
|
|
524
|
+
}
|
|
525
|
+
return agents;
|
|
526
|
+
}
|
|
527
|
+
async function deployToCloud(options, projectDir) {
|
|
528
|
+
console.log("\n\u2601\uFE0F Deploying to AgentForge Cloud...\n");
|
|
529
|
+
const credentials = await readCredentials();
|
|
530
|
+
if (!credentials?.apiKey) {
|
|
531
|
+
console.error(chalk.red("\u2716"), "Not authenticated with AgentForge Cloud.");
|
|
532
|
+
console.error(' Run "agentforge login" to authenticate.\n');
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
const spinner = ora("Reading agent configuration...").start();
|
|
536
|
+
const config = await readAgentForgeConfig(projectDir);
|
|
537
|
+
let projectId = options.project || config?.projectId;
|
|
538
|
+
if (!projectId) {
|
|
539
|
+
spinner.stop();
|
|
540
|
+
console.error(chalk.red("\u2716"), "No project ID specified.");
|
|
541
|
+
console.error(" Use --project flag or set projectId in agentforge.config.ts\n");
|
|
542
|
+
process.exit(1);
|
|
543
|
+
}
|
|
544
|
+
let agents = config?.agents || [];
|
|
545
|
+
if (agents.length === 0) {
|
|
546
|
+
spinner.stop();
|
|
547
|
+
console.error(chalk.red("\u2716"), "No agents found in configuration.");
|
|
548
|
+
console.error(" Define agents in agentforge.config.ts or convex/agents.ts\n");
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
spinner.succeed(`Found ${agents.length} agent(s) to deploy`);
|
|
552
|
+
if (options.dryRun) {
|
|
553
|
+
console.log("\n" + chalk.blue("\u2139"), "Dry run - no changes will be made\n");
|
|
554
|
+
console.log(" Project ID:", chalk.cyan(projectId));
|
|
555
|
+
console.log(" Cloud URL:", chalk.cyan(credentials.cloudUrl || "https://cloud.agentforge.ai"));
|
|
556
|
+
console.log(" Agents:");
|
|
557
|
+
for (const agent of agents) {
|
|
558
|
+
console.log(` \u2022 ${chalk.bold(agent.name)} (${agent.model})`);
|
|
559
|
+
}
|
|
560
|
+
console.log();
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const cloudSpinner = ora("Connecting to AgentForge Cloud...").start();
|
|
564
|
+
const client = new CloudClient(
|
|
565
|
+
credentials.cloudUrl || process.env.AGENTFORGE_CLOUD_URL,
|
|
566
|
+
credentials.apiKey
|
|
567
|
+
);
|
|
568
|
+
try {
|
|
569
|
+
await client.authenticate();
|
|
570
|
+
cloudSpinner.succeed("Connected to AgentForge Cloud");
|
|
571
|
+
} catch (err) {
|
|
572
|
+
cloudSpinner.fail("Failed to connect to AgentForge Cloud");
|
|
573
|
+
if (err instanceof CloudClientError) {
|
|
574
|
+
console.error(chalk.red("\u2716"), err.message);
|
|
575
|
+
} else {
|
|
576
|
+
console.error(chalk.red("\u2716"), err.message || err);
|
|
577
|
+
}
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
const projectSpinner = ora("Verifying project...").start();
|
|
581
|
+
try {
|
|
582
|
+
await client.getProject(projectId);
|
|
583
|
+
projectSpinner.succeed(`Project verified: ${chalk.cyan(projectId)}`);
|
|
584
|
+
} catch (err) {
|
|
585
|
+
projectSpinner.fail("Project verification failed");
|
|
586
|
+
if (err.status === 404) {
|
|
587
|
+
console.error(chalk.red("\u2716"), `Project "${projectId}" not found.`);
|
|
588
|
+
console.error(" Check the project ID or create the project in the dashboard.\n");
|
|
589
|
+
} else {
|
|
590
|
+
console.error(chalk.red("\u2716"), err.message || err);
|
|
591
|
+
}
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
const version = options.version || `v${Date.now()}`;
|
|
595
|
+
const deploySpinner = ora("Creating deployment...").start();
|
|
596
|
+
let deploymentId;
|
|
597
|
+
try {
|
|
598
|
+
const response = await client.createDeployment({
|
|
599
|
+
projectId,
|
|
600
|
+
agents,
|
|
601
|
+
version
|
|
602
|
+
});
|
|
603
|
+
deploymentId = response.deploymentId;
|
|
604
|
+
deploySpinner.succeed(`Deployment created: ${chalk.cyan(deploymentId)}`);
|
|
605
|
+
} catch (err) {
|
|
606
|
+
deploySpinner.fail("Failed to create deployment");
|
|
607
|
+
if (err instanceof CloudClientError) {
|
|
608
|
+
console.error(chalk.red("\u2716"), err.message);
|
|
609
|
+
} else {
|
|
610
|
+
console.error(chalk.red("\u2716"), err.message || err);
|
|
611
|
+
}
|
|
612
|
+
process.exit(1);
|
|
613
|
+
}
|
|
614
|
+
console.log("\n\u{1F4E6} Deploying agents...\n");
|
|
615
|
+
const pollSpinner = ora("Waiting for deployment to complete...").start();
|
|
616
|
+
const maxAttempts = 60;
|
|
617
|
+
let attempts = 0;
|
|
618
|
+
while (attempts < maxAttempts) {
|
|
619
|
+
let status;
|
|
620
|
+
try {
|
|
621
|
+
status = await client.getDeploymentStatus(deploymentId);
|
|
622
|
+
attempts++;
|
|
623
|
+
} catch (err) {
|
|
624
|
+
attempts++;
|
|
625
|
+
if (attempts >= maxAttempts) {
|
|
626
|
+
pollSpinner.fail("Deployment status check timed out");
|
|
627
|
+
console.error();
|
|
628
|
+
console.error(chalk.yellow("\u26A0"), "The deployment may still be in progress.");
|
|
629
|
+
console.error(` Check status at: https://cloud.agentforge.ai/projects/${projectId}/deployments/${deploymentId}`);
|
|
630
|
+
console.error();
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (status.status === "completed") {
|
|
637
|
+
pollSpinner.succeed("Deployment completed successfully!");
|
|
638
|
+
console.log();
|
|
639
|
+
console.log(chalk.green("\u2714"), `Deployed to ${chalk.bold(`https://cloud.agentforge.ai/projects/${projectId}`)}`);
|
|
640
|
+
if (status.url) {
|
|
641
|
+
console.log(chalk.green("\u2714"), `Deployment URL: ${chalk.cyan(status.url)}`);
|
|
642
|
+
}
|
|
643
|
+
console.log();
|
|
644
|
+
return;
|
|
645
|
+
} else if (status.status === "failed") {
|
|
646
|
+
pollSpinner.fail("Deployment failed");
|
|
647
|
+
console.error();
|
|
648
|
+
console.error(chalk.red("\u2716"), "Error:", status.errorMessage || "Unknown error");
|
|
649
|
+
console.error();
|
|
650
|
+
process.exit(1);
|
|
651
|
+
} else if (status.progress !== void 0) {
|
|
652
|
+
pollSpinner.text = `Deploying... ${status.progress}%`;
|
|
653
|
+
} else {
|
|
654
|
+
const statuses = {
|
|
655
|
+
pending: "Waiting to start...",
|
|
656
|
+
building: "Building...",
|
|
657
|
+
deploying: "Deploying..."
|
|
658
|
+
};
|
|
659
|
+
pollSpinner.text = statuses[status.status] || `Status: ${status.status}`;
|
|
660
|
+
}
|
|
661
|
+
await new Promise((resolve2) => setTimeout(resolve2, 3e3));
|
|
662
|
+
}
|
|
663
|
+
pollSpinner.fail("Deployment timed out");
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
async function deployToConvex(options) {
|
|
207
667
|
const projectDir = process.cwd();
|
|
208
|
-
const pkgPath =
|
|
209
|
-
if (!await
|
|
668
|
+
const pkgPath = path4.join(projectDir, "package.json");
|
|
669
|
+
if (!await fs4.pathExists(pkgPath)) {
|
|
210
670
|
console.error(
|
|
211
671
|
"Error: No package.json found. Are you in an AgentForge project directory?"
|
|
212
672
|
);
|
|
213
673
|
process.exit(1);
|
|
214
674
|
}
|
|
215
|
-
const convexDir =
|
|
216
|
-
if (!await
|
|
675
|
+
const convexDir = path4.join(projectDir, "convex");
|
|
676
|
+
if (!await fs4.pathExists(convexDir)) {
|
|
217
677
|
console.error(
|
|
218
678
|
"Error: No convex/ directory found. Are you in an AgentForge project directory?"
|
|
219
679
|
);
|
|
@@ -233,8 +693,8 @@ async function deployProject(options) {
|
|
|
233
693
|
}
|
|
234
694
|
return;
|
|
235
695
|
}
|
|
236
|
-
const envPath =
|
|
237
|
-
const envExists = await
|
|
696
|
+
const envPath = path4.resolve(projectDir, options.env);
|
|
697
|
+
const envExists = await fs4.pathExists(envPath);
|
|
238
698
|
let envVars = {};
|
|
239
699
|
if (envExists) {
|
|
240
700
|
envVars = parseEnvFile(envPath);
|
|
@@ -299,10 +759,18 @@ async function deployProject(options) {
|
|
|
299
759
|
process.exit(1);
|
|
300
760
|
}
|
|
301
761
|
}
|
|
762
|
+
async function deployProject(options) {
|
|
763
|
+
const projectDir = process.cwd();
|
|
764
|
+
if (options.provider === "cloud") {
|
|
765
|
+
await deployToCloud(options, projectDir);
|
|
766
|
+
} else {
|
|
767
|
+
await deployToConvex(options);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
302
770
|
|
|
303
771
|
// src/lib/convex-client.ts
|
|
304
|
-
import
|
|
305
|
-
import
|
|
772
|
+
import fs5 from "fs-extra";
|
|
773
|
+
import path5 from "path";
|
|
306
774
|
function safeCwd() {
|
|
307
775
|
try {
|
|
308
776
|
return process.cwd();
|
|
@@ -319,19 +787,19 @@ function getConvexUrl() {
|
|
|
319
787
|
}
|
|
320
788
|
const envFiles = [".env.local", ".env", ".env.production"];
|
|
321
789
|
for (const envFile of envFiles) {
|
|
322
|
-
const envPath =
|
|
323
|
-
if (
|
|
324
|
-
const content =
|
|
790
|
+
const envPath = path5.join(cwd, envFile);
|
|
791
|
+
if (fs5.existsSync(envPath)) {
|
|
792
|
+
const content = fs5.readFileSync(envPath, "utf-8");
|
|
325
793
|
const match = content.match(/CONVEX_URL\s*=\s*(.+)/);
|
|
326
794
|
if (match) {
|
|
327
795
|
return match[1].trim().replace(/["']/g, "");
|
|
328
796
|
}
|
|
329
797
|
}
|
|
330
798
|
}
|
|
331
|
-
const convexEnv =
|
|
332
|
-
if (
|
|
799
|
+
const convexEnv = path5.join(cwd, ".convex", "deployment.json");
|
|
800
|
+
if (fs5.existsSync(convexEnv)) {
|
|
333
801
|
try {
|
|
334
|
-
const data = JSON.parse(
|
|
802
|
+
const data = JSON.parse(fs5.readFileSync(convexEnv, "utf-8"));
|
|
335
803
|
if (data.url) return data.url;
|
|
336
804
|
} catch {
|
|
337
805
|
}
|
|
@@ -791,8 +1259,8 @@ function registerThreadsCommand(program2) {
|
|
|
791
1259
|
}
|
|
792
1260
|
|
|
793
1261
|
// src/commands/skills.ts
|
|
794
|
-
import
|
|
795
|
-
import
|
|
1262
|
+
import fs6 from "fs-extra";
|
|
1263
|
+
import path6 from "path";
|
|
796
1264
|
import readline3 from "readline";
|
|
797
1265
|
function prompt2(q) {
|
|
798
1266
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -812,9 +1280,9 @@ function registerSkillsCommand(program2) {
|
|
|
812
1280
|
}
|
|
813
1281
|
header("Skills");
|
|
814
1282
|
const localSkills = [];
|
|
815
|
-
const skillsDir =
|
|
816
|
-
if (
|
|
817
|
-
const dirs =
|
|
1283
|
+
const skillsDir = path6.join(process.cwd(), "skills");
|
|
1284
|
+
if (fs6.existsSync(skillsDir)) {
|
|
1285
|
+
const dirs = fs6.readdirSync(skillsDir).filter((d) => fs6.statSync(path6.join(skillsDir, d)).isDirectory());
|
|
818
1286
|
localSkills.push(...dirs);
|
|
819
1287
|
}
|
|
820
1288
|
const items = result || [];
|
|
@@ -826,11 +1294,11 @@ function registerSkillsCommand(program2) {
|
|
|
826
1294
|
if (localSkills.length > 0) {
|
|
827
1295
|
dim(" Local Skills:");
|
|
828
1296
|
localSkills.forEach((s) => {
|
|
829
|
-
const configPath =
|
|
1297
|
+
const configPath = path6.join(skillsDir, s, "config.json");
|
|
830
1298
|
let desc = "";
|
|
831
|
-
if (
|
|
1299
|
+
if (fs6.existsSync(configPath)) {
|
|
832
1300
|
try {
|
|
833
|
-
desc = JSON.parse(
|
|
1301
|
+
desc = JSON.parse(fs6.readFileSync(configPath, "utf-8")).description || "";
|
|
834
1302
|
} catch {
|
|
835
1303
|
}
|
|
836
1304
|
}
|
|
@@ -857,13 +1325,13 @@ function registerSkillsCommand(program2) {
|
|
|
857
1325
|
error("Skill name is required.");
|
|
858
1326
|
process.exit(1);
|
|
859
1327
|
}
|
|
860
|
-
const skillDir =
|
|
861
|
-
if (
|
|
1328
|
+
const skillDir = path6.join(process.cwd(), "skills", name);
|
|
1329
|
+
if (fs6.existsSync(skillDir)) {
|
|
862
1330
|
error(`Skill "${name}" already exists at ${skillDir}`);
|
|
863
1331
|
process.exit(1);
|
|
864
1332
|
}
|
|
865
|
-
|
|
866
|
-
|
|
1333
|
+
fs6.mkdirSync(skillDir, { recursive: true });
|
|
1334
|
+
fs6.writeFileSync(path6.join(skillDir, "config.json"), JSON.stringify({
|
|
867
1335
|
name,
|
|
868
1336
|
version: "1.0.0",
|
|
869
1337
|
description,
|
|
@@ -873,7 +1341,7 @@ function registerSkillsCommand(program2) {
|
|
|
873
1341
|
dependencies: [],
|
|
874
1342
|
agentInstructions: `You have access to the ${name} skill. ${description}`
|
|
875
1343
|
}, null, 2));
|
|
876
|
-
|
|
1344
|
+
fs6.writeFileSync(path6.join(skillDir, "index.ts"), `import { z } from 'zod';
|
|
877
1345
|
|
|
878
1346
|
/**
|
|
879
1347
|
* ${name} \u2014 AgentForge Skill
|
|
@@ -899,7 +1367,7 @@ export const tools = [
|
|
|
899
1367
|
|
|
900
1368
|
export default { tools };
|
|
901
1369
|
`);
|
|
902
|
-
|
|
1370
|
+
fs6.writeFileSync(path6.join(skillDir, "SKILL.md"), `# ${name}
|
|
903
1371
|
|
|
904
1372
|
${description}
|
|
905
1373
|
|
|
@@ -929,11 +1397,11 @@ Edit \`skills/${name}/config.json\` to customize.
|
|
|
929
1397
|
success(`Skill "${name}" installed.`);
|
|
930
1398
|
});
|
|
931
1399
|
skills.command("remove").argument("<name>", "Skill name to remove").description("Remove a skill").action(async (name) => {
|
|
932
|
-
const skillDir =
|
|
933
|
-
if (
|
|
1400
|
+
const skillDir = path6.join(process.cwd(), "skills", name);
|
|
1401
|
+
if (fs6.existsSync(skillDir)) {
|
|
934
1402
|
const confirm = await prompt2(`Remove skill "${name}" and delete files? (y/N): `);
|
|
935
1403
|
if (confirm.toLowerCase() === "y") {
|
|
936
|
-
|
|
1404
|
+
fs6.removeSync(skillDir);
|
|
937
1405
|
success(`Skill "${name}" removed from disk.`);
|
|
938
1406
|
}
|
|
939
1407
|
}
|
|
@@ -1136,8 +1604,8 @@ function registerMcpCommand(program2) {
|
|
|
1136
1604
|
}
|
|
1137
1605
|
|
|
1138
1606
|
// src/commands/files.ts
|
|
1139
|
-
import
|
|
1140
|
-
import
|
|
1607
|
+
import fs7 from "fs-extra";
|
|
1608
|
+
import path7 from "path";
|
|
1141
1609
|
function registerFilesCommand(program2) {
|
|
1142
1610
|
const files = program2.command("files").description("Manage files");
|
|
1143
1611
|
files.command("list").argument("[folder]", "Folder ID to list files from").option("--json", "Output as JSON").description("List files").action(async (folder, opts) => {
|
|
@@ -1164,14 +1632,14 @@ function registerFilesCommand(program2) {
|
|
|
1164
1632
|
})));
|
|
1165
1633
|
});
|
|
1166
1634
|
files.command("upload").argument("<filepath>", "Path to file to upload").option("--folder <id>", "Folder ID to upload to").option("--project <id>", "Project ID to associate with").description("Upload a file").action(async (filepath, opts) => {
|
|
1167
|
-
const absPath =
|
|
1168
|
-
if (!
|
|
1635
|
+
const absPath = path7.resolve(filepath);
|
|
1636
|
+
if (!fs7.existsSync(absPath)) {
|
|
1169
1637
|
error(`File not found: ${absPath}`);
|
|
1170
1638
|
process.exit(1);
|
|
1171
1639
|
}
|
|
1172
|
-
const stat =
|
|
1173
|
-
const name =
|
|
1174
|
-
const ext =
|
|
1640
|
+
const stat = fs7.statSync(absPath);
|
|
1641
|
+
const name = path7.basename(absPath);
|
|
1642
|
+
const ext = path7.extname(absPath).toLowerCase();
|
|
1175
1643
|
const mimeTypes = {
|
|
1176
1644
|
".txt": "text/plain",
|
|
1177
1645
|
".md": "text/markdown",
|
|
@@ -1335,8 +1803,8 @@ function registerProjectsCommand(program2) {
|
|
|
1335
1803
|
}
|
|
1336
1804
|
|
|
1337
1805
|
// src/commands/config.ts
|
|
1338
|
-
import
|
|
1339
|
-
import
|
|
1806
|
+
import fs8 from "fs-extra";
|
|
1807
|
+
import path8 from "path";
|
|
1340
1808
|
import readline7 from "readline";
|
|
1341
1809
|
function prompt6(q) {
|
|
1342
1810
|
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -1352,10 +1820,10 @@ function registerConfigCommand(program2) {
|
|
|
1352
1820
|
const cwd = process.cwd();
|
|
1353
1821
|
const envFiles = [".env", ".env.local", ".env.production"];
|
|
1354
1822
|
for (const envFile of envFiles) {
|
|
1355
|
-
const envPath =
|
|
1356
|
-
if (
|
|
1823
|
+
const envPath = path8.join(cwd, envFile);
|
|
1824
|
+
if (fs8.existsSync(envPath)) {
|
|
1357
1825
|
console.log(` ${colors.cyan}${envFile}${colors.reset}`);
|
|
1358
|
-
const content =
|
|
1826
|
+
const content = fs8.readFileSync(envPath, "utf-8");
|
|
1359
1827
|
content.split("\n").forEach((line) => {
|
|
1360
1828
|
if (line.trim() && !line.startsWith("#")) {
|
|
1361
1829
|
const [key, ...rest] = line.split("=");
|
|
@@ -1367,25 +1835,25 @@ function registerConfigCommand(program2) {
|
|
|
1367
1835
|
console.log();
|
|
1368
1836
|
}
|
|
1369
1837
|
}
|
|
1370
|
-
const convexDir =
|
|
1371
|
-
if (
|
|
1838
|
+
const convexDir = path8.join(cwd, ".convex");
|
|
1839
|
+
if (fs8.existsSync(convexDir)) {
|
|
1372
1840
|
info("Convex: Configured");
|
|
1373
1841
|
} else {
|
|
1374
1842
|
info("Convex: Not configured (run `npx convex dev`)");
|
|
1375
1843
|
}
|
|
1376
|
-
const skillsDir =
|
|
1377
|
-
if (
|
|
1378
|
-
const skills =
|
|
1844
|
+
const skillsDir = path8.join(cwd, "skills");
|
|
1845
|
+
if (fs8.existsSync(skillsDir)) {
|
|
1846
|
+
const skills = fs8.readdirSync(skillsDir).filter((d) => fs8.statSync(path8.join(skillsDir, d)).isDirectory());
|
|
1379
1847
|
info(`Skills: ${skills.length} installed (${skills.join(", ")})`);
|
|
1380
1848
|
} else {
|
|
1381
1849
|
info("Skills: None installed");
|
|
1382
1850
|
}
|
|
1383
1851
|
});
|
|
1384
1852
|
config.command("set").argument("<key>", "Configuration key").argument("<value>", "Configuration value").option("--env <file>", "Environment file to update", ".env.local").description("Set a configuration value").action(async (key, value, opts) => {
|
|
1385
|
-
const envPath =
|
|
1853
|
+
const envPath = path8.join(process.cwd(), opts.env);
|
|
1386
1854
|
let content = "";
|
|
1387
|
-
if (
|
|
1388
|
-
content =
|
|
1855
|
+
if (fs8.existsSync(envPath)) {
|
|
1856
|
+
content = fs8.readFileSync(envPath, "utf-8");
|
|
1389
1857
|
}
|
|
1390
1858
|
const lines = content.split("\n");
|
|
1391
1859
|
const idx = lines.findIndex((l) => l.startsWith(`${key}=`));
|
|
@@ -1394,16 +1862,16 @@ function registerConfigCommand(program2) {
|
|
|
1394
1862
|
} else {
|
|
1395
1863
|
lines.push(`${key}=${value}`);
|
|
1396
1864
|
}
|
|
1397
|
-
|
|
1865
|
+
fs8.writeFileSync(envPath, lines.join("\n"));
|
|
1398
1866
|
success(`Set ${key} in ${opts.env}`);
|
|
1399
1867
|
});
|
|
1400
1868
|
config.command("get").argument("<key>", "Configuration key").description("Get a configuration value").action(async (key) => {
|
|
1401
1869
|
const cwd = process.cwd();
|
|
1402
1870
|
const envFiles = [".env.local", ".env", ".env.production"];
|
|
1403
1871
|
for (const envFile of envFiles) {
|
|
1404
|
-
const envPath =
|
|
1405
|
-
if (
|
|
1406
|
-
const content =
|
|
1872
|
+
const envPath = path8.join(cwd, envFile);
|
|
1873
|
+
if (fs8.existsSync(envPath)) {
|
|
1874
|
+
const content = fs8.readFileSync(envPath, "utf-8");
|
|
1407
1875
|
const match = content.match(new RegExp(`^${key}=(.+)$`, "m"));
|
|
1408
1876
|
if (match) {
|
|
1409
1877
|
console.log(match[1].trim());
|
|
@@ -1429,7 +1897,7 @@ function registerConfigCommand(program2) {
|
|
|
1429
1897
|
else if (provider === "openrouter") envContent.push(`OPENROUTER_API_KEY=${apiKey}`);
|
|
1430
1898
|
else if (provider === "anthropic") envContent.push(`ANTHROPIC_API_KEY=${apiKey}`);
|
|
1431
1899
|
else if (provider === "google") envContent.push(`GOOGLE_API_KEY=${apiKey}`);
|
|
1432
|
-
|
|
1900
|
+
fs8.writeFileSync(path8.join(process.cwd(), ".env.local"), envContent.join("\n") + "\n");
|
|
1433
1901
|
success("Configuration saved to .env.local");
|
|
1434
1902
|
info("Run `npx convex dev` to start the Convex backend.");
|
|
1435
1903
|
});
|
|
@@ -1451,9 +1919,9 @@ function registerConfigCommand(program2) {
|
|
|
1451
1919
|
error("API key is required.");
|
|
1452
1920
|
process.exit(1);
|
|
1453
1921
|
}
|
|
1454
|
-
const envPath =
|
|
1922
|
+
const envPath = path8.join(process.cwd(), ".env.local");
|
|
1455
1923
|
let content = "";
|
|
1456
|
-
if (
|
|
1924
|
+
if (fs8.existsSync(envPath)) content = fs8.readFileSync(envPath, "utf-8");
|
|
1457
1925
|
const lines = content.split("\n");
|
|
1458
1926
|
const idx = lines.findIndex((l) => l.startsWith(`${keyName}=`));
|
|
1459
1927
|
if (idx >= 0) lines[idx] = `${keyName}=${apiKey}`;
|
|
@@ -1461,7 +1929,7 @@ function registerConfigCommand(program2) {
|
|
|
1461
1929
|
const provIdx = lines.findIndex((l) => l.startsWith("LLM_PROVIDER="));
|
|
1462
1930
|
if (provIdx >= 0) lines[provIdx] = `LLM_PROVIDER=${provider}`;
|
|
1463
1931
|
else lines.push(`LLM_PROVIDER=${provider}`);
|
|
1464
|
-
|
|
1932
|
+
fs8.writeFileSync(envPath, lines.join("\n"));
|
|
1465
1933
|
success(`Provider "${provider}" configured.`);
|
|
1466
1934
|
});
|
|
1467
1935
|
}
|
|
@@ -1824,19 +2292,19 @@ function registerKeysCommand(program2) {
|
|
|
1824
2292
|
|
|
1825
2293
|
// src/commands/status.ts
|
|
1826
2294
|
import { spawn as spawn2 } from "child_process";
|
|
1827
|
-
import
|
|
1828
|
-
import
|
|
2295
|
+
import path9 from "path";
|
|
2296
|
+
import fs9 from "fs-extra";
|
|
1829
2297
|
import readline9 from "readline";
|
|
1830
2298
|
function registerStatusCommand(program2) {
|
|
1831
2299
|
program2.command("status").description("Show system health and connection status").action(async () => {
|
|
1832
2300
|
header("AgentForge Status");
|
|
1833
2301
|
const cwd = process.cwd();
|
|
1834
2302
|
const checks = {};
|
|
1835
|
-
checks["Project Root"] =
|
|
1836
|
-
checks["Convex Dir"] =
|
|
1837
|
-
checks["Skills Dir"] =
|
|
1838
|
-
checks["Dashboard Dir"] =
|
|
1839
|
-
checks["Env Config"] =
|
|
2303
|
+
checks["Project Root"] = fs9.existsSync(path9.join(cwd, "package.json")) ? "\u2714 Found" : "\u2716 Not found";
|
|
2304
|
+
checks["Convex Dir"] = fs9.existsSync(path9.join(cwd, "convex")) ? "\u2714 Found" : "\u2716 Not found";
|
|
2305
|
+
checks["Skills Dir"] = fs9.existsSync(path9.join(cwd, "skills")) ? "\u2714 Found" : "\u2716 Not configured";
|
|
2306
|
+
checks["Dashboard Dir"] = fs9.existsSync(path9.join(cwd, "dashboard")) ? "\u2714 Found" : "\u2716 Not found";
|
|
2307
|
+
checks["Env Config"] = fs9.existsSync(path9.join(cwd, ".env.local")) || fs9.existsSync(path9.join(cwd, ".env")) ? "\u2714 Found" : "\u2716 Not found";
|
|
1840
2308
|
try {
|
|
1841
2309
|
const client = await createClient();
|
|
1842
2310
|
const agents = await client.query("agents:list", {});
|
|
@@ -1847,9 +2315,9 @@ function registerStatusCommand(program2) {
|
|
|
1847
2315
|
const envFiles = [".env.local", ".env"];
|
|
1848
2316
|
let provider = "Not configured";
|
|
1849
2317
|
for (const envFile of envFiles) {
|
|
1850
|
-
const envPath =
|
|
1851
|
-
if (
|
|
1852
|
-
const content =
|
|
2318
|
+
const envPath = path9.join(cwd, envFile);
|
|
2319
|
+
if (fs9.existsSync(envPath)) {
|
|
2320
|
+
const content = fs9.readFileSync(envPath, "utf-8");
|
|
1853
2321
|
const match = content.match(/LLM_PROVIDER=(.+)/);
|
|
1854
2322
|
if (match) {
|
|
1855
2323
|
provider = match[1].trim();
|
|
@@ -1871,16 +2339,16 @@ function registerStatusCommand(program2) {
|
|
|
1871
2339
|
program2.command("dashboard").description("Launch the web dashboard").option("-p, --port <port>", "Port for the dashboard", "3000").option("--install", "Install dashboard dependencies before starting").action(async (opts) => {
|
|
1872
2340
|
const cwd = process.cwd();
|
|
1873
2341
|
const searchPaths = [
|
|
1874
|
-
|
|
2342
|
+
path9.join(cwd, "dashboard"),
|
|
1875
2343
|
// 1. Bundled in project (agentforge create)
|
|
1876
|
-
|
|
2344
|
+
path9.join(cwd, "packages", "web"),
|
|
1877
2345
|
// 2. Monorepo structure
|
|
1878
|
-
|
|
2346
|
+
path9.join(cwd, "node_modules", "@agentforge-ai", "web")
|
|
1879
2347
|
// 3. Installed as dependency
|
|
1880
2348
|
];
|
|
1881
2349
|
let dashDir = "";
|
|
1882
2350
|
for (const p of searchPaths) {
|
|
1883
|
-
if (
|
|
2351
|
+
if (fs9.existsSync(path9.join(p, "package.json"))) {
|
|
1884
2352
|
dashDir = p;
|
|
1885
2353
|
break;
|
|
1886
2354
|
}
|
|
@@ -1902,10 +2370,10 @@ function registerStatusCommand(program2) {
|
|
|
1902
2370
|
console.log();
|
|
1903
2371
|
return;
|
|
1904
2372
|
}
|
|
1905
|
-
const nodeModulesExists =
|
|
2373
|
+
const nodeModulesExists = fs9.existsSync(path9.join(dashDir, "node_modules"));
|
|
1906
2374
|
if (!nodeModulesExists || opts.install) {
|
|
1907
2375
|
header("AgentForge Dashboard \u2014 Installing Dependencies");
|
|
1908
|
-
info(`Installing in ${
|
|
2376
|
+
info(`Installing in ${path9.relative(cwd, dashDir) || "."}...`);
|
|
1909
2377
|
console.log();
|
|
1910
2378
|
const installChild = spawn2("pnpm", ["install"], {
|
|
1911
2379
|
cwd: dashDir,
|
|
@@ -1923,15 +2391,15 @@ function registerStatusCommand(program2) {
|
|
|
1923
2391
|
success("Dependencies installed.");
|
|
1924
2392
|
console.log();
|
|
1925
2393
|
}
|
|
1926
|
-
const envPath =
|
|
1927
|
-
if (
|
|
1928
|
-
const envContent =
|
|
2394
|
+
const envPath = path9.join(cwd, ".env.local");
|
|
2395
|
+
if (fs9.existsSync(envPath)) {
|
|
2396
|
+
const envContent = fs9.readFileSync(envPath, "utf-8");
|
|
1929
2397
|
const convexUrlMatch = envContent.match(/CONVEX_URL=(.+)/);
|
|
1930
2398
|
if (convexUrlMatch) {
|
|
1931
|
-
const dashEnvPath =
|
|
2399
|
+
const dashEnvPath = path9.join(dashDir, ".env.local");
|
|
1932
2400
|
const dashEnvContent = `VITE_CONVEX_URL=${convexUrlMatch[1].trim()}
|
|
1933
2401
|
`;
|
|
1934
|
-
|
|
2402
|
+
fs9.writeFileSync(dashEnvPath, dashEnvContent);
|
|
1935
2403
|
}
|
|
1936
2404
|
}
|
|
1937
2405
|
header("AgentForge Dashboard");
|
|
@@ -2012,6 +2480,144 @@ function registerStatusCommand(program2) {
|
|
|
2012
2480
|
});
|
|
2013
2481
|
}
|
|
2014
2482
|
|
|
2483
|
+
// src/commands/login.ts
|
|
2484
|
+
import chalk2 from "chalk";
|
|
2485
|
+
import ora2 from "ora";
|
|
2486
|
+
import prompts from "prompts";
|
|
2487
|
+
function registerLoginCommand(program2) {
|
|
2488
|
+
program2.command("login").description("Authenticate with AgentForge Cloud").option("--api-key <key>", "API key for authentication (skips browser flow)").option("--cloud-url <url>", "Custom AgentForge Cloud URL").action(async (options) => {
|
|
2489
|
+
header("AgentForge Cloud Login");
|
|
2490
|
+
const existingCreds = await readCredentials();
|
|
2491
|
+
if (existingCreds?.apiKey) {
|
|
2492
|
+
const { overwrite } = await prompts({
|
|
2493
|
+
type: "confirm",
|
|
2494
|
+
name: "overwrite",
|
|
2495
|
+
message: "You are already logged in. Overwrite existing credentials?",
|
|
2496
|
+
initial: false
|
|
2497
|
+
});
|
|
2498
|
+
if (!overwrite) {
|
|
2499
|
+
info("Login cancelled.");
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
let apiKey;
|
|
2504
|
+
let cloudUrl;
|
|
2505
|
+
if (options.apiKey) {
|
|
2506
|
+
apiKey = options.apiKey;
|
|
2507
|
+
cloudUrl = options.cloudUrl || process.env.AGENTFORGE_CLOUD_URL || "https://cloud.agentforge.ai";
|
|
2508
|
+
} else {
|
|
2509
|
+
const { method } = await prompts({
|
|
2510
|
+
type: "select",
|
|
2511
|
+
name: "method",
|
|
2512
|
+
message: "How would you like to authenticate?",
|
|
2513
|
+
choices: [
|
|
2514
|
+
{ title: "Enter API key manually", value: "api-key" },
|
|
2515
|
+
{ title: "Browser (OAuth - coming soon)", value: "browser", disabled: true }
|
|
2516
|
+
]
|
|
2517
|
+
});
|
|
2518
|
+
if (method === "api-key") {
|
|
2519
|
+
const response = await prompts({
|
|
2520
|
+
type: "password",
|
|
2521
|
+
name: "key",
|
|
2522
|
+
message: "Enter your AgentForge API key:",
|
|
2523
|
+
validate: (value) => value.length > 0 ? true : "API key is required"
|
|
2524
|
+
});
|
|
2525
|
+
apiKey = response.key;
|
|
2526
|
+
} else {
|
|
2527
|
+
error("Browser authentication not yet implemented. Use --api-key flag.");
|
|
2528
|
+
process.exit(1);
|
|
2529
|
+
}
|
|
2530
|
+
cloudUrl = options.cloudUrl || process.env.AGENTFORGE_CLOUD_URL || "https://cloud.agentforge.ai";
|
|
2531
|
+
}
|
|
2532
|
+
const spinner = ora2("Authenticating with AgentForge Cloud...").start();
|
|
2533
|
+
try {
|
|
2534
|
+
const client = new CloudClient(cloudUrl, apiKey);
|
|
2535
|
+
const user = await client.authenticate();
|
|
2536
|
+
await writeCredentials({
|
|
2537
|
+
apiKey,
|
|
2538
|
+
cloudUrl,
|
|
2539
|
+
userEmail: user.email,
|
|
2540
|
+
userName: user.name
|
|
2541
|
+
});
|
|
2542
|
+
spinner.succeed("Authentication successful!");
|
|
2543
|
+
success(`Logged in as ${chalk2.bold(user.email)}`);
|
|
2544
|
+
info(`Cloud URL: ${cloudUrl}`);
|
|
2545
|
+
info(`Credentials stored at: ${getCredentialsPath()}`);
|
|
2546
|
+
} catch (err) {
|
|
2547
|
+
spinner.fail("Authentication failed");
|
|
2548
|
+
if (err instanceof CloudClientError) {
|
|
2549
|
+
error(err.message);
|
|
2550
|
+
if (err.code === "NETWORK_ERROR") {
|
|
2551
|
+
info("Check your internet connection and try again.");
|
|
2552
|
+
} else if (err.status === 401) {
|
|
2553
|
+
info("Your API key appears to be invalid. Please check and try again.");
|
|
2554
|
+
}
|
|
2555
|
+
} else {
|
|
2556
|
+
error(`Unexpected error: ${err.message || err}`);
|
|
2557
|
+
}
|
|
2558
|
+
process.exit(1);
|
|
2559
|
+
}
|
|
2560
|
+
});
|
|
2561
|
+
program2.command("logout").description("Clear AgentForge Cloud credentials").action(async () => {
|
|
2562
|
+
header("AgentForge Cloud Logout");
|
|
2563
|
+
const wasLoggedIn = await isAuthenticated();
|
|
2564
|
+
if (!wasLoggedIn) {
|
|
2565
|
+
info("You are not currently logged in.");
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
const { confirm } = await prompts({
|
|
2569
|
+
type: "confirm",
|
|
2570
|
+
name: "confirm",
|
|
2571
|
+
message: "Are you sure you want to log out?",
|
|
2572
|
+
initial: true
|
|
2573
|
+
});
|
|
2574
|
+
if (!confirm) {
|
|
2575
|
+
info("Logout cancelled.");
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2578
|
+
const deleted = await deleteCredentials();
|
|
2579
|
+
if (deleted) {
|
|
2580
|
+
success("Logged out successfully.");
|
|
2581
|
+
info("Your credentials have been removed.");
|
|
2582
|
+
} else {
|
|
2583
|
+
error("Failed to remove credentials.");
|
|
2584
|
+
process.exit(1);
|
|
2585
|
+
}
|
|
2586
|
+
});
|
|
2587
|
+
program2.command("whoami").description("Show current user information").action(async () => {
|
|
2588
|
+
header("Current User");
|
|
2589
|
+
const creds = await readCredentials();
|
|
2590
|
+
const cloudUrl = await getCloudUrl();
|
|
2591
|
+
if (!creds?.apiKey) {
|
|
2592
|
+
info("You are not logged in.");
|
|
2593
|
+
info('Run "agentforge login" to authenticate.');
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
details({
|
|
2597
|
+
"Cloud URL": cloudUrl,
|
|
2598
|
+
"Email": creds.userEmail || "Unknown",
|
|
2599
|
+
"Name": creds.userName || "Unknown",
|
|
2600
|
+
"Credentials File": getCredentialsPath()
|
|
2601
|
+
});
|
|
2602
|
+
const spinner = ora2("Validating session...").start();
|
|
2603
|
+
try {
|
|
2604
|
+
const client = new CloudClient(creds.cloudUrl, creds.apiKey);
|
|
2605
|
+
const user = await client.authenticate();
|
|
2606
|
+
spinner.succeed("Session is valid");
|
|
2607
|
+
success(`Authenticated as ${chalk2.bold(user.email)}`);
|
|
2608
|
+
} catch (err) {
|
|
2609
|
+
spinner.fail("Session validation failed");
|
|
2610
|
+
if (err instanceof CloudClientError && err.status === 401) {
|
|
2611
|
+
error("Your session has expired or the API key is invalid.");
|
|
2612
|
+
info('Run "agentforge login" to re-authenticate.');
|
|
2613
|
+
} else {
|
|
2614
|
+
error(`Error: ${err.message || err}`);
|
|
2615
|
+
info("Your credentials are stored but the server could not be reached.");
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2015
2621
|
// src/index.ts
|
|
2016
2622
|
import { readFileSync } from "fs";
|
|
2017
2623
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -2027,9 +2633,10 @@ program.command("create").argument("<project-name>", "Name of the project to cre
|
|
|
2027
2633
|
program.command("run").description("Start the local development environment").option("-p, --port <port>", "Port for the dev server", "3000").action(async (options) => {
|
|
2028
2634
|
await runProject(options);
|
|
2029
2635
|
});
|
|
2030
|
-
program.command("deploy").description("Deploy the
|
|
2636
|
+
program.command("deploy").description("Deploy the project to production").option("--env <path>", "Path to environment file", ".env.production").option("--dry-run", "Preview deployment without executing", false).option("--rollback", "Rollback to previous deployment", false).option("--force", "Skip confirmation prompts", false).option("--provider <provider>", "Deployment provider (convex or cloud)", "convex").option("--project <projectId>", "Project ID for cloud deployments").option("--version <tag>", "Version tag for the deployment").action(async (options) => {
|
|
2031
2637
|
await deployProject(options);
|
|
2032
2638
|
});
|
|
2639
|
+
registerLoginCommand(program);
|
|
2033
2640
|
registerAgentsCommand(program);
|
|
2034
2641
|
registerChatCommand(program);
|
|
2035
2642
|
registerSessionsCommand(program);
|