@jelou/cli 1.108.2 → 1.110.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.
Files changed (2) hide show
  1. package/bin/postinstall.js +103 -8
  2. package/package.json +11 -39
@@ -9,8 +9,15 @@
9
9
  // cold-start) with a relative symlink to the platform binary itself.
10
10
  // When npm wires `<global>/bin/jelou` -> `<wrapper>/bin/jelou`, the
11
11
  // chain now resolves straight to the native binary, no node hop.
12
+ // 3. Record where that shim is, and what it points at, in
13
+ // `~/.jelou/install-manifest.json`. The CLI cannot rediscover this at
14
+ // runtime: `Deno.execPath()` returns the invoked path on macOS and the
15
+ // resolved one on Linux, so walking up to node_modules works on one and
16
+ // not the other. Writing it down here is the only reliable source.
17
+ // Best-effort — a manifest failure must never fail an install.
12
18
 
13
19
  const fs = require("fs");
20
+ const os = require("os");
14
21
  const path = require("path");
15
22
  const {
16
23
  PLATFORMS,
@@ -50,6 +57,71 @@ if (!fs.existsSync(binPath)) {
50
57
  process.exit(1);
51
58
  }
52
59
 
60
+ // --- install manifest ---
61
+ // Keyed by shim path, and merged rather than replaced: one machine can carry
62
+ // several independent global installs (e.g. a Homebrew node prefix and a
63
+ // user-owned ~/.local one), each with its own postinstall. A single-record
64
+ // manifest would let the last install to run claim a shim it does not own.
65
+ function manifestPath() {
66
+ const home = os.homedir();
67
+ return home ? path.join(home, ".jelou", "install-manifest.json") : null;
68
+ }
69
+
70
+ function readManifest(file) {
71
+ try {
72
+ const parsed = JSON.parse(fs.readFileSync(file, "utf8"));
73
+ if (parsed && typeof parsed === "object" && parsed.installs) return parsed;
74
+ } catch (_) {
75
+ // Missing or corrupt — start fresh rather than refusing to install.
76
+ }
77
+ return { version: 1, installs: {} };
78
+ }
79
+
80
+ function recordManifest(shimKind) {
81
+ const file = manifestPath();
82
+ if (!file) return;
83
+ try {
84
+ let wrapperVersion = null;
85
+ try {
86
+ wrapperVersion = require("../package.json").version || null;
87
+ } catch (_) {
88
+ // Version is informational; a missing one must not skip the manifest.
89
+ }
90
+
91
+ const manifest = readManifest(file);
92
+ manifest.version = 1;
93
+
94
+ // Drop entries whose shim is gone (uninstalled prefix), but never touch a
95
+ // live sibling install. lstat, not existsSync: existsSync follows the
96
+ // link, so a sibling whose target is momentarily missing — that prefix
97
+ // running its own install — would look gone and be pruned.
98
+ for (const key of Object.keys(manifest.installs)) {
99
+ if (key === shimPath) continue;
100
+ try {
101
+ fs.lstatSync(key);
102
+ } catch {
103
+ delete manifest.installs[key];
104
+ }
105
+ }
106
+
107
+ manifest.installs[shimPath] = {
108
+ shimKind: shimKind,
109
+ baselineTarget: relativeTarget,
110
+ baselineBinary: binPath,
111
+ version: wrapperVersion,
112
+ writtenAt: new Date().toISOString(),
113
+ };
114
+
115
+ fs.mkdirSync(path.dirname(file), { recursive: true });
116
+ const tmp = file + ".tmp-" + process.pid;
117
+ fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2) + "\n");
118
+ fs.renameSync(tmp, file);
119
+ } catch (_) {
120
+ // Best-effort by design. Losing the manifest only disables background
121
+ // updates; failing the install here would be far worse.
122
+ }
123
+ }
124
+
53
125
  // --- fast-shim rewrite (Unix only for now) ---
54
126
  // On Windows npm generates `.cmd` and `.ps1` shims that hardcode
55
127
  // `node "<wrapper>/bin/jelou"`; replacing the wrapper script doesn't
@@ -71,28 +143,51 @@ const relativeTarget = path.relative(wrapperBin, binPath);
71
143
  try {
72
144
  const stat = fs.lstatSync(shimPath);
73
145
  if (stat.isSymbolicLink() && fs.readlinkSync(shimPath) === relativeTarget) {
146
+ // The shim is already right, but the manifest still has to be refreshed:
147
+ // relativeTarget is identical across versions, so this branch is the one
148
+ // taken on every ordinary upgrade.
149
+ recordManifest("symlink");
74
150
  process.exit(0);
75
151
  }
76
152
  } catch (_) {
77
153
  // shim not present yet — fine, we'll create it.
78
154
  }
79
155
 
80
- try {
81
- fs.rmSync(shimPath, { force: true });
82
- } catch (_) {
83
- // nothing to remove
156
+ // Write through a tmp sibling + rename so the shim is never briefly absent.
157
+ // The old rmSync-then-symlinkSync left a window where `<wrapper>/bin/jelou`
158
+ // did not exist, and a concurrent `jelou` invocation got ENOENT. rename(2)
159
+ // is atomic, so a reader sees the old shim or the new one, never neither.
160
+ const tmpPath = shimPath + ".tmp-" + process.pid;
161
+
162
+ function swapIn(write) {
163
+ try {
164
+ fs.rmSync(tmpPath, { force: true });
165
+ } catch (_) {
166
+ // nothing to remove
167
+ }
168
+ write(tmpPath);
169
+ fs.renameSync(tmpPath, shimPath);
84
170
  }
85
171
 
172
+ let shimKind;
86
173
  try {
87
- fs.symlinkSync(relativeTarget, shimPath);
88
- process.exit(0);
174
+ swapIn((p) => fs.symlinkSync(relativeTarget, p));
175
+ shimKind = "symlink";
89
176
  } catch (err) {
90
177
  // EPERM (Windows w/o developer mode), EXDEV (cross-device install),
91
178
  // EROFS (read-only FS in some sandboxes). Fall back to a tiny shell
92
179
  // wrapper. Adds ~1-3ms vs ~0ms for symlink, but still much better than
93
180
  // the 20ms node-boot hop the wrapper script costs.
181
+ try {
182
+ fs.rmSync(tmpPath, { force: true });
183
+ } catch (_) {
184
+ // nothing to remove
185
+ }
94
186
  const safeTarget = relativeTarget.replace(/(["\\$`])/g, "\\$1");
95
187
  const shell = `#!/bin/sh\nexec "$(dirname "$0")/${safeTarget}" "$@"\n`;
96
- fs.writeFileSync(shimPath, shell, { mode: 0o755 });
97
- process.exit(0);
188
+ swapIn((p) => fs.writeFileSync(p, shell, { mode: 0o755 }));
189
+ shimKind = "shell";
98
190
  }
191
+
192
+ recordManifest(shimKind);
193
+ process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jelou/cli",
3
- "version": "1.108.2",
3
+ "version": "1.110.0",
4
4
  "description": "Build AI agents on the Jelou platform — from your terminal or your AI editor.",
5
5
  "author": "Jelou Inc. (https://jelou.ai)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -23,11 +23,11 @@
23
23
  "THIRD_PARTY_NOTICES.md"
24
24
  ],
25
25
  "optionalDependencies": {
26
- "@jelou/cli-darwin-arm64": "1.108.2",
27
- "@jelou/cli-darwin-x64": "1.108.2",
28
- "@jelou/cli-linux-x64": "1.108.2",
29
- "@jelou/cli-linux-arm64": "1.108.2",
30
- "@jelou/cli-win32-x64": "1.108.2"
26
+ "@jelou/cli-darwin-arm64": "1.110.0",
27
+ "@jelou/cli-darwin-x64": "1.110.0",
28
+ "@jelou/cli-linux-x64": "1.110.0",
29
+ "@jelou/cli-linux-arm64": "1.110.0",
30
+ "@jelou/cli-win32-x64": "1.110.0"
31
31
  },
32
32
  "keywords": [
33
33
  "jelou",
@@ -58,45 +58,17 @@
58
58
  "jelou": {
59
59
  "minVersion": "1.100.2",
60
60
  "release_notes": {
61
- "version": "1.108.2",
62
- "date": "2026-09-02",
63
- "headline": "Cold start drops 167ms → 49ms, and an optional update no longer interrupts an agent mid-task.",
61
+ "version": "1.110.0",
62
+ "date": "2026-09-03",
63
+ "headline": "`jelou logs chat` now rebuilds conversations that the server returns short or empty.",
64
64
  "highlights": [
65
65
  {
66
66
  "type": "fixed",
67
- "text": "Agents no longer stop what they are doing to ask about an optional CLI update. The notice is dismissed on its own and mentioned in one line at the end of the answer, so a release landing mid-session stops derailing the task."
68
- },
69
- {
70
- "type": "fixed",
71
- "text": "Answering \"Exit\" at the update prompt now actually dismisses it. Nothing was being recorded, so the prompt came back on the very next command."
72
- },
73
- {
74
- "type": "fixed",
75
- "text": "Every failure path now exits with the code it reports. Commands that returned `error.code` 4 (re-auth) or 3 (not found) still exited 1, so agents branching on the exit code took the wrong path."
76
- },
77
- {
78
- "type": "fixed",
79
- "text": "`jelou push` now reports unresolved tool output-branch warnings in the `--json` envelope. They were printed for humans and dropped for everyone else."
80
- },
81
- {
82
- "type": "fixed",
83
- "text": "`jelou changelog` reads the shipped changelog files again — section parsing looked for a heading no file actually uses."
84
- },
85
- {
86
- "type": "improved",
87
- "text": "Cold start is down from 167ms to 49ms: command groups load on demand instead of all 37 up front, and the version check no longer blocks your command on a network round trip."
67
+ "text": "`jelou logs chat` no longer presents a trimmed conversation as the whole story. It re-reads the executions in range that the timeline failed to account for, so messages the server drops — or cannot serve from cold storage — still appear, and a new `sources` block says where each item came from."
88
68
  },
89
69
  {
90
70
  "type": "improved",
91
- "text": "The skill preamble every agent reads on each invocation shrank from ~11.5 KB to a stub, with the upgrade flow loaded only when something is actually out of date."
92
- },
93
- {
94
- "type": "improved",
95
- "text": "Skills no longer show agents examples that pass `--yes` on destructive commands."
96
- },
97
- {
98
- "type": "heads_up",
99
- "text": "Destructive commands (`jelou project delete`, the `voice` surface, `logout`, and functions removals) now fail loudly under `--agent` / `--no-input` when `--yes` is missing. They used to exit 0 having done nothing. Add `--yes` to any script that relied on the old behavior."
71
+ "text": "Reaching your project limit now shows the reason the platform gives, instead of a generic permission error."
100
72
  }
101
73
  ]
102
74
  }