@kevin0181/memoc 1.0.1 → 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.
- package/README.md +1 -1
- package/bin/cli.js +199 -1
- 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
|
|
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
|
@@ -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,194 @@ function ensurePathHelpers(dir, mark) {
|
|
|
245
253
|
}
|
|
246
254
|
}
|
|
247
255
|
|
|
256
|
+
function ensureUserLauncher(mark) {
|
|
257
|
+
const userBin = defaultUserBinDir();
|
|
258
|
+
writeLaunchers(userBin, mark, 'user bin');
|
|
259
|
+
return userBin;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function writeLaunchers(binDir, mark, label) {
|
|
263
|
+
const files = [
|
|
264
|
+
[path.join(binDir, 'memoc.cmd'), tplMemocCmdWrapper, false],
|
|
265
|
+
[path.join(binDir, 'memoc.ps1'), tplMemocPs1Wrapper, false],
|
|
266
|
+
[path.join(binDir, 'memoc'), tplMemocShWrapper, true],
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
for (const [fp, tpl, executable] of files) {
|
|
270
|
+
const added = ensure(fp, tpl());
|
|
271
|
+
if (executable) chmodExecutable(fp);
|
|
272
|
+
mark(added ? 'add' : 'skip', `${label} ${path.basename(fp)}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function ensurePathRegistration(dir, mark) {
|
|
277
|
+
ensureCurrentPathLauncher(mark);
|
|
278
|
+
const binDir = ensureUserLauncher(mark);
|
|
279
|
+
const pathSep = path.delimiter;
|
|
280
|
+
|
|
281
|
+
if ((process.env.PATH || '').split(pathSep).some(p => samePath(p, binDir))) {
|
|
282
|
+
mark('skip', 'PATH (user memoc bin already active)');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
process.env.PATH = `${binDir}${pathSep}${process.env.PATH || ''}`;
|
|
287
|
+
|
|
288
|
+
if (process.env.MEMOC_SKIP_PATH_REGISTER === '1') {
|
|
289
|
+
mark('skip', 'PATH registration (test mode)');
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (currentPlatform() !== 'win32') {
|
|
294
|
+
const updated = ensureUnixPathRegistration(binDir);
|
|
295
|
+
mark(updated ? 'update' : 'skip', `${currentPlatform()} PATH (${userPathShellHint(binDir)})`);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const current = require('child_process')
|
|
301
|
+
.execFileSync('powershell.exe', [
|
|
302
|
+
'-NoProfile',
|
|
303
|
+
'-ExecutionPolicy', 'Bypass',
|
|
304
|
+
'-Command',
|
|
305
|
+
"[Environment]::GetEnvironmentVariable('Path','User')",
|
|
306
|
+
], { encoding: 'utf8' })
|
|
307
|
+
.trim();
|
|
308
|
+
const parts = current.split(pathSep).filter(Boolean);
|
|
309
|
+
if (parts.some(p => samePath(p, binDir))) {
|
|
310
|
+
mark('skip', 'User PATH (memoc bin already registered)');
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const nextPath = [binDir, ...parts].join(pathSep);
|
|
314
|
+
require('child_process').execFileSync('powershell.exe', [
|
|
315
|
+
'-NoProfile',
|
|
316
|
+
'-ExecutionPolicy', 'Bypass',
|
|
317
|
+
'-Command',
|
|
318
|
+
`[Environment]::SetEnvironmentVariable('Path', ${JSON.stringify(nextPath)}, 'User')`,
|
|
319
|
+
], { stdio: 'ignore' });
|
|
320
|
+
mark('update', 'User PATH (memoc bin added; open a new terminal if needed)');
|
|
321
|
+
} catch {
|
|
322
|
+
mark('skip', 'User PATH registration failed (use . .\\.memoc\\env.ps1)');
|
|
323
|
+
}
|
|
324
|
+
}
|
|
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
|
+
|
|
395
|
+
function ensureUnixPathRegistration(binDir) {
|
|
396
|
+
if (process.env.MEMOC_SKIP_PATH_REGISTER === '1') return false;
|
|
397
|
+
|
|
398
|
+
const home = process.env.HOME;
|
|
399
|
+
if (!home) return false;
|
|
400
|
+
|
|
401
|
+
const block = [
|
|
402
|
+
'# memoc PATH',
|
|
403
|
+
`MEMOC_BIN=${shellSingleQuote(binDir)}`,
|
|
404
|
+
'case ":$PATH:" in *":$MEMOC_BIN:"*) ;; *) PATH="$MEMOC_BIN:$PATH"; export PATH ;; esac',
|
|
405
|
+
'# end memoc PATH',
|
|
406
|
+
].join('\n');
|
|
407
|
+
|
|
408
|
+
const candidates = [
|
|
409
|
+
path.join(home, '.profile'),
|
|
410
|
+
path.join(home, '.zshrc'),
|
|
411
|
+
path.join(home, '.bashrc'),
|
|
412
|
+
];
|
|
413
|
+
|
|
414
|
+
let changed = false;
|
|
415
|
+
for (const fp of candidates) {
|
|
416
|
+
try {
|
|
417
|
+
const src = fs.existsSync(fp) ? fs.readFileSync(fp, 'utf8') : '';
|
|
418
|
+
if (src.includes(binDir) || src.includes('# memoc PATH')) continue;
|
|
419
|
+
fs.appendFileSync(fp, `${src.endsWith('\n') || !src ? '' : '\n'}\n${block}\n`, 'utf8');
|
|
420
|
+
changed = true;
|
|
421
|
+
} catch {}
|
|
422
|
+
}
|
|
423
|
+
return changed;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function userPathShellHint(binDir) {
|
|
427
|
+
return `user bin ${binDir} ${process.env.MEMOC_SKIP_PATH_REGISTER === '1' ? 'test mode' : 'registered; open a new terminal if needed'}`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function currentPlatform() {
|
|
431
|
+
return process.env.MEMOC_PLATFORM || process.platform;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function shellSingleQuote(value) {
|
|
435
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function samePath(a, b) {
|
|
439
|
+
if (!a || !b) return false;
|
|
440
|
+
const norm = p => path.resolve(p).toLowerCase().replace(/[\\/]+$/, '');
|
|
441
|
+
try { return norm(a) === norm(b); } catch { return false; }
|
|
442
|
+
}
|
|
443
|
+
|
|
248
444
|
function updateSection(filePath, startMark, endMark, inner) {
|
|
249
445
|
if (!fs.existsSync(filePath)) return false;
|
|
250
446
|
const src = fs.readFileSync(filePath, 'utf8');
|
|
@@ -290,7 +486,7 @@ function managedBlock() {
|
|
|
290
486
|
## Session Start
|
|
291
487
|
- [ ] Read \`.memoc/session-summary.md\`
|
|
292
488
|
- [ ] \`.pending\` exists? → review changed files → update memory if needed → delete it
|
|
293
|
-
- [ ]
|
|
489
|
+
- [ ] 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
490
|
|
|
295
491
|
## Before Opening More Files
|
|
296
492
|
- [ ] 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 +1270,7 @@ function run(dir, forceUpdate) {
|
|
|
1074
1270
|
|
|
1075
1271
|
// PATH helpers — let agents run memoc even when the npm bin is not on PATH
|
|
1076
1272
|
ensurePathHelpers(dir, mark);
|
|
1273
|
+
ensurePathRegistration(dir, mark);
|
|
1077
1274
|
|
|
1078
1275
|
} else {
|
|
1079
1276
|
// ── UPDATE MODE
|
|
@@ -1166,6 +1363,7 @@ function run(dir, forceUpdate) {
|
|
|
1166
1363
|
|
|
1167
1364
|
// PATH helpers — let agents run memoc even when the npm bin is not on PATH
|
|
1168
1365
|
ensurePathHelpers(dir, mark);
|
|
1366
|
+
ensurePathRegistration(dir, mark);
|
|
1169
1367
|
|
|
1170
1368
|
// Append update record to log.md
|
|
1171
1369
|
const logPath = path.join(memDir, 'log.md');
|