@struere/cli 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1146 -627
- package/package.json +1 -1
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/
|
|
8
|
-
import { Command } from "commander";
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
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
|
|
16
|
+
import { join } from "path";
|
|
88
17
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
89
|
-
var CONFIG_DIR =
|
|
90
|
-
var CREDENTIALS_FILE =
|
|
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
|
-
|
|
133
|
-
|
|
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";
|
|
@@ -198,46 +129,967 @@ class ApiClient {
|
|
|
198
129
|
body: JSON.stringify(data)
|
|
199
130
|
});
|
|
200
131
|
}
|
|
201
|
-
async getAgent(agentId) {
|
|
202
|
-
return this.request(`/v1/agents/${agentId}`);
|
|
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}`);
|
|
203
902
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
+
}
|
|
209
919
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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 {}
|
|
215
937
|
}
|
|
216
|
-
|
|
217
|
-
|
|
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}`);
|
|
218
978
|
}
|
|
219
|
-
|
|
220
|
-
|
|
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"
|
|
1020
|
+
}
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
237
|
-
const spinner =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
391
|
-
console.log(
|
|
392
|
-
console.log(
|
|
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" ?
|
|
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(
|
|
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 = (
|
|
1169
|
+
ws.onerror = () => {
|
|
412
1170
|
spinner.fail("WebSocket error");
|
|
413
|
-
console.log(
|
|
1171
|
+
console.log(chalk3.red("Connection error"));
|
|
414
1172
|
};
|
|
415
1173
|
ws.onclose = () => {
|
|
416
1174
|
if (isConnected) {
|
|
417
1175
|
spinner.stop();
|
|
418
|
-
console.log(
|
|
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
|
|
457
|
-
return Response.json(
|
|
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([
|
|
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
|
-
|
|
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(
|
|
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: [
|
|
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', '
|
|
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', '
|
|
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',
|
|
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
|
|
714
|
-
import
|
|
715
|
-
import
|
|
716
|
-
import { join as
|
|
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
|
|
795
|
-
const spinner =
|
|
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(
|
|
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(
|
|
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 =
|
|
1577
|
+
const outdir = join8(cwd, options.outdir);
|
|
820
1578
|
const result = await Bun.build({
|
|
821
|
-
entrypoints: [
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
847
|
-
import
|
|
848
|
-
import
|
|
849
|
-
import { join as
|
|
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
|
|
853
|
-
var testCommand = new
|
|
854
|
-
const spinner =
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
1656
|
+
console.log(chalk5.green(" \u2713"), result.name);
|
|
899
1657
|
} else {
|
|
900
|
-
console.log(
|
|
1658
|
+
console.log(chalk5.red(" \u2717"), result.name);
|
|
901
1659
|
for (const error of result.errors) {
|
|
902
|
-
console.log(
|
|
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(
|
|
1668
|
+
console.log(chalk5.green("All tests passed!"), chalk5.gray(`(${passed}/${results.length})`));
|
|
911
1669
|
} else {
|
|
912
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
1024
|
-
import
|
|
1025
|
-
import
|
|
1026
|
-
import { join as
|
|
1027
|
-
var deployCommand = new
|
|
1028
|
-
const spinner =
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
1057
|
-
console.log(
|
|
1058
|
-
console.log(
|
|
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(
|
|
1068
|
-
console.log(
|
|
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: [
|
|
1847
|
+
entrypoints: [join10(cwd, "src", "agent.ts")],
|
|
1075
1848
|
target: "browser",
|
|
1076
1849
|
minify: true
|
|
1077
1850
|
});
|
|
@@ -1079,33 +1852,17 @@ 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(
|
|
1855
|
+
console.log(chalk6.red(" -"), log);
|
|
1083
1856
|
}
|
|
1084
1857
|
console.log();
|
|
1085
1858
|
process.exit(1);
|
|
1086
1859
|
}
|
|
1087
1860
|
const bundle = await result.outputs[0].text();
|
|
1088
1861
|
spinner.succeed(`Bundle created (${formatBytes(bundle.length)})`);
|
|
1089
|
-
spinner.start(`Deploying to ${options.env}`);
|
|
1090
|
-
try {
|
|
1091
|
-
const api = new ApiClient;
|
|
1092
|
-
const
|
|
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, {
|
|
1862
|
+
spinner.start(`Deploying to ${options.env}`);
|
|
1863
|
+
try {
|
|
1864
|
+
const api = new ApiClient;
|
|
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(
|
|
1878
|
+
console.log(chalk6.green("Success!"), "Agent deployed");
|
|
1122
1879
|
console.log();
|
|
1123
1880
|
console.log("Deployment details:");
|
|
1124
|
-
console.log(
|
|
1125
|
-
console.log(
|
|
1126
|
-
console.log(
|
|
1127
|
-
console.log(
|
|
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(
|
|
1130
|
-
console.log(
|
|
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(
|
|
1893
|
+
console.log(chalk6.red("Error:"), error.message);
|
|
1137
1894
|
if (error.status === 401) {
|
|
1138
1895
|
console.log();
|
|
1139
|
-
console.log(
|
|
1896
|
+
console.log(chalk6.gray("Try running"), chalk6.cyan("struere login"), chalk6.gray("to re-authenticate"));
|
|
1140
1897
|
}
|
|
1141
1898
|
} else {
|
|
1142
|
-
console.log(
|
|
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
|
|
1159
|
-
import
|
|
1160
|
-
import
|
|
1161
|
-
var validateCommand = new
|
|
1162
|
-
const spinner =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1949
|
+
console.log(chalk7.red("Errors:"));
|
|
1193
1950
|
for (const error of errors) {
|
|
1194
|
-
console.log(
|
|
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(
|
|
1958
|
+
console.log(chalk7.yellow("Warnings:"));
|
|
1202
1959
|
for (const warning of warnings) {
|
|
1203
|
-
console.log(
|
|
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
|
|
1224
|
-
import
|
|
1225
|
-
import
|
|
1226
|
-
var logsCommand = new
|
|
1227
|
-
const spinner =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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" ?
|
|
1259
|
-
console.log(
|
|
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(
|
|
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(
|
|
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" ?
|
|
1297
|
-
console.log(
|
|
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(
|
|
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
|
|
1312
|
-
import
|
|
1313
|
-
import
|
|
1314
|
-
var stateCommand = new
|
|
1315
|
-
const spinner =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1352
|
-
console.log(
|
|
1353
|
-
console.log(
|
|
1354
|
-
console.log(
|
|
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(
|
|
2113
|
+
console.log(chalk9.bold("State:"));
|
|
1357
2114
|
if (Object.keys(state.state).length === 0) {
|
|
1358
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(" \u2022"), chalk8.cyan("af dev"), chalk8.gray("- Start cloud-connected dev server"));
|
|
1530
|
-
console.log(chalk8.gray(" \u2022"), chalk8.cyan("af deploy"), chalk8.gray("- Deploy your agent"));
|
|
1531
|
-
console.log(chalk8.gray(" \u2022"), 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 === "\x7F") {
|
|
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
|
|
1616
|
-
import
|
|
1617
|
-
var logoutCommand = new
|
|
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(
|
|
2139
|
+
console.log(chalk10.yellow("Not currently logged in"));
|
|
1622
2140
|
console.log();
|
|
1623
2141
|
return;
|
|
1624
2142
|
}
|
|
1625
2143
|
clearCredentials();
|
|
1626
|
-
console.log(
|
|
1627
|
-
console.log(
|
|
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
|
|
1633
|
-
import
|
|
1634
|
-
import
|
|
1635
|
-
var whoamiCommand = new
|
|
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(
|
|
2157
|
+
console.log(chalk11.yellow("Not logged in"));
|
|
1640
2158
|
console.log();
|
|
1641
|
-
console.log(
|
|
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 =
|
|
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(
|
|
2169
|
+
console.log(chalk11.bold("Logged in as:"));
|
|
1652
2170
|
console.log();
|
|
1653
|
-
console.log(
|
|
1654
|
-
console.log(
|
|
1655
|
-
console.log(
|
|
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(
|
|
1658
|
-
console.log(
|
|
1659
|
-
console.log(
|
|
1660
|
-
console.log(
|
|
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(
|
|
2185
|
+
console.log(chalk11.red("Session expired. Please log in again."));
|
|
1668
2186
|
} else {
|
|
1669
|
-
console.log(
|
|
2187
|
+
console.log(chalk11.red("Error:"), error.message);
|
|
1670
2188
|
}
|
|
1671
2189
|
} else {
|
|
1672
|
-
console.log(
|
|
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(
|
|
2196
|
+
console.log(chalk11.bold("Logged in as:"));
|
|
1679
2197
|
console.log();
|
|
1680
|
-
console.log(
|
|
1681
|
-
console.log(
|
|
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(
|
|
1684
|
-
console.log(
|
|
1685
|
-
console.log(
|
|
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(
|
|
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
|
|
2211
|
+
program.name("struere").description("Struere CLI - Build, test, and deploy AI agents").version("0.2.1");
|
|
2212
|
+
program.addCommand(initCommand);
|
|
1694
2213
|
program.addCommand(loginCommand);
|
|
1695
2214
|
program.addCommand(logoutCommand);
|
|
1696
2215
|
program.addCommand(whoamiCommand);
|