@kevin0181/memoc 1.0.1 → 1.0.3

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 +126 -1
  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 project-local PATH helpers so agents can keep using memoc even when the global/npm bin is not on PATH:
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.
16
16
 
17
17
  ```bash
18
18
  # PowerShell
package/bin/cli.js CHANGED
@@ -220,6 +220,14 @@ function tplMemocShWrapper() {
220
220
  return `#!/bin/sh\nexec npx @kevin0181/memoc "$@"\n`;
221
221
  }
222
222
 
223
+ function defaultUserBinDir() {
224
+ if (process.env.MEMOC_USER_BIN_DIR) return process.env.MEMOC_USER_BIN_DIR;
225
+ if (currentPlatform() === 'win32') {
226
+ return path.join(process.env.LOCALAPPDATA || path.join(process.env.USERPROFILE || process.cwd(), 'AppData', 'Local'), 'memoc', 'bin');
227
+ }
228
+ return path.join(process.env.HOME || process.cwd(), '.local', 'bin');
229
+ }
230
+
223
231
  function tplEnvPs1() {
224
232
  return `$memocBin = Join-Path $PSScriptRoot 'bin'\n$parts = $env:PATH -split [IO.Path]::PathSeparator\nif ($parts -notcontains $memocBin) {\n $env:PATH = \"$memocBin$([IO.Path]::PathSeparator)$env:PATH\"\n}\n`;
225
233
  }
@@ -245,6 +253,121 @@ function ensurePathHelpers(dir, mark) {
245
253
  }
246
254
  }
247
255
 
256
+ function ensureUserLauncher(mark) {
257
+ const userBin = defaultUserBinDir();
258
+ 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],
262
+ ];
263
+
264
+ for (const [fp, tpl, executable] of files) {
265
+ const added = ensure(fp, tpl());
266
+ if (executable) chmodExecutable(fp);
267
+ mark(added ? 'add' : 'skip', `user bin ${path.basename(fp)}`);
268
+ }
269
+
270
+ return userBin;
271
+ }
272
+
273
+ function ensurePathRegistration(dir, mark) {
274
+ const binDir = ensureUserLauncher(mark);
275
+ const pathSep = path.delimiter;
276
+
277
+ if ((process.env.PATH || '').split(pathSep).some(p => samePath(p, binDir))) {
278
+ mark('skip', 'PATH (user memoc bin already active)');
279
+ return;
280
+ }
281
+
282
+ process.env.PATH = `${binDir}${pathSep}${process.env.PATH || ''}`;
283
+
284
+ if (process.env.MEMOC_SKIP_PATH_REGISTER === '1') {
285
+ mark('skip', 'PATH registration (test mode)');
286
+ return;
287
+ }
288
+
289
+ if (currentPlatform() !== 'win32') {
290
+ const updated = ensureUnixPathRegistration(binDir);
291
+ mark(updated ? 'update' : 'skip', `${currentPlatform()} PATH (${userPathShellHint(binDir)})`);
292
+ return;
293
+ }
294
+
295
+ try {
296
+ const current = require('child_process')
297
+ .execFileSync('powershell.exe', [
298
+ '-NoProfile',
299
+ '-ExecutionPolicy', 'Bypass',
300
+ '-Command',
301
+ "[Environment]::GetEnvironmentVariable('Path','User')",
302
+ ], { encoding: 'utf8' })
303
+ .trim();
304
+ const parts = current.split(pathSep).filter(Boolean);
305
+ if (parts.some(p => samePath(p, binDir))) {
306
+ mark('skip', 'User PATH (memoc bin already registered)');
307
+ return;
308
+ }
309
+ const nextPath = [binDir, ...parts].join(pathSep);
310
+ require('child_process').execFileSync('powershell.exe', [
311
+ '-NoProfile',
312
+ '-ExecutionPolicy', 'Bypass',
313
+ '-Command',
314
+ `[Environment]::SetEnvironmentVariable('Path', ${JSON.stringify(nextPath)}, 'User')`,
315
+ ], { stdio: 'ignore' });
316
+ mark('update', 'User PATH (memoc bin added; open a new terminal if needed)');
317
+ } catch {
318
+ mark('skip', 'User PATH registration failed (use . .\\.memoc\\env.ps1)');
319
+ }
320
+ }
321
+
322
+ function ensureUnixPathRegistration(binDir) {
323
+ if (process.env.MEMOC_SKIP_PATH_REGISTER === '1') return false;
324
+
325
+ const home = process.env.HOME;
326
+ if (!home) return false;
327
+
328
+ const block = [
329
+ '# memoc PATH',
330
+ `MEMOC_BIN=${shellSingleQuote(binDir)}`,
331
+ 'case ":$PATH:" in *":$MEMOC_BIN:"*) ;; *) PATH="$MEMOC_BIN:$PATH"; export PATH ;; esac',
332
+ '# end memoc PATH',
333
+ ].join('\n');
334
+
335
+ const candidates = [
336
+ path.join(home, '.profile'),
337
+ path.join(home, '.zshrc'),
338
+ path.join(home, '.bashrc'),
339
+ ];
340
+
341
+ let changed = false;
342
+ for (const fp of candidates) {
343
+ try {
344
+ const src = fs.existsSync(fp) ? fs.readFileSync(fp, 'utf8') : '';
345
+ if (src.includes(binDir) || src.includes('# memoc PATH')) continue;
346
+ fs.appendFileSync(fp, `${src.endsWith('\n') || !src ? '' : '\n'}\n${block}\n`, 'utf8');
347
+ changed = true;
348
+ } catch {}
349
+ }
350
+ return changed;
351
+ }
352
+
353
+ function userPathShellHint(binDir) {
354
+ return `user bin ${binDir} ${process.env.MEMOC_SKIP_PATH_REGISTER === '1' ? 'test mode' : 'registered; open a new terminal if needed'}`;
355
+ }
356
+
357
+ function currentPlatform() {
358
+ return process.env.MEMOC_PLATFORM || process.platform;
359
+ }
360
+
361
+ function shellSingleQuote(value) {
362
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
363
+ }
364
+
365
+ function samePath(a, b) {
366
+ if (!a || !b) return false;
367
+ const norm = p => path.resolve(p).toLowerCase().replace(/[\\/]+$/, '');
368
+ try { return norm(a) === norm(b); } catch { return false; }
369
+ }
370
+
248
371
  function updateSection(filePath, startMark, endMark, inner) {
249
372
  if (!fs.existsSync(filePath)) return false;
250
373
  const src = fs.readFileSync(filePath, 'utf8');
@@ -290,7 +413,7 @@ function managedBlock() {
290
413
  ## Session Start
291
414
  - [ ] Read \`.memoc/session-summary.md\`
292
415
  - [ ] \`.pending\` exists? → review changed files → update memory if needed → delete it
293
- - [ ] Put the project-local memoc wrapper on PATH when needed: PowerShell \`. .\\.memoc\\env.ps1\`; sh \`. ./.memoc/env.sh\`
416
+ - [ ] If \`memoc\` is not found in an existing shell, open a new terminal or load the local helper: PowerShell \`. .\\.memoc\\env.ps1\`; sh \`. ./.memoc/env.sh\`
294
417
 
295
418
  ## Before Opening More Files
296
419
  - [ ] Run memoc commands in this order: \`memoc search "<query>"\` → \`.\\.memoc\\bin\\memoc.cmd search "<query>"\` (Windows) or \`.memoc/bin/memoc search "<query>"\` (sh) → \`npx @kevin0181/memoc search "<query>"\`
@@ -1074,6 +1197,7 @@ function run(dir, forceUpdate) {
1074
1197
 
1075
1198
  // PATH helpers — let agents run memoc even when the npm bin is not on PATH
1076
1199
  ensurePathHelpers(dir, mark);
1200
+ ensurePathRegistration(dir, mark);
1077
1201
 
1078
1202
  } else {
1079
1203
  // ── UPDATE MODE
@@ -1166,6 +1290,7 @@ function run(dir, forceUpdate) {
1166
1290
 
1167
1291
  // PATH helpers — let agents run memoc even when the npm bin is not on PATH
1168
1292
  ensurePathHelpers(dir, mark);
1293
+ ensurePathRegistration(dir, mark);
1169
1294
 
1170
1295
  // Append update record to log.md
1171
1296
  const logPath = path.join(memDir, 'log.md');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kevin0181/memoc",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
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",