@gramatr/client 0.6.21 → 0.6.22
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/bin/install.ts +14 -3
- package/core/version-check.ts +90 -2
- package/hooks/session-start.hook.ts +16 -8
- package/package.json +1 -1
package/bin/install.ts
CHANGED
|
@@ -261,14 +261,24 @@ function installClientFiles(): void {
|
|
|
261
261
|
mkdirSync(join(CLIENT_DIR, sub), { recursive: true });
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
//
|
|
264
|
+
// Clean stale hooks before copying — prevents old tool names persisting across upgrades (#633)
|
|
265
|
+
const hooksDestDir = join(CLIENT_DIR, 'hooks');
|
|
266
|
+
if (existsSync(hooksDestDir)) {
|
|
267
|
+
for (const f of readdirSync(hooksDestDir)) {
|
|
268
|
+
if (f.endsWith('.hook.ts') || f.endsWith('-utils.ts')) {
|
|
269
|
+
rmSync(join(hooksDestDir, f), { force: true });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Hooks (core + utils)
|
|
265
275
|
let hookCount = 0;
|
|
266
276
|
const hooksSrc = join(SCRIPT_DIR, 'hooks');
|
|
267
277
|
if (existsSync(hooksSrc)) {
|
|
268
278
|
for (const f of readdirSync(hooksSrc)) {
|
|
269
279
|
const src = join(hooksSrc, f);
|
|
270
280
|
if (statSync(src).isFile() && (f.endsWith('.hook.ts') || f.endsWith('-utils.ts'))) {
|
|
271
|
-
cpSync(src, join(CLIENT_DIR, 'hooks', f));
|
|
281
|
+
cpSync(src, join(CLIENT_DIR, 'hooks', f), { force: true });
|
|
272
282
|
chmodSync(join(CLIENT_DIR, 'hooks', f), 0o755);
|
|
273
283
|
hookCount++;
|
|
274
284
|
}
|
|
@@ -278,7 +288,7 @@ function installClientFiles(): void {
|
|
|
278
288
|
const libHookSrc = join(hooksSrc, 'lib');
|
|
279
289
|
if (existsSync(libHookSrc)) {
|
|
280
290
|
const libs = readdirSync(libHookSrc).filter(f => f.endsWith('.ts') && !f.endsWith('.test.ts'));
|
|
281
|
-
for (const f of libs) cpSync(join(libHookSrc, f), join(CLIENT_DIR, 'hooks/lib', f));
|
|
291
|
+
for (const f of libs) cpSync(join(libHookSrc, f), join(CLIENT_DIR, 'hooks/lib', f), { force: true });
|
|
282
292
|
log(`OK Installed ${hookCount} hooks + ${libs.length} lib modules`);
|
|
283
293
|
}
|
|
284
294
|
|
|
@@ -717,6 +727,7 @@ async function main(): Promise<void> {
|
|
|
717
727
|
log('');
|
|
718
728
|
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
719
729
|
log(` gramatr v${VERSION} — Intelligence Layer`);
|
|
730
|
+
log(` Source: ${SCRIPT_DIR}`);
|
|
720
731
|
log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
721
732
|
log('');
|
|
722
733
|
|
package/core/version-check.ts
CHANGED
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
16
16
|
import { dirname, join } from 'path';
|
|
17
17
|
import { homedir } from 'os';
|
|
18
|
+
import { spawn } from 'child_process';
|
|
18
19
|
|
|
19
|
-
const REGISTRY_URL = 'https://registry.npmjs.org
|
|
20
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/@gramatr%2fclient/latest';
|
|
20
21
|
const FETCH_TIMEOUT_MS = 3000;
|
|
21
22
|
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
22
23
|
|
|
@@ -184,7 +185,7 @@ export function formatUpgradeNotification(installed: string, latest: string): st
|
|
|
184
185
|
'',
|
|
185
186
|
' To upgrade:',
|
|
186
187
|
' 1. Type /exit to leave Claude Code',
|
|
187
|
-
' 2. Run: npx gramatr@latest install claude-code',
|
|
188
|
+
' 2. Run: npx @gramatr/client@latest install claude-code',
|
|
188
189
|
' 3. Restart: claude --resume',
|
|
189
190
|
'',
|
|
190
191
|
" Why restart? gramatr's hooks are loaded by Claude Code at",
|
|
@@ -217,3 +218,90 @@ export async function runVersionCheckAndNotify(
|
|
|
217
218
|
}
|
|
218
219
|
return result;
|
|
219
220
|
}
|
|
221
|
+
|
|
222
|
+
// ── Auto-upgrade ──────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
const UPGRADE_LOCK_TTL_MS = 10 * 60 * 1000; // 10 minutes — prevent concurrent upgrades
|
|
225
|
+
|
|
226
|
+
function getUpgradeLockPath(home: string = homedir()): string {
|
|
227
|
+
return join(home, '.gramatr', '.cache', 'upgrade.lock');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isUpgradeLocked(lockPath: string = getUpgradeLockPath()): boolean {
|
|
231
|
+
try {
|
|
232
|
+
if (!existsSync(lockPath)) return false;
|
|
233
|
+
const raw = readFileSync(lockPath, 'utf8');
|
|
234
|
+
const lock = JSON.parse(raw);
|
|
235
|
+
if (typeof lock.startedAt !== 'number') return false;
|
|
236
|
+
return Date.now() - lock.startedAt < UPGRADE_LOCK_TTL_MS;
|
|
237
|
+
} catch {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function writeUpgradeLock(version: string, lockPath: string = getUpgradeLockPath()): void {
|
|
243
|
+
try {
|
|
244
|
+
mkdirSync(dirname(lockPath), { recursive: true });
|
|
245
|
+
writeFileSync(lockPath, JSON.stringify({ version, startedAt: Date.now() }) + '\n');
|
|
246
|
+
} catch {
|
|
247
|
+
// Best-effort.
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface AutoUpgradeResult {
|
|
252
|
+
triggered: boolean;
|
|
253
|
+
version: string;
|
|
254
|
+
reason?: string;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Auto-upgrade gramatr when a newer version is available.
|
|
259
|
+
*
|
|
260
|
+
* Spawns `npx @gramatr/client@{version} install claude-code --yes` as a
|
|
261
|
+
* detached background process. The install is idempotent: copies files to
|
|
262
|
+
* ~/.gramatr/, merges hooks into ~/.claude/settings.json, re-registers MCP.
|
|
263
|
+
* Existing auth token and URL are preserved (merge, not overwrite).
|
|
264
|
+
*
|
|
265
|
+
* The upgrade runs in the background so it doesn't block session start.
|
|
266
|
+
* New hook code won't take effect until the user restarts Claude Code.
|
|
267
|
+
*
|
|
268
|
+
* Safety:
|
|
269
|
+
* - Lock file prevents concurrent upgrades (10min TTL)
|
|
270
|
+
* - --yes flag ensures non-interactive (no stdin prompts)
|
|
271
|
+
* - Detached + unref'd so the parent process can exit
|
|
272
|
+
* - Never throws — returns result indicating what happened
|
|
273
|
+
*/
|
|
274
|
+
export function autoUpgrade(
|
|
275
|
+
latestVersion: string,
|
|
276
|
+
options: { stream?: NodeJS.WritableStream; lockPath?: string } = {},
|
|
277
|
+
): AutoUpgradeResult {
|
|
278
|
+
const stream = options.stream ?? process.stderr;
|
|
279
|
+
const lockPath = options.lockPath ?? getUpgradeLockPath();
|
|
280
|
+
|
|
281
|
+
if (isUpgradeLocked(lockPath)) {
|
|
282
|
+
return { triggered: false, version: latestVersion, reason: 'upgrade already in progress' };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
writeUpgradeLock(latestVersion, lockPath);
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const npxBin = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
289
|
+
const child = spawn(
|
|
290
|
+
npxBin,
|
|
291
|
+
['@gramatr/client@' + latestVersion, 'install', 'claude-code', '--yes'],
|
|
292
|
+
{
|
|
293
|
+
detached: true,
|
|
294
|
+
stdio: 'ignore',
|
|
295
|
+
env: { ...process.env, GRAMATR_AUTO_UPGRADE: '1' },
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
child.unref();
|
|
299
|
+
|
|
300
|
+
stream.write(` gramatr auto-upgrade started: v${latestVersion}\n`);
|
|
301
|
+
stream.write(' Files will be updated in ~/.gramatr/ — restart Claude Code for new hooks.\n');
|
|
302
|
+
|
|
303
|
+
return { triggered: true, version: latestVersion };
|
|
304
|
+
} catch (err: any) {
|
|
305
|
+
return { triggered: false, version: latestVersion, reason: err?.message || 'spawn failed' };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
writeCurrentProjectContextFile,
|
|
34
34
|
persistSessionRegistration,
|
|
35
35
|
} from '../core/session.ts';
|
|
36
|
-
import { runVersionCheckAndNotify } from '../core/version-check.ts';
|
|
36
|
+
import { runVersionCheckAndNotify, autoUpgrade } from '../core/version-check.ts';
|
|
37
37
|
import { VERSION } from '../core/version.ts';
|
|
38
38
|
|
|
39
39
|
// ── stdout (Claude context injection) ──
|
|
@@ -480,16 +480,24 @@ async function main(): Promise<void> {
|
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
482
|
|
|
483
|
-
// Opportunistic
|
|
484
|
-
//
|
|
485
|
-
//
|
|
483
|
+
// Opportunistic version check + auto-upgrade.
|
|
484
|
+
// 1. Check npm registry for newer @gramatr/client
|
|
485
|
+
// 2. If outdated, auto-install in background (detached, non-interactive)
|
|
486
|
+
// 3. Inject context so Claude tells the user to restart for new hooks
|
|
486
487
|
try {
|
|
487
488
|
const versionResult = await runVersionCheckAndNotify(VERSION);
|
|
488
489
|
if (versionResult?.isOutdated) {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
`
|
|
492
|
-
`
|
|
490
|
+
const upgrade = autoUpgrade(versionResult.latestVersion);
|
|
491
|
+
if (upgrade.triggered) {
|
|
492
|
+
log(` Auto-upgrading gramatr: v${versionResult.installedVersion} → v${versionResult.latestVersion}`);
|
|
493
|
+
emitStdout(`gramatr auto-upgrade in progress: v${versionResult.installedVersion} → v${versionResult.latestVersion}. ` +
|
|
494
|
+
`Files are being updated in ~/.gramatr/. Tell the user: "gramatr has auto-upgraded to v${versionResult.latestVersion}. ` +
|
|
495
|
+
`Exit Claude Code (/exit) and restart with \`claude --resume\` to activate the new hooks and features."`);
|
|
496
|
+
} else {
|
|
497
|
+
emitStdout(`gramatr update available: v${versionResult.installedVersion} → v${versionResult.latestVersion}. ` +
|
|
498
|
+
`Auto-upgrade skipped (${upgrade.reason}). Recommend the user exit Claude Code (/exit) ` +
|
|
499
|
+
`then run \`npx @gramatr/client@latest install claude-code\` followed by \`claude --resume\`.`);
|
|
500
|
+
}
|
|
493
501
|
}
|
|
494
502
|
} catch {
|
|
495
503
|
// Silent — version check is strictly optional.
|