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.
- package/bin/cypher.js +20 -4
- 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
|
|
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 ||
|
|
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
|
-
|
|
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.
|
|
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"
|
|
16
|
+
"ws": "^8.18.0"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"claude",
|