apteva 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apteva",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "Run AI agents locally. Multi-provider support for Claude, GPT, Gemini, Llama, and more.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -63,5 +63,12 @@
63
63
  "@apteva/apteva-kit": "^0.1.102",
64
64
  "react": "^18.2.0",
65
65
  "react-dom": "^18.2.0"
66
+ },
67
+ "optionalDependencies": {
68
+ "@apteva/agent-darwin-arm64": "^0.2.0",
69
+ "@apteva/agent-darwin-x64": "^0.2.0",
70
+ "@apteva/agent-linux-x64": "^0.2.0",
71
+ "@apteva/agent-linux-arm64": "^0.2.0",
72
+ "@apteva/agent-win32-x64": "^0.2.0"
66
73
  }
67
74
  }
package/src/binary.ts CHANGED
@@ -3,9 +3,10 @@ import { existsSync, mkdirSync, chmodSync } from "fs";
3
3
 
4
4
  // Binary configuration
5
5
  const BINARY_BASE_URL = "https://github.com/apteva/agent/releases/latest/download";
6
- const DOWNLOAD_TIMEOUT = 60000; // 60 seconds
6
+ const CONNECT_TIMEOUT = 15000; // 15 seconds for initial connection
7
+ const DOWNLOAD_TIMEOUT = 120000; // 120 seconds for full download
7
8
  const MAX_RETRIES = 3;
8
- const RETRY_DELAY = 2000; // 2 seconds
9
+ const RETRY_DELAY = 1000; // 1 second between retries
9
10
 
10
11
  // ANSI colors for console output
11
12
  const c = {
@@ -16,12 +17,56 @@ const c = {
16
17
  red: "\x1b[38;5;196m",
17
18
  };
18
19
 
19
- // Determine platform and architecture
20
+ // Map Node.js platform/arch to npm package names
21
+ function getNpmPackageName(): string {
22
+ const platform = process.platform; // darwin, linux, win32
23
+ const arch = process.arch; // x64, arm64
24
+
25
+ // Map to our package naming convention
26
+ const archMap: Record<string, string> = {
27
+ x64: "x64",
28
+ arm64: "arm64",
29
+ };
30
+
31
+ const mappedArch = archMap[arch] || arch;
32
+ return `@apteva/agent-${platform}-${mappedArch}`;
33
+ }
34
+
35
+ // Try to find binary from installed npm package
36
+ function findNpmBinary(): string | null {
37
+ const packageName = getNpmPackageName();
38
+
39
+ try {
40
+ // Try to require the package - it exports the binary path
41
+ const binaryPath = require(packageName);
42
+ if (existsSync(binaryPath)) {
43
+ return binaryPath;
44
+ }
45
+ } catch {
46
+ // Package not installed, fall through
47
+ }
48
+
49
+ // Also try direct path resolution in node_modules
50
+ const possiblePaths = [
51
+ join(import.meta.dir, "../../node_modules", packageName, process.platform === "win32" ? "agent.exe" : "agent"),
52
+ join(process.cwd(), "node_modules", packageName, process.platform === "win32" ? "agent.exe" : "agent"),
53
+ ];
54
+
55
+ for (const p of possiblePaths) {
56
+ if (existsSync(p)) {
57
+ return p;
58
+ }
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ // Determine platform and architecture for download fallback
20
65
  function getPlatformInfo(): { platform: string; arch: string; ext: string } {
21
66
  const platform = process.platform === "win32" ? "windows" : process.platform;
22
67
  let arch = process.arch;
23
68
 
24
- // Normalize architecture names
69
+ // Normalize architecture names for GitHub releases
25
70
  if (arch === "x64") arch = "amd64";
26
71
  if (arch === "arm64") arch = "arm64";
27
72
 
@@ -30,13 +75,13 @@ function getPlatformInfo(): { platform: string; arch: string; ext: string } {
30
75
  return { platform, arch, ext };
31
76
  }
32
77
 
33
- // Get binary filename for current platform
78
+ // Get binary filename for current platform (for download)
34
79
  export function getBinaryFilename(): string {
35
80
  const { platform, arch, ext } = getPlatformInfo();
36
81
  return `agent-${platform}-${arch}${ext}`;
37
82
  }
38
83
 
39
- // Get full binary path
84
+ // Get full binary path in bin directory
40
85
  export function getBinaryPath(binDir: string): string {
41
86
  return join(binDir, getBinaryFilename());
42
87
  }
@@ -47,31 +92,78 @@ function getDownloadUrl(): string {
47
92
  return `${BINARY_BASE_URL}/${filename}`;
48
93
  }
49
94
 
50
- // Check if binary exists
95
+ // Check if binary exists (either from npm or downloaded)
51
96
  export function binaryExists(binDir: string): boolean {
97
+ // First check npm package
98
+ const npmBinary = findNpmBinary();
99
+ if (npmBinary) return true;
100
+
101
+ // Then check downloaded binary
52
102
  return existsSync(getBinaryPath(binDir));
53
103
  }
54
104
 
105
+ // Get the actual binary path (npm or downloaded)
106
+ export function getActualBinaryPath(binDir: string): string | null {
107
+ // First check npm package
108
+ const npmBinary = findNpmBinary();
109
+ if (npmBinary) return npmBinary;
110
+
111
+ // Then check downloaded binary
112
+ const downloadedPath = getBinaryPath(binDir);
113
+ if (existsSync(downloadedPath)) return downloadedPath;
114
+
115
+ return null;
116
+ }
117
+
55
118
  // Helper to delay
56
119
  const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
57
120
 
58
- // Download with timeout
59
- async function fetchWithTimeout(url: string, timeout: number): Promise<Response> {
121
+ // Download file with timeout for both connection and body
122
+ async function downloadWithTimeout(url: string): Promise<ArrayBuffer> {
60
123
  const controller = new AbortController();
61
- const timeoutId = setTimeout(() => controller.abort(), timeout);
124
+
125
+ // Timeout for connection
126
+ let timeoutId = setTimeout(() => controller.abort(), CONNECT_TIMEOUT);
127
+
128
+ const response = await fetch(url, { signal: controller.signal });
129
+ clearTimeout(timeoutId);
130
+
131
+ if (!response.ok) {
132
+ throw new Error(`HTTP ${response.status}`);
133
+ }
134
+
135
+ // Timeout for body download
136
+ timeoutId = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT);
62
137
 
63
138
  try {
64
- const response = await fetch(url, { signal: controller.signal });
139
+ const arrayBuffer = await response.arrayBuffer();
65
140
  clearTimeout(timeoutId);
66
- return response;
141
+ return arrayBuffer;
67
142
  } catch (err) {
68
143
  clearTimeout(timeoutId);
69
144
  throw err;
70
145
  }
71
146
  }
72
147
 
73
- // Download binary if missing
74
- export async function ensureBinary(binDir: string, silent = false): Promise<{ success: boolean; path: string; error?: string; downloaded?: boolean }> {
148
+ // Ensure binary exists - check npm first, then download
149
+ export async function ensureBinary(binDir: string, silent = false): Promise<{
150
+ success: boolean;
151
+ path: string;
152
+ error?: string;
153
+ downloaded?: boolean;
154
+ source?: "npm" | "download" | "cached";
155
+ }> {
156
+ // First, check if binary is available from npm package
157
+ const npmBinary = findNpmBinary();
158
+ if (npmBinary) {
159
+ return {
160
+ success: true,
161
+ path: npmBinary,
162
+ downloaded: false,
163
+ source: "npm"
164
+ };
165
+ }
166
+
75
167
  const binaryPath = getBinaryPath(binDir);
76
168
 
77
169
  // Ensure bin directory exists
@@ -79,15 +171,19 @@ export async function ensureBinary(binDir: string, silent = false): Promise<{ su
79
171
  mkdirSync(binDir, { recursive: true });
80
172
  }
81
173
 
82
- // Check if already exists
174
+ // Check if already downloaded
83
175
  if (existsSync(binaryPath)) {
84
- return { success: true, path: binaryPath, downloaded: false };
176
+ return {
177
+ success: true,
178
+ path: binaryPath,
179
+ downloaded: false,
180
+ source: "cached"
181
+ };
85
182
  }
86
183
 
184
+ // Need to download - show message
87
185
  const url = getDownloadUrl();
88
- const filename = getBinaryFilename();
89
186
 
90
- // Show downloading message (server.ts already printed "Binary ")
91
187
  if (!silent) {
92
188
  process.stdout.write(`${c.orange}downloading...${c.reset}`);
93
189
  }
@@ -97,47 +193,41 @@ export async function ensureBinary(binDir: string, silent = false): Promise<{ su
97
193
  for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
98
194
  try {
99
195
  if (!silent && attempt > 1) {
100
- // Clear line and show retry status
101
- process.stdout.write(`\r Binary ${c.orange}retry ${attempt}/${MAX_RETRIES}...${c.reset} `);
196
+ process.stdout.write(`\r Agent ${c.orange}retry ${attempt}/${MAX_RETRIES}...${c.reset} `);
102
197
  }
103
198
 
104
- const response = await fetchWithTimeout(url, DOWNLOAD_TIMEOUT);
105
-
106
- if (!response.ok) {
107
- if (response.status === 404) {
108
- if (!silent) {
109
- console.log(`\r${c.red}not found${c.reset} `);
110
- }
111
- return {
112
- success: false,
113
- path: binaryPath,
114
- error: `Binary not found at ${url}`,
115
- };
116
- }
117
- lastError = `HTTP ${response.status}`;
118
- if (attempt < MAX_RETRIES) {
119
- await sleep(RETRY_DELAY);
120
- continue;
121
- }
122
- } else {
123
- // Download the binary
124
- const arrayBuffer = await response.arrayBuffer();
125
- await Bun.write(binaryPath, arrayBuffer);
126
-
127
- // Make executable on Unix systems
128
- if (process.platform !== "win32") {
129
- chmodSync(binaryPath, 0o755);
130
- }
199
+ const arrayBuffer = await downloadWithTimeout(url);
200
+ await Bun.write(binaryPath, arrayBuffer);
131
201
 
132
- // Show success
133
- if (!silent) {
134
- const sizeMB = (arrayBuffer.byteLength / 1024 / 1024).toFixed(1);
135
- console.log(`\r${c.green}ready${c.reset} ${c.gray}(${sizeMB}MB downloaded)${c.reset} `);
136
- }
202
+ // Make executable on Unix systems
203
+ if (process.platform !== "win32") {
204
+ chmodSync(binaryPath, 0o755);
205
+ }
137
206
 
138
- return { success: true, path: binaryPath, downloaded: true };
207
+ // Show success
208
+ if (!silent) {
209
+ const sizeMB = (arrayBuffer.byteLength / 1024 / 1024).toFixed(1);
210
+ console.log(`\r${c.green}ready${c.reset} ${c.gray}(${sizeMB}MB downloaded)${c.reset} `);
139
211
  }
212
+
213
+ return {
214
+ success: true,
215
+ path: binaryPath,
216
+ downloaded: true,
217
+ source: "download"
218
+ };
140
219
  } catch (err: any) {
220
+ if (err.message?.startsWith("HTTP 404")) {
221
+ if (!silent) {
222
+ console.log(`\r${c.red}not found${c.reset} `);
223
+ }
224
+ return {
225
+ success: false,
226
+ path: binaryPath,
227
+ error: `Binary not found at ${url}`,
228
+ };
229
+ }
230
+
141
231
  lastError = err.name === "AbortError" ? "timeout" : String(err.message || err);
142
232
  if (attempt < MAX_RETRIES) {
143
233
  await sleep(RETRY_DELAY);
@@ -166,16 +256,45 @@ export function getBinaryStatus(binDir: string): {
166
256
  downloadUrl: string;
167
257
  platform: string;
168
258
  arch: string;
259
+ source?: "npm" | "download" | "none";
169
260
  } {
170
261
  const { platform, arch } = getPlatformInfo();
171
- const path = getBinaryPath(binDir);
262
+
263
+ // Check npm first
264
+ const npmBinary = findNpmBinary();
265
+ if (npmBinary) {
266
+ return {
267
+ exists: true,
268
+ path: npmBinary,
269
+ filename: getBinaryFilename(),
270
+ downloadUrl: getDownloadUrl(),
271
+ platform,
272
+ arch,
273
+ source: "npm",
274
+ };
275
+ }
276
+
277
+ // Check downloaded
278
+ const downloadedPath = getBinaryPath(binDir);
279
+ if (existsSync(downloadedPath)) {
280
+ return {
281
+ exists: true,
282
+ path: downloadedPath,
283
+ filename: getBinaryFilename(),
284
+ downloadUrl: getDownloadUrl(),
285
+ platform,
286
+ arch,
287
+ source: "download",
288
+ };
289
+ }
172
290
 
173
291
  return {
174
- exists: existsSync(path),
175
- path,
292
+ exists: false,
293
+ path: downloadedPath,
176
294
  filename: getBinaryFilename(),
177
295
  downloadUrl: getDownloadUrl(),
178
296
  platform,
179
297
  arch,
298
+ source: "none",
180
299
  };
181
300
  }
package/src/server.ts CHANGED
@@ -3,7 +3,7 @@ import { handleApiRequest } from "./routes/api";
3
3
  import { serveStatic } from "./routes/static";
4
4
  import { join } from "path";
5
5
  import { initDatabase, AgentDB, ProviderKeysDB } from "./db";
6
- import { ensureBinary, getBinaryPath, getBinaryStatus } from "./binary";
6
+ import { ensureBinary, getBinaryPath, getBinaryStatus, getActualBinaryPath } from "./binary";
7
7
 
8
8
  const PORT = parseInt(process.env.PORT || "4280");
9
9
  const DATA_DIR = process.env.DATA_DIR || join(import.meta.dir, "../data");
@@ -31,8 +31,18 @@ AgentDB.resetAllStatus();
31
31
  // In-memory store for running agent processes only
32
32
  export const agentProcesses: Map<string, Subprocess> = new Map();
33
33
 
34
- // Binary path - can be overridden via environment variable
35
- export const BINARY_PATH = process.env.AGENT_BINARY_PATH || getBinaryPath(BIN_DIR);
34
+ // Binary path - can be overridden via environment variable, or found from npm/downloaded
35
+ export function getBinaryPathForAgent(): string {
36
+ // Environment override takes priority
37
+ if (process.env.AGENT_BINARY_PATH) {
38
+ return process.env.AGENT_BINARY_PATH;
39
+ }
40
+ // Otherwise use npm package or downloaded binary
41
+ return getActualBinaryPath(BIN_DIR) || getBinaryPath(BIN_DIR);
42
+ }
43
+
44
+ // Export for legacy compatibility
45
+ export const BINARY_PATH = getBinaryPathForAgent();
36
46
 
37
47
  // Export binary status function for API
38
48
  export { getBinaryStatus, BIN_DIR };
@@ -74,12 +84,12 @@ console.log(`
74
84
  `);
75
85
 
76
86
  // Check binary - ensureBinary handles progress output when downloading
77
- process.stdout.write(` ${c.darkGray}Binary${c.reset} `);
87
+ process.stdout.write(` ${c.darkGray}Agent${c.reset} `);
78
88
  const binaryResult = await ensureBinary(BIN_DIR);
79
89
  // ensureBinary prints its own status when downloading/failing
80
90
  // We only need to print "ready" if binary already existed
81
91
  if (binaryResult.success && !binaryResult.downloaded) {
82
- console.log(`${c.gray}ready${c.reset}`);
92
+ console.log(`${c.gray}binary ready${c.reset}`);
83
93
  }
84
94
 
85
95
  // Check database
@@ -93,7 +103,8 @@ console.log(`${c.gray}${configuredProviders.length} configured${c.reset}`);
93
103
 
94
104
  const server = Bun.serve({
95
105
  port: PORT,
96
- development: false, // Suppress "Started development server" message
106
+ hostname: "0.0.0.0", // Listen on all interfaces
107
+ development: false, // Suppress "Started server" message
97
108
 
98
109
  async fetch(req: Request): Promise<Response> {
99
110
  const url = new URL(req.url);
@@ -132,4 +143,4 @@ console.log(`
132
143
  ${c.darkGray}Click link or Cmd/Ctrl+C to copy${c.reset}
133
144
  `);
134
145
 
135
- export default server;
146
+ // Note: Don't use "export default server" - it causes Bun to print "Started server" message