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.
Files changed (2) hide show
  1. package/dist/index.js +103 -57
  2. 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 Box7, Text as Text7, useApp, useInput as useInput3 } from "ink";
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 Spinner2 } from "@inkjs/ui";
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 createSimpleSpinner() {
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
- console.error(`Tool call limit exceeded (${maxToolCalls}). Stopping scaffold loop.`);
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
- spinner.start(`Running scaffold: ${toolBlock.input.tool}`);
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
- spinner.stop(`Scaffold complete: ${outputDir}`);
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 integrationDesc = Object.keys(
1163
+ const files = Object.keys(
1132
1164
  toolBlock.input.files ?? {}
1133
- ).join(", ");
1134
- spinner.start(`Adding integration: ${integrationDesc}`);
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
- spinner.stop("Integration added");
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
- spinner.stop(`Unknown tool: ${toolBlock.name}`);
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
- spinner.stop(`Error: ${errorMessage}`);
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 jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
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
- onBuild();
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__ */ jsxs5(Box7, { flexDirection: "column", width, height, children: [
1391
- /* @__PURE__ */ jsx7(
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__ */ jsxs5(Box7, { flexDirection: "column", height: contentHeight, paddingX: 1, borderStyle: "single", borderTop: false, borderBottom: false, children: [
1401
- view === "project_info" && /* @__PURE__ */ jsx7(ProjectInfoForm, { onSubmit: handleProjectInfo }),
1402
- view === "loading" && /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", children: /* @__PURE__ */ jsx7(Spinner2, { label: "Analyzing your project and recommending a stack..." }) }),
1403
- view === "stage_list" && /* @__PURE__ */ jsx7(
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__ */ jsx7(ConversationView, { bridge, maxLines: contentHeight }),
1413
- view === "input" && /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", children: [
1414
- /* @__PURE__ */ jsx7(ConversationView, { bridge, maxLines: contentHeight - 3 }),
1415
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(
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__ */ jsxs5(Box7, { flexDirection: "column", children: [
1424
- /* @__PURE__ */ jsx7(ConversationView, { bridge, maxLines: contentHeight - 8 }),
1425
- /* @__PURE__ */ jsx7(OptionSelect, { options, onSelect: handleOptionSelect })
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__ */ jsxs5(Box7, { flexDirection: "column", children: [
1428
- /* @__PURE__ */ jsxs5(Text7, { color: "red", bold: true, children: [
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__ */ jsx7(Text7, { dimColor: true, children: "Press Esc to return to stage list" })
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__ */ jsx7(
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
- console.log("\nScaffolding your project...\n");
1985
- const success = await runScaffoldLoop(manager.progress);
1986
- if (success) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stack-agent",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "AI-powered CLI that helps developers choose and scaffold full-stack applications through conversational interaction",
5
5
  "type": "module",
6
6
  "license": "MIT",