stack-agent 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +103 -57
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,14 +6,14 @@ import { withFullScreen } from "fullscreen-ink";
|
|
|
6
6
|
|
|
7
7
|
// src/cli/app.tsx
|
|
8
8
|
import { useState as useState5, useEffect as useEffect2, useCallback } from "react";
|
|
9
|
-
import { Box as
|
|
9
|
+
import { Box as Box8, Text as Text8, useApp, useInput as useInput3 } from "ink";
|
|
10
10
|
import { useScreenSize } from "fullscreen-ink";
|
|
11
11
|
|
|
12
12
|
// src/cli/components/header.tsx
|
|
13
13
|
import { Box, Text } from "ink";
|
|
14
14
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
-
function Header({ appName, currentStage, stages, showDots = false }) {
|
|
16
|
-
const stageName = currentStage?.label ?? "Stack Progress";
|
|
15
|
+
function Header({ appName, currentStage, stages, showDots = false, title }) {
|
|
16
|
+
const stageName = title ?? currentStage?.label ?? "Stack Progress";
|
|
17
17
|
return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderBottom: false, paddingX: 1, justifyContent: "space-between", children: [
|
|
18
18
|
/* @__PURE__ */ jsxs(Box, { gap: 2, children: [
|
|
19
19
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: appName }),
|
|
@@ -53,6 +53,9 @@ function Footer({ progress, stages, terminalWidth, mode = "decisions" }) {
|
|
|
53
53
|
case "input":
|
|
54
54
|
display = "Enter submit \xB7 Esc stages";
|
|
55
55
|
break;
|
|
56
|
+
case "scaffold":
|
|
57
|
+
display = "Scaffolding your project...";
|
|
58
|
+
break;
|
|
56
59
|
default:
|
|
57
60
|
display = buildDecisionsDisplay(progress, stages, terminalWidth);
|
|
58
61
|
break;
|
|
@@ -391,7 +394,7 @@ function getMissingDecisions(progress) {
|
|
|
391
394
|
}
|
|
392
395
|
|
|
393
396
|
// src/cli/app.tsx
|
|
394
|
-
import { TextInput as TextInput2, Spinner as
|
|
397
|
+
import { TextInput as TextInput2, Spinner as Spinner3 } from "@inkjs/ui";
|
|
395
398
|
|
|
396
399
|
// src/cli/components/project-info-form.tsx
|
|
397
400
|
import { useState as useState4 } from "react";
|
|
@@ -444,6 +447,26 @@ function ProjectInfoForm({ onSubmit }) {
|
|
|
444
447
|
] });
|
|
445
448
|
}
|
|
446
449
|
|
|
450
|
+
// src/cli/components/scaffold-view.tsx
|
|
451
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
452
|
+
import { Spinner as Spinner2 } from "@inkjs/ui";
|
|
453
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
454
|
+
function ScaffoldView({ steps }) {
|
|
455
|
+
return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: steps.map((step, i) => /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", children: [
|
|
456
|
+
/* @__PURE__ */ jsxs5(Box7, { children: [
|
|
457
|
+
step.status === "running" && /* @__PURE__ */ jsx7(Spinner2, {}),
|
|
458
|
+
step.status === "done" && /* @__PURE__ */ jsx7(Text7, { color: "green", children: "\u2713" }),
|
|
459
|
+
step.status === "error" && /* @__PURE__ */ jsx7(Text7, { color: "red", children: "\u2717" }),
|
|
460
|
+
/* @__PURE__ */ jsxs5(Text7, { bold: step.status === "running", children: [
|
|
461
|
+
" ",
|
|
462
|
+
step.name
|
|
463
|
+
] })
|
|
464
|
+
] }),
|
|
465
|
+
step.status === "done" && step.files && step.files.length > 0 && /* @__PURE__ */ jsx7(Box7, { paddingLeft: 3, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: step.files.join(", ") }) }),
|
|
466
|
+
step.status === "error" && step.error && /* @__PURE__ */ jsx7(Box7, { paddingLeft: 3, children: /* @__PURE__ */ jsx7(Text7, { color: "red", children: step.error }) })
|
|
467
|
+
] }, i)) });
|
|
468
|
+
}
|
|
469
|
+
|
|
447
470
|
// src/cli/bridge.ts
|
|
448
471
|
function createBridge() {
|
|
449
472
|
const listeners = /* @__PURE__ */ new Map();
|
|
@@ -709,6 +732,10 @@ function scaffoldToolDefinitions() {
|
|
|
709
732
|
input_schema: {
|
|
710
733
|
type: "object",
|
|
711
734
|
properties: {
|
|
735
|
+
name: {
|
|
736
|
+
type: "string",
|
|
737
|
+
description: 'Short name for this integration (e.g. "Database", "Auth", "AI Chat", "Deploy Config").'
|
|
738
|
+
},
|
|
712
739
|
files: {
|
|
713
740
|
type: "object",
|
|
714
741
|
additionalProperties: { type: "string" },
|
|
@@ -735,7 +762,7 @@ function scaffoldToolDefinitions() {
|
|
|
735
762
|
description: "List of environment variable names required by the integration."
|
|
736
763
|
}
|
|
737
764
|
},
|
|
738
|
-
required: ["files"]
|
|
765
|
+
required: ["name", "files"]
|
|
739
766
|
}
|
|
740
767
|
}
|
|
741
768
|
];
|
|
@@ -1068,14 +1095,7 @@ async function runStageLoop(stage, manager, bridge, mcpServers) {
|
|
|
1068
1095
|
messages.push({ role: "user", content: inputResult.value });
|
|
1069
1096
|
}
|
|
1070
1097
|
}
|
|
1071
|
-
function
|
|
1072
|
-
return {
|
|
1073
|
-
start: (msg) => process.stdout.write(` ${msg}...`),
|
|
1074
|
-
stop: (msg) => process.stdout.write(` ${msg}
|
|
1075
|
-
`)
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
async function runScaffoldLoop(progress, mcpServers) {
|
|
1098
|
+
async function runScaffoldLoop(progress, onProgress, mcpServers) {
|
|
1079
1099
|
const messages = [];
|
|
1080
1100
|
const system = buildScaffoldPrompt(progress);
|
|
1081
1101
|
const cwd = process.cwd();
|
|
@@ -1083,11 +1103,22 @@ async function runScaffoldLoop(progress, mcpServers) {
|
|
|
1083
1103
|
const projectDir = join2(cwd, projectName);
|
|
1084
1104
|
let toolCallCount = 0;
|
|
1085
1105
|
const maxToolCalls = 30;
|
|
1106
|
+
const steps = [];
|
|
1107
|
+
function pushStep(step) {
|
|
1108
|
+
steps.push(step);
|
|
1109
|
+
onProgress?.([...steps]);
|
|
1110
|
+
}
|
|
1111
|
+
function updateLastStep(patch) {
|
|
1112
|
+
const last = steps[steps.length - 1];
|
|
1113
|
+
if (last) Object.assign(last, patch);
|
|
1114
|
+
onProgress?.([...steps]);
|
|
1115
|
+
}
|
|
1086
1116
|
messages.push({
|
|
1087
1117
|
role: "user",
|
|
1088
1118
|
content: "Begin scaffolding the project according to the plan."
|
|
1089
1119
|
});
|
|
1090
1120
|
while (true) {
|
|
1121
|
+
pushStep({ name: "Planning next step...", status: "running" });
|
|
1091
1122
|
const response = await chat({
|
|
1092
1123
|
system,
|
|
1093
1124
|
messages,
|
|
@@ -1095,6 +1126,8 @@ async function runScaffoldLoop(progress, mcpServers) {
|
|
|
1095
1126
|
maxTokens: 16384,
|
|
1096
1127
|
mcpServers
|
|
1097
1128
|
});
|
|
1129
|
+
steps.pop();
|
|
1130
|
+
onProgress?.([...steps]);
|
|
1098
1131
|
const contentBlocks = response.content;
|
|
1099
1132
|
const toolUseBlocks = contentBlocks.filter(
|
|
1100
1133
|
(b) => b.type === "tool_use"
|
|
@@ -1108,30 +1141,30 @@ async function runScaffoldLoop(progress, mcpServers) {
|
|
|
1108
1141
|
const toolBlock = block;
|
|
1109
1142
|
toolCallCount++;
|
|
1110
1143
|
if (toolCallCount > maxToolCalls) {
|
|
1111
|
-
|
|
1144
|
+
pushStep({ name: "Tool call limit exceeded", status: "error", error: `Exceeded ${maxToolCalls} tool calls` });
|
|
1112
1145
|
return false;
|
|
1113
1146
|
}
|
|
1114
|
-
const spinner = createSimpleSpinner();
|
|
1115
1147
|
try {
|
|
1116
1148
|
if (toolBlock.name === "run_scaffold") {
|
|
1117
|
-
|
|
1149
|
+
pushStep({ name: "Creating project", status: "running" });
|
|
1118
1150
|
const outputDir = runScaffold(
|
|
1119
1151
|
toolBlock.input.tool,
|
|
1120
1152
|
toolBlock.input.args,
|
|
1121
1153
|
projectName,
|
|
1122
1154
|
cwd
|
|
1123
1155
|
);
|
|
1124
|
-
|
|
1156
|
+
updateLastStep({ name: "Created project", status: "done" });
|
|
1125
1157
|
toolResults.push({
|
|
1126
1158
|
type: "tool_result",
|
|
1127
1159
|
tool_use_id: toolBlock.id,
|
|
1128
1160
|
content: `Scaffold completed. Project created at ${outputDir}`
|
|
1129
1161
|
});
|
|
1130
1162
|
} else if (toolBlock.name === "add_integration") {
|
|
1131
|
-
const
|
|
1163
|
+
const files = Object.keys(
|
|
1132
1164
|
toolBlock.input.files ?? {}
|
|
1133
|
-
)
|
|
1134
|
-
|
|
1165
|
+
);
|
|
1166
|
+
const integrationName = toolBlock.input.name ?? "Integration";
|
|
1167
|
+
pushStep({ name: `Adding ${integrationName}`, status: "running" });
|
|
1135
1168
|
writeIntegration(projectDir, {
|
|
1136
1169
|
files: toolBlock.input.files ?? {},
|
|
1137
1170
|
dependencies: toolBlock.input.dependencies,
|
|
@@ -1139,14 +1172,14 @@ async function runScaffoldLoop(progress, mcpServers) {
|
|
|
1139
1172
|
scripts: toolBlock.input.scripts,
|
|
1140
1173
|
envVars: toolBlock.input.envVars
|
|
1141
1174
|
});
|
|
1142
|
-
|
|
1175
|
+
updateLastStep({ name: integrationName, status: "done", files });
|
|
1143
1176
|
toolResults.push({
|
|
1144
1177
|
type: "tool_result",
|
|
1145
1178
|
tool_use_id: toolBlock.id,
|
|
1146
1179
|
content: "Integration written successfully."
|
|
1147
1180
|
});
|
|
1148
1181
|
} else {
|
|
1149
|
-
|
|
1182
|
+
pushStep({ name: `Unknown tool: ${toolBlock.name}`, status: "error", error: `Unknown tool: "${toolBlock.name}"` });
|
|
1150
1183
|
toolResults.push({
|
|
1151
1184
|
type: "tool_result",
|
|
1152
1185
|
tool_use_id: toolBlock.id,
|
|
@@ -1156,7 +1189,7 @@ async function runScaffoldLoop(progress, mcpServers) {
|
|
|
1156
1189
|
}
|
|
1157
1190
|
} catch (err) {
|
|
1158
1191
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1159
|
-
|
|
1192
|
+
updateLastStep({ status: "error", error: errorMessage });
|
|
1160
1193
|
toolResults.push({
|
|
1161
1194
|
type: "tool_result",
|
|
1162
1195
|
tool_use_id: toolBlock.id,
|
|
@@ -1256,7 +1289,7 @@ function applyRecommendations(progress, stages, recommendations) {
|
|
|
1256
1289
|
}
|
|
1257
1290
|
|
|
1258
1291
|
// src/cli/app.tsx
|
|
1259
|
-
import { jsx as
|
|
1292
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1260
1293
|
function App({ manager, onBuild, onExit }) {
|
|
1261
1294
|
const app = useApp();
|
|
1262
1295
|
const { width, height } = useScreenSize();
|
|
@@ -1269,6 +1302,7 @@ function App({ manager, onBuild, onExit }) {
|
|
|
1269
1302
|
const [stages, setStages] = useState5([...manager.stages]);
|
|
1270
1303
|
const [options, setOptions] = useState5([]);
|
|
1271
1304
|
const [errorMsg, setErrorMsg] = useState5("");
|
|
1305
|
+
const [scaffoldSteps, setScaffoldSteps] = useState5([]);
|
|
1272
1306
|
const syncState = useCallback(() => {
|
|
1273
1307
|
setProgress({ ...manager.progress });
|
|
1274
1308
|
setStages([...manager.stages]);
|
|
@@ -1330,7 +1364,7 @@ function App({ manager, onBuild, onExit }) {
|
|
|
1330
1364
|
syncState();
|
|
1331
1365
|
setView("stage_list");
|
|
1332
1366
|
}, [manager, bridge, syncState]);
|
|
1333
|
-
const handleStageResult = useCallback((result) => {
|
|
1367
|
+
const handleStageResult = useCallback(async (result) => {
|
|
1334
1368
|
if (result.kind === "select") {
|
|
1335
1369
|
const stage = manager.stages.find((s) => s.id === result.stageId);
|
|
1336
1370
|
if (stage && (stage.status === "complete" || stage.status === "skipped")) {
|
|
@@ -1339,7 +1373,24 @@ function App({ manager, onBuild, onExit }) {
|
|
|
1339
1373
|
}
|
|
1340
1374
|
runStage(result.stageId);
|
|
1341
1375
|
} else if (result.kind === "build") {
|
|
1342
|
-
|
|
1376
|
+
setView("scaffold");
|
|
1377
|
+
setScaffoldSteps([]);
|
|
1378
|
+
const onScaffoldProgress = (steps) => {
|
|
1379
|
+
setScaffoldSteps([...steps]);
|
|
1380
|
+
};
|
|
1381
|
+
try {
|
|
1382
|
+
const success = await runScaffoldLoop(manager.progress, onScaffoldProgress);
|
|
1383
|
+
if (success) {
|
|
1384
|
+
manager.cleanup();
|
|
1385
|
+
onBuild();
|
|
1386
|
+
} else {
|
|
1387
|
+
onBuild();
|
|
1388
|
+
}
|
|
1389
|
+
} catch (err) {
|
|
1390
|
+
setErrorMsg(err.message);
|
|
1391
|
+
setView("error");
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1343
1394
|
app.exit();
|
|
1344
1395
|
} else if (result.kind === "cancel") {
|
|
1345
1396
|
manager.save();
|
|
@@ -1385,22 +1436,23 @@ function App({ manager, onBuild, onExit }) {
|
|
|
1385
1436
|
syncState();
|
|
1386
1437
|
setView("stage_list");
|
|
1387
1438
|
}, [manager, syncState]);
|
|
1388
|
-
const footerMode = view === "stage_list" ? "stage_list" : view === "options" ? "options" : view === "input" || view === "project_info" ? "input" : "decisions";
|
|
1439
|
+
const footerMode = view === "scaffold" ? "scaffold" : view === "stage_list" ? "stage_list" : view === "options" ? "options" : view === "input" || view === "project_info" ? "input" : "decisions";
|
|
1389
1440
|
const contentHeight = height - 4;
|
|
1390
|
-
return /* @__PURE__ */
|
|
1391
|
-
/* @__PURE__ */
|
|
1441
|
+
return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", width, height, children: [
|
|
1442
|
+
/* @__PURE__ */ jsx8(
|
|
1392
1443
|
Header,
|
|
1393
1444
|
{
|
|
1394
1445
|
appName: "stack-agent",
|
|
1395
|
-
currentStage: view === "stage_list" ? null : currentStage,
|
|
1446
|
+
currentStage: view === "stage_list" || view === "scaffold" ? null : currentStage,
|
|
1396
1447
|
stages,
|
|
1397
|
-
showDots: view !== "stage_list"
|
|
1448
|
+
showDots: view !== "stage_list" && view !== "scaffold",
|
|
1449
|
+
title: view === "scaffold" ? "Scaffolding" : void 0
|
|
1398
1450
|
}
|
|
1399
1451
|
),
|
|
1400
|
-
/* @__PURE__ */
|
|
1401
|
-
view === "project_info" && /* @__PURE__ */
|
|
1402
|
-
view === "loading" && /* @__PURE__ */
|
|
1403
|
-
view === "stage_list" && /* @__PURE__ */
|
|
1452
|
+
/* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", height: contentHeight, paddingX: 1, borderStyle: "single", borderTop: false, borderBottom: false, children: [
|
|
1453
|
+
view === "project_info" && /* @__PURE__ */ jsx8(ProjectInfoForm, { onSubmit: handleProjectInfo }),
|
|
1454
|
+
view === "loading" && /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsx8(Spinner3, { label: "Analyzing your project and recommending a stack..." }) }),
|
|
1455
|
+
view === "stage_list" && /* @__PURE__ */ jsx8(
|
|
1404
1456
|
StageListView,
|
|
1405
1457
|
{
|
|
1406
1458
|
stages,
|
|
@@ -1409,10 +1461,10 @@ function App({ manager, onBuild, onExit }) {
|
|
|
1409
1461
|
onResult: handleStageResult
|
|
1410
1462
|
}
|
|
1411
1463
|
),
|
|
1412
|
-
view === "conversation" && /* @__PURE__ */
|
|
1413
|
-
view === "input" && /* @__PURE__ */
|
|
1414
|
-
/* @__PURE__ */
|
|
1415
|
-
/* @__PURE__ */
|
|
1464
|
+
view === "conversation" && /* @__PURE__ */ jsx8(ConversationView, { bridge, maxLines: contentHeight }),
|
|
1465
|
+
view === "input" && /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", children: [
|
|
1466
|
+
/* @__PURE__ */ jsx8(ConversationView, { bridge, maxLines: contentHeight - 3 }),
|
|
1467
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(
|
|
1416
1468
|
TextInput2,
|
|
1417
1469
|
{
|
|
1418
1470
|
placeholder: "Type your response...",
|
|
@@ -1420,19 +1472,20 @@ function App({ manager, onBuild, onExit }) {
|
|
|
1420
1472
|
}
|
|
1421
1473
|
) })
|
|
1422
1474
|
] }),
|
|
1423
|
-
view === "options" && /* @__PURE__ */
|
|
1424
|
-
/* @__PURE__ */
|
|
1425
|
-
/* @__PURE__ */
|
|
1475
|
+
view === "options" && /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", children: [
|
|
1476
|
+
/* @__PURE__ */ jsx8(ConversationView, { bridge, maxLines: contentHeight - 8 }),
|
|
1477
|
+
/* @__PURE__ */ jsx8(OptionSelect, { options, onSelect: handleOptionSelect })
|
|
1426
1478
|
] }),
|
|
1427
|
-
view === "error" && /* @__PURE__ */
|
|
1428
|
-
/* @__PURE__ */
|
|
1479
|
+
view === "error" && /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", children: [
|
|
1480
|
+
/* @__PURE__ */ jsxs6(Text8, { color: "red", bold: true, children: [
|
|
1429
1481
|
"Error: ",
|
|
1430
1482
|
errorMsg
|
|
1431
1483
|
] }),
|
|
1432
|
-
/* @__PURE__ */
|
|
1433
|
-
] })
|
|
1484
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Press Esc to return to stage list" })
|
|
1485
|
+
] }),
|
|
1486
|
+
view === "scaffold" && /* @__PURE__ */ jsx8(ScaffoldView, { steps: scaffoldSteps })
|
|
1434
1487
|
] }),
|
|
1435
|
-
/* @__PURE__ */
|
|
1488
|
+
/* @__PURE__ */ jsx8(
|
|
1436
1489
|
Footer,
|
|
1437
1490
|
{
|
|
1438
1491
|
progress,
|
|
@@ -1981,16 +2034,9 @@ Found saved progress for "${existingSession.progress.projectName ?? "unnamed"}"`
|
|
|
1981
2034
|
await ink.start();
|
|
1982
2035
|
await ink.waitUntilExit();
|
|
1983
2036
|
if (shouldBuild) {
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
const readiness = manager.progress.deployment ? checkDeployReadiness(manager.progress.deployment.component) : null;
|
|
1988
|
-
renderPostScaffold(manager.progress.projectName, readiness);
|
|
1989
|
-
manager.cleanup();
|
|
1990
|
-
console.log("\nHappy building!\n");
|
|
1991
|
-
} else {
|
|
1992
|
-
console.error("\nScaffolding encountered errors. Check the output above.\n");
|
|
1993
|
-
}
|
|
2037
|
+
const readiness = manager.progress.deployment ? checkDeployReadiness(manager.progress.deployment.component) : null;
|
|
2038
|
+
renderPostScaffold(manager.progress.projectName, readiness);
|
|
2039
|
+
console.log("\nHappy building!\n");
|
|
1994
2040
|
}
|
|
1995
2041
|
}
|
|
1996
2042
|
var args = process.argv.slice(2);
|
package/package.json
CHANGED