@dougbots/avenor-core 0.1.0 → 0.2.1

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
@@ -44,11 +44,13 @@ const result = await spawnTool({
44
44
  prompt: 'Write a function to reverse a string',
45
45
  label: 'reverse-string',
46
46
  model: 'claude-sonnet-4',
47
+ backend: 'opencode-http',
48
+ serverUrl: 'http://127.0.0.1:4096',
47
49
  })
48
50
  // { run_id: '...', label: 'reverse-string', supervisor_id: '/path/to/socket' }
49
51
  ```
50
52
 
51
- Accepts `agent`, `prompt`, `promptFile`, `label`, `dir`, `timeout`, `model`, `sessionId`, and `supervisorId` (to target a specific socket).
53
+ Accepts `agent`, `prompt`, `promptFile`, `label`, `dir`, `timeout`, `model`, `backend`, `serverUrl`, `sessionId`, and `supervisorId` (to target a specific socket).
52
54
 
53
55
  ### `statusTool`
54
56
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { n as getVersion, r as installerBinaryPath, t as getInstallDir } from "./install-path-DagQIDKB.js";
1
2
  import * as net from "node:net";
2
3
 
3
4
  //#region src/client.d.ts
@@ -15,6 +16,8 @@ interface SpawnParams {
15
16
  label?: string;
16
17
  timeout?: number;
17
18
  model?: string;
19
+ backend?: string;
20
+ server_url?: string;
18
21
  session_id?: string;
19
22
  sentinel_file?: string;
20
23
  on_event?: string;
@@ -98,6 +101,8 @@ declare function spawnTool(args: {
98
101
  dir?: string;
99
102
  timeout?: string;
100
103
  model?: string;
104
+ backend?: string;
105
+ serverUrl?: string;
101
106
  sessionId?: string;
102
107
  supervisorId?: string;
103
108
  }): Promise<{
@@ -157,11 +162,13 @@ declare function eventsTool(args: {
157
162
  types?: string[];
158
163
  limit?: number;
159
164
  supervisorId?: string;
160
- }): Promise<Array<{
161
- type: string;
162
- ts: number;
163
- [key: string]: unknown;
164
- }>>;
165
+ }): Promise<{
166
+ events: Array<{
167
+ type: string;
168
+ ts: number;
169
+ [key: string]: unknown;
170
+ }>;
171
+ }>;
165
172
  //#endregion
166
173
  //#region src/tools/shutdown.d.ts
167
174
  declare function shutdownTool(args: {
@@ -172,4 +179,4 @@ declare function shutdownTool(args: {
172
179
  cleaned_up: string[];
173
180
  }>;
174
181
  //#endregion
175
- export { type Client, type Event, type RunInfo, type SpawnParams, type StatusResult, Supervisor, type SupervisorOptions, answerPermissionTool, dial, eventsTool, findAvenorBinary, followUpTool, shutdownTool, spawnTool, statusTool };
182
+ export { type Client, type Event, type RunInfo, type SpawnParams, type StatusResult, Supervisor, type SupervisorOptions, answerPermissionTool, dial, eventsTool, findAvenorBinary, followUpTool, getInstallDir, getVersion, installerBinaryPath, shutdownTool, spawnTool, statusTool };
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { n as getVersion, r as installerBinaryPath, t as getInstallDir } from "./install-path-C7QXqbJO.js";
1
2
  import * as net from "node:net";
2
3
  import * as readline from "node:readline";
3
4
  import { execSync, spawn } from "node:child_process";
@@ -222,12 +223,20 @@ function findAvenorBinary() {
222
223
  throw new Error(`AVENOR_BIN path not executable: ${envBin}`);
223
224
  }
224
225
  try {
225
- const binPath = execSync("which avenor", { encoding: "utf-8" }).trim();
226
+ const binPath = execSync("which avenor", {
227
+ encoding: "utf-8",
228
+ env: process.env
229
+ }).trim();
226
230
  if (binPath) {
227
231
  fs.accessSync(binPath, fs.constants.X_OK);
228
232
  return binPath;
229
233
  }
230
234
  } catch {}
235
+ try {
236
+ const installPath = installerBinaryPath();
237
+ fs.accessSync(installPath, fs.constants.X_OK);
238
+ return installPath;
239
+ } catch {}
231
240
  const homeBin = path.join(os.homedir(), ".botfiles", "bin", "avenor");
232
241
  try {
233
242
  fs.accessSync(homeBin, fs.constants.X_OK);
@@ -443,12 +452,33 @@ function validateSupervisorSocketPath(supervisorId) {
443
452
  return canonical;
444
453
  }
445
454
  //#endregion
455
+ //#region src/tools/get-supervisor-client.ts
456
+ async function getSupervisorClient(supervisorId) {
457
+ const id = validateSupervisorSocketPath(supervisorId);
458
+ if (Supervisor.isCurrentInstance(id)) {
459
+ const sup = Supervisor.currentInstance();
460
+ if (!sup) throw new Error(`Supervisor.isCurrentInstance() returned true but currentInstance() is null (id: ${id})`);
461
+ return {
462
+ client: sup.getClient(),
463
+ isSingleton: true,
464
+ sup,
465
+ supervisorId: id
466
+ };
467
+ }
468
+ return {
469
+ client: await dial(id),
470
+ isSingleton: false,
471
+ sup: null,
472
+ supervisorId: id
473
+ };
474
+ }
475
+ //#endregion
446
476
  //#region src/tools/spawn.ts
447
477
  async function spawnTool(args) {
448
478
  const runId = crypto.randomUUID();
449
479
  const label = args.label ?? runId;
450
480
  if (args.supervisorId) {
451
- const client = await dial(validateSupervisorSocketPath(args.supervisorId));
481
+ const { client, isSingleton, supervisorId } = await getSupervisorClient(args.supervisorId);
452
482
  const { sentinelPath, eventLogPath } = ensureRunPaths(runId);
453
483
  try {
454
484
  return {
@@ -460,15 +490,17 @@ async function spawnTool(args) {
460
490
  dir: args.dir,
461
491
  timeout: args.timeout !== void 0 ? validateTimeout(args.timeout) : void 0,
462
492
  model: args.model,
493
+ backend: args.backend,
494
+ server_url: args.serverUrl,
463
495
  session_id: args.sessionId,
464
496
  sentinel_file: sentinelPath,
465
497
  on_event: eventLogPath
466
498
  })).runtime_id ?? runId,
467
499
  label,
468
- supervisor_id: args.supervisorId
500
+ supervisor_id: supervisorId
469
501
  };
470
502
  } finally {
471
- client.close();
503
+ if (!isSingleton) client.close();
472
504
  }
473
505
  }
474
506
  const sup = await Supervisor.get();
@@ -480,6 +512,8 @@ async function spawnTool(args) {
480
512
  dir: args.dir,
481
513
  timeout: args.timeout !== void 0 ? validateTimeout(args.timeout) : void 0,
482
514
  model: args.model,
515
+ backend: args.backend,
516
+ server_url: args.serverUrl,
483
517
  session_id: args.sessionId
484
518
  });
485
519
  return {
@@ -574,7 +608,7 @@ async function buildRunStatus(client, runInfo, liveStatus) {
574
608
  }
575
609
  async function statusTool(args) {
576
610
  if (args.supervisorId) {
577
- const client = await dial(validateSupervisorSocketPath(args.supervisorId));
611
+ const { client, isSingleton } = await getSupervisorClient(args.supervisorId);
578
612
  try {
579
613
  if (args.runId) {
580
614
  validateRunId(args.runId);
@@ -612,19 +646,46 @@ async function statusTool(args) {
612
646
  session_id: entry.session_id
613
647
  }));
614
648
  } finally {
615
- client.close();
649
+ if (!isSingleton) client.close();
616
650
  }
617
651
  }
618
652
  const sup = await Supervisor.get();
619
653
  const client = sup.getClient();
620
654
  if (args.runId) {
621
655
  const runInfo = findRunByLabel$3(sup, args.runId);
622
- if (!runInfo) throw new Error(`run not found: ${args.runId}`);
656
+ if (runInfo) {
657
+ let liveStatus = null;
658
+ if (runInfo.runtimeId) try {
659
+ liveStatus = await client.status(runInfo.runtimeId);
660
+ } catch {}
661
+ return buildRunStatus(client, runInfo, liveStatus);
662
+ }
663
+ validateRunId(args.runId);
623
664
  let liveStatus = null;
624
- if (runInfo.runtimeId) try {
625
- liveStatus = await client.status(runInfo.runtimeId);
665
+ try {
666
+ liveStatus = await client.status(args.runId);
626
667
  } catch {}
627
- return buildRunStatus(client, runInfo, liveStatus);
668
+ const sentinelPath = path.join(runsRoot(), args.runId, "sentinel.done");
669
+ let sentinel = null;
670
+ if (await sentinelExists(sentinelPath)) sentinel = await parseSentinel$1(sentinelPath);
671
+ return {
672
+ run_id: args.runId,
673
+ label: args.runId,
674
+ status: translateStatus(liveStatus?.phase ?? liveStatus?.status ?? "running", sentinel),
675
+ phase: liveStatus?.phase,
676
+ phase_label: liveStatus?.phase_label,
677
+ session_id: sentinel?.SESSION ?? liveStatus?.session_id ?? void 0,
678
+ stop_reason: sentinel?._status ?? void 0,
679
+ pending_permission: liveStatus?.pending_permission ? {
680
+ request_id: liveStatus.pending_permission.request_id ?? "",
681
+ description: liveStatus.pending_permission.description ?? "",
682
+ options: Array.isArray(liveStatus.pending_permission.options) ? liveStatus.pending_permission.options.map((o) => ({
683
+ option_id: o.option_id ?? "",
684
+ label: o.label ?? "",
685
+ kind: o.kind ?? ""
686
+ })) : []
687
+ } : void 0
688
+ };
628
689
  }
629
690
  const list = await client.list();
630
691
  const results = [];
@@ -659,12 +720,15 @@ async function answerPermissionTool(args) {
659
720
  let client;
660
721
  let sup = null;
661
722
  let requestId = args.requestId;
662
- if (args.supervisorId) client = await dial(validateSupervisorSocketPath(args.supervisorId));
723
+ let supervisorId;
724
+ let isSingleton = false;
725
+ if (args.supervisorId) ({client, isSingleton, sup, supervisorId} = await getSupervisorClient(args.supervisorId));
663
726
  else {
664
727
  sup = await Supervisor.get();
665
728
  client = sup.getClient();
666
729
  }
667
730
  try {
731
+ validateRunId(args.runId);
668
732
  const runtimeId = (sup ? findRunByLabel$2(sup, args.runId) : void 0)?.runtimeId ?? args.runId;
669
733
  if (!requestId) {
670
734
  const pp = (await client.status(runtimeId))?.pending_permission;
@@ -674,7 +738,7 @@ async function answerPermissionTool(args) {
674
738
  await client.answerPermission(runtimeId, requestId, args.optionId);
675
739
  return { ok: true };
676
740
  } finally {
677
- if (args.supervisorId) client.close();
741
+ if (supervisorId && !isSingleton) client.close();
678
742
  }
679
743
  }
680
744
  //#endregion
@@ -705,7 +769,7 @@ async function parseSentinel(filePath) {
705
769
  }
706
770
  async function followUpTool(args) {
707
771
  if (args.supervisorId) {
708
- const client = await dial(validateSupervisorSocketPath(args.supervisorId));
772
+ const { client, isSingleton } = await getSupervisorClient(args.supervisorId);
709
773
  try {
710
774
  validateRunId(args.runId);
711
775
  let liveStatus = null;
@@ -731,7 +795,7 @@ async function followUpTool(args) {
731
795
  label: followUpLabel
732
796
  };
733
797
  } finally {
734
- client.close();
798
+ if (!isSingleton) client.close();
735
799
  }
736
800
  }
737
801
  const sup = await Supervisor.get();
@@ -783,7 +847,7 @@ async function eventsTool(args) {
783
847
  await handle.close();
784
848
  }
785
849
  } catch {
786
- return [];
850
+ return { events: [] };
787
851
  }
788
852
  const lines = raw.split("\n").filter((line) => line.trim());
789
853
  let events = [];
@@ -792,7 +856,7 @@ async function eventsTool(args) {
792
856
  } catch {}
793
857
  if (args.types && args.types.length > 0) events = events.filter((e) => args.types.includes(e.type ?? e.event ?? ""));
794
858
  if (events.length > limit) events = events.slice(events.length - limit);
795
- return events;
859
+ return { events };
796
860
  }
797
861
  //#endregion
798
862
  //#region src/tools/shutdown.ts
@@ -851,4 +915,4 @@ async function shutdownTool(args) {
851
915
  };
852
916
  }
853
917
  //#endregion
854
- export { Supervisor, answerPermissionTool, dial, eventsTool, findAvenorBinary, followUpTool, shutdownTool, spawnTool, statusTool };
918
+ export { Supervisor, answerPermissionTool, dial, eventsTool, findAvenorBinary, followUpTool, getInstallDir, getVersion, installerBinaryPath, shutdownTool, spawnTool, statusTool };
@@ -0,0 +1,30 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ //#region src/install-path.ts
5
+ const SEMVER_RE = /^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$/;
6
+ function getVersion(packageJsonPath) {
7
+ const envVer = process.env.AVENOR_VERSION;
8
+ if (envVer) {
9
+ if (!SEMVER_RE.test(envVer)) throw new Error(`Invalid AVENOR_VERSION: ${envVer}. Must be a semver string like 1.2.3`);
10
+ return envVer;
11
+ }
12
+ const pkgVer = process.env.npm_package_version;
13
+ if (pkgVer) {
14
+ if (!SEMVER_RE.test(pkgVer)) throw new Error(`Invalid npm_package_version: ${pkgVer}. Must be a semver string like 1.2.3`);
15
+ return pkgVer;
16
+ }
17
+ const pkgPath = packageJsonPath ?? new URL("../package.json", import.meta.url);
18
+ const v = JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
19
+ if (!SEMVER_RE.test(v)) throw new Error(`Invalid package version: ${v}. Must be a semver string like 1.2.3`);
20
+ return v;
21
+ }
22
+ function getInstallDir(version) {
23
+ if (process.env.AVENOR_INSTALL_DIR) return process.env.AVENOR_INSTALL_DIR;
24
+ return path.join(os.homedir(), ".cache", "avenor", "bin", "avenor", version);
25
+ }
26
+ function installerBinaryPath(version = getVersion()) {
27
+ return path.join(getInstallDir(version), "avenor");
28
+ }
29
+ //#endregion
30
+ export { getVersion as n, installerBinaryPath as r, getInstallDir as t };
@@ -0,0 +1,6 @@
1
+ //#region src/install-path.d.ts
2
+ declare function getVersion(packageJsonPath?: string): string;
3
+ declare function getInstallDir(version: string): string;
4
+ declare function installerBinaryPath(version?: string): string;
5
+ //#endregion
6
+ export { getVersion as n, installerBinaryPath as r, getInstallDir as t };
@@ -0,0 +1,9 @@
1
+ import { n as getVersion, r as installerBinaryPath, t as getInstallDir } from "./install-path-DagQIDKB.js";
2
+
3
+ //#region src/postinstall.d.ts
4
+ declare function platformMapping(platform: string, arch: string): string | null;
5
+ declare function verifyChecksum(buffer: Buffer, expectedHash: string): boolean;
6
+ declare function parseChecksums(content: string, assetName: string): string | null;
7
+ declare function postinstall(fetchFn?: typeof globalThis.fetch, platform?: string, arch?: string): Promise<void>;
8
+ //#endregion
9
+ export { getInstallDir, getVersion, installerBinaryPath, parseChecksums, platformMapping, postinstall, verifyChecksum };
@@ -0,0 +1,95 @@
1
+ import { n as getVersion, r as installerBinaryPath, t as getInstallDir } from "./install-path-C7QXqbJO.js";
2
+ import { execSync } from "node:child_process";
3
+ import * as fs from "node:fs";
4
+ import * as crypto from "node:crypto";
5
+ //#region src/postinstall.ts
6
+ function platformMapping(platform, arch) {
7
+ return {
8
+ "darwin/arm64": "avenor_darwin_arm64",
9
+ "darwin/x64": "avenor_darwin_amd64",
10
+ "linux/x64": "avenor_linux_amd64",
11
+ "linux/arm64": "avenor_linux_arm64"
12
+ }[`${platform}/${arch}`] ?? null;
13
+ }
14
+ function verifyChecksum(buffer, expectedHash) {
15
+ return crypto.createHash("sha256").update(buffer).digest("hex") === expectedHash;
16
+ }
17
+ function parseChecksums(content, assetName) {
18
+ for (const line of content.split("\n")) {
19
+ const trimmed = line.trim();
20
+ if (!trimmed) continue;
21
+ const parts = trimmed.split(/\s+/);
22
+ if (parts.length >= 2 && parts[1] === assetName) return parts[0];
23
+ }
24
+ return null;
25
+ }
26
+ async function postinstall(fetchFn = globalThis.fetch, platform, arch) {
27
+ const envBin = process.env.AVENOR_BIN;
28
+ if (envBin) try {
29
+ fs.accessSync(envBin, fs.constants.X_OK);
30
+ return;
31
+ } catch {}
32
+ try {
33
+ const whichBin = execSync("which avenor", {
34
+ encoding: "utf-8",
35
+ env: process.env
36
+ }).trim();
37
+ if (whichBin) try {
38
+ fs.accessSync(whichBin, fs.constants.X_OK);
39
+ return;
40
+ } catch {}
41
+ } catch {}
42
+ if (process.env.AVENOR_SKIP_DOWNLOAD === "1") {
43
+ console.warn("[avenor-postinstall] AVENOR_SKIP_DOWNLOAD=1, skipping download. Binary may not be available.");
44
+ return;
45
+ }
46
+ const plat = platform ?? process.platform;
47
+ const a = arch ?? process.arch;
48
+ const asset = platformMapping(plat, a);
49
+ if (!asset) {
50
+ console.warn(`[avenor-postinstall] Unsupported platform: ${plat}/${a}`);
51
+ return;
52
+ }
53
+ const version = getVersion();
54
+ const installDir = getInstallDir(version);
55
+ const binaryPath = installerBinaryPath(version);
56
+ const url = `https://github.com/sdougbrown/avenor/releases/download/v${version}/${asset}`;
57
+ try {
58
+ fs.mkdirSync(installDir, {
59
+ recursive: true,
60
+ mode: 448
61
+ });
62
+ const response = await fetchFn(url);
63
+ if (!response.ok) {
64
+ console.warn(`[avenor-postinstall] Download failed: HTTP ${response.status} from ${url}`);
65
+ return;
66
+ }
67
+ const buffer = Buffer.from(await response.arrayBuffer());
68
+ const checksumsUrl = `https://github.com/sdougbrown/avenor/releases/download/v${version}/checksums.txt`;
69
+ try {
70
+ const checksumsResponse = await fetchFn(checksumsUrl);
71
+ if (checksumsResponse.ok) {
72
+ const expectedHash = parseChecksums(await checksumsResponse.text(), asset);
73
+ if (expectedHash) {
74
+ if (!verifyChecksum(buffer, expectedHash)) {
75
+ console.warn(`[avenor-postinstall] Checksum mismatch for ${asset}, skipping installation`);
76
+ return;
77
+ }
78
+ } else console.warn(`[avenor-postinstall] Checksums not available for ${asset}, skipping verification`);
79
+ } else console.warn(`[avenor-postinstall] Checksums not available (HTTP ${checksumsResponse.status}), skipping verification`);
80
+ } catch {
81
+ console.warn(`[avenor-postinstall] Checksums not available, skipping verification`);
82
+ }
83
+ fs.writeFileSync(binaryPath, buffer);
84
+ fs.chmodSync(binaryPath, 493);
85
+ } catch (err) {
86
+ console.warn(`[avenor-postinstall] Failed to install avenor binary: ${err instanceof Error ? err.message : String(err)}`);
87
+ }
88
+ }
89
+ const arg1 = process.argv[1];
90
+ if (!!arg1 && (arg1.endsWith("postinstall.js") || arg1.endsWith("postinstall.ts"))) postinstall().catch((err) => {
91
+ console.warn(`[avenor-postinstall] Unhandled error: ${err instanceof Error ? err.message : String(err)}`);
92
+ process.exit(0);
93
+ });
94
+ //#endregion
95
+ export { getInstallDir, getVersion, installerBinaryPath, parseChecksums, platformMapping, postinstall, verifyChecksum };
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@dougbots/avenor-core",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/sdougbrown/avenor",
8
+ "directory": "packages/core"
9
+ },
5
10
  "files": [
6
11
  "dist"
7
12
  ],
@@ -20,7 +25,8 @@
20
25
  "build": "tsdown",
21
26
  "test": "bun test",
22
27
  "test:e2e": "AVENOR_REAL_INTEGRATION=1 bun test",
23
- "dev": "tsdown --watch"
28
+ "dev": "tsdown --watch",
29
+ "postinstall": "node -e \"const fs=require('fs');if(fs.existsSync('dist/postinstall.js')){require('./dist/postinstall.js')}\""
24
30
  },
25
31
  "devDependencies": {
26
32
  "@types/bun": "^1.3.12",