@kevin0181/memoc 1.0.3 → 1.0.5

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/README.md +1 -1
  2. package/bin/cli.js +96 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -12,7 +12,7 @@ npx @kevin0181/memoc init
12
12
 
13
13
  Run inside your project directory. Detects your stack automatically and generates everything agents need.
14
14
 
15
- `init` also creates PATH helpers so agents can keep using memoc even when the global/npm bin is not on PATH. It installs a user-local `memoc` launcher and adds that launcher directory to PATH on Windows, macOS, and Linux; open a new terminal if the current shell does not pick it up immediately.
15
+ `init` also creates PATH helpers so agents can keep using memoc even when the global/npm bin is not on PATH. It installs a `memoc` launcher into a writable directory already on the current PATH when possible, then also installs a user-local launcher and registers that launcher directory on Windows, macOS, and Linux.
16
16
 
17
17
  ```bash
18
18
  # PowerShell
package/bin/cli.js CHANGED
@@ -209,15 +209,15 @@ function write(filePath, content) {
209
209
  }
210
210
 
211
211
  function tplMemocCmdWrapper() {
212
- return `@echo off\r\nnpx @kevin0181/memoc %*\r\n`;
212
+ return `@echo off\r\nnpm exec --yes --package "@kevin0181/memoc" -- memoc %*\r\n`;
213
213
  }
214
214
 
215
215
  function tplMemocPs1Wrapper() {
216
- return `npx @kevin0181/memoc @args\nexit $LASTEXITCODE\n`;
216
+ return `npm exec --yes --package '@kevin0181/memoc' -- memoc @args\nexit $LASTEXITCODE\n`;
217
217
  }
218
218
 
219
219
  function tplMemocShWrapper() {
220
- return `#!/bin/sh\nexec npx @kevin0181/memoc "$@"\n`;
220
+ return `#!/bin/sh\nexec npm exec --yes --package '@kevin0181/memoc' -- memoc "$@"\n`;
221
221
  }
222
222
 
223
223
  function defaultUserBinDir() {
@@ -247,30 +247,46 @@ function ensurePathHelpers(dir, mark) {
247
247
 
248
248
  for (const [fp, tpl, executable] of files) {
249
249
  const rel = path.relative(dir, fp);
250
- const added = ensure(fp, tpl());
250
+ const added = writeIfChanged(fp, tpl());
251
251
  if (executable) chmodExecutable(fp);
252
- mark(added ? 'add' : 'skip', rel);
252
+ mark(added, rel);
253
253
  }
254
254
  }
255
255
 
256
256
  function ensureUserLauncher(mark) {
257
257
  const userBin = defaultUserBinDir();
258
+ writeLaunchers(userBin, mark, 'user bin');
259
+ return userBin;
260
+ }
261
+
262
+ function writeLaunchers(binDir, mark, label) {
258
263
  const files = [
259
- [path.join(userBin, 'memoc.cmd'), tplMemocCmdWrapper, false],
260
- [path.join(userBin, 'memoc.ps1'), tplMemocPs1Wrapper, false],
261
- [path.join(userBin, 'memoc'), tplMemocShWrapper, true],
264
+ [path.join(binDir, 'memoc.cmd'), tplMemocCmdWrapper, false],
265
+ [path.join(binDir, 'memoc.ps1'), tplMemocPs1Wrapper, false],
266
+ [path.join(binDir, 'memoc'), tplMemocShWrapper, true],
262
267
  ];
263
268
 
264
269
  for (const [fp, tpl, executable] of files) {
265
- const added = ensure(fp, tpl());
270
+ const added = writeIfChanged(fp, tpl());
266
271
  if (executable) chmodExecutable(fp);
267
- mark(added ? 'add' : 'skip', `user bin ${path.basename(fp)}`);
272
+ mark(added, `${label} ${path.basename(fp)}`);
268
273
  }
274
+ }
269
275
 
270
- return userBin;
276
+ function writeIfChanged(filePath, content) {
277
+ if (!fs.existsSync(filePath)) {
278
+ write(filePath, content);
279
+ return 'add';
280
+ }
281
+ try {
282
+ if (fs.readFileSync(filePath, 'utf8') === content) return 'skip';
283
+ } catch {}
284
+ write(filePath, content);
285
+ return 'update';
271
286
  }
272
287
 
273
288
  function ensurePathRegistration(dir, mark) {
289
+ ensureCurrentPathLauncher(mark);
274
290
  const binDir = ensureUserLauncher(mark);
275
291
  const pathSep = path.delimiter;
276
292
 
@@ -319,6 +335,75 @@ function ensurePathRegistration(dir, mark) {
319
335
  }
320
336
  }
321
337
 
338
+ function ensureCurrentPathLauncher(mark) {
339
+ const target = findWritablePathDir();
340
+ if (!target) {
341
+ mark('skip', 'active PATH launcher (no writable PATH directory found)');
342
+ return false;
343
+ }
344
+ writeLaunchers(target, mark, 'active PATH');
345
+ return true;
346
+ }
347
+
348
+ function findWritablePathDir() {
349
+ const dirs = [...new Set((process.env.PATH || '').split(path.delimiter).filter(Boolean))];
350
+ const npmBin = npmGlobalBinDir();
351
+ const ranked = dirs
352
+ .filter(d => !isVolatilePathDir(d))
353
+ .filter(d => {
354
+ try { return fs.existsSync(d) && fs.statSync(d).isDirectory() && canWriteDir(d); }
355
+ catch { return false; }
356
+ })
357
+ .sort((a, b) => pathRank(a, npmBin) - pathRank(b, npmBin));
358
+ return ranked[0] || null;
359
+ }
360
+
361
+ function pathRank(dir, npmBin) {
362
+ if (npmBin && samePath(dir, npmBin)) return 0;
363
+ const lower = dir.toLowerCase();
364
+ for (const root of userWritableRoots()) {
365
+ if (root && lower.startsWith(root.toLowerCase())) return 1;
366
+ }
367
+ return 5;
368
+ }
369
+
370
+ function userWritableRoots() {
371
+ return [
372
+ process.env.APPDATA,
373
+ process.env.LOCALAPPDATA,
374
+ process.env.HOME,
375
+ process.env.USERPROFILE,
376
+ ].filter(Boolean).map(p => path.resolve(p));
377
+ }
378
+
379
+ function npmGlobalBinDir() {
380
+ try {
381
+ const prefix = require('child_process').execFileSync('npm', ['config', 'get', 'prefix'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
382
+ if (!prefix) return null;
383
+ return currentPlatform() === 'win32' ? prefix : path.join(prefix, 'bin');
384
+ } catch {
385
+ return null;
386
+ }
387
+ }
388
+
389
+ function isVolatilePathDir(dir) {
390
+ const lower = dir.toLowerCase();
391
+ return lower.includes(`${path.sep}_npx${path.sep}`) ||
392
+ lower.includes(`${path.sep}node_modules${path.sep}.bin`) ||
393
+ lower.includes(`${path.sep}npm-cache${path.sep}_npx${path.sep}`);
394
+ }
395
+
396
+ function canWriteDir(dir) {
397
+ const probe = path.join(dir, `.memoc-write-test-${process.pid}-${Date.now()}`);
398
+ try {
399
+ fs.writeFileSync(probe, '');
400
+ fs.unlinkSync(probe);
401
+ return true;
402
+ } catch {
403
+ return false;
404
+ }
405
+ }
406
+
322
407
  function ensureUnixPathRegistration(binDir) {
323
408
  if (process.env.MEMOC_SKIP_PATH_REGISTER === '1') return false;
324
409
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevin0181/memoc",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Give AI agents a memory. Scaffolds session-to-session context for Claude Code, Codex, Cursor, and more.",
5
5
  "keywords": [
6
6
  "ai",