eklavya 1.25.3 → 1.26.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/dist/update.js ADDED
@@ -0,0 +1,360 @@
1
+ /**
2
+ * The auto-updater: Eklavya keeps itself current the way Claude Code does —
3
+ * a background check at session start, an install nobody waits on, one line
4
+ * when it worked, and one line naming the fix when it did not.
5
+ *
6
+ * What moves, and how:
7
+ *
8
+ * - The runtime (`~/.eklavya/runtime`) is the only thing npm installs. It is
9
+ * under the user's home, so an update never needs sudo — which is why the
10
+ * global `eklavya` command is not updated at all: `cli.ts` hands off to the
11
+ * runtime whenever the runtime is newer, so a stale global binary runs new
12
+ * code without anyone touching npm.
13
+ * - Everything else `eklavya install` sets up — the plugin files, the chat
14
+ * skill, the registration, the status line — is refreshed by running the
15
+ * NEW runtime's own `install --auto`. One code path for a manual install and
16
+ * an automatic one, so an update can never do less than an install would.
17
+ * `--auto` never asks a question and never changes a setting.
18
+ *
19
+ * `update.json` beside the database is the whole state: when it last checked,
20
+ * which version `install` last finished for, and the last failure. The
21
+ * SessionStart hook reads it to decide whether to start a run and what to say;
22
+ * `eklavya doctor` reads it to report.
23
+ *
24
+ * Deliberately light on imports: the SessionStart hook loads this module on
25
+ * every session, and only `runUpdate` (in its own background process) spawns
26
+ * anything slow.
27
+ */
28
+ import fs from 'node:fs';
29
+ import path from 'node:path';
30
+ import { spawn, spawnSync } from 'node:child_process';
31
+ import { eklavyaHome } from './paths.js';
32
+ import { loadGlobalConfig } from './config.js';
33
+ /** How often the background check runs, at most. Every session would ask npm otherwise. */
34
+ export const CHECK_EVERY_MS = 60 * 60 * 1000;
35
+ /** A stamp older than this is a run that died, not one in progress. Shared with `hooks/run.mjs`. */
36
+ const STAMP_TTL_MS = 60 * 60 * 1000;
37
+ /**
38
+ * `run.mjs`'s heal stamps a date, not a pid, and never removes it — its npm is
39
+ * detached. Past this age that npm has long finished, so the stamp is not a
40
+ * reason to wait out the rest of the hour.
41
+ */
42
+ const HEAL_STAMP_MS = 10 * 60 * 1000;
43
+ /** Offline is not a failure worth a line — until it has lasted this long. */
44
+ export const OFFLINE_GRACE_MS = 7 * 24 * 60 * 60 * 1000;
45
+ export function statePath() {
46
+ return path.join(eklavyaHome(), 'update.json');
47
+ }
48
+ export function logPath() {
49
+ return path.join(eklavyaHome(), 'update.log');
50
+ }
51
+ /** The runtime this updater owns. Never an `EKLAVYA_RUNTIME` build: that one is pinned on purpose. */
52
+ function runtimeDir() {
53
+ return path.join(eklavyaHome(), 'runtime');
54
+ }
55
+ export function runtimeCli() {
56
+ return path.join(runtimeDir(), 'node_modules', 'eklavya', 'dist', 'cli.js');
57
+ }
58
+ export function runtimeVersion() {
59
+ try {
60
+ const pkg = path.join(runtimeDir(), 'node_modules', 'eklavya', 'package.json');
61
+ return JSON.parse(fs.readFileSync(pkg, 'utf8')).version ?? null;
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ export function readState() {
68
+ try {
69
+ const value = JSON.parse(fs.readFileSync(statePath(), 'utf8'));
70
+ return value && typeof value === 'object' ? value : {};
71
+ }
72
+ catch {
73
+ return {};
74
+ }
75
+ }
76
+ /** Merges `patch` in. Never throws: losing a state write costs one extra check, not a session. */
77
+ export function writeState(patch) {
78
+ try {
79
+ fs.mkdirSync(eklavyaHome(), { recursive: true });
80
+ const tmp = `${statePath()}.${process.pid}.tmp`;
81
+ fs.writeFileSync(tmp, `${JSON.stringify({ ...readState(), ...patch }, null, 2)}\n`);
82
+ fs.renameSync(tmp, statePath());
83
+ }
84
+ catch {
85
+ /* see above */
86
+ }
87
+ }
88
+ /** `auto_update`, from the global file only: a project cannot opt the machine out. */
89
+ export function autoUpdateEnabled() {
90
+ return loadGlobalConfig().auto_update;
91
+ }
92
+ /** -1, 0 or 1 over the numeric parts of two versions; a prerelease tag is ignored. */
93
+ export function compareVersions(a, b) {
94
+ const parts = (v) => v.split('-')[0].split('.').map((n) => Number(n) || 0);
95
+ const [x, y] = [parts(a), parts(b)];
96
+ for (let i = 0; i < Math.max(x.length, y.length); i++) {
97
+ const d = (x[i] ?? 0) - (y[i] ?? 0);
98
+ if (d !== 0)
99
+ return Math.sign(d);
100
+ }
101
+ return 0;
102
+ }
103
+ /**
104
+ * Should a session start a background run? Only for an installed runtime (a
105
+ * missing one is `run.mjs`'s heal), never under `EKLAVYA_RUNTIME`, and only
106
+ * when the last check is an hour old — or when the runtime moved without
107
+ * `install` following it, which is what the pinned heal in `run.mjs` does.
108
+ */
109
+ export function updateDue(now = Date.now()) {
110
+ if (process.env.EKLAVYA_RUNTIME || !autoUpdateEnabled())
111
+ return false;
112
+ const installed = runtimeVersion();
113
+ if (!installed)
114
+ return false;
115
+ const state = readState();
116
+ const recent = now - (state.checked_at ? Date.parse(state.checked_at) : NaN) < CHECK_EVERY_MS;
117
+ // A failure waits out the hour too: a broken npm retried every session is
118
+ // a spawn per session for nothing.
119
+ return !recent || (state.applied !== installed && !state.error);
120
+ }
121
+ /**
122
+ * Starts `eklavya update --background` from the runtime, detached, when one is
123
+ * due. Returns at once; every failure is swallowed — the caller is a hook.
124
+ */
125
+ export function startBackgroundUpdate() {
126
+ try {
127
+ if (!updateDue())
128
+ return;
129
+ fs.mkdirSync(eklavyaHome(), { recursive: true });
130
+ // stdio ignored: the run writes its own log, and only once it holds the
131
+ // claim, so a run that finds another in progress cannot wipe that log.
132
+ const child = spawn(process.execPath, [runtimeCli(), 'update', '--background'], {
133
+ detached: true,
134
+ stdio: 'ignore',
135
+ windowsHide: true,
136
+ });
137
+ // An async spawn failure lands here, after this hook has moved on. Without
138
+ // a listener it is an uncaught error and the hook exits 1.
139
+ child.on('error', () => { });
140
+ child.unref();
141
+ }
142
+ catch {
143
+ /* an update that cannot start is retried next session */
144
+ }
145
+ }
146
+ /**
147
+ * The one line a session shows about updates, or null. "Updated" once per
148
+ * version; a failure every session until it clears, like Claude Code's own —
149
+ * except being offline, which says nothing until it has lasted a week.
150
+ *
151
+ * `announces` is set on the "updated" line: the caller passes it to
152
+ * `markAnnounced` once the line has actually gone out, so a session that
153
+ * returns early does not swallow it.
154
+ */
155
+ export function updateNotice(now = Date.now()) {
156
+ const state = readState();
157
+ if (state.error) {
158
+ const lastOk = state.ok_at ? Date.parse(state.ok_at) : NaN;
159
+ const longOffline = !(now - lastOk < OFFLINE_GRACE_MS);
160
+ if (state.error_class !== 'network' || longOffline) {
161
+ return { text: `Eklavya can't update itself · ${state.error} · run: eklavya update` };
162
+ }
163
+ }
164
+ const version = state.applied;
165
+ if (version && version !== state.announced && version === runtimeVersion()) {
166
+ return { text: `Eklavya updated to ${version}`, announces: version };
167
+ }
168
+ return null;
169
+ }
170
+ export function markAnnounced(version) {
171
+ writeState({ announced: version });
172
+ }
173
+ // --- the run ----------------------------------------------------------------
174
+ /**
175
+ * The claim `hooks/run.mjs` and `installRuntime` already use, so no two npm
176
+ * installs ever land in the runtime at once. Taken the way `claimHeal` takes
177
+ * it: an exclusive create (`wx`), which only one process can win, and a stamp
178
+ * nobody holds any more is taken by renaming it away — again one winner — and
179
+ * put back if what was renamed turned out to be live.
180
+ */
181
+ function claim() {
182
+ const stamp = path.join(runtimeDir(), '.installing');
183
+ const create = () => {
184
+ try {
185
+ fs.writeFileSync(stamp, String(process.pid), { flag: 'wx' });
186
+ return true;
187
+ }
188
+ catch {
189
+ return false;
190
+ }
191
+ };
192
+ try {
193
+ fs.mkdirSync(runtimeDir(), { recursive: true });
194
+ }
195
+ catch {
196
+ return false;
197
+ }
198
+ if (create())
199
+ return true;
200
+ try {
201
+ if (held(stamp))
202
+ return false;
203
+ const taken = `${stamp}.${process.pid}`;
204
+ fs.renameSync(stamp, taken);
205
+ if (held(taken)) {
206
+ fs.renameSync(taken, stamp);
207
+ return false;
208
+ }
209
+ fs.rmSync(taken, { force: true });
210
+ }
211
+ catch {
212
+ // Cannot read or move it, so it cannot be judged: wait for the next run.
213
+ return false;
214
+ }
215
+ return create();
216
+ }
217
+ /**
218
+ * Is this stamp someone's live claim? A pid stamp (this updater,
219
+ * `installRuntime`) is while its process runs; the heal's date stamp only for
220
+ * as long as its detached npm plausibly does. Nothing past the hour is.
221
+ */
222
+ function held(stamp) {
223
+ const age = Date.now() - fs.statSync(stamp).mtimeMs;
224
+ if (age >= STAMP_TTL_MS)
225
+ return false;
226
+ const owner = fs.readFileSync(stamp, 'utf8').trim();
227
+ return /^\d+$/.test(owner) ? alive(Number(owner)) : age < HEAL_STAMP_MS;
228
+ }
229
+ function alive(pid) {
230
+ try {
231
+ process.kill(pid, 0);
232
+ return true;
233
+ }
234
+ catch (err) {
235
+ // EPERM: it exists, it is just not ours to signal.
236
+ return err.code === 'EPERM';
237
+ }
238
+ }
239
+ function release() {
240
+ try {
241
+ fs.rmSync(path.join(runtimeDir(), '.installing'), { force: true });
242
+ }
243
+ catch {
244
+ /* the TTL covers it */
245
+ }
246
+ }
247
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
248
+ function run(cmd, args, timeout) {
249
+ return spawnSync(cmd, args, {
250
+ encoding: 'utf8',
251
+ timeout,
252
+ shell: process.platform === 'win32',
253
+ windowsHide: true,
254
+ });
255
+ }
256
+ /**
257
+ * The line of a failed command that says why: a Node `…Error:` line, else an
258
+ * npm error code (`EACCES`, `ENOTFOUND`), else the last line that is not Node's
259
+ * `Node.js v…` trailer.
260
+ */
261
+ function lastLine(text) {
262
+ const lines = (text ?? '').split('\n').map((l) => l.replace(/^npm (ERR!|error)\s*/, '').trim()).filter(Boolean);
263
+ const why = lines.find((l) => /^[A-Za-z]*Error:/.test(l)) ??
264
+ lines.find((l) => /\bE[A-Z]{3,}\b/.test(l)) ??
265
+ lines.filter((l) => !/^Node\.js v/.test(l)).at(-1);
266
+ return (why ?? 'unknown error').slice(0, 160);
267
+ }
268
+ const NETWORK = /ENOTFOUND|EAI_AGAIN|ETIMEDOUT|ECONNREFUSED|ECONNRESET|ENETUNREACH|network|socket hang up/i;
269
+ class UpdateError extends Error {
270
+ cls;
271
+ constructor(message, cls) {
272
+ super(message);
273
+ this.cls = cls;
274
+ }
275
+ }
276
+ /**
277
+ * One update run: ask npm for the latest version, install it into the runtime
278
+ * if it is newer, then let the runtime's own `install --auto` refresh
279
+ * everything else. `say` is where progress goes — nowhere in the background.
280
+ */
281
+ export async function runUpdate(opts) {
282
+ if (opts.background && !updateDue())
283
+ return { status: 'skipped' };
284
+ if (!claim())
285
+ return { status: 'busy' };
286
+ // The background run's log is this run's alone: truncated here, after the claim.
287
+ const log = (text) => {
288
+ if (!opts.background)
289
+ return;
290
+ try {
291
+ fs.appendFileSync(logPath(), text);
292
+ }
293
+ catch {
294
+ /* a log is a courtesy */
295
+ }
296
+ };
297
+ if (opts.background) {
298
+ try {
299
+ fs.writeFileSync(logPath(), `${new Date().toISOString()} eklavya update\n`);
300
+ }
301
+ catch {
302
+ /* see above */
303
+ }
304
+ }
305
+ const say = (line) => {
306
+ log(`${line}\n`);
307
+ opts.say?.(line);
308
+ };
309
+ const now = new Date().toISOString();
310
+ const from = runtimeVersion();
311
+ try {
312
+ const view = run(npm, ['view', 'eklavya@latest', 'version'], 60_000);
313
+ if (view.error || view.status !== 0) {
314
+ const why = view.error?.message ?? lastLine(view.stderr);
315
+ throw new UpdateError(view.error?.code === 'ENOENT' ? 'npm not found on PATH' : `npm view failed: ${why}`, NETWORK.test(why) ? 'network' : 'npm');
316
+ }
317
+ const latest = view.stdout.trim();
318
+ // Anything else is npm talking (a warning on stdout, an .npmrc notice), and
319
+ // installing `eklavya@<that>` would fail in a way that names the wrong thing.
320
+ if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(latest)) {
321
+ throw new UpdateError(`npm view returned no version: ${lastLine(latest || view.stderr)}`, 'npm');
322
+ }
323
+ say(`latest ${latest}, runtime ${from ?? 'not installed'}`);
324
+ if (!from || compareVersions(latest, from) > 0) {
325
+ say(`installing eklavya@${latest}…`);
326
+ const install = run(npm, ['install', `eklavya@${latest}`, '--prefix', runtimeDir(), '--omit=dev', '--no-audit', '--no-fund', '--loglevel=error'], 10 * 60_000);
327
+ if (install.error || install.status !== 0) {
328
+ const why = install.error?.message ?? lastLine(install.stderr);
329
+ throw new UpdateError(`npm install failed: ${why}`, NETWORK.test(why) ? 'network' : 'npm');
330
+ }
331
+ }
332
+ const version = runtimeVersion();
333
+ if (!version)
334
+ throw new UpdateError('the runtime is missing after npm install', 'npm');
335
+ // The new runtime's install, so the plugin files, skill and registration
336
+ // match the code that will run them. Skipped when it already finished for
337
+ // this version: an hourly re-registration would rewrite Claude Code's files
338
+ // for nothing.
339
+ if (readState().applied !== version) {
340
+ say(`refreshing the plugin, skill and registration for ${version}…`);
341
+ const refresh = run(process.execPath, [runtimeCli(), 'install', '--auto'], 5 * 60_000);
342
+ log(`${refresh.stdout ?? ''}${refresh.stderr ?? ''}`);
343
+ if (refresh.error || refresh.status !== 0) {
344
+ throw new UpdateError(`eklavya install failed: ${lastLine(refresh.stderr || refresh.stdout)}`, 'install');
345
+ }
346
+ }
347
+ writeState({ checked_at: now, ok_at: now, latest, applied: version, error: null, error_class: null });
348
+ return from === version ? { status: 'current', version } : { status: 'updated', from, to: version };
349
+ }
350
+ catch (err) {
351
+ const error = err instanceof Error ? err.message : String(err);
352
+ log(`failed: ${error}\n`);
353
+ writeState({ checked_at: now, error, error_class: err instanceof UpdateError ? err.cls : 'install' });
354
+ return { status: 'failed', error };
355
+ }
356
+ finally {
357
+ release();
358
+ }
359
+ }
360
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: eklavya
3
- description: "Operate Eklavya, the local memory and learning tool that records what this developer's agent did and quizzes them on it. Use when the user mentions Eklavya by name, asks what Eklavya remembers about a project or whether it is still capturing, or asks to change how often or how hard it quizzes them (its quiz, focus, cadence or difficulty dials), see their learning progress or mastery, open the dashboard, check the commit gate, or find where their data lives. Do not use for ordinary coding help, for teaching a concept, or merely because a task is educational."
3
+ description: "Operate Eklavya, the local memory and learning tool that records what this developer's agent did and quizzes them on it. Use when the user mentions Eklavya by name, asks what Eklavya remembers about a project or whether it is still capturing, or asks to change how often or how hard it quizzes them (its quiz, focus, cadence or difficulty dials), see their learning progress or mastery, open the dashboard, check the commit gate, update Eklavya, or find where their data lives. Do not use for ordinary coding help, for teaching a concept, or merely because a task is educational."
4
4
  ---
5
5
 
6
6
  # Eklavya
@@ -222,6 +222,15 @@ the learning history. Two cases it can't fix on its own, and `doctor` says which
222
222
  `~/.claude/skills/eklavya/`. Never overwrite it. Tell them to move theirs
223
223
  first, then run `eklavya install`.
224
224
 
225
+ **Updates are automatic.** Eklavya checks npm at session start, at most hourly,
226
+ and installs a newer release on its own, settings untouched. So "update
227
+ Eklavya" needs nothing, and if they want it now, run `eklavya update`. If
228
+ session start said `Eklavya can't update itself · <reason> · run: eklavya
229
+ update`, run `eklavya update` to see the full error, and `doctor`'s `updates`
230
+ row says the same. Never suggest `npm install -g eklavya` for an update: the
231
+ `eklavya` command already runs the newer runtime's copy on its own.
232
+ `auto_update false` turns updates off, globally only.
233
+
225
234
  Restarting Claude Code is what picks up a repaired install — the plugin and MCP
226
235
  server are read at session start. There is no background process to restart.
227
236
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eklavya",
3
- "version": "1.25.3",
3
+ "version": "1.26.0",
4
4
  "description": "Learn while your agent works — local knowledge graph, spaced repetition, and quiz gating for agent-assisted development.",
5
5
  "license": "MIT",
6
6
  "repository": {