@stackmemoryai/stackmemory 0.5.49 → 0.5.52

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 (42) hide show
  1. package/README.md +17 -3
  2. package/dist/cli/claude-sm.js +246 -5
  3. package/dist/cli/claude-sm.js.map +3 -3
  4. package/dist/cli/commands/handoff.js +27 -12
  5. package/dist/cli/commands/handoff.js.map +2 -2
  6. package/dist/cli/commands/sweep.js +190 -421
  7. package/dist/cli/commands/sweep.js.map +3 -3
  8. package/dist/cli/index.js +10 -2
  9. package/dist/cli/index.js.map +2 -2
  10. package/dist/core/config/feature-flags.js +7 -1
  11. package/dist/core/config/feature-flags.js.map +2 -2
  12. package/dist/core/context/enhanced-rehydration.js +355 -9
  13. package/dist/core/context/enhanced-rehydration.js.map +3 -3
  14. package/dist/core/context/shared-context-layer.js +229 -0
  15. package/dist/core/context/shared-context-layer.js.map +2 -2
  16. package/dist/features/sweep/index.js +20 -0
  17. package/dist/features/sweep/index.js.map +7 -0
  18. package/dist/features/sweep/prediction-client.js +155 -0
  19. package/dist/features/sweep/prediction-client.js.map +7 -0
  20. package/dist/features/sweep/prompt-builder.js +85 -0
  21. package/dist/features/sweep/prompt-builder.js.map +7 -0
  22. package/dist/features/sweep/pty-wrapper.js +171 -0
  23. package/dist/features/sweep/pty-wrapper.js.map +7 -0
  24. package/dist/features/sweep/state-watcher.js +87 -0
  25. package/dist/features/sweep/state-watcher.js.map +7 -0
  26. package/dist/features/sweep/status-bar.js +88 -0
  27. package/dist/features/sweep/status-bar.js.map +7 -0
  28. package/dist/features/sweep/sweep-server-manager.js +226 -0
  29. package/dist/features/sweep/sweep-server-manager.js.map +7 -0
  30. package/dist/features/sweep/tab-interceptor.js +38 -0
  31. package/dist/features/sweep/tab-interceptor.js.map +7 -0
  32. package/dist/features/sweep/types.js +18 -0
  33. package/dist/features/sweep/types.js.map +7 -0
  34. package/dist/integrations/claude-code/lifecycle-hooks.js +3 -3
  35. package/dist/integrations/claude-code/lifecycle-hooks.js.map +1 -1
  36. package/package.json +1 -1
  37. package/scripts/auto-handoff.sh +1 -1
  38. package/scripts/claude-sm-autostart.js +174 -132
  39. package/scripts/setup-claude-integration.js +14 -10
  40. package/scripts/stackmemory-auto-handoff.sh +3 -3
  41. package/scripts/test-session-handoff.sh +2 -2
  42. package/scripts/test-setup-e2e.sh +154 -0
@@ -5,476 +5,245 @@ const __dirname = __pathDirname(__filename);
5
5
  import { Command } from "commander";
6
6
  import chalk from "chalk";
7
7
  import ora from "ora";
8
- import {
9
- existsSync,
10
- readFileSync,
11
- writeFileSync,
12
- mkdirSync,
13
- copyFileSync,
14
- chmodSync
15
- } from "fs";
8
+ import { existsSync } from "fs";
16
9
  import { join } from "path";
17
- import { spawn, execSync } from "child_process";
18
- function findPythonScript() {
19
- const locations = [
20
- join(
21
- process.cwd(),
22
- "packages",
23
- "sweep-addon",
24
- "python",
25
- "sweep_predict.py"
26
- ),
27
- join(
28
- process.cwd(),
29
- "node_modules",
30
- "@stackmemoryai",
31
- "sweep-addon",
32
- "python",
33
- "sweep_predict.py"
34
- ),
35
- join(process.env.HOME || "", ".stackmemory", "sweep", "sweep_predict.py")
36
- ];
37
- for (const loc of locations) {
38
- if (existsSync(loc)) {
39
- return loc;
40
- }
41
- }
42
- return null;
43
- }
44
- function findHookSource() {
45
- const locations = [
46
- join(process.cwd(), "templates", "claude-hooks", "post-edit-sweep.js"),
47
- join(
48
- process.cwd(),
49
- "node_modules",
50
- "@stackmemoryai",
51
- "stackmemory",
52
- "templates",
53
- "claude-hooks",
54
- "post-edit-sweep.js"
55
- ),
56
- join(
57
- dirname(dirname(dirname(__dirname))),
58
- "templates",
59
- "claude-hooks",
60
- "post-edit-sweep.js"
61
- )
62
- ];
63
- for (const loc of locations) {
64
- if (existsSync(loc)) {
65
- return loc;
66
- }
67
- }
68
- return null;
69
- }
70
- async function findPython() {
71
- const candidates = ["python3", "python"];
72
- for (const cmd of candidates) {
73
- try {
74
- execSync(`${cmd} --version`, { stdio: "pipe" });
75
- return cmd;
76
- } catch {
77
- continue;
78
- }
79
- }
80
- return null;
81
- }
82
- async function checkSweepStatus() {
83
- const pythonPath = await findPython();
84
- if (!pythonPath) {
85
- return {
86
- installed: false,
87
- model_downloaded: false,
88
- error: "Python not found. Install Python 3.10+"
89
- };
90
- }
91
- const scriptPath = findPythonScript();
92
- if (!scriptPath) {
93
- return {
94
- installed: false,
95
- model_downloaded: false,
96
- python_path: pythonPath,
97
- error: "Sweep addon not installed. Run: stackmemory sweep setup"
98
- };
99
- }
100
- const homeDir = process.env.HOME || "";
101
- const modelPath = join(
102
- homeDir,
103
- ".stackmemory",
104
- "models",
105
- "sweep",
106
- "sweep-next-edit-1.5b.q8_0.v2.gguf"
107
- );
108
- const modelDownloaded = existsSync(modelPath);
109
- return {
110
- installed: true,
111
- model_downloaded: modelDownloaded,
112
- python_path: pythonPath,
113
- model_path: modelDownloaded ? modelPath : void 0
114
- };
115
- }
116
- async function runPrediction(filePath, pythonPath, scriptPath) {
117
- if (!existsSync(filePath)) {
118
- return {
119
- success: false,
120
- error: "file_not_found",
121
- message: `File not found: ${filePath}`
122
- };
123
- }
124
- const currentContent = readFileSync(filePath, "utf-8");
125
- const input = {
126
- file_path: filePath,
127
- current_content: currentContent
128
- };
129
- return new Promise((resolve) => {
130
- const proc = spawn(pythonPath, [scriptPath], {
131
- stdio: ["pipe", "pipe", "pipe"]
132
- });
133
- let stdout = "";
134
- let stderr = "";
135
- proc.stdout.on("data", (data) => stdout += data);
136
- proc.stderr.on("data", (data) => stderr += data);
137
- proc.on("close", (code) => {
138
- try {
139
- if (stdout.trim()) {
140
- const result = JSON.parse(stdout.trim());
141
- resolve(result);
142
- } else if (code !== 0) {
143
- resolve({
144
- success: false,
145
- error: "process_error",
146
- message: stderr || `Process exited with code ${code}`
147
- });
148
- } else {
149
- resolve({
150
- success: false,
151
- error: "no_output",
152
- message: "No output from prediction script"
153
- });
154
- }
155
- } catch {
156
- resolve({
157
- success: false,
158
- error: "parse_error",
159
- message: `Failed to parse output: ${stdout}`
160
- });
161
- }
162
- });
163
- proc.on("error", (error) => {
164
- resolve({
165
- success: false,
166
- error: "spawn_error",
167
- message: error.message
168
- });
169
- });
170
- proc.stdin.write(JSON.stringify(input));
171
- proc.stdin.end();
172
- });
173
- }
10
+ import {
11
+ createServerManager,
12
+ createPredictionClient,
13
+ launchWrapper,
14
+ DEFAULT_SERVER_CONFIG
15
+ } from "../../features/sweep/index.js";
16
+ const HOME = process.env["HOME"] || "/tmp";
174
17
  function createSweepCommand() {
175
- const cmd = new Command("sweep").description(
176
- "Next-edit predictions using Sweep 1.5B model (optional addon)"
177
- ).addHelpText(
18
+ const cmd = new Command("sweep").description("Manage Sweep Next-Edit prediction server").addHelpText(
178
19
  "after",
179
20
  `
180
21
  Examples:
181
- stackmemory sweep setup Install Python dependencies
182
- stackmemory sweep setup --download Also download the model (1.5GB)
183
- stackmemory sweep status Check addon status
184
- stackmemory sweep predict src/app.ts Predict next edit for a file
185
-
186
- Requirements:
187
- - Python 3.10+
188
- - pip packages: huggingface_hub, llama-cpp-python
22
+ stackmemory sweep start Start the Sweep server
23
+ stackmemory sweep stop Stop the Sweep server
24
+ stackmemory sweep status Check server status
25
+ stackmemory sweep predict Run a prediction manually
26
+ stackmemory sweep hook Install/check Claude Code hook
189
27
 
190
- The Sweep 1.5B model predicts what code changes you'll make next based on:
191
- - Current file content
192
- - Recent changes (diffs)
193
- - Context from other files
194
-
195
- Model is downloaded from HuggingFace on first prediction (~1.5GB).
28
+ The Sweep server uses the Sweep 1.5B model for next-edit predictions.
29
+ Predictions are triggered after file edits via Claude Code hooks.
196
30
  `
197
31
  );
198
- cmd.command("setup").description("Install Python dependencies for Sweep addon").option("--download", "Also download the model now").action(async (options) => {
199
- const spinner = ora("Checking Python...").start();
200
- const pythonPath = await findPython();
201
- if (!pythonPath) {
202
- spinner.fail(chalk.red("Python not found"));
203
- console.log(chalk.gray("Please install Python 3.10+"));
32
+ cmd.command("start").description("Start the Sweep prediction server").option(
33
+ "--port <number>",
34
+ "Server port",
35
+ String(DEFAULT_SERVER_CONFIG.port)
36
+ ).option("--model <path>", "Path to GGUF model file").option(
37
+ "--context <size>",
38
+ "Context size",
39
+ String(DEFAULT_SERVER_CONFIG.contextSize)
40
+ ).option("--gpu-layers <n>", "Number of GPU layers (0 for CPU only)", "0").action(async (options) => {
41
+ const spinner = ora("Starting Sweep server...").start();
42
+ try {
43
+ const config = {
44
+ port: parseInt(options.port, 10),
45
+ contextSize: parseInt(options.context, 10),
46
+ gpuLayers: parseInt(options.gpuLayers, 10)
47
+ };
48
+ if (options.model) {
49
+ config.modelPath = options.model;
50
+ }
51
+ const manager = createServerManager(config);
52
+ const status = await manager.startServer();
53
+ spinner.succeed(chalk.green("Sweep server started"));
54
+ console.log(chalk.gray(` PID: ${status.pid}`));
55
+ console.log(chalk.gray(` Port: ${status.port}`));
56
+ console.log(chalk.gray(` Model: ${status.modelPath}`));
57
+ } catch (error) {
58
+ spinner.fail(chalk.red("Failed to start Sweep server"));
59
+ console.error(chalk.red(error.message));
204
60
  process.exit(1);
205
61
  }
206
- spinner.text = "Installing Python dependencies...";
62
+ });
63
+ cmd.command("stop").description("Stop the Sweep prediction server").action(async () => {
64
+ const spinner = ora("Stopping Sweep server...").start();
207
65
  try {
208
- execSync(
209
- `${pythonPath} -m pip install --quiet huggingface_hub llama-cpp-python`,
210
- {
211
- stdio: "pipe"
212
- }
213
- );
214
- spinner.succeed(chalk.green("Python dependencies installed"));
215
- } catch {
216
- spinner.fail(chalk.red("Failed to install dependencies"));
217
- console.log(
218
- chalk.gray(
219
- `Run: ${pythonPath} -m pip install huggingface_hub llama-cpp-python`
220
- )
221
- );
66
+ const manager = createServerManager();
67
+ await manager.stopServer();
68
+ spinner.succeed(chalk.green("Sweep server stopped"));
69
+ } catch (error) {
70
+ spinner.fail(chalk.red("Failed to stop Sweep server"));
71
+ console.error(chalk.red(error.message));
222
72
  process.exit(1);
223
73
  }
224
- if (options.download) {
225
- const downloadSpinner = ora("Downloading Sweep 1.5B model...").start();
226
- downloadSpinner.text = "Downloading model from HuggingFace (~1.5GB)...";
227
- try {
228
- execSync(
229
- `${pythonPath} -c "
230
- from huggingface_hub import hf_hub_download
231
- import os
232
- model_dir = os.path.expanduser('~/.stackmemory/models/sweep')
233
- os.makedirs(model_dir, exist_ok=True)
234
- hf_hub_download(
235
- repo_id='sweepai/sweep-next-edit-1.5B',
236
- filename='sweep-next-edit-1.5b.q8_0.v2.gguf',
237
- repo_type='model',
238
- local_dir=model_dir,
239
- local_dir_use_symlinks=False
240
- )
241
- "`,
242
- { stdio: "pipe", timeout: 6e5 }
243
- );
244
- downloadSpinner.succeed(chalk.green("Model downloaded"));
245
- } catch {
246
- downloadSpinner.fail(chalk.red("Model download failed"));
247
- console.log(chalk.gray("Model will be downloaded on first use"));
74
+ });
75
+ cmd.command("status").description("Check Sweep server status").action(async () => {
76
+ try {
77
+ const manager = createServerManager();
78
+ const status = await manager.getStatus();
79
+ if (status.running) {
80
+ console.log(chalk.green("Sweep server is running"));
81
+ console.log(chalk.gray(` PID: ${status.pid}`));
82
+ console.log(chalk.gray(` Port: ${status.port}`));
83
+ console.log(chalk.gray(` Host: ${status.host}`));
84
+ if (status.startedAt) {
85
+ const uptime = Math.floor((Date.now() - status.startedAt) / 1e3);
86
+ console.log(chalk.gray(` Uptime: ${formatUptime(uptime)}`));
87
+ }
88
+ if (status.modelPath) {
89
+ console.log(chalk.gray(` Model: ${status.modelPath}`));
90
+ }
91
+ } else {
92
+ console.log(chalk.yellow("Sweep server is not running"));
93
+ console.log(chalk.gray(" Start with: stackmemory sweep start"));
248
94
  }
249
- } else {
250
- console.log(
251
- chalk.gray("\nModel will be downloaded on first prediction (~1.5GB)")
95
+ const defaultModelPath = join(
96
+ HOME,
97
+ ".stackmemory",
98
+ "models",
99
+ "sweep",
100
+ "sweep-next-edit-1.5b.q8_0.v2.gguf"
252
101
  );
253
- console.log(chalk.gray("Or run: stackmemory sweep setup --download"));
254
- }
255
- console.log(chalk.bold("\nSetup complete!"));
256
- });
257
- cmd.command("status").description("Check Sweep addon status").action(async () => {
258
- console.log(chalk.bold("\nSweep 1.5B Addon Status\n"));
259
- const status = await checkSweepStatus();
260
- if (status.error) {
261
- console.log(chalk.red(`Error: ${status.error}`));
262
- console.log("");
263
- }
264
- console.log(
265
- `Python: ${status.python_path ? chalk.green(status.python_path) : chalk.red("Not found")}`
266
- );
267
- console.log(
268
- `Addon installed: ${status.installed ? chalk.green("Yes") : chalk.yellow("No")}`
269
- );
270
- console.log(
271
- `Model downloaded: ${status.model_downloaded ? chalk.green("Yes") : chalk.yellow("No (will download on first use)")}`
272
- );
273
- if (status.model_path) {
274
- console.log(chalk.gray(`Model path: ${status.model_path}`));
275
- }
276
- if (!status.installed) {
277
- console.log(chalk.bold("\nTo install:"));
278
- console.log(" stackmemory sweep setup");
279
- }
280
- });
281
- cmd.command("predict <file>").description("Predict next edit for a file").option("-o, --output <path>", "Write prediction to file instead of stdout").option("--json", "Output raw JSON result").action(async (file, options) => {
282
- const status = await checkSweepStatus();
283
- if (!status.installed) {
284
- console.error(chalk.red("Sweep addon not installed"));
285
- console.log(chalk.gray("Run: stackmemory sweep setup"));
286
- process.exit(1);
287
- }
288
- const scriptPath = findPythonScript();
289
- if (!scriptPath || !status.python_path) {
290
- console.error(chalk.red("Could not find Sweep prediction script"));
291
- process.exit(1);
292
- }
293
- const spinner = ora("Running prediction...").start();
294
- if (!status.model_downloaded) {
295
- spinner.text = "Downloading model (first time only, ~1.5GB)...";
296
- }
297
- const result = await runPrediction(file, status.python_path, scriptPath);
298
- if (!result.success) {
299
- spinner.fail(
300
- chalk.red(`Prediction failed: ${result.message || result.error}`)
102
+ if (!existsSync(defaultModelPath)) {
103
+ console.log("");
104
+ console.log(chalk.yellow("Model not found at default location"));
105
+ console.log(
106
+ chalk.gray(
107
+ " Download with:\n huggingface-cli download sweepai/sweep-next-edit-1.5B \\\n sweep-next-edit-1.5b.q8_0.v2.gguf \\\n --local-dir ~/.stackmemory/models/sweep"
108
+ )
109
+ );
110
+ }
111
+ } catch (error) {
112
+ console.error(
113
+ chalk.red("Error checking status:"),
114
+ error.message
301
115
  );
302
116
  process.exit(1);
303
117
  }
304
- spinner.succeed(chalk.green("Prediction complete"));
305
- if (options.json) {
306
- console.log(JSON.stringify(result, null, 2));
307
- return;
308
- }
309
- console.log(chalk.bold("\nPredicted content:"));
310
- console.log(chalk.gray("\u2500".repeat(50)));
311
- console.log(result.predicted_content);
312
- console.log(chalk.gray("\u2500".repeat(50)));
313
- if (result.latency_ms) {
314
- console.log(chalk.gray(`Latency: ${result.latency_ms}ms`));
315
- }
316
- if (result.tokens_generated) {
317
- console.log(chalk.gray(`Tokens: ${result.tokens_generated}`));
318
- }
319
- if (options.output) {
320
- const { writeFileSync: writeFileSync2 } = await import("fs");
321
- writeFileSync2(options.output, result.predicted_content || "");
322
- console.log(chalk.green(`
323
- Written to: ${options.output}`));
324
- }
325
118
  });
326
- const hookCmd = cmd.command("hook").description("Manage Claude Code integration hook");
327
- hookCmd.command("install").description("Install Sweep prediction hook for Claude Code").action(async () => {
328
- const spinner = ora("Installing Sweep hook...").start();
329
- const homeDir = process.env.HOME || "";
330
- const hookDir = join(homeDir, ".claude", "hooks");
331
- const sweepDir = join(homeDir, ".stackmemory", "sweep");
332
- const hooksJsonPath = join(homeDir, ".claude", "hooks.json");
119
+ cmd.command("predict").description("Run a prediction manually (for testing)").argument("<file>", "File to predict edits for").option(
120
+ "--port <number>",
121
+ "Server port",
122
+ String(DEFAULT_SERVER_CONFIG.port)
123
+ ).action(async (file, options) => {
124
+ const spinner = ora("Running prediction...").start();
333
125
  try {
334
- mkdirSync(hookDir, { recursive: true });
335
- mkdirSync(sweepDir, { recursive: true });
336
- const hookSource = findHookSource();
337
- if (!hookSource) {
338
- spinner.fail(chalk.red("Hook template not found"));
339
- console.log(
340
- chalk.gray("Ensure stackmemory is installed from the repository")
341
- );
126
+ const { readFileSync } = await import("fs");
127
+ const content = readFileSync(file, "utf-8");
128
+ const client = createPredictionClient({
129
+ port: parseInt(options.port, 10)
130
+ });
131
+ const healthy = await client.checkHealth();
132
+ if (!healthy) {
133
+ spinner.fail(chalk.red("Server not running"));
134
+ console.log(chalk.gray("Start with: stackmemory sweep start"));
342
135
  process.exit(1);
343
136
  }
344
- const hookDest = join(hookDir, "post-edit-sweep.js");
345
- copyFileSync(hookSource, hookDest);
346
- chmodSync(hookDest, "755");
347
- const pythonScriptSource = findPythonScript();
348
- if (pythonScriptSource) {
349
- const pythonDest = join(sweepDir, "sweep_predict.py");
350
- copyFileSync(pythonScriptSource, pythonDest);
351
- }
352
- if (existsSync(hooksJsonPath)) {
353
- const hooks = JSON.parse(readFileSync(hooksJsonPath, "utf-8"));
354
- if (!hooks["post-tool-use"]) {
355
- hooks["post-tool-use"] = hookDest;
356
- writeFileSync(hooksJsonPath, JSON.stringify(hooks, null, 2));
357
- } else if (!hooks["post-tool-use"].includes("sweep")) {
358
- spinner.warn(chalk.yellow("post-tool-use hook already configured"));
359
- console.log(chalk.gray(`Existing: ${hooks["post-tool-use"]}`));
360
- console.log(chalk.gray(`Hook installed at: ${hookDest}`));
361
- console.log(
362
- chalk.gray("You may need to manually configure the hook chain")
363
- );
364
- return;
137
+ const result = await client.predict({
138
+ file_path: file,
139
+ current_content: content,
140
+ recent_diffs: []
141
+ });
142
+ spinner.stop();
143
+ if (result.success && result.predicted_content) {
144
+ console.log(chalk.green("Prediction complete"));
145
+ console.log(chalk.gray(`Latency: ${result.latency_ms}ms`));
146
+ console.log(chalk.gray(`Tokens: ${result.tokens_generated}`));
147
+ console.log("");
148
+ console.log(chalk.cyan("Predicted content:"));
149
+ console.log(result.predicted_content.slice(0, 500));
150
+ if (result.predicted_content.length > 500) {
151
+ console.log(chalk.gray("... (truncated)"));
365
152
  }
153
+ } else if (result.success) {
154
+ console.log(chalk.yellow("No changes predicted"));
366
155
  } else {
367
- const hooks = { "post-tool-use": hookDest };
368
- writeFileSync(hooksJsonPath, JSON.stringify(hooks, null, 2));
156
+ console.log(chalk.red("Prediction failed:"), result.message);
369
157
  }
370
- spinner.succeed(chalk.green("Sweep hook installed"));
371
- console.log(chalk.gray(`Hook: ${hookDest}`));
372
- console.log(chalk.gray(`Config: ${hooksJsonPath}`));
373
- console.log("");
374
- console.log(chalk.bold("Usage:"));
375
- console.log(" Hook runs automatically after Edit/Write operations");
376
- console.log(" Predictions appear after 2+ edits in session");
377
- console.log(" Disable: export SWEEP_ENABLED=false");
378
158
  } catch (error) {
379
- spinner.fail(chalk.red("Installation failed"));
380
- console.log(chalk.gray(error.message));
159
+ spinner.fail(chalk.red("Prediction failed"));
160
+ console.error(chalk.red(error.message));
381
161
  process.exit(1);
382
162
  }
383
163
  });
384
- hookCmd.command("status").description("Check hook installation status").action(async () => {
385
- const homeDir = process.env.HOME || "";
386
- const hookPath = join(homeDir, ".claude", "hooks", "post-edit-sweep.js");
387
- const hooksJsonPath = join(homeDir, ".claude", "hooks.json");
388
- const statePath = join(homeDir, ".stackmemory", "sweep-state.json");
389
- console.log(chalk.bold("\nSweep Hook Status\n"));
390
- const hookInstalled = existsSync(hookPath);
391
- console.log(
392
- `Hook installed: ${hookInstalled ? chalk.green("Yes") : chalk.yellow("No")}`
164
+ cmd.command("hook").description("Check Claude Code hook status").action(async () => {
165
+ const hookPath = join(HOME, ".claude", "hooks", "post-edit-sweep.js");
166
+ const templatePath = join(
167
+ process.cwd(),
168
+ "templates",
169
+ "claude-hooks",
170
+ "post-edit-sweep.js"
393
171
  );
394
- if (existsSync(hooksJsonPath)) {
395
- const hooks = JSON.parse(readFileSync(hooksJsonPath, "utf-8"));
396
- const configured = hooks["post-tool-use"] && hooks["post-tool-use"].includes("sweep");
397
- console.log(
398
- `Hook configured: ${configured ? chalk.green("Yes") : chalk.yellow("No")}`
399
- );
172
+ console.log(chalk.cyan("Sweep Hook Status"));
173
+ console.log("");
174
+ if (existsSync(hookPath)) {
175
+ console.log(chalk.green("Hook installed:"), hookPath);
400
176
  } else {
401
- console.log(`Hook configured: ${chalk.yellow("No hooks.json")}`);
177
+ console.log(chalk.yellow("Hook not installed"));
178
+ console.log(chalk.gray(` Copy from: ${templatePath}`));
179
+ console.log(chalk.gray(` To: ${hookPath}`));
402
180
  }
403
- const enabled = process.env.SWEEP_ENABLED !== "false";
404
- console.log(
405
- `Enabled: ${enabled ? chalk.green("Yes") : chalk.yellow("Disabled (SWEEP_ENABLED=false)")}`
406
- );
181
+ const settingsPath = join(HOME, ".claude", "settings.json");
182
+ if (existsSync(settingsPath)) {
183
+ try {
184
+ const { readFileSync } = await import("fs");
185
+ const settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
186
+ const hasHook = JSON.stringify(settings).includes("post-edit-sweep");
187
+ if (hasHook) {
188
+ console.log(chalk.green("Hook registered in settings.json"));
189
+ } else {
190
+ console.log(chalk.yellow("Hook not registered in settings.json"));
191
+ console.log(
192
+ chalk.gray(
193
+ ' Add to hooks.PostToolUse:\n { "matcher": "Edit", "hooks": [{ "type": "command", "command": "node ~/.claude/hooks/post-edit-sweep.js" }] }'
194
+ )
195
+ );
196
+ }
197
+ } catch {
198
+ console.log(chalk.yellow("Could not read settings.json"));
199
+ }
200
+ } else {
201
+ console.log(chalk.yellow("settings.json not found"));
202
+ }
203
+ const statePath = join(HOME, ".stackmemory", "sweep-state.json");
407
204
  if (existsSync(statePath)) {
408
205
  try {
206
+ const { readFileSync } = await import("fs");
409
207
  const state = JSON.parse(readFileSync(statePath, "utf-8"));
410
208
  console.log(
411
209
  chalk.gray(
412
- `
413
- Recent diffs tracked: ${state.recentDiffs?.length || 0}`
210
+ ` Recent diffs tracked: ${state.recentDiffs?.length || 0}`
414
211
  )
415
212
  );
416
213
  if (state.lastPrediction) {
417
- const age = Date.now() - state.lastPrediction.timestamp;
418
- const ageStr = age < 6e4 ? `${Math.round(age / 1e3)}s ago` : `${Math.round(age / 6e4)}m ago`;
419
- console.log(chalk.gray(`Last prediction: ${ageStr}`));
214
+ const ago = Math.floor(
215
+ (Date.now() - state.lastPrediction.timestamp) / 1e3
216
+ );
217
+ console.log(
218
+ chalk.gray(` Last prediction: ${formatUptime(ago)} ago`)
219
+ );
420
220
  }
421
221
  } catch {
422
222
  }
423
223
  }
424
- if (!hookInstalled) {
425
- console.log(chalk.bold("\nTo install: stackmemory sweep hook install"));
426
- }
427
- });
428
- hookCmd.command("disable").description("Disable the Sweep hook").action(() => {
429
- console.log(chalk.bold("\nTo disable Sweep predictions:\n"));
430
- console.log(" Temporarily: export SWEEP_ENABLED=false");
431
- console.log(" Permanently: Add to ~/.zshrc or ~/.bashrc");
432
- console.log("");
433
- console.log("Or remove the hook:");
434
- console.log(" rm ~/.claude/hooks/post-edit-sweep.js");
435
- });
436
- hookCmd.command("clear").description("Clear hook state (recent diffs and predictions)").action(() => {
437
- const homeDir = process.env.HOME || "";
438
- const statePath = join(homeDir, ".stackmemory", "sweep-state.json");
439
- if (existsSync(statePath)) {
440
- writeFileSync(
441
- statePath,
442
- JSON.stringify(
443
- {
444
- recentDiffs: [],
445
- lastPrediction: null,
446
- pendingPrediction: null,
447
- fileContents: {}
448
- },
449
- null,
450
- 2
451
- )
452
- );
453
- console.log(chalk.green("Sweep state cleared"));
454
- } else {
455
- console.log(chalk.gray("No state file found"));
456
- }
457
224
  });
458
- cmd.action(async () => {
459
- const status = await checkSweepStatus();
460
- console.log(chalk.bold("\nSweep 1.5B Addon Status\n"));
461
- console.log(
462
- `Installed: ${status.installed ? chalk.green("Yes") : chalk.yellow("No")}`
463
- );
464
- console.log(
465
- `Model ready: ${status.model_downloaded ? chalk.green("Yes") : chalk.yellow("No")}`
466
- );
467
- if (!status.installed) {
468
- console.log(chalk.bold("\nRun: stackmemory sweep setup"));
469
- } else {
470
- console.log(chalk.bold("\nUsage: stackmemory sweep predict <file>"));
225
+ cmd.command("wrap").description("Launch Claude Code with Sweep prediction status bar").option("--claude-bin <path>", "Path to claude binary").allowUnknownOption(true).action(async (options, command) => {
226
+ try {
227
+ const claudeArgs = command.args || [];
228
+ await launchWrapper({
229
+ claudeBin: options.claudeBin,
230
+ claudeArgs
231
+ });
232
+ } catch (error) {
233
+ console.error(chalk.red(error.message));
234
+ process.exit(1);
471
235
  }
472
236
  });
473
237
  return cmd;
474
238
  }
475
- var sweep_default = createSweepCommand();
239
+ function formatUptime(seconds) {
240
+ if (seconds < 60) return `${seconds}s`;
241
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
242
+ const hours = Math.floor(seconds / 3600);
243
+ const mins = Math.floor(seconds % 3600 / 60);
244
+ return `${hours}h ${mins}m`;
245
+ }
476
246
  export {
477
- createSweepCommand,
478
- sweep_default as default
247
+ createSweepCommand
479
248
  };
480
249
  //# sourceMappingURL=sweep.js.map