claude-cup 0.2.2 → 0.2.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.
@@ -4,13 +4,15 @@
4
4
  // It supports both long-lived stdio MCP server mode and short-lived --hook mode.
5
5
  // The source path is baked in at build time so the launcher works from any directory.
6
6
 
7
- const SRC = 'C:/Users/itaib/nice claude iedeas/Claude-/claude-jar/mcp-server/src';
7
+ import { pathToFileURL } from 'node:url';
8
+ const SRC = "C:\\Users\\itaib\\nice claude iedeas\\Claude-\\claude-jar\\mcp-server\\src";
9
+ const toURL = f => pathToFileURL(f).href;
8
10
  const isHook = process.argv.includes('--hook');
9
11
 
10
12
  if (isHook) {
11
- const { runHookIngest } = await import('file:///' + SRC + '/hook-ingest.js');
13
+ const { runHookIngest } = await import(toURL(SRC + '/hook-ingest.js'));
12
14
  await runHookIngest(process.argv);
13
15
  } else {
14
- const { startMcpServer } = await import('file:///' + SRC + '/index.js');
16
+ const { startMcpServer } = await import(toURL(SRC + '/index.js'));
15
17
  await startMcpServer();
16
18
  }
@@ -75,13 +75,24 @@ export async function runHookIngest(argv = process.argv) {
75
75
  const pjIdx = argv.indexOf('--payload-json');
76
76
  if (pjIdx !== -1 && argv[pjIdx + 1]) {
77
77
  try { payload = JSON.parse(argv[pjIdx + 1]); } catch { /* ignore */ }
78
- } else {
79
- // read stdin
80
- let raw = '';
81
- for await (const chunk of process.stdin) raw += chunk;
82
- if (raw.trim()) {
83
- try { payload = JSON.parse(raw); } catch { /* ignore */ }
84
- }
78
+ } else if (!process.stdin.isTTY) {
79
+ // Read stdin with a timeout so the hook doesn't hang if Claude Code
80
+ // closes the pipe slowly or sends an empty payload.
81
+ try {
82
+ const chunks = [];
83
+ const timeout = new Promise(r => setTimeout(r, 2000));
84
+ const read = new Promise(resolve => {
85
+ process.stdin.on('data', d => chunks.push(d));
86
+ process.stdin.on('end', resolve);
87
+ process.stdin.on('error', resolve);
88
+ });
89
+ await Promise.race([read, timeout]);
90
+ process.stdin.removeAllListeners();
91
+ const raw = Buffer.concat(chunks).toString('utf8');
92
+ if (raw.trim()) {
93
+ try { payload = JSON.parse(raw); } catch { /* ignore */ }
94
+ }
95
+ } catch { /* ignore stdin errors */ }
85
96
  }
86
97
 
87
98
  const configDir = process.env.CLAUDE_CONFIG_DIR || undefined;
@@ -28,9 +28,9 @@ function deepMergeOnlyOurKeys(target, our) {
28
28
  result.hooks = { ...(target.hooks || {}) };
29
29
  for (const [k, arr] of Object.entries(our.hooks)) {
30
30
  const existing = Array.isArray(result.hooks[k]) ? result.hooks[k] : [];
31
- const seen = new Set(existing.map(e => JSON.stringify({ c: e.command, a: e.args })));
31
+ const seen = new Set(existing.map(e => JSON.stringify(e)));
32
32
  for (const item of arr) {
33
- const key = JSON.stringify({ c: item.command, a: item.args });
33
+ const key = JSON.stringify(item);
34
34
  if (!seen.has(key)) {
35
35
  existing.push(item);
36
36
  seen.add(key);
@@ -68,9 +68,9 @@ export function registerClaudeCode(configDir) {
68
68
  },
69
69
  },
70
70
  hooks: {
71
- SessionStart: [{ command: 'node', args: [launcher, '--hook', 'SessionStart'] }],
72
- PreToolUse: [{ command: 'node', args: [launcher, '--hook', 'PreToolUse'] }],
73
- PostToolUse: [{ command: 'node', args: [launcher, '--hook', 'PostToolUse'] }],
71
+ SessionStart: [{ matcher: '', hooks: [{ type: 'command', command: `node "${launcher}" --hook SessionStart` }] }],
72
+ PreToolUse: [{ matcher: '', hooks: [{ type: 'command', command: `node "${launcher}" --hook PreToolUse` }] }],
73
+ PostToolUse: [{ matcher: '', hooks: [{ type: 'command', command: `node "${launcher}" --hook PostToolUse` }] }],
74
74
  },
75
75
  };
76
76
 
@@ -78,7 +78,7 @@ export function registerClaudeCode(configDir) {
78
78
  let backup = null;
79
79
  try {
80
80
  if (existsSync(settingsPath)) {
81
- const raw = readFileSync(settingsPath, 'utf8');
81
+ const raw = readFileSync(settingsPath, 'utf8').replace(/^\uFEFF/, '');
82
82
  if (raw.trim()) original = JSON.parse(raw);
83
83
  }
84
84
  } catch (e) {
@@ -117,7 +117,7 @@ export function disableClaudeCode(configDir) {
117
117
 
118
118
  let settings = {};
119
119
  if (existsSync(settingsPath)) {
120
- try { settings = JSON.parse(readFileSync(settingsPath, 'utf8')); } catch { return { ok: false, error: 'corrupt settings.json', didWrite: false, backupPath: null, launcherPath: launcher }; }
120
+ try { settings = JSON.parse(readFileSync(settingsPath, 'utf8').replace(/^\uFEFF/, '')); } catch { return { ok: false, error: 'corrupt settings.json', didWrite: false, backupPath: null, launcherPath: launcher }; }
121
121
  }
122
122
 
123
123
  let changed = false;
@@ -129,10 +129,10 @@ export function disableClaudeCode(configDir) {
129
129
  if (settings.hooks) {
130
130
  const launcherBase = 'mcp-server.mjs';
131
131
  for (const k of Object.keys(settings.hooks)) {
132
- settings.hooks[k] = (settings.hooks[k] || []).filter(h => {
133
- if (!h || !h.args) return true;
134
- const argsStr = JSON.stringify(h.args);
135
- return !argsStr.includes(launcherBase);
132
+ settings.hooks[k] = (settings.hooks[k] || []).filter(rule => {
133
+ if (!rule) return true;
134
+ const ruleStr = JSON.stringify(rule);
135
+ return !ruleStr.includes(launcherBase);
136
136
  });
137
137
  if (settings.hooks[k].length === 0) delete settings.hooks[k];
138
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-cup",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Claude Jar v2 — native desktop visual companion (Tauri + Svelte) with MCP/hook integration for live Claude activity. Beautiful accumulating jar + live intensity meter. The jar is the usage meter.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,14 +24,16 @@ const launcher = `#!/usr/bin/env node
24
24
  // It supports both long-lived stdio MCP server mode and short-lived --hook mode.
25
25
  // The source path is baked in at build time so the launcher works from any directory.
26
26
 
27
- const SRC = '${srcAbsolute}';
27
+ import { pathToFileURL } from 'node:url';
28
+ const SRC = ${JSON.stringify(mcpSrc)};
29
+ const toURL = f => pathToFileURL(f).href;
28
30
  const isHook = process.argv.includes('--hook');
29
31
 
30
32
  if (isHook) {
31
- const { runHookIngest } = await import('file:///' + SRC + '/hook-ingest.js');
33
+ const { runHookIngest } = await import(toURL(SRC + '/hook-ingest.js'));
32
34
  await runHookIngest(process.argv);
33
35
  } else {
34
- const { startMcpServer } = await import('file:///' + SRC + '/index.js');
36
+ const { startMcpServer } = await import(toURL(SRC + '/index.js'));
35
37
  await startMcpServer();
36
38
  }
37
39
  `;