@struere/cli 0.2.6 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +17 -264
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -976,11 +976,11 @@ async function loadAgent(cwd) {
976
976
  }
977
977
 
978
978
  // src/commands/dev.ts
979
- 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").option("--local", "Run locally without cloud sync").action(async (options) => {
979
+ var devCommand = new Command3("dev").description("Sync agent to development environment").action(async () => {
980
980
  const spinner = ora3();
981
981
  const cwd = process.cwd();
982
982
  console.log();
983
- console.log(chalk3.bold("Struere Dev Server"));
983
+ console.log(chalk3.bold("Struere Dev"));
984
984
  console.log();
985
985
  if (!hasProject(cwd)) {
986
986
  console.log(chalk3.yellow("No struere.json found"));
@@ -997,8 +997,7 @@ var devCommand = new Command3("dev").description("Start development server with
997
997
  console.log(chalk3.gray("Agent:"), chalk3.cyan(project.agent.name));
998
998
  console.log();
999
999
  spinner.start("Loading configuration");
1000
- const config = await loadConfig(cwd);
1001
- const port = parseInt(options.port) || config.port || 3000;
1000
+ await loadConfig(cwd);
1002
1001
  spinner.succeed("Configuration loaded");
1003
1002
  spinner.start("Loading agent");
1004
1003
  let agent = await loadAgent(cwd);
@@ -1012,16 +1011,10 @@ var devCommand = new Command3("dev").description("Start development server with
1012
1011
  console.log();
1013
1012
  process.exit(1);
1014
1013
  }
1015
- await runCloudDev(agent, project, cwd, port, options, spinner);
1016
- });
1017
- async function runCloudDev(agent, project, cwd, port, options, spinner) {
1018
- const credentials = loadCredentials();
1019
- const apiKey = getApiKey();
1020
1014
  spinner.start("Connecting to Struere Cloud");
1021
1015
  const syncUrl = getSyncUrl();
1022
1016
  const ws = new WebSocket(`${syncUrl}/v1/dev/sync`);
1023
1017
  let cloudUrl = null;
1024
- let sessionId = null;
1025
1018
  let isConnected = false;
1026
1019
  ws.onopen = () => {
1027
1020
  ws.send(JSON.stringify({
@@ -1047,23 +1040,19 @@ async function runCloudDev(agent, project, cwd, port, options, spinner) {
1047
1040
  case "synced":
1048
1041
  isConnected = true;
1049
1042
  cloudUrl = data.url || null;
1050
- sessionId = data.agentId || null;
1051
- spinner.succeed("Connected to Struere Cloud");
1043
+ spinner.succeed("Synced to development");
1052
1044
  console.log();
1053
- console.log(chalk3.gray("Mode:"), chalk3.green("Cloud"));
1054
- console.log(chalk3.green("Agent running at"), chalk3.cyan(cloudUrl));
1055
- console.log(chalk3.green("Local server at"), chalk3.cyan(`http://localhost:${port}`));
1045
+ console.log(chalk3.green("Development URL:"), chalk3.cyan(cloudUrl));
1046
+ console.log();
1047
+ console.log(chalk3.gray("Watching for changes... Press Ctrl+C to stop"));
1056
1048
  console.log();
1057
- spinner.start("Watching for changes");
1058
1049
  break;
1059
1050
  case "log":
1060
1051
  const logColor = data.level === "error" ? chalk3.red : data.level === "warn" ? chalk3.yellow : data.level === "debug" ? chalk3.gray : chalk3.blue;
1061
- spinner.stop();
1062
1052
  console.log(logColor(`[${data.level}]`), data.message);
1063
- spinner.start("Watching for changes");
1064
1053
  break;
1065
1054
  case "error":
1066
- spinner.fail(`Cloud error: ${data.message}`);
1055
+ spinner.fail(`Error: ${data.message}`);
1067
1056
  if (data.code === "INVALID_API_KEY" || data.code === "NOT_AUTHENTICATED") {
1068
1057
  console.log();
1069
1058
  console.log(chalk3.gray("Run"), chalk3.cyan("struere login"), chalk3.gray("to authenticate"));
@@ -1072,74 +1061,20 @@ async function runCloudDev(agent, project, cwd, port, options, spinner) {
1072
1061
  }
1073
1062
  };
1074
1063
  ws.onerror = () => {
1075
- spinner.fail("WebSocket error");
1076
- console.log(chalk3.red("Connection error"));
1064
+ spinner.fail("Connection error");
1077
1065
  };
1078
1066
  ws.onclose = () => {
1079
1067
  if (isConnected) {
1080
- spinner.stop();
1081
1068
  console.log(chalk3.yellow("Disconnected from cloud"));
1082
1069
  }
1083
1070
  };
1084
- const server = Bun.serve({
1085
- port,
1086
- async fetch(req) {
1087
- const url = new URL(req.url);
1088
- if (url.pathname === "/health") {
1089
- return Response.json({
1090
- status: "ok",
1091
- agent: agent.name,
1092
- mode: "cloud",
1093
- cloudUrl
1094
- });
1095
- }
1096
- if (url.pathname === "/api/chat" && req.method === "POST") {
1097
- if (!cloudUrl || !sessionId) {
1098
- return Response.json({ error: "Not connected to cloud" }, { status: 503 });
1099
- }
1100
- const body = await req.json();
1101
- const response = await fetch(`${process.env.STRUERE_GATEWAY_URL || "https://gateway.struere.dev"}/v1/dev/${sessionId}/chat`, {
1102
- method: "POST",
1103
- headers: {
1104
- "Content-Type": "application/json",
1105
- Authorization: `Bearer ${apiKey || credentials?.token}`
1106
- },
1107
- body: JSON.stringify(body)
1108
- });
1109
- if (body.stream) {
1110
- return new Response(response.body, {
1111
- headers: {
1112
- "Content-Type": "text/event-stream",
1113
- "Cache-Control": "no-cache",
1114
- Connection: "keep-alive"
1115
- }
1116
- });
1117
- }
1118
- const responseData = await response.json();
1119
- return Response.json(responseData);
1120
- }
1121
- if (url.pathname === "/" && options.channel === "web") {
1122
- return new Response(getDevHtml(agent.name, "cloud", cloudUrl), {
1123
- headers: { "Content-Type": "text/html" }
1124
- });
1125
- }
1126
- return new Response("Not Found", { status: 404 });
1127
- }
1128
- });
1129
- if (options.channel === "web" && options.open) {
1130
- const openUrl = `http://localhost:${port}`;
1131
- if (process.platform === "darwin") {
1132
- Bun.spawn(["open", openUrl]);
1133
- } else if (process.platform === "linux") {
1134
- Bun.spawn(["xdg-open", openUrl]);
1135
- }
1136
- }
1137
1071
  const watcher = chokidar.watch([join7(cwd, "src"), join7(cwd, "struere.config.ts")], {
1138
1072
  ignoreInitial: true,
1139
1073
  ignored: /node_modules/
1140
1074
  });
1141
1075
  watcher.on("change", async (path) => {
1142
- spinner.text = `Syncing (${path.replace(cwd, ".")})`;
1076
+ const relativePath = path.replace(cwd, ".");
1077
+ console.log(chalk3.gray(`Changed: ${relativePath}`));
1143
1078
  try {
1144
1079
  agent = await loadAgent(cwd);
1145
1080
  const bundle = await bundleAgent(cwd);
@@ -1154,23 +1089,20 @@ async function runCloudDev(agent, project, cwd, port, options, spinner) {
1154
1089
  }));
1155
1090
  }
1156
1091
  } catch (error) {
1157
- spinner.fail(`Sync failed: ${error}`);
1158
- spinner.start("Watching for changes");
1092
+ console.log(chalk3.red("Sync failed:"), error);
1159
1093
  }
1160
1094
  });
1161
1095
  process.on("SIGINT", () => {
1162
1096
  console.log();
1163
- spinner.stop();
1164
1097
  if (ws.readyState === WebSocket.OPEN) {
1165
1098
  ws.send(JSON.stringify({ type: "unsync" }));
1166
1099
  ws.close();
1167
1100
  }
1168
1101
  watcher.close();
1169
- server.stop();
1170
- console.log(chalk3.gray("Server stopped"));
1102
+ console.log(chalk3.gray("Stopped"));
1171
1103
  process.exit(0);
1172
1104
  });
1173
- }
1105
+ });
1174
1106
  async function bundleAgent(cwd) {
1175
1107
  const result = await Bun.build({
1176
1108
  entrypoints: [join7(cwd, "src", "agent.ts")],
@@ -1192,185 +1124,6 @@ function hashString(str) {
1192
1124
  }
1193
1125
  return Math.abs(hash).toString(16);
1194
1126
  }
1195
- function getDevHtml(agentName, mode, cloudUrl) {
1196
- const modeLabel = mode === "cloud" ? `<span style="color: #22c55e;">Cloud</span>${cloudUrl ? ` - <a href="${cloudUrl}" target="_blank" style="color: #60a5fa;">${cloudUrl}</a>` : ""}` : '<span style="color: #eab308;">Local</span>';
1197
- return `<!DOCTYPE html>
1198
- <html lang="en">
1199
- <head>
1200
- <meta charset="UTF-8">
1201
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1202
- <title>${agentName} - Dev</title>
1203
- <style>
1204
- * { box-sizing: border-box; margin: 0; padding: 0; }
1205
- body { font-family: system-ui, -apple-system, sans-serif; background: #0a0a0a; color: #fafafa; height: 100vh; display: flex; flex-direction: column; }
1206
- header { padding: 1rem; border-bottom: 1px solid #333; display: flex; justify-content: space-between; align-items: center; }
1207
- header h1 { font-size: 1rem; font-weight: 500; }
1208
- header .mode { font-size: 0.875rem; }
1209
- header a { text-decoration: none; }
1210
- #messages { flex: 1; overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.75rem; }
1211
- .message { max-width: 80%; padding: 0.75rem 1rem; border-radius: 0.75rem; line-height: 1.5; white-space: pre-wrap; }
1212
- .message.user { align-self: flex-end; background: #2563eb; }
1213
- .message.assistant { align-self: flex-start; background: #27272a; }
1214
- .message.tool { align-self: flex-start; background: #1e3a5f; font-family: monospace; font-size: 0.875rem; border-left: 3px solid #3b82f6; }
1215
- .message.streaming { opacity: 0.9; }
1216
- form { padding: 1rem; border-top: 1px solid #333; display: flex; gap: 0.5rem; }
1217
- input { flex: 1; padding: 0.75rem 1rem; background: #18181b; border: 1px solid #333; border-radius: 0.5rem; color: #fafafa; font-size: 1rem; outline: none; }
1218
- input:focus { border-color: #2563eb; }
1219
- input:disabled { opacity: 0.5; }
1220
- button { padding: 0.75rem 1.5rem; background: #2563eb; border: none; border-radius: 0.5rem; color: white; font-size: 1rem; cursor: pointer; }
1221
- button:hover { background: #1d4ed8; }
1222
- button:disabled { opacity: 0.5; cursor: not-allowed; }
1223
- .toggle-container { padding: 0.5rem 1rem; display: flex; align-items: center; gap: 0.5rem; border-top: 1px solid #333; }
1224
- .toggle-container label { font-size: 0.875rem; color: #888; }
1225
- .toggle-container input[type="checkbox"] { width: 1rem; height: 1rem; }
1226
- </style>
1227
- </head>
1228
- <body>
1229
- <header>
1230
- <h1>${agentName}</h1>
1231
- <span class="mode">${modeLabel}</span>
1232
- </header>
1233
- <div id="messages"></div>
1234
- <div class="toggle-container">
1235
- <input type="checkbox" id="stream-toggle" checked />
1236
- <label for="stream-toggle">Enable streaming</label>
1237
- </div>
1238
- <form id="chat-form">
1239
- <input type="text" id="input" placeholder="Type a message..." autocomplete="off" />
1240
- <button type="submit">Send</button>
1241
- </form>
1242
- <script>
1243
- const messages = document.getElementById('messages');
1244
- const form = document.getElementById('chat-form');
1245
- const input = document.getElementById('input');
1246
- const button = form.querySelector('button');
1247
- const streamToggle = document.getElementById('stream-toggle');
1248
- let conversationId = null;
1249
- let isProcessing = false;
1250
-
1251
- function addMessage(role, content, isStreaming = false) {
1252
- const div = document.createElement('div');
1253
- div.className = 'message ' + role + (isStreaming ? ' streaming' : '');
1254
- div.textContent = content;
1255
- messages.appendChild(div);
1256
- messages.scrollTop = messages.scrollHeight;
1257
- return div;
1258
- }
1259
-
1260
- function setProcessing(processing) {
1261
- isProcessing = processing;
1262
- input.disabled = processing;
1263
- button.disabled = processing;
1264
- }
1265
-
1266
- async function sendWithStreaming(message) {
1267
- const assistantDiv = addMessage('assistant', '', true);
1268
-
1269
- const response = await fetch('/api/chat', {
1270
- method: 'POST',
1271
- headers: { 'Content-Type': 'application/json' },
1272
- body: JSON.stringify({ message, conversationId, stream: true }),
1273
- });
1274
-
1275
- const reader = response.body.getReader();
1276
- const decoder = new TextDecoder();
1277
- let buffer = '';
1278
- let fullText = '';
1279
-
1280
- while (true) {
1281
- const { done, value } = await reader.read();
1282
- if (done) break;
1283
-
1284
- buffer += decoder.decode(value, { stream: true });
1285
- const lines = buffer.split('\\n');
1286
- buffer = lines.pop() || '';
1287
-
1288
- for (const line of lines) {
1289
- if (line.startsWith('data: ')) {
1290
- try {
1291
- const data = JSON.parse(line.slice(6));
1292
-
1293
- if (data.conversationId) {
1294
- conversationId = data.conversationId;
1295
- }
1296
-
1297
- if (data.type === 'text-delta' && (data.textDelta || data.content)) {
1298
- fullText += data.textDelta || data.content;
1299
- assistantDiv.textContent = fullText;
1300
- messages.scrollTop = messages.scrollHeight;
1301
- } else if (data.type === 'tool-call-start') {
1302
- addMessage('tool', 'Calling tool: ' + (data.toolName || data.toolCall?.name));
1303
- } else if (data.type === 'tool-result') {
1304
- const resultText = typeof data.toolResult === 'string'
1305
- ? data.toolResult
1306
- : JSON.stringify(data.toolResult || data.toolCall?.result, null, 2);
1307
- addMessage('tool', 'Result: ' + resultText);
1308
- } else if (data.type === 'finish') {
1309
- assistantDiv.classList.remove('streaming');
1310
- } else if (data.type === 'error') {
1311
- assistantDiv.textContent = 'Error: ' + (data.error || data.message);
1312
- assistantDiv.classList.remove('streaming');
1313
- }
1314
- } catch (e) {}
1315
- }
1316
- }
1317
- }
1318
-
1319
- if (!fullText) {
1320
- assistantDiv.remove();
1321
- }
1322
- }
1323
-
1324
- async function sendWithoutStreaming(message) {
1325
- const res = await fetch('/api/chat', {
1326
- method: 'POST',
1327
- headers: { 'Content-Type': 'application/json' },
1328
- body: JSON.stringify({ message, conversationId }),
1329
- });
1330
- const data = await res.json();
1331
- conversationId = data.conversationId;
1332
-
1333
- if (data.toolCalls && data.toolCalls.length > 0) {
1334
- for (const tc of data.toolCalls) {
1335
- const resultText = typeof tc.result === 'string'
1336
- ? tc.result
1337
- : JSON.stringify(tc.result, null, 2);
1338
- addMessage('tool', tc.name + ': ' + resultText);
1339
- }
1340
- }
1341
-
1342
- addMessage('assistant', data.response || data.content);
1343
- }
1344
-
1345
- form.addEventListener('submit', async (e) => {
1346
- e.preventDefault();
1347
- if (isProcessing) return;
1348
-
1349
- const message = input.value.trim();
1350
- if (!message) return;
1351
-
1352
- addMessage('user', message);
1353
- input.value = '';
1354
- setProcessing(true);
1355
-
1356
- try {
1357
- if (streamToggle.checked) {
1358
- await sendWithStreaming(message);
1359
- } else {
1360
- await sendWithoutStreaming(message);
1361
- }
1362
- } catch (err) {
1363
- addMessage('assistant', 'Error: ' + err.message);
1364
- } finally {
1365
- setProcessing(false);
1366
- }
1367
- });
1368
-
1369
- input.focus();
1370
- </script>
1371
- </body>
1372
- </html>`;
1373
- }
1374
1127
 
1375
1128
  // src/commands/build.ts
1376
1129
  import { Command as Command4 } from "commander";
@@ -1687,8 +1440,8 @@ import { Command as Command6 } from "commander";
1687
1440
  import chalk6 from "chalk";
1688
1441
  import ora6 from "ora";
1689
1442
  import { join as join10 } from "path";
1690
- var deployCommand = new Command6("deploy").description("Deploy agent to Struere Cloud").option("-e, --env <environment>", "Target environment (development, production)", "production").option("--dev", "Deploy to development environment (shorthand for --env development)").option("--dry-run", "Show what would be deployed without deploying").action(async (options) => {
1691
- const environment = options.dev ? "development" : options.env;
1443
+ var deployCommand = new Command6("deploy").description("Deploy agent to production").option("--dry-run", "Show what would be deployed without deploying").action(async (options) => {
1444
+ const environment = "production";
1692
1445
  const spinner = ora6();
1693
1446
  const cwd = process.cwd();
1694
1447
  console.log();
@@ -2114,7 +1867,7 @@ var whoamiCommand = new Command11("whoami").description("Show current logged in
2114
1867
  });
2115
1868
 
2116
1869
  // src/index.ts
2117
- var CURRENT_VERSION = "0.2.6";
1870
+ var CURRENT_VERSION = "0.2.8";
2118
1871
  async function checkForUpdates() {
2119
1872
  try {
2120
1873
  const response = await fetch("https://registry.npmjs.org/@struere/cli/latest", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@struere/cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "CLI tool for developing, testing, and deploying AI agents",
5
5
  "keywords": [
6
6
  "ai",