@struere/cli 0.2.7 → 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.
- package/dist/index.js +15 -262
- 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("
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1051
|
-
spinner.succeed("Connected to Struere Cloud");
|
|
1043
|
+
spinner.succeed("Synced to development");
|
|
1052
1044
|
console.log();
|
|
1053
|
-
console.log(chalk3.
|
|
1054
|
-
console.log(
|
|
1055
|
-
console.log(chalk3.
|
|
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(`
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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";
|
|
@@ -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.
|
|
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", {
|