@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.
Files changed (43) hide show
  1. package/dist/default/agentforge.config.ts +78 -0
  2. package/dist/default/convex/agents.ts +16 -16
  3. package/dist/default/convex/apiKeys.ts +7 -7
  4. package/dist/default/convex/cronJobs.ts +12 -12
  5. package/dist/default/convex/files.ts +7 -7
  6. package/dist/default/convex/folders.ts +11 -11
  7. package/dist/default/convex/heartbeat.ts +20 -20
  8. package/dist/default/convex/mastraIntegration.ts +3 -2
  9. package/dist/default/convex/mcpConnections.ts +5 -5
  10. package/dist/default/convex/messages.ts +4 -4
  11. package/dist/default/convex/projects.ts +10 -10
  12. package/dist/default/convex/schema.ts +150 -83
  13. package/dist/default/convex/sessions.ts +12 -12
  14. package/dist/default/convex/settings.ts +3 -3
  15. package/dist/default/convex/skills.ts +8 -8
  16. package/dist/default/convex/threads.ts +9 -9
  17. package/dist/default/convex/usage.ts +16 -16
  18. package/dist/default/convex/vault.ts +50 -33
  19. package/dist/default/package.json +1 -0
  20. package/dist/default/tsconfig.json +1 -0
  21. package/dist/index.js +691 -84
  22. package/dist/index.js.map +1 -1
  23. package/package.json +2 -1
  24. package/templates/default/agentforge.config.ts +78 -0
  25. package/templates/default/convex/agents.ts +16 -16
  26. package/templates/default/convex/apiKeys.ts +7 -7
  27. package/templates/default/convex/cronJobs.ts +12 -12
  28. package/templates/default/convex/files.ts +7 -7
  29. package/templates/default/convex/folders.ts +11 -11
  30. package/templates/default/convex/heartbeat.ts +20 -20
  31. package/templates/default/convex/mastraIntegration.ts +3 -2
  32. package/templates/default/convex/mcpConnections.ts +5 -5
  33. package/templates/default/convex/messages.ts +4 -4
  34. package/templates/default/convex/projects.ts +10 -10
  35. package/templates/default/convex/schema.ts +150 -83
  36. package/templates/default/convex/sessions.ts +12 -12
  37. package/templates/default/convex/settings.ts +3 -3
  38. package/templates/default/convex/skills.ts +8 -8
  39. package/templates/default/convex/threads.ts +9 -9
  40. package/templates/default/convex/usage.ts +16 -16
  41. package/templates/default/convex/vault.ts +50 -33
  42. package/templates/default/package.json +1 -0
  43. 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 path3 from "path";
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 = fs3.readFileSync(filePath, "utf-8");
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 deployProject(options) {
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 = path3.join(projectDir, "package.json");
209
- if (!await fs3.pathExists(pkgPath)) {
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 = path3.join(projectDir, "convex");
216
- if (!await fs3.pathExists(convexDir)) {
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 = path3.resolve(projectDir, options.env);
237
- const envExists = await fs3.pathExists(envPath);
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 fs4 from "fs-extra";
305
- import path4 from "path";
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 = path4.join(cwd, envFile);
323
- if (fs4.existsSync(envPath)) {
324
- const content = fs4.readFileSync(envPath, "utf-8");
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 = path4.join(cwd, ".convex", "deployment.json");
332
- if (fs4.existsSync(convexEnv)) {
799
+ const convexEnv = path5.join(cwd, ".convex", "deployment.json");
800
+ if (fs5.existsSync(convexEnv)) {
333
801
  try {
334
- const data = JSON.parse(fs4.readFileSync(convexEnv, "utf-8"));
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 fs5 from "fs-extra";
795
- import path5 from "path";
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 = path5.join(process.cwd(), "skills");
816
- if (fs5.existsSync(skillsDir)) {
817
- const dirs = fs5.readdirSync(skillsDir).filter((d) => fs5.statSync(path5.join(skillsDir, d)).isDirectory());
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 = path5.join(skillsDir, s, "config.json");
1297
+ const configPath = path6.join(skillsDir, s, "config.json");
830
1298
  let desc = "";
831
- if (fs5.existsSync(configPath)) {
1299
+ if (fs6.existsSync(configPath)) {
832
1300
  try {
833
- desc = JSON.parse(fs5.readFileSync(configPath, "utf-8")).description || "";
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 = path5.join(process.cwd(), "skills", name);
861
- if (fs5.existsSync(skillDir)) {
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
- fs5.mkdirSync(skillDir, { recursive: true });
866
- fs5.writeFileSync(path5.join(skillDir, "config.json"), JSON.stringify({
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
- fs5.writeFileSync(path5.join(skillDir, "index.ts"), `import { z } from 'zod';
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
- fs5.writeFileSync(path5.join(skillDir, "SKILL.md"), `# ${name}
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 = path5.join(process.cwd(), "skills", name);
933
- if (fs5.existsSync(skillDir)) {
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
- fs5.removeSync(skillDir);
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 fs6 from "fs-extra";
1140
- import path6 from "path";
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 = path6.resolve(filepath);
1168
- if (!fs6.existsSync(absPath)) {
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 = fs6.statSync(absPath);
1173
- const name = path6.basename(absPath);
1174
- const ext = path6.extname(absPath).toLowerCase();
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 fs7 from "fs-extra";
1339
- import path7 from "path";
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 = path7.join(cwd, envFile);
1356
- if (fs7.existsSync(envPath)) {
1823
+ const envPath = path8.join(cwd, envFile);
1824
+ if (fs8.existsSync(envPath)) {
1357
1825
  console.log(` ${colors.cyan}${envFile}${colors.reset}`);
1358
- const content = fs7.readFileSync(envPath, "utf-8");
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 = path7.join(cwd, ".convex");
1371
- if (fs7.existsSync(convexDir)) {
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 = path7.join(cwd, "skills");
1377
- if (fs7.existsSync(skillsDir)) {
1378
- const skills = fs7.readdirSync(skillsDir).filter((d) => fs7.statSync(path7.join(skillsDir, d)).isDirectory());
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 = path7.join(process.cwd(), opts.env);
1853
+ const envPath = path8.join(process.cwd(), opts.env);
1386
1854
  let content = "";
1387
- if (fs7.existsSync(envPath)) {
1388
- content = fs7.readFileSync(envPath, "utf-8");
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
- fs7.writeFileSync(envPath, lines.join("\n"));
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 = path7.join(cwd, envFile);
1405
- if (fs7.existsSync(envPath)) {
1406
- const content = fs7.readFileSync(envPath, "utf-8");
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
- fs7.writeFileSync(path7.join(process.cwd(), ".env.local"), envContent.join("\n") + "\n");
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 = path7.join(process.cwd(), ".env.local");
1922
+ const envPath = path8.join(process.cwd(), ".env.local");
1455
1923
  let content = "";
1456
- if (fs7.existsSync(envPath)) content = fs7.readFileSync(envPath, "utf-8");
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
- fs7.writeFileSync(envPath, lines.join("\n"));
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 path8 from "path";
1828
- import fs8 from "fs-extra";
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"] = fs8.existsSync(path8.join(cwd, "package.json")) ? "\u2714 Found" : "\u2716 Not found";
1836
- checks["Convex Dir"] = fs8.existsSync(path8.join(cwd, "convex")) ? "\u2714 Found" : "\u2716 Not found";
1837
- checks["Skills Dir"] = fs8.existsSync(path8.join(cwd, "skills")) ? "\u2714 Found" : "\u2716 Not configured";
1838
- checks["Dashboard Dir"] = fs8.existsSync(path8.join(cwd, "dashboard")) ? "\u2714 Found" : "\u2716 Not found";
1839
- checks["Env Config"] = fs8.existsSync(path8.join(cwd, ".env.local")) || fs8.existsSync(path8.join(cwd, ".env")) ? "\u2714 Found" : "\u2716 Not found";
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 = path8.join(cwd, envFile);
1851
- if (fs8.existsSync(envPath)) {
1852
- const content = fs8.readFileSync(envPath, "utf-8");
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
- path8.join(cwd, "dashboard"),
2342
+ path9.join(cwd, "dashboard"),
1875
2343
  // 1. Bundled in project (agentforge create)
1876
- path8.join(cwd, "packages", "web"),
2344
+ path9.join(cwd, "packages", "web"),
1877
2345
  // 2. Monorepo structure
1878
- path8.join(cwd, "node_modules", "@agentforge-ai", "web")
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 (fs8.existsSync(path8.join(p, "package.json"))) {
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 = fs8.existsSync(path8.join(dashDir, "node_modules"));
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 ${path8.relative(cwd, dashDir) || "."}...`);
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 = path8.join(cwd, ".env.local");
1927
- if (fs8.existsSync(envPath)) {
1928
- const envContent = fs8.readFileSync(envPath, "utf-8");
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 = path8.join(dashDir, ".env.local");
2399
+ const dashEnvPath = path9.join(dashDir, ".env.local");
1932
2400
  const dashEnvContent = `VITE_CONVEX_URL=${convexUrlMatch[1].trim()}
1933
2401
  `;
1934
- fs8.writeFileSync(dashEnvPath, dashEnvContent);
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 Convex backend 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).action(async (options) => {
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);