grepmax 0.26.33 → 0.26.35

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.
@@ -10,7 +10,7 @@
10
10
  "name": "grepmax",
11
11
  "source": "./plugins/grepmax",
12
12
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
13
- "version": "0.26.33",
13
+ "version": "0.26.35",
14
14
  "author": {
15
15
  "name": "Robert Owens",
16
16
  "email": "robowens@me.com"
package/dist/bin.js ADDED
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.commandTarget = commandTarget;
38
+ exports.registeredForm = registeredForm;
39
+ // Entry point. Picks the gmax home before anything reads config.ts: a command
40
+ // aimed at a project under a secondary store's prefix (src/lib/utils/stores.ts)
41
+ // runs against that store, in-process, so the drive it lives on can always
42
+ // eject. Only Node built-ins and stores.ts may be imported here.
43
+ const fs = __importStar(require("node:fs"));
44
+ const path = __importStar(require("node:path"));
45
+ const stores_1 = require("./lib/utils/stores");
46
+ const DIR_ARG_COMMANDS = new Set(["add", "remove"]);
47
+ function optionValue(argv, names) {
48
+ for (let i = 0; i < argv.length; i++) {
49
+ const a = argv[i];
50
+ for (const n of names) {
51
+ if (a === n && argv[i + 1])
52
+ return argv[i + 1];
53
+ if (a.startsWith(`${n}=`))
54
+ return a.slice(n.length + 1);
55
+ }
56
+ }
57
+ return undefined;
58
+ }
59
+ function commandTarget(argv, cwd) {
60
+ var _a, _b;
61
+ const explicit = (_b = (_a = optionValue(argv, ["--root", "--store"])) !== null && _a !== void 0 ? _a : (argv[0] === "index" ? optionValue(argv, ["--path", "-p"]) : undefined)) !== null && _b !== void 0 ? _b : process.env.GMAX_STORE;
62
+ if (explicit)
63
+ return path.resolve(cwd, explicit);
64
+ if (DIR_ARG_COMMANDS.has(argv[0])) {
65
+ const positional = argv.slice(1).find((a) => !a.startsWith("-"));
66
+ if (positional &&
67
+ (positional.includes(path.sep) ||
68
+ fs.existsSync(path.resolve(cwd, positional)))) {
69
+ return path.resolve(cwd, positional);
70
+ }
71
+ }
72
+ return cwd;
73
+ }
74
+ function selectHome() {
75
+ if (process.env.GMAX_HOME)
76
+ return;
77
+ const argv = process.argv.slice(2);
78
+ const match = (0, stores_1.matchStore)(commandTarget(argv, process.cwd()));
79
+ if (match.kind === "primary")
80
+ return;
81
+ if (match.kind === "offline") {
82
+ process.stderr.write(`gmax: this project's index lives on ${match.volume}, which is not mounted (store ${match.store.home}). Connect the drive and run the command again.\n`);
83
+ process.exit(2);
84
+ }
85
+ process.env.GMAX_HOME = match.store.home;
86
+ // A secondary store never runs a daemon: nothing may hold the drive's files
87
+ // open between commands, or the drive could not eject.
88
+ process.env.GMAX_NO_AUTOSTART = "1";
89
+ process.env.GMAX_SECONDARY_STORE = "1";
90
+ seedConfig(match.store.home);
91
+ alignCwdWithRegistry(match.store.home);
92
+ }
93
+ // Node reports the physical working directory, so a shell standing in
94
+ // ~/Development/packages/x (a symlink onto the drive) looks like
95
+ // /Volumes/External/dev/packages/x, while the store registered the path it was
96
+ // given. When the physical cwd sits inside a registered root's realpath,
97
+ // report the cwd in the registered form so project lookup finds it.
98
+ function registeredForm(cwd, roots, real = realpathOr) {
99
+ for (const root of roots) {
100
+ const rootReal = real(root);
101
+ const rel = path.relative(rootReal, cwd);
102
+ if (rel === "" || (rel && !rel.startsWith("..") && !path.isAbsolute(rel))) {
103
+ return rel ? path.join(root, rel) : root;
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ function realpathOr(p) {
109
+ try {
110
+ return fs.realpathSync(p);
111
+ }
112
+ catch (_a) {
113
+ return path.resolve(p);
114
+ }
115
+ }
116
+ function alignCwdWithRegistry(home) {
117
+ let roots;
118
+ try {
119
+ const parsed = JSON.parse(fs.readFileSync(path.join(home, "projects.json"), "utf-8"));
120
+ roots = parsed
121
+ .map((p) => p.root)
122
+ .filter((r) => typeof r === "string");
123
+ }
124
+ catch (_a) {
125
+ return;
126
+ }
127
+ const physical = process.cwd();
128
+ const logical = registeredForm(physical, roots);
129
+ if (logical && logical !== physical)
130
+ process.cwd = () => logical;
131
+ }
132
+ // A new store embeds with the same model as the primary one.
133
+ function seedConfig(home) {
134
+ var _a;
135
+ const target = path.join(home, "config.json");
136
+ if (fs.existsSync(target))
137
+ return;
138
+ const primary = path.join((_a = process.env.HOME) !== null && _a !== void 0 ? _a : "", ".gmax", "config.json");
139
+ try {
140
+ fs.mkdirSync(home, { recursive: true });
141
+ fs.copyFileSync(primary, target);
142
+ }
143
+ catch (_b) {
144
+ // no primary config: the store starts from the defaults, as ~/.gmax did
145
+ }
146
+ }
147
+ if (require.main === module) {
148
+ selectHome();
149
+ require("./index");
150
+ }
@@ -72,6 +72,15 @@ exports.watch = new commander_1.Command("watch")
72
72
  .option("--no-idle-timeout", "Disable the 30-minute idle shutdown")
73
73
  .action((options) => __awaiter(void 0, void 0, void 0, function* () {
74
74
  var _a, _b;
75
+ // A secondary store (src/bin.ts) never runs a daemon or a watcher: one
76
+ // would hold the external drive open, and its startup sweep of stale
77
+ // daemons judges liveness by its own home's lock and socket, so it takes
78
+ // the primary daemon for dead and shuts it down (seen 2026-09-22).
79
+ if (process.env.GMAX_SECONDARY_STORE === "1") {
80
+ console.error("gmax: this project's index lives on an external drive and runs in-process; it is never watched. Run `gmax index` after changing it.");
81
+ process.exitCode = 2;
82
+ return;
83
+ }
75
84
  // --- Daemon mode ---
76
85
  if (options.daemon) {
77
86
  if (options.path) {
package/dist/config.js CHANGED
@@ -218,14 +218,23 @@ exports.MAX_WORKER_MEMORY_MB = (() => {
218
218
  return Math.min(HARD_CEILING, Math.max(2048, Math.floor((os.totalmem() / 1024 / 1024) * 0.5)));
219
219
  })();
220
220
  const HOME = os.homedir();
221
- const GLOBAL_ROOT = path.join(HOME, ".gmax");
221
+ // Models, grammars and the pinned HF cache are shared by every store and always
222
+ // live on the internal disk. GMAX_HOME moves only the data root: a secondary
223
+ // store on an external drive (see src/lib/utils/stores.ts) sets it before this
224
+ // module loads, so a project's index can live on the drive with its files.
225
+ const SHARED_ROOT = path.join(HOME, ".gmax");
226
+ const GLOBAL_ROOT = process.env.GMAX_HOME
227
+ ? path.resolve(process.env.GMAX_HOME)
228
+ : SHARED_ROOT;
222
229
  exports.PATHS = {
223
230
  globalRoot: GLOBAL_ROOT,
224
- models: path.join(GLOBAL_ROOT, "models"),
225
- grammars: path.join(GLOBAL_ROOT, "grammars"),
231
+ sharedRoot: SHARED_ROOT,
232
+ storesFile: path.join(SHARED_ROOT, "stores.json"),
233
+ models: path.join(SHARED_ROOT, "models"),
234
+ grammars: path.join(SHARED_ROOT, "grammars"),
226
235
  // HF cache for the MLX embed server, pinned to internal disk so the server
227
236
  // survives an unmounted external volume behind the user's HF_HOME.
228
- hfDir: path.join(GLOBAL_ROOT, "hf"),
237
+ hfDir: path.join(SHARED_ROOT, "hf"),
229
238
  logsDir: path.join(GLOBAL_ROOT, "logs"),
230
239
  daemonSocket: path.join(GLOBAL_ROOT, "daemon.sock"),
231
240
  daemonPidFile: path.join(GLOBAL_ROOT, "daemon.pid"),
@@ -81,6 +81,12 @@ function autostartDisabledUndo() {
81
81
  : `rm ${config_1.PATHS.autostartDisabledFile}`;
82
82
  }
83
83
  function autostartDisabledNotice() {
84
+ // A secondary store (src/bin.ts) runs in-process by design, not because the
85
+ // kill switch is on, so say that instead of naming an undo step. Callers use
86
+ // a non-null notice as the gate, so this must stay non-null.
87
+ if (process.env.GMAX_SECONDARY_STORE === "1") {
88
+ return `This project's index lives in ${config_1.PATHS.globalRoot} on an external drive — running in-process, not watched. Run \`gmax index\` after changing it.`;
89
+ }
84
90
  const undo = autostartDisabledUndo();
85
91
  if (!undo)
86
92
  return null;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.storesFilePath = storesFilePath;
37
+ exports.readStores = readStores;
38
+ exports.volumeOf = volumeOf;
39
+ exports.matchStore = matchStore;
40
+ const fs = __importStar(require("node:fs"));
41
+ const os = __importStar(require("node:os"));
42
+ const path = __importStar(require("node:path"));
43
+ function storesFilePath(home = os.homedir()) {
44
+ return path.join(home, ".gmax", "stores.json");
45
+ }
46
+ function readStores(file = storesFilePath()) {
47
+ let raw;
48
+ try {
49
+ raw = fs.readFileSync(file, "utf-8");
50
+ }
51
+ catch (_a) {
52
+ return [];
53
+ }
54
+ try {
55
+ const parsed = JSON.parse(raw);
56
+ if (!Array.isArray(parsed.stores))
57
+ return [];
58
+ return parsed.stores.filter((s) => !!s &&
59
+ typeof s.prefix === "string" &&
60
+ typeof s.home === "string");
61
+ }
62
+ catch (_b) {
63
+ return [];
64
+ }
65
+ }
66
+ // The volume a path lives on, for paths under /Volumes; null for the boot disk.
67
+ function volumeOf(p) {
68
+ const parts = path.resolve(p).split(path.sep);
69
+ return parts[1] === "Volumes" && parts[2]
70
+ ? path.join("/Volumes", parts[2])
71
+ : null;
72
+ }
73
+ function realOrResolved(p) {
74
+ try {
75
+ return fs.realpathSync(p);
76
+ }
77
+ catch (_a) {
78
+ return path.resolve(p);
79
+ }
80
+ }
81
+ function within(child, parent) {
82
+ const rel = path.relative(parent, child);
83
+ return (rel === "" || (!!rel && !rel.startsWith("..") && !path.isAbsolute(rel)));
84
+ }
85
+ // A path behind a symlink (~/Development/packages -> /Volumes/External/...)
86
+ // matches through its realpath. When the drive is ejected the symlink dangles
87
+ // and realpath fails, so the link target is read directly to still name it.
88
+ function candidates(target) {
89
+ const out = new Set([path.resolve(target), realOrResolved(target)]);
90
+ let cur = path.resolve(target);
91
+ const tail = [];
92
+ while (cur !== path.dirname(cur)) {
93
+ try {
94
+ if (fs.lstatSync(cur).isSymbolicLink()) {
95
+ const link = path.resolve(path.dirname(cur), fs.readlinkSync(cur));
96
+ out.add(path.join(link, ...tail.slice().reverse()));
97
+ break;
98
+ }
99
+ }
100
+ catch (_a) {
101
+ // not there; keep walking up
102
+ }
103
+ tail.push(path.basename(cur));
104
+ cur = path.dirname(cur);
105
+ }
106
+ return [...out];
107
+ }
108
+ function matchStore(target, stores = readStores()) {
109
+ var _a;
110
+ if (!stores.length)
111
+ return { kind: "primary" };
112
+ for (const cand of candidates(target)) {
113
+ for (const store of stores) {
114
+ if (!within(cand, path.resolve(store.prefix)))
115
+ continue;
116
+ const volume = (_a = volumeOf(store.home)) !== null && _a !== void 0 ? _a : volumeOf(store.prefix);
117
+ if (volume && !fs.existsSync(volume)) {
118
+ return { kind: "offline", store, volume };
119
+ }
120
+ return { kind: "store", store };
121
+ }
122
+ }
123
+ return { kind: "primary" };
124
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.33",
3
+ "version": "0.26.35",
4
4
  "author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
5
5
  "homepage": "https://github.com/reowens/grepmax",
6
6
  "bugs": {
@@ -12,7 +12,7 @@
12
12
  },
13
13
  "main": "dist/index.js",
14
14
  "bin": {
15
- "gmax": "dist/index.js"
15
+ "gmax": "dist/bin.js"
16
16
  },
17
17
  "engines": {
18
18
  "node": ">=22.12.0"
@@ -21,8 +21,8 @@
21
21
  "postinstall": "node scripts/postinstall.js",
22
22
  "prebuild": "rm -rf dist tsconfig.tsbuildinfo && mkdir -p dist",
23
23
  "build": "tsc",
24
- "postbuild": "chmod +x dist/index.js",
25
- "dev": "npx tsc && node dist/index.js",
24
+ "postbuild": "chmod +x dist/bin.js dist/index.js",
25
+ "dev": "npx tsc && node dist/bin.js",
26
26
  "test": "vitest run",
27
27
  "test:watch": "vitest",
28
28
  "bench:recall": "npx tsx src/eval.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grepmax",
3
- "version": "0.26.33",
3
+ "version": "0.26.35",
4
4
  "description": "Semantic code search for Claude Code. Automatically indexes your project and provides intelligent search capabilities.",
5
5
  "author": {
6
6
  "name": "Robert Owens",