apteva 0.11.2 → 0.12.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.
Files changed (3) hide show
  1. package/cli.js +46 -11
  2. package/install.js +102 -24
  3. package/package.json +1 -1
package/cli.js CHANGED
@@ -1,6 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
+ // cli.js — npm shim. The user runs `apteva` (or `npx apteva`); npm
5
+ // resolves it to this file via package.json's "bin" field, and we
6
+ // hand off to the Go binary.
7
+ //
8
+ // Path resolution order:
9
+ //
10
+ // 1. ~/.apteva/bin/apteva — the load-bearing symlink chain that
11
+ // `apteva update` flips. Preferred so `apteva update` followed
12
+ // by re-running `apteva` actually picks up the new version
13
+ // (vs running the stale __dirname copy from npm's install).
14
+ //
15
+ // 2. __dirname/apteva — the defensive copy install.js drops into
16
+ // npm's package install dir. Fallback for environments where
17
+ // ~/.apteva can't host symlinks (CI, restricted containers,
18
+ // Windows without elevated privileges).
19
+ //
20
+ // APTEVA_SERVER_BIN / APTEVA_CORE_BIN propagate to the Go process
21
+ // so its findServerBinary / findCoreBinary helpers prefer the
22
+ // symlinked siblings rather than re-resolving against $PATH.
23
+
4
24
  const os = require("os");
5
25
  const path = require("path");
6
26
  const fs = require("fs");
@@ -8,29 +28,44 @@ const { spawnSync } = require("child_process");
8
28
 
9
29
  const ext = os.platform() === "win32" ? ".exe" : "";
10
30
  const DIR = __dirname;
31
+ const APTEVA_HOME = process.env.APTEVA_HOME || path.join(os.homedir(), ".apteva");
32
+ const SYMLINKED_BIN_DIR = path.join(APTEVA_HOME, "bin");
33
+
34
+ function resolveBin(name) {
35
+ const linked = path.join(SYMLINKED_BIN_DIR, `${name}${ext}`);
36
+ // Use the symlink path even if the target is missing — when
37
+ // present, it's the canonical "active version" pointer that
38
+ // `apteva update` rewrites. fs.existsSync follows symlinks, so
39
+ // we get a positive hit only when the entire chain resolves.
40
+ if (fs.existsSync(linked)) return linked;
41
+ // Fallback: the per-package copy install.js dropped here.
42
+ const local = path.join(DIR, `${name}${ext}`);
43
+ if (fs.existsSync(local)) return local;
44
+ return null;
45
+ }
11
46
 
12
- const aptevaBin = path.join(DIR, `apteva${ext}`);
13
- const serverBin = path.join(DIR, `apteva-server${ext}`);
14
- const coreBin = path.join(DIR, `apteva-core${ext}`);
47
+ const aptevaBin = resolveBin("apteva");
48
+ const serverBin = resolveBin("apteva-server");
49
+ const coreBin = resolveBin("apteva-core");
15
50
 
16
- if (!fs.existsSync(aptevaBin)) {
17
- console.error(`apteva: binary not found at ${aptevaBin}`);
51
+ if (!aptevaBin) {
52
+ console.error("apteva: binary not found — symlinks at ~/.apteva/bin/ and the npm install dir are both empty.");
18
53
  console.error("Try: npm install -g apteva");
19
54
  process.exit(1);
20
55
  }
21
- if (!fs.existsSync(serverBin)) {
22
- console.error(`apteva: warning - server binary not found at ${serverBin}`);
56
+ if (!serverBin) {
57
+ console.error(`apteva: warning - server binary not found`);
23
58
  }
24
- if (!fs.existsSync(coreBin)) {
25
- console.error(`apteva: warning - core binary not found at ${coreBin}`);
59
+ if (!coreBin) {
60
+ console.error(`apteva: warning - core binary not found`);
26
61
  }
27
62
 
28
63
  const result = spawnSync(aptevaBin, process.argv.slice(2), {
29
64
  stdio: "inherit",
30
65
  env: {
31
66
  ...process.env,
32
- APTEVA_SERVER_BIN: serverBin,
33
- APTEVA_CORE_BIN: coreBin,
67
+ APTEVA_SERVER_BIN: serverBin || "",
68
+ APTEVA_CORE_BIN: coreBin || "",
34
69
  },
35
70
  });
36
71
 
package/install.js CHANGED
@@ -1,6 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
+ // install.js — npm postinstall hook.
5
+ //
6
+ // Downloads the platform tarball for `apteva@<VERSION>` from
7
+ // GitHub releases and unpacks it into ~/.apteva/versions/<VERSION>/.
8
+ // Sets up the symlink chain
9
+ //
10
+ // ~/.apteva/bin/current → ../versions/<VERSION>
11
+ // ~/.apteva/bin/apteva → current/apteva
12
+ // ~/.apteva/bin/apteva-server → current/apteva-server
13
+ // ~/.apteva/bin/apteva-core → current/apteva-core
14
+ //
15
+ // so cli.js (and `apteva update`'s atomic-flip path, and any systemd
16
+ // unit that points at ~/.apteva/bin/apteva-server) all resolve to
17
+ // the right binary regardless of which version is "active".
18
+ //
19
+ // Pre-v0.12, the layout was ~/.apteva/bin/<v>/<binaries> and the
20
+ // install dir held its own copies. We still drop copies into the
21
+ // install dir as a defensive fallback — cli.js prefers the symlink
22
+ // path but falls back to __dirname for environments where home
23
+ // isn't writable (CI, restricted containers, etc).
24
+
4
25
  const os = require("os");
5
26
  const fs = require("fs");
6
27
  const path = require("path");
@@ -15,6 +36,17 @@ const DIR = __dirname;
15
36
  const PLATFORM_MAP = { linux: "linux", darwin: "darwin", win32: "windows" };
16
37
  const ARCH_MAP = { x64: "amd64", arm64: "arm64" };
17
38
 
39
+ // Path helpers — mirror layout.go in the apteva CLI. Kept in sync
40
+ // by hand; both files are short and the shape is fixed.
41
+ const APTEVA_HOME = process.env.APTEVA_HOME || path.join(os.homedir(), ".apteva");
42
+ const VERSIONS_DIR = path.join(APTEVA_HOME, "versions");
43
+ const BIN_DIR = path.join(APTEVA_HOME, "bin");
44
+ const RELEASES_DIR = path.join(APTEVA_HOME, "releases");
45
+ const VERSION_DIR = path.join(VERSIONS_DIR, VERSION);
46
+ const CURRENT_LINK = path.join(BIN_DIR, "current");
47
+
48
+ const BIN_NAMES = ["apteva", "apteva-server", "apteva-core"];
49
+
18
50
  function download(url) {
19
51
  return new Promise((resolve, reject) => {
20
52
  const follow = (url, redirects) => {
@@ -39,6 +71,33 @@ function download(url) {
39
71
  });
40
72
  }
41
73
 
74
+ // pointSymlinks (re)creates the bin/ symlink chain pointing at
75
+ // VERSION_DIR. Idempotent — running it twice is a no-op. Mirrors
76
+ // the Go-side pointSymlinks() in apteva/layout.go; we duplicate it
77
+ // here so npm's postinstall doesn't need a Go runtime.
78
+ function pointSymlinks() {
79
+ fs.mkdirSync(BIN_DIR, { recursive: true });
80
+
81
+ // current → ../versions/<VERSION>. Relative target keeps the
82
+ // tree portable (move ~/.apteva to a different host home and it
83
+ // still resolves).
84
+ const relTarget = path.join("..", "versions", VERSION);
85
+ const tmp = CURRENT_LINK + ".tmp";
86
+ try { fs.unlinkSync(tmp); } catch {}
87
+ fs.symlinkSync(relTarget, tmp);
88
+ // POSIX-atomic rename onto current.
89
+ fs.renameSync(tmp, CURRENT_LINK);
90
+
91
+ // Per-binary shims: bin/<name> → current/<name>. Replace if
92
+ // already present — old shims pointing at a since-deleted version
93
+ // dir would fail to resolve.
94
+ for (const name of BIN_NAMES) {
95
+ const dst = path.join(BIN_DIR, name);
96
+ try { fs.unlinkSync(dst); } catch {}
97
+ fs.symlinkSync(path.join("current", name), dst);
98
+ }
99
+ }
100
+
42
101
  async function main() {
43
102
  const platform = PLATFORM_MAP[os.platform()];
44
103
  const arch = ARCH_MAP[os.arch()];
@@ -46,23 +105,25 @@ async function main() {
46
105
  console.error(`apteva: unsupported platform ${os.platform()}-${os.arch()}`);
47
106
  process.exit(1);
48
107
  }
49
-
50
- // Cache binaries in ~/.apteva/bin/<version>/ so repeated npx runs are instant
51
- const cacheDir = path.join(os.homedir(), ".apteva", "bin", VERSION);
52
108
  const ext = platform === "windows" ? ".exe" : "";
53
- const cachedBin = path.join(cacheDir, `apteva${ext}`);
54
109
 
55
- if (fs.existsSync(cachedBin)) {
56
- // Copy from cache to install dir
57
- for (const name of ["apteva", "apteva-server", "apteva-core"]) {
58
- const src = path.join(cacheDir, `${name}${ext}`);
110
+ // If versions/<VERSION>/ already has our binaries (cached from
111
+ // a prior npx install of the same version), just point the
112
+ // symlinks and stash a defensive copy in DIR. No download.
113
+ const cachedAptevaBin = path.join(VERSION_DIR, `apteva${ext}`);
114
+ if (fs.existsSync(cachedAptevaBin)) {
115
+ if (platform !== "windows") {
116
+ try { pointSymlinks(); } catch (e) { /* non-fatal */ }
117
+ }
118
+ for (const name of BIN_NAMES) {
119
+ const src = path.join(VERSION_DIR, `${name}${ext}`);
59
120
  const dst = path.join(DIR, `${name}${ext}`);
60
121
  if (fs.existsSync(src)) {
61
122
  fs.copyFileSync(src, dst);
62
123
  if (platform !== "windows") fs.chmodSync(dst, 0o755);
63
124
  }
64
125
  }
65
- console.log(`apteva: v${VERSION} loaded from cache.`);
126
+ console.log(`apteva: v${VERSION} loaded from ~/.apteva/versions/${VERSION}/`);
66
127
  return;
67
128
  }
68
129
 
@@ -73,36 +134,53 @@ async function main() {
73
134
 
74
135
  try {
75
136
  const buffer = await download(url);
76
- const tmpFile = path.join(DIR, "_download.tar.gz");
77
- fs.writeFileSync(tmpFile, buffer);
137
+
138
+ // Stash the tarball in releases/ so future flake-recovery
139
+ // paths can reuse it without re-downloading.
140
+ fs.mkdirSync(RELEASES_DIR, { recursive: true });
141
+ const releasePath = path.join(RELEASES_DIR, tarName);
142
+ fs.writeFileSync(releasePath, buffer);
78
143
 
79
144
  console.log(`apteva: extracting (${(buffer.length / 1024 / 1024).toFixed(1)} MB)...`);
80
- execSync(`tar -xzf "${tmpFile}" -C "${DIR}"`, { stdio: "pipe" });
81
- fs.unlinkSync(tmpFile);
145
+ fs.mkdirSync(VERSION_DIR, { recursive: true });
146
+ execSync(`tar -xzf "${releasePath}" -C "${VERSION_DIR}"`, { stdio: "pipe" });
82
147
 
83
- // Make binaries executable
84
148
  if (platform !== "windows") {
85
- for (const name of ["apteva", "apteva-server", "apteva-core"]) {
86
- const bin = path.join(DIR, name);
149
+ for (const name of BIN_NAMES) {
150
+ const bin = path.join(VERSION_DIR, name);
87
151
  if (fs.existsSync(bin)) fs.chmodSync(bin, 0o755);
88
152
  }
89
153
  }
90
154
 
91
- // Save to cache for next run
92
- fs.mkdirSync(cacheDir, { recursive: true });
93
- for (const name of ["apteva", "apteva-server", "apteva-core"]) {
94
- const src = path.join(DIR, `${name}${ext}`);
95
- const dst = path.join(cacheDir, `${name}${ext}`);
96
- if (fs.existsSync(src)) fs.copyFileSync(src, dst);
155
+ // Set up the symlink chain pointing at this version. Skip on
156
+ // Windows where symlinks need elevated privileges.
157
+ if (platform !== "windows") {
158
+ try { pointSymlinks(); }
159
+ catch (e) {
160
+ console.warn(`apteva: symlink setup failed: ${e.message}`);
161
+ console.warn("(falling back to per-package install dir; updates won't work without symlinks)");
162
+ }
163
+ }
164
+
165
+ // Defensive copy into the npm package install dir so cli.js's
166
+ // fallback path works when ~/.apteva isn't usable.
167
+ for (const name of BIN_NAMES) {
168
+ const src = path.join(VERSION_DIR, `${name}${ext}`);
169
+ const dst = path.join(DIR, `${name}${ext}`);
170
+ if (fs.existsSync(src)) {
171
+ fs.copyFileSync(src, dst);
172
+ if (platform !== "windows") fs.chmodSync(dst, 0o755);
173
+ }
97
174
  }
98
175
 
99
- console.log("apteva: installed successfully.");
176
+ console.log(`apteva: installed to ~/.apteva/versions/${VERSION}/`);
177
+ console.log("apteva: ready. (run 'apteva' to start, or 'apteva service install' for a background service)");
100
178
  } catch (err) {
101
179
  console.error(`apteva: binary download failed: ${err.message}`);
102
180
  console.error("");
103
181
  console.error("Falling back to local Go build...");
104
182
 
105
- // Fallback: build from source if Go is available
183
+ // Fallback: build from source if Go is available.
106
184
  try {
107
185
  execSync("go version", { stdio: "pipe" });
108
186
  console.log("apteva: building from source...");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apteva",
3
- "version": "0.11.2",
3
+ "version": "0.12.1",
4
4
  "description": "AI agent platform with multi-thread orchestration, MCP integrations, and dashboard",
5
5
  "bin": {
6
6
  "apteva": "./cli.js"