project-cypher 0.13.0 → 0.15.0

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 (2) hide show
  1. package/bin/cypher.js +20 -4
  2. package/package.json +2 -2
package/bin/cypher.js CHANGED
@@ -11,7 +11,8 @@ function readRegistry() {
11
11
  .map(p => ({ id: p.id, name: String(p.name || p.id).slice(0, 60), path: p.path, summary: String(p.summary || '').slice(0, 300) }));
12
12
  }
13
13
  const writeRegistry = list => { fs.mkdirSync(ROOT_DIR, { recursive: true }); fs.writeFileSync(REG, JSON.stringify(list, null, 2)); };
14
- const rawCfg = () => { try { return JSON.parse(fs.readFileSync(CFG, 'utf8')); } catch { return { server: 'http://140.238.248.30:3300', token: '', sessions: {}, permissionMode: 'acceptEdits' }; } };
14
+ const SERVER = 'https://hub.reubenreny.in/cypher', OLD_SERVERS = ['http://140.238.248.30:3300'];
15
+ const rawCfg = () => { try { const c = JSON.parse(fs.readFileSync(CFG, 'utf8')); if (!c.server || OLD_SERVERS.includes(String(c.server).replace(/\/$/, ''))) c.server = SERVER; return c; } catch { return { server: SERVER, token: '', sessions: {}, permissionMode: 'acceptEdits' }; } };
15
16
  const load = () => { const c = rawCfg(); if (c.projects?.length && !fs.existsSync(REG)) writeRegistry(c.projects); delete c.projects; return { ...c, projects: readRegistry() }; };
16
17
  const save = c => { const { projects, ...rest } = c; fs.mkdirSync(CFG_DIR, { recursive: true }); fs.writeFileSync(CFG, JSON.stringify(rest, null, 2), { mode: 0o600 }); };
17
18
  const ROOT_TEMPLATE = (machine, ws) => `# Root
@@ -199,7 +200,7 @@ const cmds = {
199
200
  console.log(`This fully removes Cypher from this computer:\n - unlinks it from your phone app\n - stops and deletes the background service\n - deletes ${CFG_DIR} (login, project list, Root notes, chat sessions)\nYour actual project folders are NOT touched.`);
200
201
  if (id !== '--yes' && !rest.includes('--yes') && !yes(await ask('Continue? [y/N] '), false)) return console.log('cancelled.');
201
202
  const c = rawCfg();
202
- if (c.token) await fetch(httpOf(c.server || 'http://140.238.248.30:3300') + '/unlink', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ token: c.token }) }).then(() => console.log('[ok] unlinked from the app')).catch(() => console.log('[!!] could not reach the server; remove this computer from the app Projects list'));
203
+ if (c.token) await fetch(httpOf(c.server || SERVER) + '/unlink', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ token: c.token }) }).then(() => console.log('[ok] unlinked from the app')).catch(() => console.log('[!!] could not reach the server; remove this computer from the app Projects list'));
203
204
  SVC.remove(); console.log('[ok] background service removed');
204
205
  fs.rmSync(CFG_DIR, { recursive: true, force: true }); console.log('[ok] deleted ' + CFG_DIR);
205
206
  console.log('\nClean. To reinstall: npm i -g project-cypher@latest && cypher init\nTo also remove the program itself: npm uninstall -g project-cypher');
@@ -242,11 +243,26 @@ const cmds = {
242
243
  if (m.type !== 'prompt') return;
243
244
  if (!proj) return tx({ ...ctx, type: 'error', message: 'unknown project' });
244
245
  if (runs.has(proj.id)) return tx({ ...ctx, type: 'error', message: 'Claude is still working on this project.' });
245
- const cfg = load(); const args = ['-p', m.text, '--output-format', 'stream-json', '--verbose', '--permission-mode', cfg.permissionMode || 'acceptEdits'];
246
+ // Attachments from the phone: saved under ~/.cypher/uploads/<project>/, then referenced in the prompt so Claude can Read them.
247
+ let text = String(m.text || ''); const saved = []; const upDir = path.join(CFG_DIR, 'uploads', proj.id);
248
+ if (Array.isArray(m.files) && m.files.length) {
249
+ try {
250
+ fs.mkdirSync(upDir, { recursive: true });
251
+ for (const f of m.files.slice(0, 6)) {
252
+ if (!f || typeof f.data !== 'string' || f.data.length > 16 * 1024 * 1024) continue;
253
+ const nm = String(f.name || 'file').replace(/[^\w.\- ]+/g, '_').slice(-80) || 'file'; const fp = path.join(upDir, Date.now() + '-' + nm);
254
+ fs.writeFileSync(fp, Buffer.from(f.data, 'base64')); saved.push(fp);
255
+ }
256
+ } catch (e) { console.log('attachment save failed: ' + e.message); }
257
+ if (saved.length) text += '\n\n[The user attached ' + saved.length + ' file(s) from their phone. Read them with the Read tool:]\n' + saved.map(x => '- ' + x).join('\n');
258
+ }
259
+ try { for (const d of fs.readdirSync(path.join(CFG_DIR, 'uploads'))) for (const f of fs.readdirSync(path.join(CFG_DIR, 'uploads', d))) { const fp = path.join(CFG_DIR, 'uploads', d, f); if (Date.now() - fs.statSync(fp).mtimeMs > 7 * 864e5) fs.rmSync(fp, { force: true }); } } catch {}
260
+ const cfg = load(); const args = ['-p', text, '--output-format', 'stream-json', '--verbose', '--permission-mode', cfg.permissionMode || 'acceptEdits'];
246
261
  if (proj.root) { const wsd = rawCfg().workspace; args.push('--allowedTools', ROOT_TOOLS); if (wsd) args.push('--add-dir', wsd); }
262
+ if (saved.length) args.push('--add-dir', upDir);
247
263
  if (typeof m.model === 'string' && /^[a-z][a-z0-9.\-\[\]]{0,60}$/i.test(m.model) && m.model !== 'default') args.push('--model', m.model);
248
264
  if (cfg.sessions?.[proj.id]) args.push('--resume', cfg.sessions[proj.id]);
249
- console.log(`[${proj.id}] ${m.text.slice(0, 60)}`);
265
+ console.log(`[${proj.id}] ${String(m.text || '').slice(0, 60)}${saved.length ? ' (+' + saved.length + ' files)' : ''}`);
250
266
  const ch = spawn('claude', args, { cwd: proj.path, stdio: ['ignore', 'pipe', 'pipe'] }); runs.set(proj.id, ch);
251
267
  let buf = '', err = '';
252
268
  const line = l => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-cypher",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Connect your machine's Claude Code projects to the Project Cypher phone app.",
5
5
  "bin": {
6
6
  "cypher": "bin/cypher.js"
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "dependencies": {
15
15
  "qrcode-terminal": "^0.12.0",
16
- "ws": "^8.18.0", "qrcode-terminal": "^0.12.0"
16
+ "ws": "^8.18.0"
17
17
  },
18
18
  "keywords": [
19
19
  "claude",