specmem-hardwicksoftware 3.7.40 → 3.7.42

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specmem-hardwicksoftware",
3
- "version": "3.7.40",
3
+ "version": "3.7.42",
4
4
  "type": "module",
5
5
  "description": "Your Claude Code sessions don't have to start from scratch anymore — SpecMem gives your AI real memory. It won't forget your conversations, your code, or your architecture decisions between sessions. That's the whole point. Semantic code indexing that actually works: TypeScript, JavaScript, Python, Go, Rust, Java, Kotlin, C, C++, HTML and more. It doesn't just track functions — it gets classes, methods, fields, constants, enums, macros, imports, structs, the whole codebase graph. There's chat memory too, powered by pgvector embeddings. You've also got token compression, team coordination, multi-agent comms, and file watching built in. 74+ MCP tools. Runs on PostgreSQL + Docker. It's kind of a big deal. justcalljon.pro",
6
6
  "main": "dist/index.js",
@@ -9193,14 +9193,46 @@ async function ensureModels(ui) {
9193
9193
  // Permanent models release — version-independent, all npm versions pull from here
9194
9194
  const releaseUrl = 'https://github.com/jonhardwick-spec/specmem/releases/download/models/specmem-models.tar.gz';
9195
9195
  const tmpTarball = path.join(os.tmpdir(), 'specmem-models.tar.gz');
9196
+ const expectedSizeMB = 562; // approximate tarball size
9196
9197
 
9197
9198
  initLog(`[MODELS] Downloading: ${releaseUrl}`);
9198
9199
 
9200
+ // Use spawn (async) instead of execSync so blessed TUI can render during download
9199
9201
  try {
9200
- const dlCmd = hasCurl
9201
- ? `curl -fSL --progress-bar -o "${tmpTarball}" "${releaseUrl}"`
9202
- : `wget --progress=bar:force -O "${tmpTarball}" "${releaseUrl}"`;
9203
- execSync(dlCmd, { stdio: 'pipe', timeout: 900000 }); // 15 min timeout for ~570MB
9202
+ await new Promise((resolve, reject) => {
9203
+ const args = hasCurl
9204
+ ? ['curl', ['-fSL', '-o', tmpTarball, releaseUrl]]
9205
+ : ['wget', ['-O', tmpTarball, releaseUrl]];
9206
+ const dl = spawn(args[0], args[1], { stdio: 'ignore' });
9207
+
9208
+ // Poll file size for progress updates
9209
+ const progressInterval = setInterval(() => {
9210
+ try {
9211
+ const stat = fs.statSync(tmpTarball);
9212
+ const mb = (stat.size / (1024 * 1024)).toFixed(0);
9213
+ const pct = Math.min(99, Math.round((stat.size / (expectedSizeMB * 1024 * 1024)) * 100));
9214
+ if (ui) ui.setSubStatus(`↓ Downloading ML models... ${mb}MB / ~${expectedSizeMB}MB (${pct}%)`);
9215
+ } catch {}
9216
+ }, 2000);
9217
+
9218
+ const timeout = setTimeout(() => {
9219
+ clearInterval(progressInterval);
9220
+ dl.kill('SIGTERM');
9221
+ reject(new Error('Download timed out after 15 minutes'));
9222
+ }, 900000);
9223
+
9224
+ dl.on('close', (code) => {
9225
+ clearInterval(progressInterval);
9226
+ clearTimeout(timeout);
9227
+ if (code === 0) resolve();
9228
+ else reject(new Error(`Download exited with code ${code}`));
9229
+ });
9230
+ dl.on('error', (err) => {
9231
+ clearInterval(progressInterval);
9232
+ clearTimeout(timeout);
9233
+ reject(err);
9234
+ });
9235
+ });
9204
9236
  } catch (e) {
9205
9237
  initLog(`[MODELS] Download failed: ${e.message}`);
9206
9238
  if (ui) ui.setSubStatus(`⚠ Model download failed — mini-COT/translation disabled`);
@@ -10125,6 +10157,42 @@ ${lastOutput}
10125
10157
  try { execSync('which podman 2>/dev/null', { stdio: 'pipe' }); return 'podman'; } catch { return 'docker'; }
10126
10158
  })();
10127
10159
 
10160
+ // Helper: run a long command async so blessed TUI stays responsive
10161
+ const spawnAsync = (cmd, args, label, timeoutMs = 600000) => {
10162
+ return new Promise((resolve, reject) => {
10163
+ const child = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
10164
+ let lastLine = '';
10165
+ const onData = (chunk) => {
10166
+ const lines = chunk.toString().split('\n').filter(Boolean);
10167
+ if (lines.length) lastLine = lines[lines.length - 1].slice(0, 80);
10168
+ };
10169
+ if (child.stdout) child.stdout.on('data', onData);
10170
+ if (child.stderr) child.stderr.on('data', onData);
10171
+
10172
+ const statusInterval = setInterval(() => {
10173
+ if (ui && lastLine) ui.setSubStatus(lastLine);
10174
+ }, 2000);
10175
+
10176
+ const timer = setTimeout(() => {
10177
+ clearInterval(statusInterval);
10178
+ child.kill('SIGTERM');
10179
+ reject(new Error(`${label} timed out`));
10180
+ }, timeoutMs);
10181
+
10182
+ child.on('close', (code) => {
10183
+ clearInterval(statusInterval);
10184
+ clearTimeout(timer);
10185
+ if (code === 0) resolve();
10186
+ else reject(new Error(`${label} exited with code ${code}`));
10187
+ });
10188
+ child.on('error', (err) => {
10189
+ clearInterval(statusInterval);
10190
+ clearTimeout(timer);
10191
+ reject(err);
10192
+ });
10193
+ });
10194
+ };
10195
+
10128
10196
  let imageExists = false;
10129
10197
  try {
10130
10198
  execSync(`${rtCmd} image inspect ${containerImage} 2>/dev/null`, { stdio: 'pipe' });
@@ -10133,7 +10201,7 @@ ${lastOutput}
10133
10201
  } catch {
10134
10202
  ui.setStatus(`Pulling ${containerImage}...`);
10135
10203
  try {
10136
- execSync(`${rtCmd} pull ${containerImage}`, { stdio: 'ignore', timeout: 600000 });
10204
+ await spawnAsync(rtCmd, ['pull', containerImage], 'Image pull', 600000);
10137
10205
  imageExists = true;
10138
10206
  ui.setStatus('Image pulled');
10139
10207
  } catch (pullErr) {
@@ -10142,13 +10210,9 @@ ${lastOutput}
10142
10210
  ui.setStatus('Pull failed, building locally...');
10143
10211
  const dockerfilePath = path.join(path.dirname(__dirname), 'container', 'Dockerfile');
10144
10212
  if (fs.existsSync(dockerfilePath)) {
10145
- // stdio:'ignore' build output can be gigabytes (no .dockerignore at context root)
10146
- // Without this, Node's default 1MB maxBuffer causes ERR_CHILD_PROCESS_STDIO_MAXBUFFER
10147
- // which crashes init before the catch can fire cleanly
10213
+ ui.setStatus('Building container image locally (this may take 10-20 min)...');
10148
10214
  try {
10149
- execSync(`${rtCmd} build -t ${containerImage} -f ${dockerfilePath} ${path.dirname(__dirname)}`, {
10150
- stdio: 'ignore', timeout: 1200000 // 20 min for local build
10151
- });
10215
+ await spawnAsync(rtCmd, ['build', '-t', containerImage, '-f', dockerfilePath, path.dirname(__dirname)], 'Image build', 1200000);
10152
10216
  imageExists = true;
10153
10217
  ui.setStatus('Image built locally');
10154
10218
  } catch (buildErr) {
@@ -10157,7 +10221,6 @@ ${lastOutput}
10157
10221
  }
10158
10222
  }
10159
10223
  } catch (fallbackErr) {
10160
- // Catch-all: even if ui.setStatus or fs.existsSync throws, don't crash init
10161
10224
  initLog(`[CONTAINER] Pull fallback error: ${fallbackErr.message}`);
10162
10225
  ui.setStatus('Image unavailable');
10163
10226
  }