@struere/cli 0.1.1 → 0.2.0

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 (2) hide show
  1. package/dist/index.js +1148 -629
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -4,90 +4,19 @@
4
4
  // src/index.ts
5
5
  import { program } from "commander";
6
6
 
7
- // src/commands/dev.ts
8
- import { Command } from "commander";
9
- import chalk from "chalk";
10
- import ora from "ora";
11
- import chokidar from "chokidar";
12
- import { join as join4 } from "path";
13
-
14
- // src/utils/config.ts
15
- import { join } from "path";
16
- var defaultConfig = {
17
- port: 3000,
18
- host: "localhost",
19
- cors: {
20
- origins: ["http://localhost:3000"],
21
- credentials: true
22
- },
23
- logging: {
24
- level: "info",
25
- format: "pretty"
26
- },
27
- auth: {
28
- type: "none"
29
- }
30
- };
31
- async function loadConfig(cwd) {
32
- const configPath = join(cwd, "af.config.ts");
33
- try {
34
- const module = await import(configPath);
35
- const config = module.default || module;
36
- return {
37
- ...defaultConfig,
38
- ...config,
39
- cors: {
40
- ...defaultConfig.cors,
41
- ...config.cors
42
- },
43
- logging: {
44
- ...defaultConfig.logging,
45
- ...config.logging
46
- },
47
- auth: {
48
- ...defaultConfig.auth,
49
- ...config.auth
50
- }
51
- };
52
- } catch {
53
- return defaultConfig;
54
- }
55
- }
56
-
57
- // src/utils/agent.ts
58
- import { join as join2 } from "path";
59
- async function loadAgent(cwd) {
60
- const agentPath = join2(cwd, "src/agent.ts");
61
- try {
62
- const module = await import(agentPath);
63
- const agent = module.default || module;
64
- if (!agent.name) {
65
- throw new Error("Agent must have a name");
66
- }
67
- if (!agent.version) {
68
- throw new Error("Agent must have a version");
69
- }
70
- if (!agent.systemPrompt) {
71
- throw new Error("Agent must have a systemPrompt");
72
- }
73
- return agent;
74
- } catch (error) {
75
- if (error instanceof Error && error.message.includes("Cannot find module")) {
76
- throw new Error(`Agent not found at ${agentPath}`);
77
- }
78
- throw error;
79
- }
80
- }
81
-
82
- // src/commands/dev.ts
83
- import { AgentExecutor } from "@struere/runtime";
7
+ // src/commands/init.ts
8
+ import { Command as Command2 } from "commander";
9
+ import chalk2 from "chalk";
10
+ import ora2 from "ora";
11
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
12
+ import { join as join4, basename } from "path";
84
13
 
85
14
  // src/utils/credentials.ts
86
15
  import { homedir } from "os";
87
- import { join as join3 } from "path";
16
+ import { join } from "path";
88
17
  import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
89
- var CONFIG_DIR = join3(homedir(), ".struere");
90
- var CREDENTIALS_FILE = join3(CONFIG_DIR, "credentials.json");
18
+ var CONFIG_DIR = join(homedir(), ".struere");
19
+ var CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
91
20
  function ensureConfigDir() {
92
21
  if (!existsSync(CONFIG_DIR)) {
93
22
  mkdirSync(CONFIG_DIR, { recursive: true });
@@ -129,9 +58,11 @@ function getToken() {
129
58
  const credentials = loadCredentials();
130
59
  return credentials?.token || null;
131
60
  }
132
- function isLoggedIn() {
133
- return loadCredentials() !== null;
134
- }
61
+
62
+ // src/commands/login.ts
63
+ import { Command } from "commander";
64
+ import chalk from "chalk";
65
+ import ora from "ora";
135
66
 
136
67
  // src/utils/api.ts
137
68
  var DEFAULT_API_URL = "https://api.struere.dev";
@@ -192,52 +123,973 @@ class ApiClient {
192
123
  async listAgents() {
193
124
  return this.request("/v1/agents");
194
125
  }
195
- async createAgent(data) {
196
- return this.request("/v1/agents", {
197
- method: "POST",
198
- body: JSON.stringify(data)
199
- });
126
+ async createAgent(data) {
127
+ return this.request("/v1/agents", {
128
+ method: "POST",
129
+ body: JSON.stringify(data)
130
+ });
131
+ }
132
+ async getAgent(agentId) {
133
+ return this.request(`/v1/agents/${agentId}`);
134
+ }
135
+ async deployAgent(agentId, data) {
136
+ return this.request(`/v1/deployments/agents/${agentId}/deploy`, {
137
+ method: "POST",
138
+ body: JSON.stringify(data)
139
+ });
140
+ }
141
+ async createApiKey(data) {
142
+ return this.request("/v1/api-keys", {
143
+ method: "POST",
144
+ body: JSON.stringify(data)
145
+ });
146
+ }
147
+ async listApiKeys() {
148
+ return this.request("/v1/api-keys");
149
+ }
150
+ async getUsage(period = "day") {
151
+ return this.request(`/v1/usage?period=${period}`);
152
+ }
153
+ }
154
+
155
+ class ApiError extends Error {
156
+ code;
157
+ status;
158
+ constructor(message, code, status) {
159
+ super(message);
160
+ this.code = code;
161
+ this.status = status;
162
+ this.name = "ApiError";
163
+ }
164
+ }
165
+
166
+ // src/commands/login.ts
167
+ var AUTH_CALLBACK_PORT = 9876;
168
+ var loginCommand = new Command("login").description("Log in to Struere").option("--headless", "Login with email/password (no browser)").action(async (options) => {
169
+ const spinner = ora();
170
+ console.log();
171
+ console.log(chalk.bold("Struere Login"));
172
+ console.log();
173
+ const existing = loadCredentials();
174
+ if (existing) {
175
+ console.log(chalk.yellow("Already logged in as"), chalk.cyan(existing.user.email));
176
+ console.log(chalk.gray("Run"), chalk.cyan("struere logout"), chalk.gray("to log out first"));
177
+ console.log();
178
+ return;
179
+ }
180
+ if (options.headless) {
181
+ await headlessLogin(spinner);
182
+ } else {
183
+ await browserLogin(spinner);
184
+ }
185
+ });
186
+ async function performLogin(options = {}) {
187
+ const spinner = ora();
188
+ console.log();
189
+ console.log(chalk.bold("Struere Login"));
190
+ console.log();
191
+ if (options.headless) {
192
+ return headlessLoginInternal(spinner);
193
+ } else {
194
+ return browserLoginInternal(spinner);
195
+ }
196
+ }
197
+ async function browserLogin(spinner) {
198
+ const result = await browserLoginInternal(spinner);
199
+ if (result) {
200
+ printNextSteps();
201
+ }
202
+ }
203
+ async function browserLoginInternal(spinner) {
204
+ spinner.start("Starting authentication server");
205
+ const authPromise = new Promise((resolve, reject) => {
206
+ const server = Bun.serve({
207
+ port: AUTH_CALLBACK_PORT,
208
+ async fetch(req) {
209
+ const url = new URL(req.url);
210
+ if (url.pathname === "/callback") {
211
+ const token = url.searchParams.get("token");
212
+ const sessionId = url.searchParams.get("session_id");
213
+ if (token && sessionId) {
214
+ resolve({ token, sessionId });
215
+ return new Response(getSuccessHtml(), {
216
+ headers: { "Content-Type": "text/html" }
217
+ });
218
+ }
219
+ return new Response(getErrorHtml("Missing token"), {
220
+ status: 400,
221
+ headers: { "Content-Type": "text/html" }
222
+ });
223
+ }
224
+ if (url.pathname === "/") {
225
+ const authUrl = getAuthUrl();
226
+ return Response.redirect(authUrl, 302);
227
+ }
228
+ return new Response("Not Found", { status: 404 });
229
+ }
230
+ });
231
+ setTimeout(() => {
232
+ server.stop();
233
+ reject(new Error("Authentication timed out"));
234
+ }, 5 * 60 * 1000);
235
+ });
236
+ spinner.succeed("Authentication server started");
237
+ const loginUrl = `http://localhost:${AUTH_CALLBACK_PORT}`;
238
+ console.log();
239
+ console.log(chalk.gray("Opening browser to log in..."));
240
+ console.log(chalk.gray("If browser does not open, visit:"), chalk.cyan(loginUrl));
241
+ console.log();
242
+ if (process.platform === "darwin") {
243
+ Bun.spawn(["open", loginUrl]);
244
+ } else if (process.platform === "linux") {
245
+ Bun.spawn(["xdg-open", loginUrl]);
246
+ } else if (process.platform === "win32") {
247
+ Bun.spawn(["cmd", "/c", "start", loginUrl]);
248
+ }
249
+ spinner.start("Waiting for authentication");
250
+ try {
251
+ const { token } = await authPromise;
252
+ spinner.text = "Fetching user info";
253
+ const api = new ApiClient;
254
+ const { user, organization } = await api.getMe();
255
+ const credentials = {
256
+ token,
257
+ user: {
258
+ id: user.id,
259
+ email: user.email,
260
+ name: user.name,
261
+ organizationId: user.organizationId
262
+ },
263
+ organization: {
264
+ id: organization.id,
265
+ name: organization.name,
266
+ slug: organization.slug
267
+ },
268
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
269
+ };
270
+ saveCredentials(credentials);
271
+ spinner.succeed("Logged in successfully");
272
+ console.log();
273
+ console.log(chalk.green("Welcome,"), chalk.cyan(user.name));
274
+ console.log(chalk.gray("Organization:"), organization.name);
275
+ console.log();
276
+ return credentials;
277
+ } catch (error) {
278
+ spinner.fail("Login failed");
279
+ console.log();
280
+ console.log(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
281
+ console.log();
282
+ console.log(chalk.gray("Try"), chalk.cyan("struere login --headless"), chalk.gray("for email/password login"));
283
+ console.log();
284
+ return null;
285
+ }
286
+ }
287
+ async function headlessLogin(spinner) {
288
+ const result = await headlessLoginInternal(spinner);
289
+ if (result) {
290
+ printNextSteps();
291
+ }
292
+ }
293
+ async function headlessLoginInternal(spinner) {
294
+ const email = await prompt("Email: ");
295
+ const password = await prompt("Password: ", true);
296
+ if (!email || !password) {
297
+ console.log(chalk.red("Email and password are required"));
298
+ return null;
299
+ }
300
+ spinner.start("Logging in");
301
+ try {
302
+ const api = new ApiClient;
303
+ const { token, user } = await api.login(email, password);
304
+ const { organization } = await api.getMe();
305
+ const credentials = {
306
+ token,
307
+ user: {
308
+ id: user.id,
309
+ email: user.email,
310
+ name: user.name,
311
+ organizationId: user.organizationId
312
+ },
313
+ organization: {
314
+ id: organization.id,
315
+ name: organization.name,
316
+ slug: organization.slug
317
+ },
318
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
319
+ };
320
+ saveCredentials(credentials);
321
+ spinner.succeed("Logged in successfully");
322
+ console.log();
323
+ console.log(chalk.green("Welcome,"), chalk.cyan(user.name));
324
+ console.log(chalk.gray("Organization:"), organization.name);
325
+ console.log();
326
+ return credentials;
327
+ } catch (error) {
328
+ spinner.fail("Login failed");
329
+ console.log();
330
+ if (error instanceof ApiError) {
331
+ console.log(chalk.red("Error:"), error.message);
332
+ } else {
333
+ console.log(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
334
+ }
335
+ console.log();
336
+ return null;
337
+ }
338
+ }
339
+ function printNextSteps() {
340
+ console.log(chalk.gray("You can now use:"));
341
+ console.log(chalk.gray(" \u2022"), chalk.cyan("struere dev"), chalk.gray("- Start cloud-connected dev server"));
342
+ console.log(chalk.gray(" \u2022"), chalk.cyan("struere deploy"), chalk.gray("- Deploy your agent"));
343
+ console.log(chalk.gray(" \u2022"), chalk.cyan("struere logs"), chalk.gray("- View agent logs"));
344
+ console.log();
345
+ }
346
+ function getAuthUrl() {
347
+ const baseUrl = process.env.STRUERE_AUTH_URL || "https://struere.dev";
348
+ const callbackUrl = `http://localhost:${AUTH_CALLBACK_PORT}/callback`;
349
+ return `${baseUrl}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
350
+ }
351
+ function getSuccessHtml() {
352
+ return `<!DOCTYPE html>
353
+ <html>
354
+ <head>
355
+ <title>Login Successful</title>
356
+ <style>
357
+ body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0a0a0a; color: #fafafa; }
358
+ .container { text-align: center; }
359
+ h1 { color: #22c55e; }
360
+ p { color: #888; }
361
+ </style>
362
+ </head>
363
+ <body>
364
+ <div class="container">
365
+ <h1>Login Successful</h1>
366
+ <p>You can close this window and return to the terminal.</p>
367
+ </div>
368
+ <script>setTimeout(() => window.close(), 3000)</script>
369
+ </body>
370
+ </html>`;
371
+ }
372
+ function getErrorHtml(message) {
373
+ return `<!DOCTYPE html>
374
+ <html>
375
+ <head>
376
+ <title>Login Failed</title>
377
+ <style>
378
+ body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0a0a0a; color: #fafafa; }
379
+ .container { text-align: center; }
380
+ h1 { color: #ef4444; }
381
+ p { color: #888; }
382
+ </style>
383
+ </head>
384
+ <body>
385
+ <div class="container">
386
+ <h1>Login Failed</h1>
387
+ <p>${message}</p>
388
+ </div>
389
+ </body>
390
+ </html>`;
391
+ }
392
+ async function prompt(message, hidden = false) {
393
+ process.stdout.write(chalk.gray(message));
394
+ return new Promise((resolve) => {
395
+ let input = "";
396
+ if (hidden) {
397
+ process.stdin.setRawMode(true);
398
+ }
399
+ process.stdin.resume();
400
+ process.stdin.setEncoding("utf8");
401
+ const onData = (char) => {
402
+ if (char === `
403
+ ` || char === "\r") {
404
+ process.stdin.removeListener("data", onData);
405
+ process.stdin.pause();
406
+ if (hidden) {
407
+ process.stdin.setRawMode(false);
408
+ console.log();
409
+ }
410
+ resolve(input);
411
+ } else if (char === "\x03") {
412
+ process.exit();
413
+ } else if (char === "\x7F") {
414
+ input = input.slice(0, -1);
415
+ } else {
416
+ input += char;
417
+ if (!hidden) {
418
+ process.stdout.write(char);
419
+ }
420
+ }
421
+ };
422
+ process.stdin.on("data", onData);
423
+ });
424
+ }
425
+
426
+ // src/utils/project.ts
427
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
428
+ import { join as join2 } from "path";
429
+ var PROJECT_FILE = "struere.json";
430
+ function loadProject(cwd) {
431
+ const projectPath = join2(cwd, PROJECT_FILE);
432
+ if (!existsSync2(projectPath)) {
433
+ return null;
434
+ }
435
+ try {
436
+ const data = readFileSync2(projectPath, "utf-8");
437
+ return JSON.parse(data);
438
+ } catch {
439
+ return null;
440
+ }
441
+ }
442
+ function saveProject(cwd, project) {
443
+ const projectPath = join2(cwd, PROJECT_FILE);
444
+ writeFileSync2(projectPath, JSON.stringify(project, null, 2) + `
445
+ `);
446
+ }
447
+ function hasProject(cwd) {
448
+ return existsSync2(join2(cwd, PROJECT_FILE));
449
+ }
450
+
451
+ // src/utils/scaffold.ts
452
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync3, appendFileSync } from "fs";
453
+ import { join as join3, dirname } from "path";
454
+
455
+ // src/templates/index.ts
456
+ function getPackageJson(name) {
457
+ return JSON.stringify({
458
+ name,
459
+ version: "0.1.0",
460
+ type: "module",
461
+ scripts: {
462
+ dev: "struere dev",
463
+ build: "struere build",
464
+ test: "struere test",
465
+ deploy: "struere deploy"
466
+ },
467
+ dependencies: {
468
+ "@struere/core": "^0.1.0",
469
+ "@struere/runtime": "^0.1.0"
470
+ },
471
+ devDependencies: {
472
+ "@struere/cli": "^0.1.0",
473
+ "bun-types": "^1.0.0",
474
+ typescript: "^5.3.0"
475
+ }
476
+ }, null, 2);
477
+ }
478
+ function getTsConfig() {
479
+ return JSON.stringify({
480
+ compilerOptions: {
481
+ target: "ES2022",
482
+ module: "ESNext",
483
+ moduleResolution: "bundler",
484
+ lib: ["ES2022"],
485
+ strict: true,
486
+ esModuleInterop: true,
487
+ skipLibCheck: true,
488
+ forceConsistentCasingInFileNames: true,
489
+ outDir: "dist",
490
+ rootDir: "src",
491
+ types: ["bun-types"]
492
+ },
493
+ include: ["src/**/*"],
494
+ exclude: ["node_modules", "dist"]
495
+ }, null, 2);
496
+ }
497
+ function getStruereConfig() {
498
+ return `import { defineConfig } from '@struere/core'
499
+
500
+ export default defineConfig({
501
+ port: 3000,
502
+ host: 'localhost',
503
+ cors: {
504
+ origins: ['http://localhost:3000'],
505
+ credentials: true,
506
+ },
507
+ logging: {
508
+ level: 'info',
509
+ format: 'pretty',
510
+ },
511
+ })
512
+ `;
513
+ }
514
+ function getAgentTs(name) {
515
+ const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
516
+ return `import { defineAgent } from '@struere/core'
517
+ import { context } from './context'
518
+ import { tools } from './tools'
519
+
520
+ export default defineAgent({
521
+ name: '${name}',
522
+ version: '0.1.0',
523
+ description: '${displayName} Agent',
524
+ model: {
525
+ provider: 'anthropic',
526
+ name: 'claude-sonnet-4-20250514',
527
+ temperature: 0.7,
528
+ maxTokens: 4096,
529
+ },
530
+ systemPrompt: \`You are ${displayName}, a helpful AI assistant.
531
+
532
+ Your capabilities:
533
+ - Answer questions accurately and helpfully
534
+ - Use available tools when appropriate
535
+ - Maintain conversation context
536
+
537
+ Always be concise, accurate, and helpful.\`,
538
+ tools,
539
+ context,
540
+ state: {
541
+ storage: 'memory',
542
+ ttl: 3600,
543
+ },
544
+ })
545
+ `;
546
+ }
547
+ function getContextTs() {
548
+ return `import { defineContext } from '@struere/core'
549
+
550
+ export const context = defineContext(async (request) => {
551
+ const { conversationId, userId, channel, state } = request
552
+
553
+ return {
554
+ additionalContext: \`
555
+ Current conversation: \${conversationId}
556
+ Channel: \${channel}
557
+ \`,
558
+ variables: {
559
+ userId,
560
+ timestamp: new Date().toISOString(),
561
+ },
562
+ }
563
+ })
564
+ `;
565
+ }
566
+ function getToolsTs() {
567
+ return `import { defineTools } from '@struere/core'
568
+
569
+ export const tools = defineTools([
570
+ {
571
+ name: 'get_current_time',
572
+ description: 'Get the current date and time',
573
+ parameters: {
574
+ type: 'object',
575
+ properties: {
576
+ timezone: {
577
+ type: 'string',
578
+ description: 'Timezone (e.g., "America/New_York", "UTC")',
579
+ },
580
+ },
581
+ },
582
+ handler: async (params) => {
583
+ const timezone = (params.timezone as string) || 'UTC'
584
+ const now = new Date()
585
+ return {
586
+ timestamp: now.toISOString(),
587
+ formatted: now.toLocaleString('en-US', { timeZone: timezone }),
588
+ timezone,
589
+ }
590
+ },
591
+ },
592
+ {
593
+ name: 'calculate',
594
+ description: 'Perform a mathematical calculation',
595
+ parameters: {
596
+ type: 'object',
597
+ properties: {
598
+ expression: {
599
+ type: 'string',
600
+ description: 'Mathematical expression to evaluate (e.g., "2 + 2")',
601
+ },
602
+ },
603
+ required: ['expression'],
604
+ },
605
+ handler: async (params) => {
606
+ const expression = params.expression as string
607
+ const sanitized = expression.replace(/[^0-9+*/().\\s-]/g, '')
608
+ try {
609
+ const result = new Function(\`return \${sanitized}\`)()
610
+ return { expression, result }
611
+ } catch {
612
+ return { expression, error: 'Invalid expression' }
613
+ }
614
+ },
615
+ },
616
+ ])
617
+ `;
618
+ }
619
+ function getBasicTestYaml() {
620
+ return `name: Basic conversation test
621
+ description: Verify the agent responds correctly to basic queries
622
+
623
+ conversation:
624
+ - role: user
625
+ content: Hello, what can you do?
626
+ - role: assistant
627
+ assertions:
628
+ - type: contains
629
+ value: help
630
+
631
+ - role: user
632
+ content: What time is it?
633
+ - role: assistant
634
+ assertions:
635
+ - type: toolCalled
636
+ value: get_current_time
637
+ `;
638
+ }
639
+ function getEnvExample() {
640
+ return `# Anthropic API Key (default provider)
641
+ ANTHROPIC_API_KEY=your_api_key_here
642
+
643
+ # Optional: OpenAI API Key (if using OpenAI models)
644
+ # OPENAI_API_KEY=your_openai_api_key
645
+
646
+ # Optional: Google AI API Key (if using Gemini models)
647
+ # GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key
648
+
649
+ # Optional: Custom API endpoint
650
+ # STRUERE_API_URL=https://api.struere.dev
651
+ `;
652
+ }
653
+ function getGitignore() {
654
+ return `node_modules/
655
+ dist/
656
+ .env
657
+ .env.local
658
+ .env.*.local
659
+ .idea/
660
+ .vscode/
661
+ *.swp
662
+ *.swo
663
+ .DS_Store
664
+ Thumbs.db
665
+ *.log
666
+ logs/
667
+ .vercel/
668
+ `;
669
+ }
670
+ function getVercelApiHandler() {
671
+ return `import agent from '../src/agent'
672
+ import { createVercelHandler } from '@struere/runtime/serverless/vercel'
673
+
674
+ export default createVercelHandler(agent, {
675
+ streaming: true,
676
+ corsOrigins: ['*'],
677
+ })
678
+
679
+ export const config = {
680
+ runtime: 'edge',
681
+ }
682
+ `;
683
+ }
684
+ function getStruereJson(agentId, team, slug, name) {
685
+ return JSON.stringify({
686
+ agentId,
687
+ team,
688
+ agent: {
689
+ slug,
690
+ name
691
+ }
692
+ }, null, 2);
693
+ }
694
+ function getEnvLocal(deploymentUrl) {
695
+ return `STRUERE_DEPLOYMENT_URL=${deploymentUrl}
696
+ `;
697
+ }
698
+
699
+ // src/utils/scaffold.ts
700
+ function ensureDir(filePath) {
701
+ const dir = dirname(filePath);
702
+ if (!existsSync3(dir)) {
703
+ mkdirSync2(dir, { recursive: true });
704
+ }
705
+ }
706
+ function writeFile(cwd, relativePath, content) {
707
+ const fullPath = join3(cwd, relativePath);
708
+ ensureDir(fullPath);
709
+ writeFileSync3(fullPath, content);
710
+ }
711
+ function writeProjectConfig(cwd, options) {
712
+ const result = {
713
+ createdFiles: [],
714
+ updatedFiles: []
715
+ };
716
+ writeFile(cwd, "struere.json", getStruereJson(options.agentId, options.team, options.agentSlug, options.agentName));
717
+ result.createdFiles.push("struere.json");
718
+ writeFile(cwd, ".env.local", getEnvLocal(options.deploymentUrl));
719
+ result.createdFiles.push(".env.local");
720
+ updateGitignore(cwd, result);
721
+ return result;
722
+ }
723
+ function scaffoldAgentFiles(cwd, projectName) {
724
+ const result = {
725
+ createdFiles: [],
726
+ updatedFiles: []
727
+ };
728
+ const files = {
729
+ "package.json": getPackageJson(projectName),
730
+ "tsconfig.json": getTsConfig(),
731
+ "struere.config.ts": getStruereConfig(),
732
+ "src/agent.ts": getAgentTs(projectName),
733
+ "src/context.ts": getContextTs(),
734
+ "src/tools.ts": getToolsTs(),
735
+ "src/workflows/.gitkeep": "",
736
+ "api/chat.ts": getVercelApiHandler(),
737
+ "tests/basic.test.yaml": getBasicTestYaml(),
738
+ ".env.example": getEnvExample()
739
+ };
740
+ for (const [relativePath, content] of Object.entries(files)) {
741
+ const fullPath = join3(cwd, relativePath);
742
+ if (existsSync3(fullPath)) {
743
+ continue;
744
+ }
745
+ writeFile(cwd, relativePath, content);
746
+ result.createdFiles.push(relativePath);
747
+ }
748
+ updateGitignore(cwd, result);
749
+ return result;
750
+ }
751
+ function updateGitignore(cwd, result) {
752
+ const gitignorePath = join3(cwd, ".gitignore");
753
+ const linesToAdd = [".env.local"];
754
+ if (existsSync3(gitignorePath)) {
755
+ const content = readFileSync3(gitignorePath, "utf-8");
756
+ const lines = content.split(`
757
+ `);
758
+ const missingLines = linesToAdd.filter((line) => !lines.some((l) => l.trim() === line));
759
+ if (missingLines.length > 0) {
760
+ const toAppend = `
761
+ ` + missingLines.join(`
762
+ `) + `
763
+ `;
764
+ appendFileSync(gitignorePath, toAppend);
765
+ result.updatedFiles.push(".gitignore");
766
+ }
767
+ } else {
768
+ writeFile(cwd, ".gitignore", getGitignore());
769
+ result.createdFiles.push(".gitignore");
770
+ }
771
+ }
772
+ function hasAgentFiles(cwd) {
773
+ return existsSync3(join3(cwd, "src", "agent.ts"));
774
+ }
775
+
776
+ // src/commands/init.ts
777
+ var initCommand = new Command2("init").description("Initialize a new Struere project").argument("[project-name]", "Project name").option("-y, --yes", "Skip prompts and use defaults").option("--headless", "Use headless login if authentication is needed").action(async (projectNameArg, options) => {
778
+ const cwd = process.cwd();
779
+ const spinner = ora2();
780
+ console.log();
781
+ console.log(chalk2.bold("Struere CLI"));
782
+ console.log();
783
+ if (hasProject(cwd)) {
784
+ const existingProject = loadProject(cwd);
785
+ if (existingProject) {
786
+ console.log(chalk2.yellow("This project is already initialized."));
787
+ console.log();
788
+ console.log(chalk2.gray(" Agent:"), chalk2.cyan(existingProject.agent.name));
789
+ console.log(chalk2.gray(" ID:"), chalk2.gray(existingProject.agentId));
790
+ console.log(chalk2.gray(" Team:"), chalk2.cyan(existingProject.team));
791
+ console.log();
792
+ const shouldRelink = await promptYesNo("Would you like to relink to a different agent?");
793
+ if (!shouldRelink) {
794
+ console.log();
795
+ console.log(chalk2.gray("Run"), chalk2.cyan("struere dev"), chalk2.gray("to start development"));
796
+ console.log();
797
+ return;
798
+ }
799
+ }
800
+ }
801
+ let credentials = loadCredentials();
802
+ if (!credentials) {
803
+ console.log(chalk2.gray("Authentication required"));
804
+ console.log();
805
+ credentials = await performLogin({ headless: options.headless });
806
+ if (!credentials) {
807
+ console.log(chalk2.red("Authentication failed"));
808
+ process.exit(1);
809
+ }
810
+ } else {
811
+ console.log(chalk2.green("\u2713"), "Logged in as", chalk2.cyan(credentials.user.name));
812
+ console.log();
813
+ }
814
+ let projectName = projectNameArg;
815
+ if (!projectName) {
816
+ projectName = await deriveProjectName(cwd);
817
+ if (!options.yes) {
818
+ const confirmed = await promptText("Agent name:", projectName);
819
+ projectName = confirmed || projectName;
820
+ }
821
+ }
822
+ projectName = slugify(projectName);
823
+ spinner.start("Fetching agents");
824
+ const api = new ApiClient;
825
+ let agents = [];
826
+ try {
827
+ const { agents: existingAgents } = await api.listAgents();
828
+ agents = existingAgents;
829
+ spinner.succeed(`Found ${agents.length} existing agent(s)`);
830
+ } catch (error) {
831
+ if (error instanceof ApiError && error.status === 401) {
832
+ spinner.fail("Session expired");
833
+ console.log();
834
+ console.log(chalk2.gray("Run"), chalk2.cyan("struere login"), chalk2.gray("to re-authenticate"));
835
+ process.exit(1);
836
+ }
837
+ spinner.succeed("Ready to create agent");
838
+ }
839
+ let selectedAgent = null;
840
+ let deploymentUrl = "";
841
+ if (agents.length > 0 && !options.yes) {
842
+ console.log();
843
+ const choice = await promptChoice("Create new agent or link existing?", [
844
+ { value: "new", label: "Create new agent" },
845
+ ...agents.map((a) => ({ value: a.id, label: `${a.name} (${a.slug})` }))
846
+ ]);
847
+ if (choice !== "new") {
848
+ selectedAgent = agents.find((a) => a.id === choice) || null;
849
+ }
850
+ }
851
+ if (!selectedAgent) {
852
+ const displayName = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
853
+ spinner.start("Creating agent");
854
+ try {
855
+ const { agent } = await api.createAgent({
856
+ name: displayName,
857
+ slug: projectName,
858
+ description: `${displayName} Agent`
859
+ });
860
+ selectedAgent = { id: agent.id, name: displayName, slug: projectName };
861
+ deploymentUrl = `https://${projectName}-dev.struere.dev`;
862
+ spinner.succeed(`Created agent "${projectName}"`);
863
+ } catch (error) {
864
+ spinner.fail("Failed to create agent");
865
+ console.log();
866
+ if (error instanceof ApiError) {
867
+ console.log(chalk2.red("Error:"), error.message);
868
+ } else {
869
+ console.log(chalk2.red("Error:"), error instanceof Error ? error.message : String(error));
870
+ }
871
+ process.exit(1);
872
+ }
873
+ } else {
874
+ deploymentUrl = `https://${selectedAgent.slug}-dev.struere.dev`;
875
+ console.log();
876
+ console.log(chalk2.green("\u2713"), `Linked to "${selectedAgent.name}"`);
877
+ }
878
+ saveProject(cwd, {
879
+ agentId: selectedAgent.id,
880
+ team: credentials.organization.slug,
881
+ agent: {
882
+ slug: selectedAgent.slug,
883
+ name: selectedAgent.name
884
+ }
885
+ });
886
+ console.log(chalk2.green("\u2713"), "Created struere.json");
887
+ const configResult = writeProjectConfig(cwd, {
888
+ projectName,
889
+ agentId: selectedAgent.id,
890
+ team: credentials.organization.slug,
891
+ agentSlug: selectedAgent.slug,
892
+ agentName: selectedAgent.name,
893
+ deploymentUrl
894
+ });
895
+ for (const file of configResult.createdFiles) {
896
+ if (file !== "struere.json") {
897
+ console.log(chalk2.green("\u2713"), `Created ${file}`);
898
+ }
899
+ }
900
+ for (const file of configResult.updatedFiles) {
901
+ console.log(chalk2.green("\u2713"), `Updated ${file}`);
200
902
  }
201
- async getAgent(agentId) {
202
- return this.request(`/v1/agents/${agentId}`);
903
+ if (!hasAgentFiles(cwd)) {
904
+ let shouldScaffold = options.yes;
905
+ if (!options.yes) {
906
+ console.log();
907
+ shouldScaffold = await promptYesNo("Scaffold starter files?");
908
+ }
909
+ if (shouldScaffold) {
910
+ const scaffoldResult = scaffoldAgentFiles(cwd, projectName);
911
+ console.log();
912
+ for (const file of scaffoldResult.createdFiles) {
913
+ console.log(chalk2.green("\u2713"), `Created ${file}`);
914
+ }
915
+ for (const file of scaffoldResult.updatedFiles) {
916
+ console.log(chalk2.green("\u2713"), `Updated ${file}`);
917
+ }
918
+ }
203
919
  }
204
- async deployAgent(agentId, data) {
205
- return this.request(`/v1/deployments/agents/${agentId}/deploy`, {
206
- method: "POST",
207
- body: JSON.stringify(data)
208
- });
920
+ console.log();
921
+ console.log(chalk2.green("Success!"), "Project initialized");
922
+ console.log();
923
+ console.log(chalk2.gray("Next steps:"));
924
+ console.log(chalk2.gray(" $"), chalk2.cyan("bun install"));
925
+ console.log(chalk2.gray(" $"), chalk2.cyan("struere dev"));
926
+ console.log();
927
+ });
928
+ async function deriveProjectName(cwd) {
929
+ const packageJsonPath = join4(cwd, "package.json");
930
+ if (existsSync4(packageJsonPath)) {
931
+ try {
932
+ const pkg = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
933
+ if (pkg.name && typeof pkg.name === "string") {
934
+ return slugify(pkg.name);
935
+ }
936
+ } catch {}
209
937
  }
210
- async createApiKey(data) {
211
- return this.request("/v1/api-keys", {
212
- method: "POST",
213
- body: JSON.stringify(data)
214
- });
938
+ return slugify(basename(cwd));
939
+ }
940
+ function slugify(name) {
941
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
942
+ }
943
+ async function promptYesNo(message) {
944
+ process.stdout.write(chalk2.gray(`${message} (Y/n) `));
945
+ return new Promise((resolve) => {
946
+ process.stdin.resume();
947
+ process.stdin.setEncoding("utf8");
948
+ const onData = (data) => {
949
+ process.stdin.removeListener("data", onData);
950
+ process.stdin.pause();
951
+ const answer = data.trim().toLowerCase();
952
+ resolve(answer === "" || answer === "y" || answer === "yes");
953
+ };
954
+ process.stdin.on("data", onData);
955
+ });
956
+ }
957
+ async function promptText(message, defaultValue) {
958
+ process.stdout.write(chalk2.gray(`${message} `));
959
+ process.stdout.write(chalk2.cyan(`(${defaultValue}) `));
960
+ return new Promise((resolve) => {
961
+ process.stdin.resume();
962
+ process.stdin.setEncoding("utf8");
963
+ const onData = (data) => {
964
+ process.stdin.removeListener("data", onData);
965
+ process.stdin.pause();
966
+ const answer = data.trim();
967
+ resolve(answer || defaultValue);
968
+ };
969
+ process.stdin.on("data", onData);
970
+ });
971
+ }
972
+ async function promptChoice(message, choices) {
973
+ console.log(chalk2.gray(message));
974
+ console.log();
975
+ for (let i = 0;i < choices.length; i++) {
976
+ const prefix = i === 0 ? chalk2.cyan("\u276F") : chalk2.gray(" ");
977
+ console.log(`${prefix} ${choices[i].label}`);
215
978
  }
216
- async listApiKeys() {
217
- return this.request("/v1/api-keys");
979
+ console.log();
980
+ process.stdout.write(chalk2.gray("Enter choice (1-" + choices.length + "): "));
981
+ return new Promise((resolve) => {
982
+ process.stdin.resume();
983
+ process.stdin.setEncoding("utf8");
984
+ const onData = (data) => {
985
+ process.stdin.removeListener("data", onData);
986
+ process.stdin.pause();
987
+ const num = parseInt(data.trim(), 10);
988
+ if (num >= 1 && num <= choices.length) {
989
+ resolve(choices[num - 1].value);
990
+ } else {
991
+ resolve(choices[0].value);
992
+ }
993
+ };
994
+ process.stdin.on("data", onData);
995
+ });
996
+ }
997
+
998
+ // src/commands/dev.ts
999
+ import { Command as Command3 } from "commander";
1000
+ import chalk3 from "chalk";
1001
+ import ora3 from "ora";
1002
+ import chokidar from "chokidar";
1003
+ import { join as join7 } from "path";
1004
+
1005
+ // src/utils/config.ts
1006
+ import { join as join5 } from "path";
1007
+ var defaultConfig = {
1008
+ port: 3000,
1009
+ host: "localhost",
1010
+ cors: {
1011
+ origins: ["http://localhost:3000"],
1012
+ credentials: true
1013
+ },
1014
+ logging: {
1015
+ level: "info",
1016
+ format: "pretty"
1017
+ },
1018
+ auth: {
1019
+ type: "none"
218
1020
  }
219
- async getUsage(period = "day") {
220
- return this.request(`/v1/usage?period=${period}`);
1021
+ };
1022
+ async function loadConfig(cwd) {
1023
+ const configPath = join5(cwd, "struere.config.ts");
1024
+ try {
1025
+ const module = await import(configPath);
1026
+ const config = module.default || module;
1027
+ return {
1028
+ ...defaultConfig,
1029
+ ...config,
1030
+ cors: {
1031
+ ...defaultConfig.cors,
1032
+ ...config.cors
1033
+ },
1034
+ logging: {
1035
+ ...defaultConfig.logging,
1036
+ ...config.logging
1037
+ },
1038
+ auth: {
1039
+ ...defaultConfig.auth,
1040
+ ...config.auth
1041
+ }
1042
+ };
1043
+ } catch {
1044
+ return defaultConfig;
221
1045
  }
222
1046
  }
223
1047
 
224
- class ApiError extends Error {
225
- code;
226
- status;
227
- constructor(message, code, status) {
228
- super(message);
229
- this.code = code;
230
- this.status = status;
231
- this.name = "ApiError";
1048
+ // src/utils/agent.ts
1049
+ import { join as join6 } from "path";
1050
+ async function loadAgent(cwd) {
1051
+ const agentPath = join6(cwd, "src/agent.ts");
1052
+ try {
1053
+ const module = await import(agentPath);
1054
+ const agent = module.default || module;
1055
+ if (!agent.name) {
1056
+ throw new Error("Agent must have a name");
1057
+ }
1058
+ if (!agent.version) {
1059
+ throw new Error("Agent must have a version");
1060
+ }
1061
+ if (!agent.systemPrompt) {
1062
+ throw new Error("Agent must have a systemPrompt");
1063
+ }
1064
+ return agent;
1065
+ } catch (error) {
1066
+ if (error instanceof Error && error.message.includes("Cannot find module")) {
1067
+ throw new Error(`Agent not found at ${agentPath}`);
1068
+ }
1069
+ throw error;
232
1070
  }
233
1071
  }
234
1072
 
235
1073
  // src/commands/dev.ts
236
- var devCommand = new Command("dev").description("Start development server with hot reload").option("-p, --port <port>", "Port to run on", "3000").option("-c, --channel <channel>", "Channel to open (web, api)", "web").option("--no-open", "Do not open browser").option("--local", "Run locally without cloud sync").option("--cloud", "Force cloud-connected mode").action(async (options) => {
237
- const spinner = ora();
1074
+ var devCommand = new Command3("dev").description("Start development server with cloud sync").option("-p, --port <port>", "Port to run on", "3000").option("-c, --channel <channel>", "Channel to open (web, api)", "web").option("--no-open", "Do not open browser").action(async (options) => {
1075
+ const spinner = ora3();
238
1076
  const cwd = process.cwd();
239
1077
  console.log();
240
- console.log(chalk.bold("Struere Dev Server"));
1078
+ console.log(chalk3.bold("Struere Dev Server"));
1079
+ console.log();
1080
+ if (!hasProject(cwd)) {
1081
+ console.log(chalk3.yellow("No struere.json found"));
1082
+ console.log();
1083
+ console.log(chalk3.gray("Run"), chalk3.cyan("struere init"), chalk3.gray("to initialize this project"));
1084
+ console.log();
1085
+ process.exit(1);
1086
+ }
1087
+ const project = loadProject(cwd);
1088
+ if (!project) {
1089
+ console.log(chalk3.red("Failed to load struere.json"));
1090
+ process.exit(1);
1091
+ }
1092
+ console.log(chalk3.gray("Agent:"), chalk3.cyan(project.agent.name));
241
1093
  console.log();
242
1094
  spinner.start("Loading configuration");
243
1095
  const config = await loadConfig(cwd);
@@ -246,115 +1098,20 @@ var devCommand = new Command("dev").description("Start development server with h
246
1098
  spinner.start("Loading agent");
247
1099
  let agent = await loadAgent(cwd);
248
1100
  spinner.succeed(`Agent "${agent.name}" loaded`);
249
- const useCloud = options.cloud || !options.local && isLoggedIn();
250
- if (useCloud) {
251
- await runCloudDev(agent, cwd, port, options, spinner);
252
- } else {
253
- await runLocalDev(agent, cwd, port, options, spinner);
254
- }
255
- });
256
- async function runLocalDev(agent, cwd, port, options, spinner) {
257
- let executor = new AgentExecutor(agent);
258
- const server = Bun.serve({
259
- port,
260
- async fetch(req) {
261
- const url = new URL(req.url);
262
- if (url.pathname === "/health") {
263
- return Response.json({ status: "ok", agent: agent.name, mode: "local" });
264
- }
265
- if (url.pathname === "/api/chat" && req.method === "POST") {
266
- const body = await req.json();
267
- const conversationId = body.conversationId || crypto.randomUUID();
268
- if (body.stream) {
269
- const stream = new ReadableStream({
270
- async start(controller) {
271
- const encoder = new TextEncoder;
272
- const sendEvent = (event, data) => {
273
- controller.enqueue(encoder.encode(`event: ${event}
274
- data: ${JSON.stringify(data)}
275
-
276
- `));
277
- };
278
- sendEvent("start", { conversationId });
279
- for await (const chunk of executor.stream({ conversationId, message: body.message })) {
280
- sendEvent(chunk.type, chunk);
281
- }
282
- controller.close();
283
- }
284
- });
285
- return new Response(stream, {
286
- headers: {
287
- "Content-Type": "text/event-stream",
288
- "Cache-Control": "no-cache",
289
- Connection: "keep-alive"
290
- }
291
- });
292
- }
293
- const response = await executor.execute({ conversationId, message: body.message });
294
- return Response.json({
295
- response: response.message,
296
- conversationId: response.conversationId,
297
- toolCalls: response.toolCalls,
298
- usage: response.usage
299
- });
300
- }
301
- if (url.pathname === "/" && options.channel === "web") {
302
- return new Response(getDevHtml(agent.name, "local"), {
303
- headers: { "Content-Type": "text/html" }
304
- });
305
- }
306
- return new Response("Not Found", { status: 404 });
307
- }
308
- });
309
- console.log();
310
- console.log(chalk.gray("Mode:"), chalk.yellow("Local"));
311
- console.log(chalk.green("Server running at"), chalk.cyan(`http://localhost:${port}`));
312
- console.log();
313
- if (options.channel === "web" && options.open) {
314
- const openUrl = `http://localhost:${port}`;
315
- if (process.platform === "darwin") {
316
- Bun.spawn(["open", openUrl]);
317
- } else if (process.platform === "linux") {
318
- Bun.spawn(["xdg-open", openUrl]);
319
- }
320
- }
321
- spinner.start("Watching for changes");
322
- const watcher = chokidar.watch([join4(cwd, "src"), join4(cwd, "af.config.ts")], {
323
- ignoreInitial: true,
324
- ignored: /node_modules/
325
- });
326
- watcher.on("change", async (path) => {
327
- spinner.text = `Reloading (${path.replace(cwd, ".")})`;
328
- try {
329
- agent = await loadAgent(cwd);
330
- executor = new AgentExecutor(agent);
331
- spinner.succeed(`Reloaded "${agent.name}"`);
332
- spinner.start("Watching for changes");
333
- } catch (error) {
334
- spinner.fail(`Reload failed: ${error}`);
335
- spinner.start("Watching for changes");
336
- }
337
- });
338
- process.on("SIGINT", () => {
339
- console.log();
340
- spinner.stop();
341
- watcher.close();
342
- server.stop();
343
- console.log(chalk.gray("Server stopped"));
344
- process.exit(0);
345
- });
346
- }
347
- async function runCloudDev(agent, cwd, port, options, spinner) {
348
1101
  const credentials = loadCredentials();
349
1102
  const apiKey = getApiKey();
350
1103
  if (!credentials && !apiKey) {
351
1104
  spinner.fail("Not logged in");
352
1105
  console.log();
353
- console.log(chalk.gray("Run"), chalk.cyan("af login"), chalk.gray("to connect to Struere Cloud"));
354
- console.log(chalk.gray("Or use"), chalk.cyan("af dev --local"), chalk.gray("for local development"));
1106
+ console.log(chalk3.gray("Run"), chalk3.cyan("struere login"), chalk3.gray("to authenticate"));
355
1107
  console.log();
356
1108
  process.exit(1);
357
1109
  }
1110
+ await runCloudDev(agent, project, cwd, port, options, spinner);
1111
+ });
1112
+ async function runCloudDev(agent, project, cwd, port, options, spinner) {
1113
+ const credentials = loadCredentials();
1114
+ const apiKey = getApiKey();
358
1115
  spinner.start("Connecting to Struere Cloud");
359
1116
  const syncUrl = getSyncUrl();
360
1117
  const ws = new WebSocket(`${syncUrl}/v1/dev/sync`);
@@ -376,7 +1133,8 @@ async function runCloudDev(agent, cwd, port, options, spinner) {
376
1133
  const configHash = hashString(bundle);
377
1134
  ws.send(JSON.stringify({
378
1135
  type: "sync",
379
- agentSlug: agent.name.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
1136
+ agentId: project.agentId,
1137
+ agentSlug: project.agent.slug,
380
1138
  bundle,
381
1139
  configHash
382
1140
  }));
@@ -387,14 +1145,14 @@ async function runCloudDev(agent, cwd, port, options, spinner) {
387
1145
  sessionId = data.agentId || null;
388
1146
  spinner.succeed("Connected to Struere Cloud");
389
1147
  console.log();
390
- console.log(chalk.gray("Mode:"), chalk.green("Cloud"));
391
- console.log(chalk.green("Agent running at"), chalk.cyan(cloudUrl));
392
- console.log(chalk.green("Local server at"), chalk.cyan(`http://localhost:${port}`));
1148
+ console.log(chalk3.gray("Mode:"), chalk3.green("Cloud"));
1149
+ console.log(chalk3.green("Agent running at"), chalk3.cyan(cloudUrl));
1150
+ console.log(chalk3.green("Local server at"), chalk3.cyan(`http://localhost:${port}`));
393
1151
  console.log();
394
1152
  spinner.start("Watching for changes");
395
1153
  break;
396
1154
  case "log":
397
- const logColor = data.level === "error" ? chalk.red : data.level === "warn" ? chalk.yellow : data.level === "debug" ? chalk.gray : chalk.blue;
1155
+ const logColor = data.level === "error" ? chalk3.red : data.level === "warn" ? chalk3.yellow : data.level === "debug" ? chalk3.gray : chalk3.blue;
398
1156
  spinner.stop();
399
1157
  console.log(logColor(`[${data.level}]`), data.message);
400
1158
  spinner.start("Watching for changes");
@@ -403,19 +1161,19 @@ async function runCloudDev(agent, cwd, port, options, spinner) {
403
1161
  spinner.fail(`Cloud error: ${data.message}`);
404
1162
  if (data.code === "INVALID_API_KEY" || data.code === "NOT_AUTHENTICATED") {
405
1163
  console.log();
406
- console.log(chalk.gray("Run"), chalk.cyan("af login"), chalk.gray("to authenticate"));
1164
+ console.log(chalk3.gray("Run"), chalk3.cyan("struere login"), chalk3.gray("to authenticate"));
407
1165
  }
408
1166
  break;
409
1167
  }
410
1168
  };
411
- ws.onerror = (error) => {
1169
+ ws.onerror = () => {
412
1170
  spinner.fail("WebSocket error");
413
- console.log(chalk.red("Connection error. Falling back to local mode."));
1171
+ console.log(chalk3.red("Connection error"));
414
1172
  };
415
1173
  ws.onclose = () => {
416
1174
  if (isConnected) {
417
1175
  spinner.stop();
418
- console.log(chalk.yellow("Disconnected from cloud"));
1176
+ console.log(chalk3.yellow("Disconnected from cloud"));
419
1177
  }
420
1178
  };
421
1179
  const server = Bun.serve({
@@ -435,7 +1193,6 @@ async function runCloudDev(agent, cwd, port, options, spinner) {
435
1193
  return Response.json({ error: "Not connected to cloud" }, { status: 503 });
436
1194
  }
437
1195
  const body = await req.json();
438
- const gatewayUrl = cloudUrl.replace("https://", "https://gateway.").replace(/-dev\.struere\.dev.*/, ".struere.dev");
439
1196
  const response = await fetch(`${process.env.STRUERE_GATEWAY_URL || "https://gateway.struere.dev"}/v1/dev/${sessionId}/chat`, {
440
1197
  method: "POST",
441
1198
  headers: {
@@ -453,8 +1210,8 @@ async function runCloudDev(agent, cwd, port, options, spinner) {
453
1210
  }
454
1211
  });
455
1212
  }
456
- const data = await response.json();
457
- return Response.json(data);
1213
+ const responseData = await response.json();
1214
+ return Response.json(responseData);
458
1215
  }
459
1216
  if (url.pathname === "/" && options.channel === "web") {
460
1217
  return new Response(getDevHtml(agent.name, "cloud", cloudUrl), {
@@ -472,7 +1229,7 @@ async function runCloudDev(agent, cwd, port, options, spinner) {
472
1229
  Bun.spawn(["xdg-open", openUrl]);
473
1230
  }
474
1231
  }
475
- const watcher = chokidar.watch([join4(cwd, "src"), join4(cwd, "af.config.ts")], {
1232
+ const watcher = chokidar.watch([join7(cwd, "src"), join7(cwd, "struere.config.ts")], {
476
1233
  ignoreInitial: true,
477
1234
  ignored: /node_modules/
478
1235
  });
@@ -485,7 +1242,8 @@ async function runCloudDev(agent, cwd, port, options, spinner) {
485
1242
  if (ws.readyState === WebSocket.OPEN) {
486
1243
  ws.send(JSON.stringify({
487
1244
  type: "sync",
488
- agentSlug: agent.name.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
1245
+ agentId: project.agentId,
1246
+ agentSlug: project.agent.slug,
489
1247
  bundle,
490
1248
  configHash
491
1249
  }));
@@ -504,13 +1262,13 @@ async function runCloudDev(agent, cwd, port, options, spinner) {
504
1262
  }
505
1263
  watcher.close();
506
1264
  server.stop();
507
- console.log(chalk.gray("Server stopped"));
1265
+ console.log(chalk3.gray("Server stopped"));
508
1266
  process.exit(0);
509
1267
  });
510
1268
  }
511
1269
  async function bundleAgent(cwd) {
512
1270
  const result = await Bun.build({
513
- entrypoints: [join4(cwd, "src", "agent.ts")],
1271
+ entrypoints: [join7(cwd, "src", "agent.ts")],
514
1272
  target: "browser",
515
1273
  minify: true
516
1274
  });
@@ -636,12 +1394,12 @@ function getDevHtml(agentName, mode, cloudUrl) {
636
1394
  assistantDiv.textContent = fullText;
637
1395
  messages.scrollTop = messages.scrollHeight;
638
1396
  } else if (data.type === 'tool-call-start') {
639
- addMessage('tool', '\uD83D\uDD27 Calling tool: ' + (data.toolName || data.toolCall?.name));
1397
+ addMessage('tool', 'Calling tool: ' + (data.toolName || data.toolCall?.name));
640
1398
  } else if (data.type === 'tool-result') {
641
1399
  const resultText = typeof data.toolResult === 'string'
642
1400
  ? data.toolResult
643
1401
  : JSON.stringify(data.toolResult || data.toolCall?.result, null, 2);
644
- addMessage('tool', 'Result: ' + resultText);
1402
+ addMessage('tool', 'Result: ' + resultText);
645
1403
  } else if (data.type === 'finish') {
646
1404
  assistantDiv.classList.remove('streaming');
647
1405
  } else if (data.type === 'error') {
@@ -672,7 +1430,7 @@ function getDevHtml(agentName, mode, cloudUrl) {
672
1430
  const resultText = typeof tc.result === 'string'
673
1431
  ? tc.result
674
1432
  : JSON.stringify(tc.result, null, 2);
675
- addMessage('tool', '\uD83D\uDD27 ' + tc.name + ': ' + resultText);
1433
+ addMessage('tool', tc.name + ': ' + resultText);
676
1434
  }
677
1435
  }
678
1436
 
@@ -710,10 +1468,10 @@ function getDevHtml(agentName, mode, cloudUrl) {
710
1468
  }
711
1469
 
712
1470
  // src/commands/build.ts
713
- import { Command as Command2 } from "commander";
714
- import chalk2 from "chalk";
715
- import ora2 from "ora";
716
- import { join as join5 } from "path";
1471
+ import { Command as Command4 } from "commander";
1472
+ import chalk4 from "chalk";
1473
+ import ora4 from "ora";
1474
+ import { join as join8 } from "path";
717
1475
 
718
1476
  // src/utils/validate.ts
719
1477
  function validateAgent(agent) {
@@ -791,11 +1549,11 @@ function validateTool(tool) {
791
1549
  }
792
1550
 
793
1551
  // src/commands/build.ts
794
- var buildCommand = new Command2("build").description("Build and validate agent for production").option("-o, --outdir <dir>", "Output directory", "dist").action(async (options) => {
795
- const spinner = ora2();
1552
+ var buildCommand = new Command4("build").description("Build and validate agent for production").option("-o, --outdir <dir>", "Output directory", "dist").action(async (options) => {
1553
+ const spinner = ora4();
796
1554
  const cwd = process.cwd();
797
1555
  console.log();
798
- console.log(chalk2.bold("Building Agent"));
1556
+ console.log(chalk4.bold("Building Agent"));
799
1557
  console.log();
800
1558
  spinner.start("Loading configuration");
801
1559
  const config = await loadConfig(cwd);
@@ -809,16 +1567,16 @@ var buildCommand = new Command2("build").description("Build and validate agent f
809
1567
  spinner.fail("Validation failed");
810
1568
  console.log();
811
1569
  for (const error of errors) {
812
- console.log(chalk2.red(" "), error);
1570
+ console.log(chalk4.red(" \u2717"), error);
813
1571
  }
814
1572
  console.log();
815
1573
  process.exit(1);
816
1574
  }
817
1575
  spinner.succeed("Agent validated");
818
1576
  spinner.start("Building");
819
- const outdir = join5(cwd, options.outdir);
1577
+ const outdir = join8(cwd, options.outdir);
820
1578
  const result = await Bun.build({
821
- entrypoints: [join5(cwd, "src/agent.ts")],
1579
+ entrypoints: [join8(cwd, "src/agent.ts")],
822
1580
  outdir,
823
1581
  target: "node",
824
1582
  minify: true
@@ -827,40 +1585,40 @@ var buildCommand = new Command2("build").description("Build and validate agent f
827
1585
  spinner.fail("Build failed");
828
1586
  console.log();
829
1587
  for (const log of result.logs) {
830
- console.log(chalk2.red(" "), log.message);
1588
+ console.log(chalk4.red(" \u2717"), log.message);
831
1589
  }
832
1590
  process.exit(1);
833
1591
  }
834
1592
  spinner.succeed("Build completed");
835
1593
  console.log();
836
- console.log(chalk2.green("Success!"), `Built to ${chalk2.cyan(options.outdir)}`);
1594
+ console.log(chalk4.green("Success!"), `Built to ${chalk4.cyan(options.outdir)}`);
837
1595
  console.log();
838
1596
  console.log("Output files:");
839
1597
  for (const output of result.outputs) {
840
- console.log(chalk2.gray(" "), output.path.replace(cwd, "."));
1598
+ console.log(chalk4.gray(" \u2022"), output.path.replace(cwd, "."));
841
1599
  }
842
1600
  console.log();
843
1601
  });
844
1602
 
845
1603
  // src/commands/test.ts
846
- import { Command as Command3 } from "commander";
847
- import chalk3 from "chalk";
848
- import ora3 from "ora";
849
- import { join as join6 } from "path";
1604
+ import { Command as Command5 } from "commander";
1605
+ import chalk5 from "chalk";
1606
+ import ora5 from "ora";
1607
+ import { join as join9 } from "path";
850
1608
  import { readdir, readFile } from "fs/promises";
851
1609
  import YAML from "yaml";
852
- import { AgentExecutor as AgentExecutor2 } from "@struere/runtime";
853
- var testCommand = new Command3("test").description("Run test conversations").argument("[pattern]", "Test file pattern", "*.test.yaml").option("-v, --verbose", "Show detailed output").option("--dry-run", "Parse tests without executing (no API calls)").action(async (pattern, options) => {
854
- const spinner = ora3();
1610
+ import { AgentExecutor } from "@struere/runtime";
1611
+ var testCommand = new Command5("test").description("Run test conversations").argument("[pattern]", "Test file pattern", "*.test.yaml").option("-v, --verbose", "Show detailed output").option("--dry-run", "Parse tests without executing (no API calls)").action(async (pattern, options) => {
1612
+ const spinner = ora5();
855
1613
  const cwd = process.cwd();
856
1614
  console.log();
857
- console.log(chalk3.bold("Running Tests"));
1615
+ console.log(chalk5.bold("Running Tests"));
858
1616
  console.log();
859
1617
  spinner.start("Loading agent");
860
1618
  const agent = await loadAgent(cwd);
861
1619
  spinner.succeed(`Agent "${agent.name}" loaded`);
862
1620
  spinner.start("Finding test files");
863
- const testsDir = join6(cwd, "tests");
1621
+ const testsDir = join9(cwd, "tests");
864
1622
  let testFiles = [];
865
1623
  try {
866
1624
  const files = await readdir(testsDir);
@@ -868,7 +1626,7 @@ var testCommand = new Command3("test").description("Run test conversations").arg
868
1626
  } catch {
869
1627
  spinner.warn("No tests directory found");
870
1628
  console.log();
871
- console.log(chalk3.gray("Create tests in"), chalk3.cyan("tests/*.test.yaml"));
1629
+ console.log(chalk5.gray("Create tests in"), chalk5.cyan("tests/*.test.yaml"));
872
1630
  console.log();
873
1631
  return;
874
1632
  }
@@ -880,26 +1638,26 @@ var testCommand = new Command3("test").description("Run test conversations").arg
880
1638
  spinner.succeed(`Found ${testFiles.length} test file(s)`);
881
1639
  if (options.dryRun) {
882
1640
  console.log();
883
- console.log(chalk3.yellow("Dry run mode - skipping execution"));
1641
+ console.log(chalk5.yellow("Dry run mode - skipping execution"));
884
1642
  console.log();
885
1643
  }
886
1644
  const results = [];
887
1645
  for (const file of testFiles) {
888
- const filePath = join6(testsDir, file);
1646
+ const filePath = join9(testsDir, file);
889
1647
  const content = await readFile(filePath, "utf-8");
890
1648
  const testCase = YAML.parse(content);
891
1649
  if (options.verbose) {
892
1650
  console.log();
893
- console.log(chalk3.gray("Running:"), testCase.name);
1651
+ console.log(chalk5.gray("Running:"), testCase.name);
894
1652
  }
895
1653
  const result = options.dryRun ? await runDryTest(testCase) : await runTest(testCase, agent, options.verbose);
896
1654
  results.push(result);
897
1655
  if (result.passed) {
898
- console.log(chalk3.green(" "), result.name);
1656
+ console.log(chalk5.green(" \u2713"), result.name);
899
1657
  } else {
900
- console.log(chalk3.red(" "), result.name);
1658
+ console.log(chalk5.red(" \u2717"), result.name);
901
1659
  for (const error of result.errors) {
902
- console.log(chalk3.red(" "), error);
1660
+ console.log(chalk5.red(" \u2192"), error);
903
1661
  }
904
1662
  }
905
1663
  }
@@ -907,9 +1665,9 @@ var testCommand = new Command3("test").description("Run test conversations").arg
907
1665
  const failed = results.filter((r) => !r.passed).length;
908
1666
  console.log();
909
1667
  if (failed === 0) {
910
- console.log(chalk3.green("All tests passed!"), chalk3.gray(`(${passed}/${results.length})`));
1668
+ console.log(chalk5.green("All tests passed!"), chalk5.gray(`(${passed}/${results.length})`));
911
1669
  } else {
912
- console.log(chalk3.red("Tests failed:"), chalk3.gray(`${passed}/${results.length} passed`));
1670
+ console.log(chalk5.red("Tests failed:"), chalk5.gray(`${passed}/${results.length} passed`));
913
1671
  }
914
1672
  console.log();
915
1673
  if (failed > 0) {
@@ -925,7 +1683,7 @@ async function runDryTest(testCase) {
925
1683
  }
926
1684
  async function runTest(testCase, agent, verbose) {
927
1685
  const errors = [];
928
- const executor = new AgentExecutor2(agent);
1686
+ const executor = new AgentExecutor(agent);
929
1687
  const conversationId = `test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
930
1688
  const context = {
931
1689
  lastResponse: "",
@@ -936,7 +1694,7 @@ async function runTest(testCase, agent, verbose) {
936
1694
  for (const message of testCase.conversation || []) {
937
1695
  if (message.role === "user") {
938
1696
  if (verbose) {
939
- console.log(chalk3.cyan(" User:"), message.content.slice(0, 50) + (message.content.length > 50 ? "..." : ""));
1697
+ console.log(chalk5.cyan(" User:"), message.content.slice(0, 50) + (message.content.length > 50 ? "..." : ""));
940
1698
  }
941
1699
  const result = await executor.execute({
942
1700
  conversationId,
@@ -945,9 +1703,9 @@ async function runTest(testCase, agent, verbose) {
945
1703
  context.lastResponse = result.message;
946
1704
  context.toolCalls = result.toolCalls || [];
947
1705
  if (verbose) {
948
- console.log(chalk3.green(" Assistant:"), result.message.slice(0, 50) + (result.message.length > 50 ? "..." : ""));
1706
+ console.log(chalk5.green(" Assistant:"), result.message.slice(0, 50) + (result.message.length > 50 ? "..." : ""));
949
1707
  if (result.toolCalls && result.toolCalls.length > 0) {
950
- console.log(chalk3.yellow(" Tools:"), result.toolCalls.map((t) => t.name).join(", "));
1708
+ console.log(chalk5.yellow(" Tools:"), result.toolCalls.map((t) => t.name).join(", "));
951
1709
  }
952
1710
  }
953
1711
  }
@@ -1020,18 +1778,32 @@ function formatAssertionError(assertion, context) {
1020
1778
  }
1021
1779
 
1022
1780
  // src/commands/deploy.ts
1023
- import { Command as Command4 } from "commander";
1024
- import chalk4 from "chalk";
1025
- import ora4 from "ora";
1026
- import { join as join7 } from "path";
1027
- var deployCommand = new Command4("deploy").description("Deploy agent to Agent Factory cloud").option("-e, --env <environment>", "Target environment (preview, staging, production)", "preview").option("--dry-run", "Show what would be deployed without deploying").action(async (options) => {
1028
- const spinner = ora4();
1781
+ import { Command as Command6 } from "commander";
1782
+ import chalk6 from "chalk";
1783
+ import ora6 from "ora";
1784
+ import { join as join10 } from "path";
1785
+ var deployCommand = new Command6("deploy").description("Deploy agent to Struere Cloud").option("-e, --env <environment>", "Target environment (preview, staging, production)", "preview").option("--dry-run", "Show what would be deployed without deploying").action(async (options) => {
1786
+ const spinner = ora6();
1029
1787
  const cwd = process.cwd();
1030
1788
  console.log();
1031
- console.log(chalk4.bold("Deploying Agent"));
1789
+ console.log(chalk6.bold("Deploying Agent"));
1790
+ console.log();
1791
+ if (!hasProject(cwd)) {
1792
+ console.log(chalk6.yellow("No struere.json found"));
1793
+ console.log();
1794
+ console.log(chalk6.gray("Run"), chalk6.cyan("struere init"), chalk6.gray("to initialize this project"));
1795
+ console.log();
1796
+ process.exit(1);
1797
+ }
1798
+ const project = loadProject(cwd);
1799
+ if (!project) {
1800
+ console.log(chalk6.red("Failed to load struere.json"));
1801
+ process.exit(1);
1802
+ }
1803
+ console.log(chalk6.gray("Agent:"), chalk6.cyan(project.agent.name));
1032
1804
  console.log();
1033
1805
  spinner.start("Loading configuration");
1034
- const config = await loadConfig(cwd);
1806
+ await loadConfig(cwd);
1035
1807
  spinner.succeed("Configuration loaded");
1036
1808
  spinner.start("Loading agent");
1037
1809
  const agent = await loadAgent(cwd);
@@ -1042,7 +1814,7 @@ var deployCommand = new Command4("deploy").description("Deploy agent to Agent Fa
1042
1814
  spinner.fail("Validation failed");
1043
1815
  console.log();
1044
1816
  for (const error of errors) {
1045
- console.log(chalk4.red(" "), error);
1817
+ console.log(chalk6.red(" x"), error);
1046
1818
  }
1047
1819
  console.log();
1048
1820
  process.exit(1);
@@ -1050,12 +1822,13 @@ var deployCommand = new Command4("deploy").description("Deploy agent to Agent Fa
1050
1822
  spinner.succeed("Agent validated");
1051
1823
  if (options.dryRun) {
1052
1824
  console.log();
1053
- console.log(chalk4.yellow("Dry run mode - no changes will be made"));
1825
+ console.log(chalk6.yellow("Dry run mode - no changes will be made"));
1054
1826
  console.log();
1055
1827
  console.log("Would deploy:");
1056
- console.log(chalk4.gray(" "), `Agent: ${chalk4.cyan(agent.name)}`);
1057
- console.log(chalk4.gray(" "), `Version: ${chalk4.cyan(agent.version)}`);
1058
- console.log(chalk4.gray(" "), `Environment: ${chalk4.cyan(options.env)}`);
1828
+ console.log(chalk6.gray(" -"), `Agent: ${chalk6.cyan(agent.name)}`);
1829
+ console.log(chalk6.gray(" -"), `Version: ${chalk6.cyan(agent.version)}`);
1830
+ console.log(chalk6.gray(" -"), `Environment: ${chalk6.cyan(options.env)}`);
1831
+ console.log(chalk6.gray(" -"), `Agent ID: ${chalk6.cyan(project.agentId)}`);
1059
1832
  console.log();
1060
1833
  return;
1061
1834
  }
@@ -1064,14 +1837,14 @@ var deployCommand = new Command4("deploy").description("Deploy agent to Agent Fa
1064
1837
  if (!credentials && !apiKey) {
1065
1838
  spinner.fail("Not authenticated");
1066
1839
  console.log();
1067
- console.log(chalk4.gray("Run"), chalk4.cyan("af login"), chalk4.gray("to authenticate"));
1068
- console.log(chalk4.gray("Or set"), chalk4.cyan("AGENT_FACTORY_API_KEY"), chalk4.gray("environment variable"));
1840
+ console.log(chalk6.gray("Run"), chalk6.cyan("struere login"), chalk6.gray("to authenticate"));
1841
+ console.log(chalk6.gray("Or set"), chalk6.cyan("STRUERE_API_KEY"), chalk6.gray("environment variable"));
1069
1842
  console.log();
1070
1843
  process.exit(1);
1071
1844
  }
1072
1845
  spinner.start("Building agent bundle");
1073
1846
  const result = await Bun.build({
1074
- entrypoints: [join7(cwd, "src", "agent.ts")],
1847
+ entrypoints: [join10(cwd, "src", "agent.ts")],
1075
1848
  target: "browser",
1076
1849
  minify: true
1077
1850
  });
@@ -1079,7 +1852,7 @@ var deployCommand = new Command4("deploy").description("Deploy agent to Agent Fa
1079
1852
  spinner.fail("Build failed");
1080
1853
  console.log();
1081
1854
  for (const log of result.logs) {
1082
- console.log(chalk4.red(" "), log);
1855
+ console.log(chalk6.red(" -"), log);
1083
1856
  }
1084
1857
  console.log();
1085
1858
  process.exit(1);
@@ -1089,23 +1862,7 @@ var deployCommand = new Command4("deploy").description("Deploy agent to Agent Fa
1089
1862
  spinner.start(`Deploying to ${options.env}`);
1090
1863
  try {
1091
1864
  const api = new ApiClient;
1092
- const agentSlug = agent.name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
1093
- let agentRecord = null;
1094
- try {
1095
- const { agents } = await api.listAgents();
1096
- agentRecord = agents.find((a) => a.slug === agentSlug) || null;
1097
- } catch {}
1098
- if (!agentRecord) {
1099
- spinner.text = "Creating agent";
1100
- const { agent: newAgent } = await api.createAgent({
1101
- name: agent.name,
1102
- slug: agentSlug,
1103
- description: agent.description
1104
- });
1105
- agentRecord = newAgent;
1106
- }
1107
- spinner.text = `Deploying to ${options.env}`;
1108
- const { deployment } = await api.deployAgent(agentRecord.id, {
1865
+ const { deployment } = await api.deployAgent(project.agentId, {
1109
1866
  bundle,
1110
1867
  version: agent.version,
1111
1868
  environment: options.env,
@@ -1118,28 +1875,28 @@ var deployCommand = new Command4("deploy").description("Deploy agent to Agent Fa
1118
1875
  });
1119
1876
  spinner.succeed(`Deployed to ${options.env}`);
1120
1877
  console.log();
1121
- console.log(chalk4.green("Success!"), "Agent deployed");
1878
+ console.log(chalk6.green("Success!"), "Agent deployed");
1122
1879
  console.log();
1123
1880
  console.log("Deployment details:");
1124
- console.log(chalk4.gray(" "), `ID: ${chalk4.cyan(deployment.id)}`);
1125
- console.log(chalk4.gray(" "), `Version: ${chalk4.cyan(deployment.version)}`);
1126
- console.log(chalk4.gray(" "), `Environment: ${chalk4.cyan(deployment.environment)}`);
1127
- console.log(chalk4.gray(" "), `URL: ${chalk4.cyan(deployment.url)}`);
1881
+ console.log(chalk6.gray(" -"), `ID: ${chalk6.cyan(deployment.id)}`);
1882
+ console.log(chalk6.gray(" -"), `Version: ${chalk6.cyan(deployment.version)}`);
1883
+ console.log(chalk6.gray(" -"), `Environment: ${chalk6.cyan(deployment.environment)}`);
1884
+ console.log(chalk6.gray(" -"), `URL: ${chalk6.cyan(deployment.url)}`);
1128
1885
  console.log();
1129
- console.log(chalk4.gray("Test your agent:"));
1130
- console.log(chalk4.gray(" $"), chalk4.cyan(`curl -X POST ${deployment.url}/chat -H "Authorization: Bearer YOUR_API_KEY" -d '{"message": "Hello"}'`));
1886
+ console.log(chalk6.gray("Test your agent:"));
1887
+ console.log(chalk6.gray(" $"), chalk6.cyan(`curl -X POST ${deployment.url}/chat -H "Authorization: Bearer YOUR_API_KEY" -d '{"message": "Hello"}'`));
1131
1888
  console.log();
1132
1889
  } catch (error) {
1133
1890
  spinner.fail("Deployment failed");
1134
1891
  console.log();
1135
1892
  if (error instanceof ApiError) {
1136
- console.log(chalk4.red("Error:"), error.message);
1893
+ console.log(chalk6.red("Error:"), error.message);
1137
1894
  if (error.status === 401) {
1138
1895
  console.log();
1139
- console.log(chalk4.gray("Try running"), chalk4.cyan("af login"), chalk4.gray("to re-authenticate"));
1896
+ console.log(chalk6.gray("Try running"), chalk6.cyan("struere login"), chalk6.gray("to re-authenticate"));
1140
1897
  }
1141
1898
  } else {
1142
- console.log(chalk4.red("Error:"), error instanceof Error ? error.message : String(error));
1899
+ console.log(chalk6.red("Error:"), error instanceof Error ? error.message : String(error));
1143
1900
  }
1144
1901
  console.log();
1145
1902
  process.exit(1);
@@ -1155,14 +1912,14 @@ function formatBytes(bytes) {
1155
1912
  }
1156
1913
 
1157
1914
  // src/commands/validate.ts
1158
- import { Command as Command5 } from "commander";
1159
- import chalk5 from "chalk";
1160
- import ora5 from "ora";
1161
- var validateCommand = new Command5("validate").description("Validate agent configuration").option("--strict", "Enable strict validation").action(async (options) => {
1162
- const spinner = ora5();
1915
+ import { Command as Command7 } from "commander";
1916
+ import chalk7 from "chalk";
1917
+ import ora7 from "ora";
1918
+ var validateCommand = new Command7("validate").description("Validate agent configuration").option("--strict", "Enable strict validation").action(async (options) => {
1919
+ const spinner = ora7();
1163
1920
  const cwd = process.cwd();
1164
1921
  console.log();
1165
- console.log(chalk5.bold("Validating Agent"));
1922
+ console.log(chalk7.bold("Validating Agent"));
1166
1923
  console.log();
1167
1924
  spinner.start("Loading agent");
1168
1925
  let agent;
@@ -1172,7 +1929,7 @@ var validateCommand = new Command5("validate").description("Validate agent confi
1172
1929
  } catch (error) {
1173
1930
  spinner.fail("Failed to load agent");
1174
1931
  console.log();
1175
- console.log(chalk5.red("Error:"), error instanceof Error ? error.message : String(error));
1932
+ console.log(chalk7.red("Error:"), error instanceof Error ? error.message : String(error));
1176
1933
  console.log();
1177
1934
  process.exit(1);
1178
1935
  }
@@ -1182,25 +1939,25 @@ var validateCommand = new Command5("validate").description("Validate agent confi
1182
1939
  if (errors.length === 0 && warnings.length === 0) {
1183
1940
  spinner.succeed("Agent is valid");
1184
1941
  console.log();
1185
- console.log(chalk5.green(""), "No issues found");
1942
+ console.log(chalk7.green("\u2713"), "No issues found");
1186
1943
  console.log();
1187
1944
  return;
1188
1945
  }
1189
1946
  if (errors.length > 0) {
1190
1947
  spinner.fail("Validation failed");
1191
1948
  console.log();
1192
- console.log(chalk5.red("Errors:"));
1949
+ console.log(chalk7.red("Errors:"));
1193
1950
  for (const error of errors) {
1194
- console.log(chalk5.red(" "), error);
1951
+ console.log(chalk7.red(" \u2717"), error);
1195
1952
  }
1196
1953
  } else {
1197
1954
  spinner.succeed("Validation passed with warnings");
1198
1955
  }
1199
1956
  if (warnings.length > 0) {
1200
1957
  console.log();
1201
- console.log(chalk5.yellow("Warnings:"));
1958
+ console.log(chalk7.yellow("Warnings:"));
1202
1959
  for (const warning of warnings) {
1203
- console.log(chalk5.yellow(" "), warning);
1960
+ console.log(chalk7.yellow(" \u26A0"), warning);
1204
1961
  }
1205
1962
  }
1206
1963
  console.log();
@@ -1220,20 +1977,20 @@ function getStrictWarnings(agent) {
1220
1977
  }
1221
1978
 
1222
1979
  // src/commands/logs.ts
1223
- import { Command as Command6 } from "commander";
1224
- import chalk6 from "chalk";
1225
- import ora6 from "ora";
1226
- var logsCommand = new Command6("logs").description("Stream production logs").option("-e, --env <environment>", "Environment to stream from", "production").option("-f, --follow", "Follow log output").option("-n, --lines <number>", "Number of lines to show", "100").action(async (options) => {
1227
- const spinner = ora6();
1980
+ import { Command as Command8 } from "commander";
1981
+ import chalk8 from "chalk";
1982
+ import ora8 from "ora";
1983
+ var logsCommand = new Command8("logs").description("Stream production logs").option("-e, --env <environment>", "Environment to stream from", "production").option("-f, --follow", "Follow log output").option("-n, --lines <number>", "Number of lines to show", "100").action(async (options) => {
1984
+ const spinner = ora8();
1228
1985
  console.log();
1229
- console.log(chalk6.bold("Streaming Logs"));
1986
+ console.log(chalk8.bold("Streaming Logs"));
1230
1987
  console.log();
1231
1988
  const apiKey = process.env.STRUERE_API_KEY;
1232
1989
  if (!apiKey) {
1233
- console.log(chalk6.red("Error:"), "Missing STRUERE_API_KEY environment variable");
1990
+ console.log(chalk8.red("Error:"), "Missing STRUERE_API_KEY environment variable");
1234
1991
  console.log();
1235
1992
  console.log("Set your API key:");
1236
- console.log(chalk6.gray(" $"), chalk6.cyan("export STRUERE_API_KEY=your_api_key"));
1993
+ console.log(chalk8.gray(" $"), chalk8.cyan("export STRUERE_API_KEY=your_api_key"));
1237
1994
  console.log();
1238
1995
  process.exit(1);
1239
1996
  }
@@ -1250,13 +2007,13 @@ var logsCommand = new Command6("logs").description("Stream production logs").opt
1250
2007
  ws.onopen = () => {
1251
2008
  spinner.succeed(`Connected to ${options.env}`);
1252
2009
  console.log();
1253
- console.log(chalk6.gray("Streaming logs... (Ctrl+C to stop)"));
2010
+ console.log(chalk8.gray("Streaming logs... (Ctrl+C to stop)"));
1254
2011
  console.log();
1255
2012
  };
1256
2013
  ws.onmessage = (event) => {
1257
2014
  const log = JSON.parse(event.data);
1258
- const levelColor = log.level === "error" ? chalk6.red : log.level === "warn" ? chalk6.yellow : chalk6.gray;
1259
- console.log(chalk6.gray(new Date(log.timestamp).toISOString()), levelColor(`[${log.level}]`), log.message);
2015
+ const levelColor = log.level === "error" ? chalk8.red : log.level === "warn" ? chalk8.yellow : chalk8.gray;
2016
+ console.log(chalk8.gray(new Date(log.timestamp).toISOString()), levelColor(`[${log.level}]`), log.message);
1260
2017
  };
1261
2018
  ws.onerror = () => {
1262
2019
  spinner.fail("Connection error");
@@ -1264,7 +2021,7 @@ var logsCommand = new Command6("logs").description("Stream production logs").opt
1264
2021
  };
1265
2022
  ws.onclose = () => {
1266
2023
  console.log();
1267
- console.log(chalk6.gray("Connection closed"));
2024
+ console.log(chalk8.gray("Connection closed"));
1268
2025
  process.exit(0);
1269
2026
  };
1270
2027
  process.on("SIGINT", () => {
@@ -1274,7 +2031,7 @@ var logsCommand = new Command6("logs").description("Stream production logs").opt
1274
2031
  } catch (error) {
1275
2032
  spinner.fail("Failed to connect");
1276
2033
  console.log();
1277
- console.log(chalk6.red("Error:"), error instanceof Error ? error.message : String(error));
2034
+ console.log(chalk8.red("Error:"), error instanceof Error ? error.message : String(error));
1278
2035
  console.log();
1279
2036
  process.exit(1);
1280
2037
  }
@@ -1293,14 +2050,14 @@ var logsCommand = new Command6("logs").description("Stream production logs").opt
1293
2050
  spinner.succeed(`Fetched ${logs.length} log entries`);
1294
2051
  console.log();
1295
2052
  for (const log of logs) {
1296
- const levelColor = log.level === "error" ? chalk6.red : log.level === "warn" ? chalk6.yellow : chalk6.gray;
1297
- console.log(chalk6.gray(new Date(log.timestamp).toISOString()), levelColor(`[${log.level}]`), log.message);
2053
+ const levelColor = log.level === "error" ? chalk8.red : log.level === "warn" ? chalk8.yellow : chalk8.gray;
2054
+ console.log(chalk8.gray(new Date(log.timestamp).toISOString()), levelColor(`[${log.level}]`), log.message);
1298
2055
  }
1299
2056
  console.log();
1300
2057
  } catch (error) {
1301
2058
  spinner.fail("Failed to fetch logs");
1302
2059
  console.log();
1303
- console.log(chalk6.red("Error:"), error instanceof Error ? error.message : String(error));
2060
+ console.log(chalk8.red("Error:"), error instanceof Error ? error.message : String(error));
1304
2061
  console.log();
1305
2062
  process.exit(1);
1306
2063
  }
@@ -1308,20 +2065,20 @@ var logsCommand = new Command6("logs").description("Stream production logs").opt
1308
2065
  });
1309
2066
 
1310
2067
  // src/commands/state.ts
1311
- import { Command as Command7 } from "commander";
1312
- import chalk7 from "chalk";
1313
- import ora7 from "ora";
1314
- var stateCommand = new Command7("state").description("Inspect conversation state").argument("<id>", "Conversation ID").option("-e, --env <environment>", "Environment", "production").option("--json", "Output as JSON").action(async (id, options) => {
1315
- const spinner = ora7();
2068
+ import { Command as Command9 } from "commander";
2069
+ import chalk9 from "chalk";
2070
+ import ora9 from "ora";
2071
+ var stateCommand = new Command9("state").description("Inspect conversation state").argument("<id>", "Conversation ID").option("-e, --env <environment>", "Environment", "production").option("--json", "Output as JSON").action(async (id, options) => {
2072
+ const spinner = ora9();
1316
2073
  console.log();
1317
- console.log(chalk7.bold("Conversation State"));
2074
+ console.log(chalk9.bold("Conversation State"));
1318
2075
  console.log();
1319
2076
  const apiKey = process.env.STRUERE_API_KEY;
1320
2077
  if (!apiKey) {
1321
- console.log(chalk7.red("Error:"), "Missing STRUERE_API_KEY environment variable");
2078
+ console.log(chalk9.red("Error:"), "Missing STRUERE_API_KEY environment variable");
1322
2079
  console.log();
1323
2080
  console.log("Set your API key:");
1324
- console.log(chalk7.gray(" $"), chalk7.cyan("export STRUERE_API_KEY=your_api_key"));
2081
+ console.log(chalk9.gray(" $"), chalk9.cyan("export STRUERE_API_KEY=your_api_key"));
1325
2082
  console.log();
1326
2083
  process.exit(1);
1327
2084
  }
@@ -1348,349 +2105,111 @@ var stateCommand = new Command7("state").description("Inspect conversation state
1348
2105
  return;
1349
2106
  }
1350
2107
  console.log();
1351
- console.log(chalk7.gray("Conversation:"), chalk7.cyan(state.conversationId));
1352
- console.log(chalk7.gray("Created:"), new Date(state.createdAt).toLocaleString());
1353
- console.log(chalk7.gray("Updated:"), new Date(state.updatedAt).toLocaleString());
1354
- console.log(chalk7.gray("Messages:"), state.messageCount);
2108
+ console.log(chalk9.gray("Conversation:"), chalk9.cyan(state.conversationId));
2109
+ console.log(chalk9.gray("Created:"), new Date(state.createdAt).toLocaleString());
2110
+ console.log(chalk9.gray("Updated:"), new Date(state.updatedAt).toLocaleString());
2111
+ console.log(chalk9.gray("Messages:"), state.messageCount);
1355
2112
  console.log();
1356
- console.log(chalk7.bold("State:"));
2113
+ console.log(chalk9.bold("State:"));
1357
2114
  if (Object.keys(state.state).length === 0) {
1358
- console.log(chalk7.gray(" (empty)"));
2115
+ console.log(chalk9.gray(" (empty)"));
1359
2116
  } else {
1360
2117
  for (const [key, value] of Object.entries(state.state)) {
1361
2118
  const displayValue = typeof value === "object" ? JSON.stringify(value) : String(value);
1362
- console.log(chalk7.gray(" "), `${key}:`, chalk7.cyan(displayValue));
2119
+ console.log(chalk9.gray(" \u2022"), `${key}:`, chalk9.cyan(displayValue));
1363
2120
  }
1364
2121
  }
1365
2122
  console.log();
1366
2123
  } catch (error) {
1367
2124
  spinner.fail("Failed to fetch state");
1368
2125
  console.log();
1369
- console.log(chalk7.red("Error:"), error instanceof Error ? error.message : String(error));
2126
+ console.log(chalk9.red("Error:"), error instanceof Error ? error.message : String(error));
1370
2127
  console.log();
1371
2128
  process.exit(1);
1372
2129
  }
1373
2130
  });
1374
2131
 
1375
- // src/commands/login.ts
1376
- import { Command as Command8 } from "commander";
1377
- import chalk8 from "chalk";
1378
- import ora8 from "ora";
1379
- var CLERK_PUBLISHABLE_KEY = process.env.CLERK_PUBLISHABLE_KEY || "pk_test_placeholder";
1380
- var AUTH_CALLBACK_PORT = 9876;
1381
- var loginCommand = new Command8("login").description("Log in to Agent Factory").option("--headless", "Login with email/password (no browser)").action(async (options) => {
1382
- const spinner = ora8();
1383
- console.log();
1384
- console.log(chalk8.bold("Struere Login"));
1385
- console.log();
1386
- const existing = loadCredentials();
1387
- if (existing) {
1388
- console.log(chalk8.yellow("Already logged in as"), chalk8.cyan(existing.user.email));
1389
- console.log(chalk8.gray("Run"), chalk8.cyan("af logout"), chalk8.gray("to log out first"));
1390
- console.log();
1391
- return;
1392
- }
1393
- if (options.headless) {
1394
- await headlessLogin(spinner);
1395
- } else {
1396
- await browserLogin(spinner);
1397
- }
1398
- });
1399
- async function browserLogin(spinner) {
1400
- spinner.start("Starting authentication server");
1401
- const authPromise = new Promise((resolve, reject) => {
1402
- const server = Bun.serve({
1403
- port: AUTH_CALLBACK_PORT,
1404
- async fetch(req) {
1405
- const url = new URL(req.url);
1406
- if (url.pathname === "/callback") {
1407
- const token = url.searchParams.get("token");
1408
- const sessionId = url.searchParams.get("session_id");
1409
- if (token && sessionId) {
1410
- resolve({ token, sessionId });
1411
- return new Response(getSuccessHtml(), {
1412
- headers: { "Content-Type": "text/html" }
1413
- });
1414
- }
1415
- return new Response(getErrorHtml("Missing token"), {
1416
- status: 400,
1417
- headers: { "Content-Type": "text/html" }
1418
- });
1419
- }
1420
- if (url.pathname === "/") {
1421
- const authUrl = getAuthUrl();
1422
- return Response.redirect(authUrl, 302);
1423
- }
1424
- return new Response("Not Found", { status: 404 });
1425
- }
1426
- });
1427
- setTimeout(() => {
1428
- server.stop();
1429
- reject(new Error("Authentication timed out"));
1430
- }, 5 * 60 * 1000);
1431
- });
1432
- spinner.succeed("Authentication server started");
1433
- const loginUrl = `http://localhost:${AUTH_CALLBACK_PORT}`;
1434
- console.log();
1435
- console.log(chalk8.gray("Opening browser to log in..."));
1436
- console.log(chalk8.gray("If browser does not open, visit:"), chalk8.cyan(loginUrl));
1437
- console.log();
1438
- if (process.platform === "darwin") {
1439
- Bun.spawn(["open", loginUrl]);
1440
- } else if (process.platform === "linux") {
1441
- Bun.spawn(["xdg-open", loginUrl]);
1442
- } else if (process.platform === "win32") {
1443
- Bun.spawn(["cmd", "/c", "start", loginUrl]);
1444
- }
1445
- spinner.start("Waiting for authentication");
1446
- try {
1447
- const { token, sessionId } = await authPromise;
1448
- spinner.text = "Fetching user info";
1449
- const api = new ApiClient;
1450
- const { user, organization } = await api.getMe();
1451
- saveCredentials({
1452
- token,
1453
- user: {
1454
- id: user.id,
1455
- email: user.email,
1456
- name: user.name,
1457
- organizationId: user.organizationId
1458
- },
1459
- organization: {
1460
- id: organization.id,
1461
- name: organization.name,
1462
- slug: organization.slug
1463
- },
1464
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
1465
- });
1466
- spinner.succeed("Logged in successfully");
1467
- console.log();
1468
- console.log(chalk8.green("Welcome,"), chalk8.cyan(user.name));
1469
- console.log(chalk8.gray("Organization:"), organization.name);
1470
- console.log();
1471
- printNextSteps();
1472
- } catch (error) {
1473
- spinner.fail("Login failed");
1474
- console.log();
1475
- console.log(chalk8.red("Error:"), error instanceof Error ? error.message : String(error));
1476
- console.log();
1477
- console.log(chalk8.gray("Try"), chalk8.cyan("af login --headless"), chalk8.gray("for email/password login"));
1478
- console.log();
1479
- process.exit(1);
1480
- }
1481
- }
1482
- async function headlessLogin(spinner) {
1483
- const email = await prompt("Email: ");
1484
- const password = await prompt("Password: ", true);
1485
- if (!email || !password) {
1486
- console.log(chalk8.red("Email and password are required"));
1487
- process.exit(1);
1488
- }
1489
- spinner.start("Logging in");
1490
- try {
1491
- const api = new ApiClient;
1492
- const { token, user } = await api.login(email, password);
1493
- const { organization } = await api.getMe();
1494
- saveCredentials({
1495
- token,
1496
- user: {
1497
- id: user.id,
1498
- email: user.email,
1499
- name: user.name,
1500
- organizationId: user.organizationId
1501
- },
1502
- organization: {
1503
- id: organization.id,
1504
- name: organization.name,
1505
- slug: organization.slug
1506
- },
1507
- expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
1508
- });
1509
- spinner.succeed("Logged in successfully");
1510
- console.log();
1511
- console.log(chalk8.green("Welcome,"), chalk8.cyan(user.name));
1512
- console.log(chalk8.gray("Organization:"), organization.name);
1513
- console.log();
1514
- printNextSteps();
1515
- } catch (error) {
1516
- spinner.fail("Login failed");
1517
- console.log();
1518
- if (error instanceof ApiError) {
1519
- console.log(chalk8.red("Error:"), error.message);
1520
- } else {
1521
- console.log(chalk8.red("Error:"), error instanceof Error ? error.message : String(error));
1522
- }
1523
- console.log();
1524
- process.exit(1);
1525
- }
1526
- }
1527
- function printNextSteps() {
1528
- console.log(chalk8.gray("You can now use:"));
1529
- console.log(chalk8.gray(" •"), chalk8.cyan("af dev"), chalk8.gray("- Start cloud-connected dev server"));
1530
- console.log(chalk8.gray(" •"), chalk8.cyan("af deploy"), chalk8.gray("- Deploy your agent"));
1531
- console.log(chalk8.gray(" •"), chalk8.cyan("af logs"), chalk8.gray("- View agent logs"));
1532
- console.log();
1533
- }
1534
- function getAuthUrl() {
1535
- const baseUrl = process.env.STRUERE_AUTH_URL || "https://struere.dev";
1536
- const callbackUrl = `http://localhost:${AUTH_CALLBACK_PORT}/callback`;
1537
- return `${baseUrl}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
1538
- }
1539
- function getSuccessHtml() {
1540
- return `<!DOCTYPE html>
1541
- <html>
1542
- <head>
1543
- <title>Login Successful</title>
1544
- <style>
1545
- body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0a0a0a; color: #fafafa; }
1546
- .container { text-align: center; }
1547
- h1 { color: #22c55e; }
1548
- p { color: #888; }
1549
- </style>
1550
- </head>
1551
- <body>
1552
- <div class="container">
1553
- <h1>Login Successful</h1>
1554
- <p>You can close this window and return to the terminal.</p>
1555
- </div>
1556
- <script>setTimeout(() => window.close(), 3000)</script>
1557
- </body>
1558
- </html>`;
1559
- }
1560
- function getErrorHtml(message) {
1561
- return `<!DOCTYPE html>
1562
- <html>
1563
- <head>
1564
- <title>Login Failed</title>
1565
- <style>
1566
- body { font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #0a0a0a; color: #fafafa; }
1567
- .container { text-align: center; }
1568
- h1 { color: #ef4444; }
1569
- p { color: #888; }
1570
- </style>
1571
- </head>
1572
- <body>
1573
- <div class="container">
1574
- <h1>Login Failed</h1>
1575
- <p>${message}</p>
1576
- </div>
1577
- </body>
1578
- </html>`;
1579
- }
1580
- async function prompt(message, hidden = false) {
1581
- process.stdout.write(chalk8.gray(message));
1582
- return new Promise((resolve) => {
1583
- let input = "";
1584
- if (hidden) {
1585
- process.stdin.setRawMode(true);
1586
- }
1587
- process.stdin.resume();
1588
- process.stdin.setEncoding("utf8");
1589
- const onData = (char) => {
1590
- if (char === `
1591
- ` || char === "\r") {
1592
- process.stdin.removeListener("data", onData);
1593
- process.stdin.pause();
1594
- if (hidden) {
1595
- process.stdin.setRawMode(false);
1596
- console.log();
1597
- }
1598
- resolve(input);
1599
- } else if (char === "\x03") {
1600
- process.exit();
1601
- } else if (char === "") {
1602
- input = input.slice(0, -1);
1603
- } else {
1604
- input += char;
1605
- if (!hidden) {
1606
- process.stdout.write(char);
1607
- }
1608
- }
1609
- };
1610
- process.stdin.on("data", onData);
1611
- });
1612
- }
1613
-
1614
2132
  // src/commands/logout.ts
1615
- import { Command as Command9 } from "commander";
1616
- import chalk9 from "chalk";
1617
- var logoutCommand = new Command9("logout").description("Log out of Agent Factory").action(async () => {
2133
+ import { Command as Command10 } from "commander";
2134
+ import chalk10 from "chalk";
2135
+ var logoutCommand = new Command10("logout").description("Log out of Struere").action(async () => {
1618
2136
  console.log();
1619
2137
  const credentials = loadCredentials();
1620
2138
  if (!credentials) {
1621
- console.log(chalk9.yellow("Not currently logged in"));
2139
+ console.log(chalk10.yellow("Not currently logged in"));
1622
2140
  console.log();
1623
2141
  return;
1624
2142
  }
1625
2143
  clearCredentials();
1626
- console.log(chalk9.green("Logged out successfully"));
1627
- console.log(chalk9.gray("Goodbye,"), chalk9.cyan(credentials.user.name));
2144
+ console.log(chalk10.green("Logged out successfully"));
2145
+ console.log(chalk10.gray("Goodbye,"), chalk10.cyan(credentials.user.name));
1628
2146
  console.log();
1629
2147
  });
1630
2148
 
1631
2149
  // src/commands/whoami.ts
1632
- import { Command as Command10 } from "commander";
1633
- import chalk10 from "chalk";
1634
- import ora9 from "ora";
1635
- var whoamiCommand = new Command10("whoami").description("Show current logged in user").option("--refresh", "Refresh user info from server").action(async (options) => {
2150
+ import { Command as Command11 } from "commander";
2151
+ import chalk11 from "chalk";
2152
+ import ora10 from "ora";
2153
+ var whoamiCommand = new Command11("whoami").description("Show current logged in user").option("--refresh", "Refresh user info from server").action(async (options) => {
1636
2154
  console.log();
1637
2155
  const credentials = loadCredentials();
1638
2156
  if (!credentials) {
1639
- console.log(chalk10.yellow("Not logged in"));
2157
+ console.log(chalk11.yellow("Not logged in"));
1640
2158
  console.log();
1641
- console.log(chalk10.gray("Run"), chalk10.cyan("af login"), chalk10.gray("to log in"));
2159
+ console.log(chalk11.gray("Run"), chalk11.cyan("struere login"), chalk11.gray("to log in"));
1642
2160
  console.log();
1643
2161
  return;
1644
2162
  }
1645
2163
  if (options.refresh) {
1646
- const spinner = ora9("Fetching user info").start();
2164
+ const spinner = ora10("Fetching user info").start();
1647
2165
  try {
1648
2166
  const api = new ApiClient;
1649
2167
  const { user, organization } = await api.getMe();
1650
2168
  spinner.stop();
1651
- console.log(chalk10.bold("Logged in as:"));
2169
+ console.log(chalk11.bold("Logged in as:"));
1652
2170
  console.log();
1653
- console.log(chalk10.gray(" User: "), chalk10.cyan(user.name), chalk10.gray(`<${user.email}>`));
1654
- console.log(chalk10.gray(" User ID: "), chalk10.gray(user.id));
1655
- console.log(chalk10.gray(" Role: "), chalk10.cyan(user.role));
2171
+ console.log(chalk11.gray(" User: "), chalk11.cyan(user.name), chalk11.gray(`<${user.email}>`));
2172
+ console.log(chalk11.gray(" User ID: "), chalk11.gray(user.id));
2173
+ console.log(chalk11.gray(" Role: "), chalk11.cyan(user.role));
1656
2174
  console.log();
1657
- console.log(chalk10.gray(" Organization:"), chalk10.cyan(organization.name));
1658
- console.log(chalk10.gray(" Org ID: "), chalk10.gray(organization.id));
1659
- console.log(chalk10.gray(" Slug: "), chalk10.cyan(organization.slug));
1660
- console.log(chalk10.gray(" Plan: "), chalk10.cyan(organization.plan));
2175
+ console.log(chalk11.gray(" Organization:"), chalk11.cyan(organization.name));
2176
+ console.log(chalk11.gray(" Org ID: "), chalk11.gray(organization.id));
2177
+ console.log(chalk11.gray(" Slug: "), chalk11.cyan(organization.slug));
2178
+ console.log(chalk11.gray(" Plan: "), chalk11.cyan(organization.plan));
1661
2179
  console.log();
1662
2180
  } catch (error) {
1663
2181
  spinner.fail("Failed to fetch user info");
1664
2182
  console.log();
1665
2183
  if (error instanceof ApiError) {
1666
2184
  if (error.status === 401) {
1667
- console.log(chalk10.red("Session expired. Please log in again."));
2185
+ console.log(chalk11.red("Session expired. Please log in again."));
1668
2186
  } else {
1669
- console.log(chalk10.red("Error:"), error.message);
2187
+ console.log(chalk11.red("Error:"), error.message);
1670
2188
  }
1671
2189
  } else {
1672
- console.log(chalk10.red("Error:"), error instanceof Error ? error.message : String(error));
2190
+ console.log(chalk11.red("Error:"), error instanceof Error ? error.message : String(error));
1673
2191
  }
1674
2192
  console.log();
1675
2193
  process.exit(1);
1676
2194
  }
1677
2195
  } else {
1678
- console.log(chalk10.bold("Logged in as:"));
2196
+ console.log(chalk11.bold("Logged in as:"));
1679
2197
  console.log();
1680
- console.log(chalk10.gray(" User: "), chalk10.cyan(credentials.user.name), chalk10.gray(`<${credentials.user.email}>`));
1681
- console.log(chalk10.gray(" User ID: "), chalk10.gray(credentials.user.id));
2198
+ console.log(chalk11.gray(" User: "), chalk11.cyan(credentials.user.name), chalk11.gray(`<${credentials.user.email}>`));
2199
+ console.log(chalk11.gray(" User ID: "), chalk11.gray(credentials.user.id));
1682
2200
  console.log();
1683
- console.log(chalk10.gray(" Organization:"), chalk10.cyan(credentials.organization.name));
1684
- console.log(chalk10.gray(" Org ID: "), chalk10.gray(credentials.organization.id));
1685
- console.log(chalk10.gray(" Slug: "), chalk10.cyan(credentials.organization.slug));
2201
+ console.log(chalk11.gray(" Organization:"), chalk11.cyan(credentials.organization.name));
2202
+ console.log(chalk11.gray(" Org ID: "), chalk11.gray(credentials.organization.id));
2203
+ console.log(chalk11.gray(" Slug: "), chalk11.cyan(credentials.organization.slug));
1686
2204
  console.log();
1687
- console.log(chalk10.gray("Use"), chalk10.cyan("af whoami --refresh"), chalk10.gray("to fetch latest info"));
2205
+ console.log(chalk11.gray("Use"), chalk11.cyan("struere whoami --refresh"), chalk11.gray("to fetch latest info"));
1688
2206
  console.log();
1689
2207
  }
1690
2208
  });
1691
2209
 
1692
2210
  // src/index.ts
1693
- program.name("struere").description("Struere CLI - Build, test, and deploy AI agents").version("0.1.0");
2211
+ program.name("struere").description("Struere CLI - Build, test, and deploy AI agents").version("0.2.0");
2212
+ program.addCommand(initCommand);
1694
2213
  program.addCommand(loginCommand);
1695
2214
  program.addCommand(logoutCommand);
1696
2215
  program.addCommand(whoamiCommand);