@harperfast/agent 0.13.0 → 0.13.2

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/README.md CHANGED
@@ -56,6 +56,18 @@ Harper: What do you want to do together today?
56
56
  >
57
57
  ```
58
58
 
59
+ ### Non-interactive: pipe an initial prompt
60
+
61
+ You can pass an initial chat dump via stdin. This runs a one-shot interaction and exits after responding:
62
+
63
+ ```bash
64
+ cat somePrompt.md | harper-agent
65
+ # or
66
+ harper-agent < somePrompt.md
67
+ ```
68
+
69
+ In this mode, the initial greeting question is suppressed, and the agent processes the provided prompt immediately.
70
+
59
71
  ## Model Selection
60
72
 
61
73
  By default, `harper-agent` uses OpenAI. You can switch to other models using the `--model` (or `-m`) flag:
package/dist/agent.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-2ESYSVXG.js";
3
2
 
4
3
  // agent.ts
5
4
  import "dotenv/config";
@@ -93,6 +92,17 @@ var CostTracker = class {
93
92
  this.totalCompactionCost = 0;
94
93
  this.hasUnknownPrices = false;
95
94
  }
95
+ getTotalCost() {
96
+ return this.totalCost + this.totalCompactionCost;
97
+ }
98
+ getEstimatedTotalCost(currentTurnUsage, model, compactionModel) {
99
+ const { turnCost, compactionCost } = this.calculateUsageCosts(
100
+ model,
101
+ currentTurnUsage,
102
+ compactionModel
103
+ );
104
+ return this.getTotalCost() + turnCost + compactionCost;
105
+ }
96
106
  recordTurn(model, usage, compactionModel) {
97
107
  const { turnCost, compactionCost, unknownPrices } = this.calculateUsageCosts(model, usage, compactionModel);
98
108
  this.totalInputTokens += usage.inputTokens;
@@ -316,6 +326,8 @@ var trackedState = {
316
326
  useFlexTier: false,
317
327
  disableSpinner: false,
318
328
  enableInterruptions: true,
329
+ maxTurns: 30,
330
+ maxCost: null,
319
331
  session: null
320
332
  };
321
333
 
@@ -517,6 +529,12 @@ ${chalk3.bold("OPTIONS")}
517
529
  Can also be set via HARPER_AGENT_COMPACTION_MODEL environment variable.
518
530
  -s, --session Specify a path to a SQLite database file to persist the chat session.
519
531
  Can also be set via HARPER_AGENT_SESSION environment variable.
532
+ --max-turns Specify the maximum number of turns for the agent run.
533
+ In task-driven mode, this defaults to unlimited.
534
+ Can also be set via HARPER_AGENT_MAX_TURNS environment variable.
535
+ --max-cost Specify the maximum cost (in USD) for the agent run.
536
+ If exceeded, the agent will exit with a non-zero code.
537
+ Can also be set via HARPER_AGENT_MAX_COST environment variable.
520
538
  --flex-tier Force the use of the flex service tier for lower costs but potentially
521
539
  more errors under high system load.
522
540
  Can also be set via HARPER_AGENT_FLEX_TIER=true environment variable.
@@ -584,19 +602,31 @@ function parseArgs() {
584
602
  const flagPairs = [
585
603
  ["model", ["--model", "-m", "model"]],
586
604
  ["compactionModel", ["--compaction-model", "-c", "compaction-model"]],
587
- ["sessionPath", ["--session", "-s", "session"]]
605
+ ["sessionPath", ["--session", "-s", "session"]],
606
+ ["maxTurns", ["--max-turns"]],
607
+ ["maxCost", ["--max-cost"]]
588
608
  ];
589
609
  let handled = false;
590
610
  for (const [key, prefixes] of flagPairs) {
591
611
  for (const prefix of prefixes) {
592
612
  if (arg === prefix) {
593
613
  if (args[i + 1]) {
594
- trackedState[key] = stripQuotes(args[++i]);
614
+ const val = stripQuotes(args[++i]);
615
+ if (key === "maxTurns" || key === "maxCost") {
616
+ trackedState[key] = parseFloat(val);
617
+ } else {
618
+ trackedState[key] = val;
619
+ }
595
620
  }
596
621
  handled = true;
597
622
  break;
598
623
  } else if (arg.startsWith(`${prefix}=`)) {
599
- trackedState[key] = stripQuotes(arg.slice(prefix.length + 1));
624
+ const val = stripQuotes(arg.slice(prefix.length + 1));
625
+ if (key === "maxTurns" || key === "maxCost") {
626
+ trackedState[key] = parseFloat(val);
627
+ } else {
628
+ trackedState[key] = val;
629
+ }
600
630
  handled = true;
601
631
  break;
602
632
  }
@@ -632,6 +662,12 @@ function parseArgs() {
632
662
  if (!trackedState.sessionPath && process.env.HARPER_AGENT_SESSION) {
633
663
  trackedState.sessionPath = process.env.HARPER_AGENT_SESSION;
634
664
  }
665
+ if (process.env.HARPER_AGENT_MAX_TURNS) {
666
+ trackedState.maxTurns = parseFloat(process.env.HARPER_AGENT_MAX_TURNS);
667
+ }
668
+ if (process.env.HARPER_AGENT_MAX_COST) {
669
+ trackedState.maxCost = parseFloat(process.env.HARPER_AGENT_MAX_COST);
670
+ }
635
671
  const sp = trackedState.sessionPath;
636
672
  if (sp) {
637
673
  trackedState.sessionPath = sp && !sp.startsWith("~") && !path.isAbsolute(sp) ? path.resolve(process.cwd(), sp) : sp;
@@ -715,7 +751,7 @@ async function getBrowser() {
715
751
  if (!browser) {
716
752
  let puppeteer;
717
753
  try {
718
- puppeteer = await import("./puppeteer-KULXOV7T.js");
754
+ puppeteer = await import("puppeteer");
719
755
  } catch {
720
756
  throw new Error(
721
757
  "Puppeteer is not installed. Browser tools require puppeteer. Please install it with `npm install puppeteer`."
@@ -1035,7 +1071,7 @@ var ToolParameters10 = z10.object({
1035
1071
  )
1036
1072
  });
1037
1073
  var getHarperSkillTool = tool10({
1038
- name: "getHarperSkill",
1074
+ name: "get_harper_skill",
1039
1075
  description: getSkillsDescription(),
1040
1076
  parameters: ToolParameters10,
1041
1077
  execute: execute10
@@ -1232,12 +1268,12 @@ async function requiredSkillForOperation(path8, type) {
1232
1268
  }
1233
1269
  const p = normalizedPath(path8);
1234
1270
  const read = await getSkillsRead();
1235
- if (p.includes("/resources/") || p.startsWith("resources/")) {
1236
- if (!read.includes("custom-resources") && !read.includes("extending-tables")) {
1237
- return pickExistingSkill(["custom-resources", "extending-tables"]);
1271
+ if (p.includes("/resources/") || p.startsWith("resources/") || p.endsWith("resources.ts") || p.endsWith("resources.js")) {
1272
+ if (!read.includes("automatic-apis")) {
1273
+ return pickExistingSkill(["automatic-apis"]);
1238
1274
  }
1239
1275
  }
1240
- if (p.includes("/schemas/") || p.startsWith("schemas/") || p.includes("/schema/") || p.startsWith("schema/")) {
1276
+ if (p.endsWith(".graphql")) {
1241
1277
  if (!read.includes("adding-tables-with-schemas")) {
1242
1278
  return pickExistingSkill(["adding-tables-with-schemas"]);
1243
1279
  }
@@ -1276,40 +1312,42 @@ ${chalk7.bold.bgYellow.black(" Apply patch approval required: ")}`);
1276
1312
  return false;
1277
1313
  }
1278
1314
  }
1315
+ async function execute11(operation) {
1316
+ try {
1317
+ const needed = await requiredSkillForOperation(operation.path, operation.type);
1318
+ if (needed) {
1319
+ const content = await execute10({ skill: needed });
1320
+ console.log(`Understanding ${needed} is necessary before applying this patch.`);
1321
+ return { status: "failed, skill guarded", output: content };
1322
+ }
1323
+ switch (operation.type) {
1324
+ case "create_file":
1325
+ if (!operation.diff) {
1326
+ return { status: "failed", output: "Error: diff is required for create_file" };
1327
+ }
1328
+ return await editor.createFile(operation);
1329
+ case "update_file":
1330
+ if (!operation.diff) {
1331
+ return { status: "failed", output: "Error: diff is required for update_file" };
1332
+ }
1333
+ return await editor.updateFile(operation);
1334
+ case "delete_file":
1335
+ return await editor.deleteFile(operation);
1336
+ default:
1337
+ return { status: "failed", output: `Error: Unknown operation type: ${operation.type}` };
1338
+ }
1339
+ } catch (err) {
1340
+ console.error("hit unexpected error in apply patch tool", err);
1341
+ return { status: "failed", output: `apply_patch threw: ${String(err)}` };
1342
+ }
1343
+ }
1279
1344
  function createApplyPatchTool() {
1280
1345
  return tool11({
1281
1346
  name: "apply_patch",
1282
1347
  description: "Applies a patch (create, update, or delete a file) to the workspace.",
1283
1348
  parameters: ApplyPatchParameters,
1284
1349
  needsApproval,
1285
- execute: async (operation) => {
1286
- try {
1287
- const needed = await requiredSkillForOperation(operation.path, operation.type);
1288
- if (needed) {
1289
- const content = await execute10({ skill: needed });
1290
- return { status: "completed", output: content };
1291
- }
1292
- switch (operation.type) {
1293
- case "create_file":
1294
- if (!operation.diff) {
1295
- return { status: "failed", output: "Error: diff is required for create_file" };
1296
- }
1297
- return await editor.createFile(operation);
1298
- case "update_file":
1299
- if (!operation.diff) {
1300
- return { status: "failed", output: "Error: diff is required for update_file" };
1301
- }
1302
- return await editor.updateFile(operation);
1303
- case "delete_file":
1304
- return await editor.deleteFile(operation);
1305
- default:
1306
- return { status: "failed", output: `Error: Unknown operation type: ${operation.type}` };
1307
- }
1308
- } catch (err) {
1309
- console.error("hit unexpected error in apply patch tool", err);
1310
- return { status: "failed", output: `apply_patch threw: ${String(err)}` };
1311
- }
1312
- }
1350
+ execute: execute11
1313
1351
  });
1314
1352
  }
1315
1353
 
@@ -1320,7 +1358,7 @@ import { z as z12 } from "zod";
1320
1358
  var ToolParameters11 = z12.object({
1321
1359
  path: z12.string().describe("Directory to switch into. Can be absolute or relative to current workspace.")
1322
1360
  });
1323
- async function execute11({ path: path8 }) {
1361
+ async function execute12({ path: path8 }) {
1324
1362
  try {
1325
1363
  const target = resolvePath(trackedState.cwd, path8);
1326
1364
  const stat = statSync(target);
@@ -1348,10 +1386,10 @@ I strongly suggest you use these newfound skills!`;
1348
1386
  }
1349
1387
  }
1350
1388
  var changeCwdTool = tool12({
1351
- name: "changeCwd",
1389
+ name: "change_cwd",
1352
1390
  description: "Changes the current working directory for subsequent tools. Accepts absolute or relative paths.",
1353
1391
  parameters: ToolParameters11,
1354
- execute: execute11
1392
+ execute: execute12
1355
1393
  });
1356
1394
 
1357
1395
  // tools/files/egrepTool.ts
@@ -1428,7 +1466,7 @@ var ToolParameters14 = z15.object({
1428
1466
  directoryName: z15.string().describe("The name of the directory to read.")
1429
1467
  });
1430
1468
  var readDirTool = tool15({
1431
- name: "readDir",
1469
+ name: "read_dir",
1432
1470
  description: "Lists the files in a directory.",
1433
1471
  parameters: ToolParameters14,
1434
1472
  async execute({ directoryName }) {
@@ -1458,7 +1496,7 @@ var IMAGE_EXTENSIONS = {
1458
1496
  ".webp": "image/webp",
1459
1497
  ".bmp": "image/bmp"
1460
1498
  };
1461
- async function execute12({ fileName }) {
1499
+ async function execute13({ fileName }) {
1462
1500
  try {
1463
1501
  const normalized = String(fileName).replace(/\\/g, "/");
1464
1502
  const m = normalized.match(/(?:^|\/)skills\/([A-Za-z0-9_-]+)\.md(?:$|[?#])/);
@@ -1481,10 +1519,10 @@ async function execute12({ fileName }) {
1481
1519
  }
1482
1520
  }
1483
1521
  var readFileTool = tool16({
1484
- name: "readFile",
1522
+ name: "read_file",
1485
1523
  description: "Reads the contents of a specified file. If the file is an image, it returns a structured image object.",
1486
1524
  parameters: ToolParameters15,
1487
- execute: execute12
1525
+ execute: execute13
1488
1526
  });
1489
1527
 
1490
1528
  // tools/general/codeInterpreterTool.ts
@@ -1506,7 +1544,7 @@ var codeInterpreterTool = tool17({
1506
1544
  name: "code_interpreter",
1507
1545
  description: "Executes Python or JavaScript code in a local environment. This is useful for data analysis, complex calculations, and more. All code will be executed in the current workspace.",
1508
1546
  parameters: CodeInterpreterParameters,
1509
- execute: execute13,
1547
+ execute: execute14,
1510
1548
  needsApproval: needsApproval2
1511
1549
  });
1512
1550
  async function needsApproval2(runContext, { code, language }, callId) {
@@ -1528,7 +1566,7 @@ ${chalk8.bold.bgYellow.black(` Code interpreter (${language}) approval required:
1528
1566
  }
1529
1567
  return !autoApproved;
1530
1568
  }
1531
- async function execute13({ code, language }) {
1569
+ async function execute14({ code, language }) {
1532
1570
  const extension = language === "javascript" ? "js" : "py";
1533
1571
  const interpreter = language === "javascript" ? "node" : "python3";
1534
1572
  const tempFile = path6.join(trackedState.cwd, `.temp_code_${Date.now()}.${extension}`);
@@ -1582,7 +1620,7 @@ var SetInterpreterAutoApproveParameters = z18.object({
1582
1620
  autoApprove: z18.boolean()
1583
1621
  });
1584
1622
  var setInterpreterAutoApproveTool = tool18({
1585
- name: "setInterpreterAutoApproveTool",
1623
+ name: "set_interpreter_auto_approve",
1586
1624
  description: "Enable or disable automatic approval for code interpreter by setting HARPER_AGENT_AUTO_APPROVE_CODE_INTERPRETER=1 or 0 in .env and current process.",
1587
1625
  parameters: SetInterpreterAutoApproveParameters,
1588
1626
  needsApproval: async (_runContext, { autoApprove }) => {
@@ -1610,7 +1648,7 @@ var SetPatchAutoApproveParameters = z19.object({
1610
1648
  autoApprove: z19.boolean()
1611
1649
  });
1612
1650
  var setPatchAutoApproveTool = tool19({
1613
- name: "setPatchAutoApproveTool",
1651
+ name: "set_patch_auto_approve",
1614
1652
  description: "Enable or disable automatic approval for patch commands by setting HARPER_AGENT_AUTO_APPROVE_PATCHES=1 or 0 in .env and current process.",
1615
1653
  parameters: SetPatchAutoApproveParameters,
1616
1654
  needsApproval: async (_runContext, { autoApprove }) => {
@@ -1638,7 +1676,7 @@ var SetShellAutoApproveParameters = z20.object({
1638
1676
  autoApprove: z20.boolean()
1639
1677
  });
1640
1678
  var setShellAutoApproveTool = tool20({
1641
- name: "setShellAutoApproveTool",
1679
+ name: "set_shell_auto_approve",
1642
1680
  description: "Enable or disable automatic approval for shell commands by setting HARPER_AGENT_AUTO_APPROVE_SHELL=1 or 0 in .env and current process.",
1643
1681
  parameters: SetShellAutoApproveParameters,
1644
1682
  needsApproval: async (_runContext, { autoApprove }) => {
@@ -1779,8 +1817,8 @@ var ShellParameters = z21.object({
1779
1817
  });
1780
1818
  var shell = new LocalShell();
1781
1819
  var shellTool = tool21({
1782
- name: "shellToolForCommandsWithoutABetterTool",
1783
- description: "Executes shell commands.",
1820
+ name: "shell",
1821
+ description: "Executes shell commands. Only use when we do not have a better tool.",
1784
1822
  parameters: ShellParameters,
1785
1823
  execute: async ({ commands }) => {
1786
1824
  const result = await shell.run({ commands });
@@ -1844,7 +1882,7 @@ var GitAddParameters = z22.object({
1844
1882
  files: z22.array(z22.string()).describe("The files to add. If not provided, all changes will be added.")
1845
1883
  });
1846
1884
  var gitAddTool = tool22({
1847
- name: "gitAddTool",
1885
+ name: "git_add",
1848
1886
  description: "Add file contents to the index.",
1849
1887
  parameters: GitAddParameters,
1850
1888
  async execute({ files }) {
@@ -1874,7 +1912,7 @@ var GitBranchParameters = z23.object({
1874
1912
  create: z23.boolean().optional().default(false).describe("Whether to create a new branch.")
1875
1913
  });
1876
1914
  var gitBranchTool = tool23({
1877
- name: "gitBranchTool",
1915
+ name: "git_branch",
1878
1916
  description: "Create or switch to a git branch.",
1879
1917
  parameters: GitBranchParameters,
1880
1918
  needsApproval: true,
@@ -1902,7 +1940,7 @@ var GitCommitParameters = z24.object({
1902
1940
  )
1903
1941
  });
1904
1942
  var gitCommitTool = tool24({
1905
- name: "gitCommitTool",
1943
+ name: "git_commit",
1906
1944
  description: "Commit changes to the repository.",
1907
1945
  parameters: GitCommitParameters,
1908
1946
  async execute({ message, addAll }) {
@@ -1927,7 +1965,7 @@ var GitLogParameters = z25.object({
1927
1965
  oneline: z25.boolean().optional().default(true).describe("Whether to show log in oneline format.")
1928
1966
  });
1929
1967
  var gitLogTool = tool25({
1930
- name: "gitLogTool",
1968
+ name: "git_log",
1931
1969
  description: "Show commit logs.",
1932
1970
  parameters: GitLogParameters,
1933
1971
  async execute({ count, oneline }) {
@@ -1956,7 +1994,7 @@ var GitStashParameters = z26.object({
1956
1994
  message: z26.string().describe("A message for the stash change.")
1957
1995
  });
1958
1996
  var gitStashTool = tool26({
1959
- name: "gitStashTool",
1997
+ name: "git_stash",
1960
1998
  description: "Stash changes or apply a stash.",
1961
1999
  parameters: GitStashParameters,
1962
2000
  async execute({ action, message }) {
@@ -1986,7 +2024,7 @@ var GitStatusParameters = z27.object({
1986
2024
  short: z27.boolean().optional().default(false).describe("Whether to show the status in short format.")
1987
2025
  });
1988
2026
  var gitStatusTool = tool27({
1989
- name: "gitStatusTool",
2027
+ name: "git_status",
1990
2028
  description: "Show the working tree status.",
1991
2029
  parameters: GitStatusParameters,
1992
2030
  async execute({ short }) {
@@ -2015,7 +2053,7 @@ var GitWorkspaceParameters = z28.object({
2015
2053
  createBranch: z28.boolean().optional().default(false).describe("Whether to create a new branch for this workspace.")
2016
2054
  });
2017
2055
  var gitWorkspaceTool = tool28({
2018
- name: "gitWorkspaceTool",
2056
+ name: "git_workspace",
2019
2057
  description: "Create a new workspace (git worktree) for parallel work.",
2020
2058
  parameters: GitWorkspaceParameters,
2021
2059
  async execute({ path: workspacePath, branchName, createBranch }) {
@@ -2035,7 +2073,7 @@ import { tool as tool29 } from "@openai/agents";
2035
2073
  import { z as z29 } from "zod";
2036
2074
  var ToolParameters16 = z29.object({});
2037
2075
  var checkHarperStatusTool = tool29({
2038
- name: "checkHarperStatusTool",
2076
+ name: "check_harper_status",
2039
2077
  description: "Checks if a Harper application is currently running.",
2040
2078
  parameters: ToolParameters16,
2041
2079
  async execute() {
@@ -2105,7 +2143,7 @@ var ToolParameters17 = z30.object({
2105
2143
  directoryName: z30.string().describe("The name of the directory to create the application in."),
2106
2144
  template: z30.enum(["vanilla-ts", "vanilla", "react-ts", "react"]).optional().describe("The template to use for the new application. Defaults to vanilla-ts.").default("vanilla-ts")
2107
2145
  });
2108
- async function execute14({ directoryName, template }) {
2146
+ async function execute15({ directoryName, template }) {
2109
2147
  const currentCwd = trackedState.cwd;
2110
2148
  const resolvedPath = resolvePath(currentCwd, directoryName);
2111
2149
  const isCurrentDir = resolvedPath === currentCwd;
@@ -2122,7 +2160,7 @@ async function execute14({ directoryName, template }) {
2122
2160
  });
2123
2161
  console.log(`Initializing new Git repository in ${resolvedPath}...`);
2124
2162
  execSync3("git init", { cwd: resolvedPath, stdio: "ignore" });
2125
- const switchedDir = await execute11({ path: resolvedPath });
2163
+ const switchedDir = await execute12({ path: resolvedPath });
2126
2164
  return `Successfully created a new Harper application in '${resolvedPath}' using template '${template}' with a matching Git repository initialized. Use the readDir and readFile tools to inspect the contents of the application. ${switchedDir}.`;
2127
2165
  } catch (error) {
2128
2166
  let errorMsg = `Error creating new Harper application: ${error.message}`;
@@ -2136,10 +2174,10 @@ ${error.stdout}`;
2136
2174
  }
2137
2175
  }
2138
2176
  var createNewHarperApplicationTool = tool30({
2139
- name: "createNewHarperApplicationTool",
2177
+ name: "create_new_harper_application",
2140
2178
  description: "Creates a new Harper application using the best available package manager (yarn/pnpm/bun/deno, falling back to npm).",
2141
2179
  parameters: ToolParameters17,
2142
- execute: execute14
2180
+ execute: execute15
2143
2181
  });
2144
2182
 
2145
2183
  // tools/harper/getHarperConfigSchemaTool.ts
@@ -2154,7 +2192,7 @@ var ToolParameters18 = z31.object({
2154
2192
  )
2155
2193
  });
2156
2194
  var getHarperConfigSchemaTool = tool31({
2157
- name: "getHarperConfigSchemaTool",
2195
+ name: "get_harper_config_schema",
2158
2196
  description: "Returns the JSON schema for HarperDB configuration files (either app or root), which describes the config.yaml or harperdb-config.yaml files.",
2159
2197
  parameters: ToolParameters18,
2160
2198
  async execute({ schemaType }) {
@@ -2190,7 +2228,7 @@ var ToolParameters19 = z32.object({
2190
2228
  ).default("ResourceInterfaceV2")
2191
2229
  });
2192
2230
  var getHarperResourceInterfaceTool = tool32({
2193
- name: "getHarperResourceInterfaceTool",
2231
+ name: "get_harper_resource_interface",
2194
2232
  description: "Reads HarperDB resource interface and class definitions (like ResourceInterfaceV2.d.ts) to understand how resources and tables are structured.",
2195
2233
  parameters: ToolParameters19,
2196
2234
  async execute({ resourceFile }) {
@@ -2217,7 +2255,7 @@ import { dirname as dirname4, join as join10 } from "path";
2217
2255
  import { z as z33 } from "zod";
2218
2256
  var ToolParameters20 = z33.object({});
2219
2257
  var getHarperSchemaGraphQLTool = tool33({
2220
- name: "getHarperSchemaGraphQLTool",
2258
+ name: "get_harper_schema_graphql",
2221
2259
  description: "Returns the GraphQL schema for HarperDB schema files, which define the structure of HarperDB database tables.",
2222
2260
  parameters: ToolParameters20,
2223
2261
  async execute() {
@@ -2249,11 +2287,11 @@ var ToolParameters21 = z34.object({
2249
2287
  body: z34.string().optional().default("").describe("An optional JSON string body to send along with the request.")
2250
2288
  });
2251
2289
  var hitHarperAPITool = tool34({
2252
- name: "hitHarperAPITool",
2290
+ name: "hit_harper_api",
2253
2291
  description: "Performs a request against the running Harper API. Use /openapi to look up Harper APIs.",
2254
2292
  parameters: ToolParameters21,
2255
2293
  needsApproval: async (runContext, input, callId) => {
2256
- if (callId && runContext.isToolApproved({ toolName: "hitHarperAPITool", callId })) {
2294
+ if (callId && runContext.isToolApproved({ toolName: "hit_harper_api", callId })) {
2257
2295
  return false;
2258
2296
  }
2259
2297
  if (input.method === "DELETE") {
@@ -2293,7 +2331,7 @@ import { tool as tool35 } from "@openai/agents";
2293
2331
  import { z as z35 } from "zod";
2294
2332
  var ToolParameters22 = z35.object({});
2295
2333
  var readHarperLogsTool = tool35({
2296
- name: "readHarperLogsTool",
2334
+ name: "read_harper_logs",
2297
2335
  description: "Reads the most recent console logs of a started Harper app and clears them so that subsequent reads will only show new logs.",
2298
2336
  parameters: ToolParameters22,
2299
2337
  async execute() {
@@ -2325,7 +2363,7 @@ var ToolParameters23 = z36.object({
2325
2363
  directoryName: z36.string().describe("The name of the directory that the Harper app is in.")
2326
2364
  });
2327
2365
  var startHarperTool = tool36({
2328
- name: "startHarperTool",
2366
+ name: "start_harper",
2329
2367
  description: "Starts a Harper app background process, allowing you to observe the app in action (by readHarperLogsTool, hitHarperAPITool, etc).",
2330
2368
  parameters: ToolParameters23,
2331
2369
  async execute({ directoryName }) {
@@ -2360,7 +2398,7 @@ import { tool as tool37 } from "@openai/agents";
2360
2398
  import { z as z37 } from "zod";
2361
2399
  var ToolParameters24 = z37.object({});
2362
2400
  var stopHarperTool = tool37({
2363
- name: "stopHarperTool",
2401
+ name: "stop_harper",
2364
2402
  description: "Stops all previously started Harper app background process.",
2365
2403
  parameters: ToolParameters24,
2366
2404
  async execute() {
@@ -2672,6 +2710,19 @@ async function ensureApiKey() {
2672
2710
  }
2673
2711
  }
2674
2712
 
2713
+ // utils/shell/getStdin.ts
2714
+ async function getStdin() {
2715
+ if (process.stdin.isTTY) {
2716
+ return "";
2717
+ }
2718
+ let result = "";
2719
+ process.stdin.setEncoding("utf8");
2720
+ for await (const chunk of process.stdin) {
2721
+ result += chunk;
2722
+ }
2723
+ return result.trim();
2724
+ }
2725
+
2675
2726
  // utils/sessions/createSession.ts
2676
2727
  import { MemorySession as MemorySession3 } from "@openai/agents";
2677
2728
 
@@ -3116,6 +3167,7 @@ async function main() {
3116
3167
  parseArgs();
3117
3168
  await ensureApiKey();
3118
3169
  sayHi();
3170
+ const stdinPrompt = await getStdin();
3119
3171
  const agent = trackedState.agent = new Agent2({
3120
3172
  name: "Harper App Development Assistant",
3121
3173
  model: isOpenAIModel(trackedState.model) ? trackedState.model : getModel(trackedState.model),
@@ -3124,12 +3176,20 @@ async function main() {
3124
3176
  tools: createTools()
3125
3177
  });
3126
3178
  const session = trackedState.session = createSession(trackedState.sessionPath);
3179
+ let firstIteration = true;
3127
3180
  while (true) {
3128
3181
  let task = "";
3129
3182
  let lastToolCallInfo = null;
3130
3183
  trackedState.controller = new AbortController();
3131
3184
  if (!trackedState.approvalState) {
3132
- task = await askQuestion("> ");
3185
+ if (firstIteration && stdinPrompt) {
3186
+ task = stdinPrompt;
3187
+ console.log(`${chalk13.bold(">")} ${task}
3188
+ `);
3189
+ } else {
3190
+ task = await askQuestion("> ");
3191
+ }
3192
+ firstIteration = false;
3133
3193
  if (!task) {
3134
3194
  trackedState.emptyLines += 1;
3135
3195
  if (trackedState.emptyLines >= 2) {
@@ -3146,7 +3206,7 @@ async function main() {
3146
3206
  session,
3147
3207
  stream: true,
3148
3208
  signal: trackedState.controller.signal,
3149
- maxTurns: 30
3209
+ maxTurns: trackedState.maxTurns
3150
3210
  });
3151
3211
  trackedState.approvalState = null;
3152
3212
  let hasStartedResponse = false;
@@ -3223,6 +3283,26 @@ ${chalk13.yellow("\u{1F6E0}\uFE0F")} ${chalk13.cyan(name)}${chalk13.dim(display
3223
3283
  trackedState.model || "gpt-5.2",
3224
3284
  trackedState.compactionModel || "gpt-4o-mini"
3225
3285
  );
3286
+ if (trackedState.maxCost !== null) {
3287
+ const estimatedTotalCost = costTracker.getEstimatedTotalCost(
3288
+ stream.state.usage,
3289
+ trackedState.model || "gpt-5.2",
3290
+ trackedState.compactionModel || "gpt-4o-mini"
3291
+ );
3292
+ if (estimatedTotalCost > trackedState.maxCost) {
3293
+ spinner.stop();
3294
+ console.log(
3295
+ chalk13.red(
3296
+ `Cost limit exceeded: $${estimatedTotalCost.toFixed(4)} > $${trackedState.maxCost.toFixed(4)}`
3297
+ )
3298
+ );
3299
+ if (trackedState.controller) {
3300
+ trackedState.controller.abort();
3301
+ }
3302
+ process.exitCode = 1;
3303
+ return handleExit();
3304
+ }
3305
+ }
3226
3306
  }
3227
3307
  if (stream.interruptions?.length) {
3228
3308
  for (const interruption of stream.interruptions) {
@@ -3251,6 +3331,19 @@ ${chalk13.yellow("\u{1F6E0}\uFE0F")} ${chalk13.cyan(name)}${chalk13.dim(display
3251
3331
  stream.state.usage,
3252
3332
  trackedState.compactionModel || "gpt-4o-mini"
3253
3333
  );
3334
+ if (trackedState.maxCost !== null && costTracker.getTotalCost() > trackedState.maxCost) {
3335
+ spinner.stop();
3336
+ console.log(
3337
+ chalk13.red(
3338
+ `Cost limit exceeded: $${costTracker.getTotalCost().toFixed(4)} > $${trackedState.maxCost.toFixed(4)}`
3339
+ )
3340
+ );
3341
+ process.exitCode = 1;
3342
+ return handleExit();
3343
+ }
3344
+ }
3345
+ if (stdinPrompt) {
3346
+ return handleExit();
3254
3347
  }
3255
3348
  } catch (error) {
3256
3349
  spinner.stop();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@harperfast/agent",
3
3
  "description": "AI to help you with Harper app management",
4
- "version": "0.13.0",
4
+ "version": "0.13.2",
5
5
  "main": "dist/agent.js",
6
6
  "repository": "github:HarperFast/harper-agent",
7
7
  "bugs": {
@@ -9,9 +9,9 @@
9
9
  },
10
10
  "homepage": "https://github.com/harperfast",
11
11
  "scripts": {
12
- "dev": "tsup agent.ts --format esm --clean --dts --watch",
12
+ "dev": "tsup agent.ts --format esm --clean --dts --watch --external puppeteer",
13
13
  "link": "npm run build && npm link",
14
- "build": "tsup agent.ts --format esm --clean --dts",
14
+ "build": "tsup agent.ts --format esm --clean --dts --external puppeteer",
15
15
  "commitlint": "commitlint --edit",
16
16
  "start": "node ./dist/agent.js",
17
17
  "lint": "oxlint --format stylish .",
@@ -42,17 +42,17 @@
42
42
  "license": "None",
43
43
  "type": "module",
44
44
  "dependencies": {
45
- "@ai-sdk/anthropic": "^3.0.36",
46
- "@ai-sdk/google": "^3.0.20",
47
- "@ai-sdk/openai": "^3.0.25",
48
- "@openai/agents": "^0.4.5",
49
- "@openai/agents-extensions": "^0.4.5",
50
- "ai": "^6.0.69",
45
+ "@ai-sdk/anthropic": "^3.0.41",
46
+ "@ai-sdk/google": "^3.0.24",
47
+ "@ai-sdk/openai": "^3.0.26",
48
+ "@openai/agents": "^0.4.6",
49
+ "@openai/agents-extensions": "^0.4.6",
50
+ "ai": "^6.0.79",
51
51
  "chalk": "^5.6.2",
52
- "create-harper": "^0.12.0",
52
+ "create-harper": "^0.12.4",
53
53
  "cross-spawn": "^7.0.6",
54
- "dotenv": "^17.2.3",
55
- "ollama-ai-provider-v2": "^3.0.3",
54
+ "dotenv": "^17.2.4",
55
+ "ollama-ai-provider-v2": "^3.3.0",
56
56
  "zod": "^4.3.6"
57
57
  },
58
58
  "optionalDependencies": {
@@ -72,7 +72,7 @@
72
72
  "conventional-changelog-conventionalcommits": "^9.1.0",
73
73
  "dprint": "^0.51.1",
74
74
  "express": "^5.2.1",
75
- "harperdb": "^4.7.17",
75
+ "harperdb": "^4.7.19",
76
76
  "hono": "^4.11.9",
77
77
  "husky": "^9.1.7",
78
78
  "oxlint": "^1.43.0",
@@ -1,7 +0,0 @@
1
- import {
2
- BrowserWebSocketTransport
3
- } from "./chunk-PG3SGAEX.js";
4
- import "./chunk-2ESYSVXG.js";
5
- export {
6
- BrowserWebSocketTransport
7
- };
@@ -1,9 +0,0 @@
1
- import {
2
- convertPuppeteerChannelToBrowsersChannel
3
- } from "./chunk-YLJAHQTP.js";
4
- import "./chunk-SLTU5TTQ.js";
5
- import "./chunk-FCLITLWE.js";
6
- import "./chunk-2ESYSVXG.js";
7
- export {
8
- convertPuppeteerChannelToBrowsersChannel
9
- };
@@ -1,8 +0,0 @@
1
- import {
2
- NodeWebSocketTransport
3
- } from "./chunk-VD4EVG4H.js";
4
- import "./chunk-MGX7MDP2.js";
5
- import "./chunk-2ESYSVXG.js";
6
- export {
7
- NodeWebSocketTransport
8
- };