@kevin0181/memoc 1.0.3 → 1.0.4

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 +79 -6
  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
@@ -255,22 +255,26 @@ function ensurePathHelpers(dir, mark) {
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
270
  const added = ensure(fp, tpl());
266
271
  if (executable) chmodExecutable(fp);
267
- mark(added ? 'add' : 'skip', `user bin ${path.basename(fp)}`);
272
+ mark(added ? 'add' : 'skip', `${label} ${path.basename(fp)}`);
268
273
  }
269
-
270
- return userBin;
271
274
  }
272
275
 
273
276
  function ensurePathRegistration(dir, mark) {
277
+ ensureCurrentPathLauncher(mark);
274
278
  const binDir = ensureUserLauncher(mark);
275
279
  const pathSep = path.delimiter;
276
280
 
@@ -319,6 +323,75 @@ function ensurePathRegistration(dir, mark) {
319
323
  }
320
324
  }
321
325
 
326
+ function ensureCurrentPathLauncher(mark) {
327
+ const target = findWritablePathDir();
328
+ if (!target) {
329
+ mark('skip', 'active PATH launcher (no writable PATH directory found)');
330
+ return false;
331
+ }
332
+ writeLaunchers(target, mark, 'active PATH');
333
+ return true;
334
+ }
335
+
336
+ function findWritablePathDir() {
337
+ const dirs = [...new Set((process.env.PATH || '').split(path.delimiter).filter(Boolean))];
338
+ const npmBin = npmGlobalBinDir();
339
+ const ranked = dirs
340
+ .filter(d => !isVolatilePathDir(d))
341
+ .filter(d => {
342
+ try { return fs.existsSync(d) && fs.statSync(d).isDirectory() && canWriteDir(d); }
343
+ catch { return false; }
344
+ })
345
+ .sort((a, b) => pathRank(a, npmBin) - pathRank(b, npmBin));
346
+ return ranked[0] || null;
347
+ }
348
+
349
+ function pathRank(dir, npmBin) {
350
+ if (npmBin && samePath(dir, npmBin)) return 0;
351
+ const lower = dir.toLowerCase();
352
+ for (const root of userWritableRoots()) {
353
+ if (root && lower.startsWith(root.toLowerCase())) return 1;
354
+ }
355
+ return 5;
356
+ }
357
+
358
+ function userWritableRoots() {
359
+ return [
360
+ process.env.APPDATA,
361
+ process.env.LOCALAPPDATA,
362
+ process.env.HOME,
363
+ process.env.USERPROFILE,
364
+ ].filter(Boolean).map(p => path.resolve(p));
365
+ }
366
+
367
+ function npmGlobalBinDir() {
368
+ try {
369
+ const prefix = require('child_process').execFileSync('npm', ['config', 'get', 'prefix'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
370
+ if (!prefix) return null;
371
+ return currentPlatform() === 'win32' ? prefix : path.join(prefix, 'bin');
372
+ } catch {
373
+ return null;
374
+ }
375
+ }
376
+
377
+ function isVolatilePathDir(dir) {
378
+ const lower = dir.toLowerCase();
379
+ return lower.includes(`${path.sep}_npx${path.sep}`) ||
380
+ lower.includes(`${path.sep}node_modules${path.sep}.bin`) ||
381
+ lower.includes(`${path.sep}npm-cache${path.sep}_npx${path.sep}`);
382
+ }
383
+
384
+ function canWriteDir(dir) {
385
+ const probe = path.join(dir, `.memoc-write-test-${process.pid}-${Date.now()}`);
386
+ try {
387
+ fs.writeFileSync(probe, '');
388
+ fs.unlinkSync(probe);
389
+ return true;
390
+ } catch {
391
+ return false;
392
+ }
393
+ }
394
+
322
395
  function ensureUnixPathRegistration(binDir) {
323
396
  if (process.env.MEMOC_SKIP_PATH_REGISTER === '1') return false;
324
397
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevin0181/memoc",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
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",