@harperfast/agent 0.16.0 → 0.16.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/dist/agent.js CHANGED
@@ -875,9 +875,9 @@ async function execute10({ skill }) {
875
875
 
876
876
  // tools/files/workspaceEditor.ts
877
877
  import { applyDiff } from "@openai/agents";
878
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
878
+ import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
879
879
  import { mkdir, rm, writeFile } from "fs/promises";
880
- import path4 from "path";
880
+ import path5 from "path";
881
881
 
882
882
  // utils/files/normalizeDiff.ts
883
883
  function normalizeDiff(diff) {
@@ -993,6 +993,61 @@ function resolvePath(root, relativePath) {
993
993
  return resolved;
994
994
  }
995
995
 
996
+ // utils/files/validateGraphQL.ts
997
+ import { buildSchema, extendSchema, parse, validateSchema } from "graphql";
998
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
999
+ import path4 from "path";
1000
+ import { fileURLToPath } from "url";
1001
+ var __dirname = path4.dirname(fileURLToPath(import.meta.url));
1002
+ var cachedSchema = null;
1003
+ function getHarperSchema(root) {
1004
+ if (cachedSchema) {
1005
+ return cachedSchema;
1006
+ }
1007
+ const paths = [
1008
+ path4.join(root, "node_modules", "harperdb", "schema.graphql"),
1009
+ path4.join(__dirname, "schema.graphql"),
1010
+ path4.join(__dirname, "..", "schema.graphql"),
1011
+ path4.join(__dirname, "..", "..", "schema.graphql")
1012
+ ];
1013
+ for (const schemaPath of paths) {
1014
+ if (existsSync6(schemaPath)) {
1015
+ try {
1016
+ const schemaSource = readFileSync4(schemaPath, "utf8");
1017
+ cachedSchema = buildSchema(schemaSource, { assumeValidSDL: true });
1018
+ return cachedSchema;
1019
+ } catch (e) {
1020
+ console.error(`Error parsing HarperDB schema at ${schemaPath}:`, e);
1021
+ }
1022
+ }
1023
+ }
1024
+ return null;
1025
+ }
1026
+ function validateGraphQL(content, filePath, root) {
1027
+ if (!filePath.endsWith(".graphql")) {
1028
+ return null;
1029
+ }
1030
+ try {
1031
+ const document2 = parse(content);
1032
+ const schema = getHarperSchema(root);
1033
+ if (schema) {
1034
+ const extendedSchema = extendSchema(schema, document2, { assumeValidSDL: false });
1035
+ const errors = validateSchema(extendedSchema).filter(
1036
+ (e) => !e.message.includes("Query root type must be provided")
1037
+ );
1038
+ if (errors.length > 0) {
1039
+ return `GraphQL validation error in ${filePath}:
1040
+ ${errors.map((e) => e.message).join("\n")}`;
1041
+ }
1042
+ }
1043
+ return null;
1044
+ } catch (err) {
1045
+ const message = err.message || String(err);
1046
+ const type = message.includes("Syntax Error") ? "syntax" : "validation";
1047
+ return `GraphQL ${type} error in ${filePath}: ${message}`;
1048
+ }
1049
+ }
1050
+
996
1051
  // tools/files/workspaceEditor.ts
997
1052
  var WorkspaceEditor = class {
998
1053
  root;
@@ -1002,11 +1057,18 @@ var WorkspaceEditor = class {
1002
1057
  async createFile(operation) {
1003
1058
  try {
1004
1059
  const targetPath = resolvePath(this.root(), operation.path);
1005
- await mkdir(path4.dirname(targetPath), { recursive: true });
1060
+ await mkdir(path5.dirname(targetPath), { recursive: true });
1006
1061
  const normalizedDiff = normalizeDiff(operation.diff);
1007
1062
  const content = applyDiff("", normalizedDiff, "create");
1008
1063
  await writeFile(targetPath, content, "utf8");
1009
- return { status: "completed", output: `Created ${operation.path}` };
1064
+ let output = `Created ${operation.path}`;
1065
+ const validationError = validateGraphQL(content, operation.path, this.root());
1066
+ if (validationError) {
1067
+ output += `
1068
+
1069
+ ${validationError}`;
1070
+ }
1071
+ return { status: "completed", output };
1010
1072
  } catch (err) {
1011
1073
  return { status: "failed", output: `Error creating ${operation.path}: ${String(err)}` };
1012
1074
  }
@@ -1014,14 +1076,21 @@ var WorkspaceEditor = class {
1014
1076
  async updateFile(operation) {
1015
1077
  try {
1016
1078
  const targetPath = resolvePath(this.root(), operation.path);
1017
- if (!existsSync6(targetPath)) {
1079
+ if (!existsSync7(targetPath)) {
1018
1080
  return { status: "failed", output: "Error: file not found at path " + targetPath };
1019
1081
  }
1020
- const original = readFileSync4(targetPath, "utf8");
1082
+ const original = readFileSync5(targetPath, "utf8");
1021
1083
  const normalizedDiff = normalizeDiff(operation.diff);
1022
1084
  const patched = applyDiff(original, normalizedDiff);
1023
1085
  await writeFile(targetPath, patched, "utf8");
1024
- return { status: "completed", output: `Updated ${operation.path}` };
1086
+ let output = `Updated ${operation.path}`;
1087
+ const validationError = validateGraphQL(patched, operation.path, this.root());
1088
+ if (validationError) {
1089
+ output += `
1090
+
1091
+ ${validationError}`;
1092
+ }
1093
+ return { status: "completed", output };
1025
1094
  } catch (err) {
1026
1095
  return { status: "failed", output: `Error updating ${operation.path}: ${String(err)}` };
1027
1096
  }
@@ -1029,7 +1098,7 @@ var WorkspaceEditor = class {
1029
1098
  async overwriteFile(operation) {
1030
1099
  try {
1031
1100
  const targetPath = resolvePath(this.root(), operation.path);
1032
- await mkdir(path4.dirname(targetPath), { recursive: true });
1101
+ await mkdir(path5.dirname(targetPath), { recursive: true });
1033
1102
  const normalizedInput = normalizeDiff(operation.diff);
1034
1103
  const lines = normalizedInput.split(/\r?\n/);
1035
1104
  const hasDiffMarkers = lines.some((line) => line.startsWith("+") || line.startsWith("-"));
@@ -1040,7 +1109,14 @@ var WorkspaceEditor = class {
1040
1109
  finalContent = normalizedInput;
1041
1110
  }
1042
1111
  await writeFile(targetPath, finalContent, "utf8");
1043
- return { status: "completed", output: `Overwrote ${operation.path}` };
1112
+ let output = `Overwrote ${operation.path}`;
1113
+ const validationError = validateGraphQL(finalContent, operation.path, this.root());
1114
+ if (validationError) {
1115
+ output += `
1116
+
1117
+ ${validationError}`;
1118
+ }
1119
+ return { status: "completed", output };
1044
1120
  } catch (err) {
1045
1121
  return { status: "failed", output: `Error overwriting ${operation.path}: ${String(err)}` };
1046
1122
  }
@@ -1048,7 +1124,7 @@ var WorkspaceEditor = class {
1048
1124
  async deleteFile(operation) {
1049
1125
  try {
1050
1126
  const targetPath = resolvePath(this.root(), operation.path);
1051
- if (!existsSync6(targetPath)) {
1127
+ if (!existsSync7(targetPath)) {
1052
1128
  return { status: "failed", output: "Error: file not found at path " + targetPath };
1053
1129
  }
1054
1130
  await rm(targetPath, { force: true });
@@ -1089,11 +1165,11 @@ function pickExistingSkill(candidates) {
1089
1165
  }
1090
1166
  return null;
1091
1167
  }
1092
- async function requiredSkillForOperation(path9, type) {
1168
+ async function requiredSkillForOperation(path10, type) {
1093
1169
  if (type === "delete_file") {
1094
1170
  return null;
1095
1171
  }
1096
- const p = normalizedPath(path9);
1172
+ const p = normalizedPath(path10);
1097
1173
  const read = await getSkillsRead();
1098
1174
  if (p.includes("/resources/") || p.startsWith("resources/") || p.endsWith("resources.ts") || p.endsWith("resources.js")) {
1099
1175
  if (!read.includes("automatic-apis")) {
@@ -1148,7 +1224,23 @@ async function execute11(operation) {
1148
1224
  if (needed) {
1149
1225
  const content = await execute10({ skill: needed });
1150
1226
  console.error(`Understanding ${needed} is necessary before applying this patch.`);
1151
- return { status: "failed, skill guarded", output: content };
1227
+ return {
1228
+ status: "skill_required",
1229
+ output: `apply_patch blocked: you must understand the "${needed}" skill before writing to "${operation.path}".
1230
+
1231
+ REQUIRED NEXT STEP: Call apply_patch again with the EXACT same arguments:
1232
+ type="${operation.type}", path="${operation.path}", diff=<same diff as before>
1233
+
1234
+ PROHIBITED \u2014 do NOT attempt any of the following to work around this:
1235
+ - Use the shell tool to write or create files
1236
+ - Call set_patch_auto_approve or set_shell_auto_approve
1237
+ - Use any other workaround
1238
+
1239
+ Read the skill content below, then retry apply_patch immediately.
1240
+
1241
+ --- Skill: ${needed} ---
1242
+ ${content}`
1243
+ };
1152
1244
  }
1153
1245
  switch (operation.type) {
1154
1246
  case "create_file":
@@ -1179,7 +1271,12 @@ async function execute11(operation) {
1179
1271
  function createApplyPatchTool() {
1180
1272
  return tool11({
1181
1273
  name: "apply_patch",
1182
- description: "Applies a patch (create, update, or delete a file) to the workspace.",
1274
+ description: `Applies a patch (create, update, or delete a file) to the workspace.
1275
+
1276
+ SKILL GUARD: Some file paths require reading a specific Harper skill before writing. If this tool returns status "skill_required", you MUST:
1277
+ 1. Read the skill content included in the response carefully.
1278
+ 2. Call apply_patch again with the EXACT same type, path, and diff arguments.
1279
+ Do NOT use shell, set_patch_auto_approve, set_shell_auto_approve, or any other workaround to bypass this requirement.`,
1183
1280
  parameters: ApplyPatchParameters,
1184
1281
  needsApproval,
1185
1282
  execute: execute11
@@ -1193,9 +1290,9 @@ import { z as z12 } from "zod";
1193
1290
  var ToolParameters11 = z12.object({
1194
1291
  path: z12.string().describe("Directory to switch into. Can be absolute or relative to current workspace.")
1195
1292
  });
1196
- async function execute12({ path: path9 }) {
1293
+ async function execute12({ path: path10 }) {
1197
1294
  try {
1198
- const target = resolvePath(trackedState.cwd, path9);
1295
+ const target = resolvePath(trackedState.cwd, path10);
1199
1296
  const stat = statSync(target);
1200
1297
  if (!stat.isDirectory()) {
1201
1298
  return `Path is not a directory: ${target}`;
@@ -1296,7 +1393,7 @@ var findTool = tool14({
1296
1393
  // tools/files/readDirTool.ts
1297
1394
  import { tool as tool15 } from "@openai/agents";
1298
1395
  import { readdir } from "fs/promises";
1299
- import path5 from "path";
1396
+ import path6 from "path";
1300
1397
  import { z as z15 } from "zod";
1301
1398
  var ToolParameters14 = z15.object({
1302
1399
  directoryName: z15.string().describe("The name of the directory to read.")
@@ -1309,7 +1406,7 @@ var readDirTool = tool15({
1309
1406
  try {
1310
1407
  const resolvedPath = resolvePath(trackedState.cwd, directoryName);
1311
1408
  const files = await readdir(resolvedPath, "utf8");
1312
- return files.filter((file) => !isIgnored(path5.join(resolvedPath, file)));
1409
+ return files.filter((file) => !isIgnored(path6.join(resolvedPath, file)));
1313
1410
  } catch (error) {
1314
1411
  return `Error reading directory: ${error}`;
1315
1412
  }
@@ -1365,7 +1462,7 @@ var readFileTool = tool16({
1365
1462
  import { tool as tool17 } from "@openai/agents";
1366
1463
  import { exec } from "child_process";
1367
1464
  import { unlink, writeFile as writeFile2 } from "fs/promises";
1368
- import path6 from "path";
1465
+ import path7 from "path";
1369
1466
  import { promisify as promisify3 } from "util";
1370
1467
  import { z as z17 } from "zod";
1371
1468
  var execAsync = promisify3(exec);
@@ -1408,7 +1505,7 @@ async function needsApproval2(runContext, parameters, callId) {
1408
1505
  async function execute14({ code, language }) {
1409
1506
  const extension = language === "javascript" ? "js" : "py";
1410
1507
  const interpreter = language === "javascript" ? "node" : "python3";
1411
- const tempFile = path6.join(trackedState.cwd, `.temp_code_${Date.now()}.${extension}`);
1508
+ const tempFile = path7.join(trackedState.cwd, `.temp_code_${Date.now()}.${extension}`);
1412
1509
  try {
1413
1510
  await writeFile2(tempFile, code, "utf8");
1414
1511
  const { stdout, stderr } = await execAsync(`${interpreter} ${tempFile}`);
@@ -1467,7 +1564,7 @@ import { tool as tool19 } from "@openai/agents";
1467
1564
  import { z as z19 } from "zod";
1468
1565
 
1469
1566
  // utils/files/updateEnv.ts
1470
- import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync } from "fs";
1567
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync } from "fs";
1471
1568
  import { homedir as homedir3 } from "os";
1472
1569
  import { dirname as dirname4, join as join7 } from "path";
1473
1570
  function updateEnv(key, value) {
@@ -1475,14 +1572,14 @@ function updateEnv(key, value) {
1475
1572
  process.env[key] = normalizedValue;
1476
1573
  const topLevelEnvPath = join7(homedir3(), ".harper", "harper-agent-env");
1477
1574
  const localEnvPath = join7(trackedState.cwd, ".env");
1478
- const envPath = existsSync7(topLevelEnvPath) || !existsSync7(localEnvPath) ? topLevelEnvPath : localEnvPath;
1575
+ const envPath = existsSync8(topLevelEnvPath) || !existsSync8(localEnvPath) ? topLevelEnvPath : localEnvPath;
1479
1576
  const dir = dirname4(envPath);
1480
- if (!existsSync7(dir)) {
1577
+ if (!existsSync8(dir)) {
1481
1578
  mkdirSync2(dir, { recursive: true });
1482
1579
  }
1483
1580
  let envContent = "";
1484
- if (existsSync7(envPath)) {
1485
- envContent = readFileSync5(envPath, "utf8");
1581
+ if (existsSync8(envPath)) {
1582
+ envContent = readFileSync6(envPath, "utf8");
1486
1583
  }
1487
1584
  const regex = new RegExp(`^${key}=.*`, "m");
1488
1585
  if (regex.test(envContent)) {
@@ -2002,7 +2099,7 @@ var checkHarperStatusTool = tool30({
2002
2099
  // tools/harper/createNewHarperApplicationTool.ts
2003
2100
  import { tool as tool31 } from "@openai/agents";
2004
2101
  import { execSync as execSync3 } from "child_process";
2005
- import path7 from "path";
2102
+ import path8 from "path";
2006
2103
  import { z as z31 } from "zod";
2007
2104
 
2008
2105
  // utils/package/buildHarperCreateCommand.ts
@@ -2061,8 +2158,8 @@ async function execute16({ directoryName, template }) {
2061
2158
  const currentCwd = trackedState.cwd;
2062
2159
  const resolvedPath = resolvePath(currentCwd, directoryName);
2063
2160
  const isCurrentDir = resolvedPath === currentCwd;
2064
- const executionCwd = isCurrentDir ? resolvedPath : path7.dirname(resolvedPath);
2065
- const appName = isCurrentDir ? "." : path7.basename(resolvedPath);
2161
+ const executionCwd = isCurrentDir ? resolvedPath : path8.dirname(resolvedPath);
2162
+ const appName = isCurrentDir ? "." : path8.basename(resolvedPath);
2066
2163
  try {
2067
2164
  const pm = pickPreferredPackageManager();
2068
2165
  const { cmd, label } = buildCreateCommand(pm, appName, template);
@@ -2223,14 +2320,14 @@ var hitHarperAPITool = tool35({
2223
2320
  }
2224
2321
  return false;
2225
2322
  },
2226
- async execute({ path: path9 = "/openapi", port, method = "GET", body }) {
2323
+ async execute({ path: path10 = "/openapi", port, method = "GET", body }) {
2227
2324
  try {
2228
2325
  const effectivePort = port ?? (harperProcess.running ? harperProcess.httpPort : void 0);
2229
2326
  if (!effectivePort) {
2230
2327
  return `Error: No Harper application is currently running and no port was specified.`;
2231
2328
  }
2232
2329
  const response = await fetch(
2233
- `http://localhost:${effectivePort}${path9.startsWith("/") ? "" : "/"}${path9}`,
2330
+ `http://localhost:${effectivePort}${path10.startsWith("/") ? "" : "/"}${path10}`,
2234
2331
  {
2235
2332
  method,
2236
2333
  headers: body ? { "Content-Type": "application/json" } : {},
@@ -2270,7 +2367,7 @@ var readHarperLogsTool = tool36({
2270
2367
 
2271
2368
  // tools/harper/startHarperTool.ts
2272
2369
  import { tool as tool37 } from "@openai/agents";
2273
- import { existsSync as existsSync8 } from "fs";
2370
+ import { existsSync as existsSync9 } from "fs";
2274
2371
  import { basename, resolve } from "path";
2275
2372
  import { z as z37 } from "zod";
2276
2373
 
@@ -2297,7 +2394,7 @@ var startHarperTool = tool37({
2297
2394
  try {
2298
2395
  let effectiveDirectory = directoryName;
2299
2396
  const candidatePath = resolve(process.cwd(), directoryName);
2300
- if (!existsSync8(candidatePath)) {
2397
+ if (!existsSync9(candidatePath)) {
2301
2398
  const cwd = process.cwd();
2302
2399
  if (basename(cwd) === directoryName) {
2303
2400
  effectiveDirectory = cwd;
@@ -2625,7 +2722,7 @@ Stack: ${String(err.stack).split("\n").slice(0, 8).join("\n")}` : "";
2625
2722
 
2626
2723
  // utils/sessions/DiskSession.ts
2627
2724
  import { MemorySession } from "@openai/agents";
2628
- import { existsSync as existsSync9 } from "fs";
2725
+ import { existsSync as existsSync10 } from "fs";
2629
2726
  import { mkdir as mkdir2, readFile as readFile5, rename, writeFile as writeFile3 } from "fs/promises";
2630
2727
  import { dirname as dirname8 } from "path";
2631
2728
  var DiskSession = class extends MemorySession {
@@ -2676,7 +2773,7 @@ var DiskSession = class extends MemorySession {
2676
2773
  }
2677
2774
  }
2678
2775
  async loadStorage() {
2679
- if (existsSync9(this.filePath)) {
2776
+ if (existsSync10(this.filePath)) {
2680
2777
  try {
2681
2778
  const data = await readFile5(this.filePath, "utf-8");
2682
2779
  const parsed = JSON.parse(data);
@@ -2694,7 +2791,7 @@ var DiskSession = class extends MemorySession {
2694
2791
  const storage = await this.loadStorage();
2695
2792
  update(storage);
2696
2793
  const dir = dirname8(this.filePath);
2697
- if (!existsSync9(dir)) {
2794
+ if (!existsSync10(dir)) {
2698
2795
  await mkdir2(dir, { recursive: true });
2699
2796
  }
2700
2797
  const data = JSON.stringify(storage, null, 2);
@@ -3810,7 +3907,7 @@ async function runAgentForOnePass(agent, session, input, controller, isPrompt) {
3810
3907
  }
3811
3908
 
3812
3909
  // agent/AgentManager.ts
3813
- var AgentManager = class {
3910
+ var AgentManager = class _AgentManager {
3814
3911
  isInitialized = false;
3815
3912
  controller = null;
3816
3913
  queuedUserInputs = [];
@@ -3818,17 +3915,20 @@ var AgentManager = class {
3818
3915
  agent = null;
3819
3916
  session = null;
3820
3917
  initialMessages = [];
3821
- async initialize() {
3822
- if (this.isInitialized) {
3823
- return;
3824
- }
3825
- this.agent = new Agent3({
3918
+ static instantiateAgent(tools) {
3919
+ return new Agent3({
3826
3920
  name: "Harper Agent",
3827
3921
  model: isOpenAIModel(trackedState.model) ? trackedState.model : getModel(trackedState.model),
3828
3922
  modelSettings: getModelSettings(trackedState.model),
3829
3923
  instructions: readAgentSkillsRoot() || defaultInstructions(),
3830
- tools: createTools()
3924
+ tools
3831
3925
  });
3926
+ }
3927
+ async initialize() {
3928
+ if (this.isInitialized) {
3929
+ return;
3930
+ }
3931
+ this.agent = _AgentManager.instantiateAgent(createTools());
3832
3932
  this.session = createSession(trackedState.sessionPath);
3833
3933
  try {
3834
3934
  const plan = await this.session?.getPlanState?.();
@@ -4923,7 +5023,7 @@ function PlanView() {
4923
5023
 
4924
5024
  // ink/components/SettingsView.tsx
4925
5025
  import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
4926
- import path8 from "path";
5026
+ import path9 from "path";
4927
5027
  import { useEffect as useEffect6, useMemo as useMemo9, useState as useState11 } from "react";
4928
5028
 
4929
5029
  // ink/contexts/SettingsContext.tsx
@@ -5065,8 +5165,8 @@ function SettingsView({ isDense = false }) {
5065
5165
  if (!sessionPath) {
5066
5166
  return null;
5067
5167
  }
5068
- const relative = path8.relative(cwd, sessionPath);
5069
- if (!relative.startsWith("..") && !path8.isAbsolute(relative)) {
5168
+ const relative = path9.relative(cwd, sessionPath);
5169
+ if (!relative.startsWith("..") && !path9.isAbsolute(relative)) {
5070
5170
  return `./${relative}`;
5071
5171
  }
5072
5172
  return sessionPath;
@@ -6476,13 +6576,13 @@ function warnAndPersistRedirect(original, envKey, replacement, reason) {
6476
6576
  import chalk5 from "chalk";
6477
6577
 
6478
6578
  // utils/package/getOwnPackageJson.ts
6479
- import { readFileSync as readFileSync6 } from "fs";
6579
+ import { readFileSync as readFileSync7 } from "fs";
6480
6580
  import { join as join11 } from "path";
6481
- import { fileURLToPath } from "url";
6482
- var __dirname = fileURLToPath(new URL(".", import.meta.url));
6581
+ import { fileURLToPath as fileURLToPath2 } from "url";
6582
+ var __dirname2 = fileURLToPath2(new URL(".", import.meta.url));
6483
6583
  function getOwnPackageJson() {
6484
6584
  try {
6485
- const packageContents = readFileSync6(join11(__dirname, "../package.json"), "utf8");
6585
+ const packageContents = readFileSync7(join11(__dirname2, "../package.json"), "utf8");
6486
6586
  return JSON.parse(packageContents);
6487
6587
  } catch {
6488
6588
  return { name: "@harperfast/agent", version: "0.0.0" };
@@ -6686,16 +6786,16 @@ function parseArgs() {
6686
6786
 
6687
6787
  // utils/envLoader.ts
6688
6788
  import dotenv from "dotenv";
6689
- import { existsSync as existsSync10 } from "fs";
6789
+ import { existsSync as existsSync11 } from "fs";
6690
6790
  import { homedir as homedir4 } from "os";
6691
6791
  import { join as join12 } from "path";
6692
6792
  function loadEnv() {
6693
6793
  const topLevelEnvPath = join12(homedir4(), ".harper", "harper-agent-env");
6694
6794
  const localEnvPath = join12(process.cwd(), ".env");
6695
- if (existsSync10(topLevelEnvPath)) {
6795
+ if (existsSync11(topLevelEnvPath)) {
6696
6796
  dotenv.config({ path: topLevelEnvPath, quiet: true });
6697
6797
  }
6698
- if (existsSync10(localEnvPath)) {
6798
+ if (existsSync11(localEnvPath)) {
6699
6799
  dotenv.config({ path: localEnvPath, override: true, quiet: true });
6700
6800
  }
6701
6801
  }
@@ -0,0 +1,245 @@
1
+ """
2
+ A flexible JSON-like value.
3
+ Accepts objects, arrays, strings, numbers, booleans, or null.
4
+ Useful for schemaless or mixed content.
5
+ """
6
+ scalar Any
7
+
8
+ """
9
+ Arbitrary-precision integer for values larger than 32-bit/64-bit ranges.
10
+ Input may be provided as a JSON number or string; tooling may return it as a
11
+ string to avoid precision loss. Use `Long` for 64-bit integers when possible.
12
+ """
13
+ scalar BigInt
14
+
15
+ """
16
+ Binary large object. Represents binary data such as images or files.
17
+ Typically encoded as Base64 in JSON.
18
+ """
19
+ scalar Blob
20
+
21
+ """
22
+ true or false
23
+ """
24
+ scalar Boolean
25
+
26
+ """
27
+ Raw bytes. Typically encoded/transported as Base64 in JSON.
28
+ Use when you need deterministic binary data that is not a large file.
29
+ """
30
+ scalar Bytes
31
+
32
+ """
33
+ Date/time scalar. Recommended format: RFC 3339/ISO-8601 (e.g.,
34
+ "2025-12-02T19:44:00Z"). Represents an absolute timestamp.
35
+ """
36
+ scalar Date
37
+
38
+ """
39
+ A signed double-precision floating-point value
40
+ """
41
+ scalar Float
42
+
43
+ """
44
+ ID (serialized as a String): A unique identifier that's often used to refetch an
45
+ object or as the key for a cache. Although it's serialized as a String, an ID is
46
+ not intended to be human‐readable.
47
+ """
48
+ scalar ID
49
+
50
+ """
51
+ A signed 32‐bit integer
52
+ """
53
+ scalar Int
54
+
55
+ """
56
+ 64-bit signed integer. Some clients may serialize values as strings to
57
+ preserve precision across environments.
58
+ """
59
+ scalar Long
60
+
61
+ """
62
+ A UTF‐8 character sequence
63
+ """
64
+ scalar String
65
+
66
+ """
67
+ Attach to an object type to persist it as a table.
68
+
69
+ Example:
70
+ ```
71
+ type Post @table(table: "posts", database: "blog") {
72
+ id: ID @primaryKey
73
+ title: String @indexed(type: "fulltext")
74
+ body: String
75
+ createdAt: Date @createdTime
76
+ updatedAt: Date @updatedTime
77
+ }
78
+ ```
79
+ """
80
+ directive @table(
81
+ """
82
+ Explicit table name. If omitted, a sensible default derived from the
83
+ type name will be used.
84
+ """
85
+ table: String
86
+ """
87
+ Logical database/namespace to place this table in.
88
+ """
89
+ database: String
90
+ """
91
+ Default time-to-live (TTL) for records in seconds. Use a positive value to
92
+ enable automatic expiration; omit or set to 0 to disable.
93
+ """
94
+ expiration: Int
95
+ """
96
+ Enable auditing for create/update/delete operations.
97
+ """
98
+ audit: Boolean
99
+ """
100
+ The amount of time after expiration before a record can be evicted (defaults to zero).
101
+ """
102
+ eviction: Int
103
+ """
104
+ The interval for scanning for expired records (defaults to one quarter of the
105
+ total of expiration and eviction).
106
+ """
107
+ scanInterval: Int
108
+ """
109
+ By default, all tables within a replicated database will be replicated. Transactions
110
+ are replicated atomically, which may involve data across multiple tables. However,
111
+ you can also configure replication for individual tables, and disable and exclude
112
+ replication for specific tables in a database by setting replicate to false in the
113
+ table definition.
114
+ """
115
+ replicate: Boolean
116
+ ) on OBJECT
117
+
118
+ """
119
+ Expose the table via the REST API. When applied to a `@table` type, routes are
120
+ generated using the type name or the provided alias.
121
+ """
122
+ directive @export(
123
+ """
124
+ Optional alias to use for REST endpoints. If omitted, the type/table name is
125
+ used.
126
+ """
127
+ name: String
128
+ """
129
+ REST support is, by default, turned on for any exported resource. You may specify
130
+ false to disable this automatic API support.
131
+ """
132
+ rest: Boolean
133
+ """
134
+ MQTT support is, by default, turned on for any exported resource. You may specify
135
+ false to disable this automatic API support.
136
+ """
137
+ mqtt: Boolean
138
+ ) on OBJECT
139
+ """
140
+ As a NoSQL database, HarperDB supports heterogeneous records (also referred to as
141
+ documents), so you can freely specify additional properties on any record. If you
142
+ do want to restrict the records to only defined properties, you can always do that
143
+ by adding the sealed directive.
144
+ """
145
+ directive @sealed on OBJECT
146
+
147
+ """
148
+ Marks the primary key field for the table. The value must be unique per record
149
+ and is used for lookups, updates, and relationships.
150
+ """
151
+ directive @primaryKey on FIELD_DEFINITION
152
+
153
+ """
154
+ Allows enumeration over a computed field, causing it to be included in serialized responses. (Non-computed fields are always enumerable, and don't need to be flagged.)
155
+ """
156
+ directive @enumerable on FIELD_DEFINITION
157
+
158
+ """
159
+ Flags the field as containing the expiration time of the entry.
160
+ """
161
+ directive @expiresAt on FIELD_DEFINITION
162
+
163
+ """
164
+ Permit access to the field based on the named roles, only.
165
+ """
166
+ directive @allow(role: String) on FIELD_DEFINITION
167
+
168
+ """
169
+ Create an index for the annotated field. Supports traditional and vector/ANN
170
+ index types.
171
+ """
172
+ directive @indexed(
173
+ """
174
+ Optional index type, e.g. "HNSW"
175
+ """
176
+ type: String
177
+ """
178
+ Distance metric for vector indexes (e.g., "euclidean", "cosine").
179
+ Ignored for non-vector index types.
180
+ """
181
+ distance: String
182
+ """
183
+ Construction effort/recall parameter (HNSW). Higher values improve recall at
184
+ the cost of build time and memory.
185
+ """
186
+ efConstruction: Int
187
+ """
188
+ Maximum number of bi-directional connections per node (HNSW).
189
+ Typical range: 4–64.
190
+ """
191
+ M: Int
192
+ """
193
+ Routing optimization level for search graphs (implementation-specific).
194
+ """
195
+ optimizeRouting: Int
196
+ """
197
+ Additional multiplier for graph links (implementation-specific).
198
+ """
199
+ mL: Int
200
+ """
201
+ Search-time effort/recall parameter used during construction (implementation-
202
+ specific). Larger values typically yield better accuracy.
203
+ """
204
+ efConstructionSearch: Int
205
+ ) on FIELD_DEFINITION
206
+
207
+ """
208
+ Define a derived field whose value is computed from other fields.
209
+ Useful for denormalized or presentation-friendly data.
210
+ """
211
+ directive @computed(
212
+ """
213
+ Computation expression or reference describing how to derive this field from
214
+ other fields (engine-specific syntax).
215
+ """
216
+ from: String
217
+ """
218
+ Increment when the computation changes to trigger recomputation.
219
+ """
220
+ version: Int
221
+ ) on FIELD_DEFINITION
222
+ """
223
+ Automatically sets this field to the record's creation timestamp. The server
224
+ assigns the value on insert.
225
+ """
226
+ directive @createdTime on FIELD_DEFINITION
227
+ """
228
+ Automatically updates this field to the current timestamp whenever the record
229
+ is updated.
230
+ """
231
+ directive @updatedTime on FIELD_DEFINITION
232
+ """
233
+ Declares a relationship to another record or records.
234
+ Use on fields that should resolve to related entities by ID.
235
+ """
236
+ directive @relationship(
237
+ """
238
+ Name of field in THIS table containing foreign key(s) (for one-to-many or many-to-many).
239
+ """
240
+ from: String
241
+ """
242
+ Name of field in OTHER table containing foreign key (for one-to-one or many-to-one).
243
+ """
244
+ to: String
245
+ ) on FIELD_DEFINITION
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.16.0",
4
+ "version": "0.16.2",
5
5
  "main": "dist/agent.js",
6
6
  "repository": "github:HarperFast/harper-agent",
7
7
  "bugs": {
@@ -11,7 +11,7 @@
11
11
  "scripts": {
12
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 --external puppeteer",
14
+ "build": "tsup agent.ts --format esm --clean --dts --external puppeteer && cp node_modules/harperdb/schema.graphql dist/",
15
15
  "commitlint": "commitlint --edit",
16
16
  "start": "node ./dist/agent.js",
17
17
  "lint": "oxlint --format stylish .",
@@ -54,6 +54,7 @@
54
54
  "chalk": "^5.6.2",
55
55
  "cross-spawn": "^7.0.6",
56
56
  "dotenv": "^17.2.4",
57
+ "graphql": "^16.13.1",
57
58
  "ink": "^6.7.0",
58
59
  "ink-stepper": "^0.2.1",
59
60
  "ink-task-list": "^2.0.0",