project-cypher 0.9.0 → 0.11.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 +12 -8
  2. package/package.json +1 -1
package/bin/cypher.js CHANGED
@@ -41,7 +41,7 @@ function ensureRoot(workspace) {
41
41
  const c = rawCfg(); const ws = path.resolve(workspace || c.workspace || path.join(os.homedir(), 'cypher-projects'));
42
42
  fs.mkdirSync(ROOT_DIR, { recursive: true }); fs.mkdirSync(ws, { recursive: true });
43
43
  if (!fs.existsSync(REG)) writeRegistry([]);
44
- if (!fs.existsSync(ROOT_MD)) fs.writeFileSync(ROOT_MD, ROOT_TEMPLATE(os.hostname(), ws));
44
+ if (!fs.existsSync(ROOT_MD)) fs.writeFileSync(ROOT_MD, ROOT_TEMPLATE(devName(), ws));
45
45
  else { const t = fs.readFileSync(ROOT_MD, 'utf8'); const n = t.replace(/^Workspace: .*$/m, `Workspace: ${ws} (new projects are created inside it)`); if (n !== t) fs.writeFileSync(ROOT_MD, n); }
46
46
  if (c.workspace !== ws) { c.workspace = ws; save(c); }
47
47
  return ws;
@@ -51,11 +51,12 @@ const slug = p => path.basename(p).toLowerCase().replace(/[^a-z0-9]+/g, '-').rep
51
51
  const httpOf = s => s.replace(/\/$/, ''), wsOf = s => httpOf(s).replace(/^http/, 'ws');
52
52
 
53
53
  const ask = q => new Promise(r => { const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout }); rl.question(q, a => { rl.close(); r(a.trim()); }); });
54
+ const devName = () => rawCfg().name || os.hostname();
54
55
  const yes = (a, d) => (a === '' ? d : /^y/i.test(a));
55
56
  const has = (cmd, args = ['--version']) => { const r = require('child_process').spawnSync(cmd, args, { encoding: 'utf8' }); return r.error ? null : (r.stdout || r.stderr || '').trim().split('\n')[0]; };
56
57
  async function doLink(server) {
57
58
  const c = load(); const base = httpOf(server || c.server);
58
- const r = await fetch(base + '/link/start', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: os.hostname() }) }).catch(() => null);
59
+ const r = await fetch(base + '/link/start', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: devName() }) }).catch(() => null);
59
60
  if (!r || !r.ok) { console.log('cannot reach ' + base); process.exit(1); }
60
61
  const { code, linkId } = await r.json();
61
62
  console.log('\nOpen Project Cypher on your phone and scan this, or type the code.\n');
@@ -98,8 +99,9 @@ const cmds = {
98
99
  const h = await fetch(base + '/health').then(r => r.json()).catch(() => null);
99
100
  if (!h?.ok) { console.log(`[!!] Cannot reach the server ${base}. Check your internet (outbound port may be blocked).`); process.exit(1); }
100
101
  console.log(`[ok] Server ${base}`);
101
- if (c.token) { const again = await ask('This computer is already linked. Link again? [y/N] '); if (!yes(again, false)) { console.log('Keeping the existing link.'); } else await doLink(base); }
102
- else await doLink(base);
102
+ const askName = async () => { let n = ''; while (!n) n = (await ask(`What should this computer be called in the app? [${devName()}] `)) || devName(); const k = load(); k.name = n.slice(0, 40); save(k); };
103
+ if (c.token) { const again = await ask('This computer is already linked. Link again? [y/N] '); if (!yes(again, false)) { console.log('Keeping the existing link.'); } else { await askName(); await doLink(base); } }
104
+ else { await askName(); await doLink(base); }
103
105
  // Workspace = where Root creates new projects. Default: the folder you ran init in.
104
106
  const cur = rawCfg().workspace; const cwd = process.cwd();
105
107
  let ws = cur || cwd;
@@ -114,7 +116,7 @@ const cmds = {
114
116
  async link(server) { await doLink(server); cmds.add('.'); cmds.connect(); },
115
117
  async _unused(server) {
116
118
  const c = load(); const base = httpOf(server || c.server);
117
- const r = await fetch(base + '/link/start', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: os.hostname() }) }).catch(() => null);
119
+ const r = await fetch(base + '/link/start', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: devName() }) }).catch(() => null);
118
120
  if (!r || !r.ok) return console.log('cannot reach ' + base);
119
121
  const { code, linkId } = await r.json();
120
122
  console.log('\nOpen Project Cypher on your phone and scan this, or type the code.\n');
@@ -132,7 +134,7 @@ const cmds = {
132
134
  },
133
135
  async pair(server, code) {
134
136
  if (!server || !code) return console.log('usage: cypher pair <server-url> <code> (get the code in the app: Projects > Add computer)');
135
- const r = await fetch(httpOf(server) + '/pair', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ code, name: os.hostname() }) });
137
+ const r = await fetch(httpOf(server) + '/pair', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ code, name: devName() }) });
136
138
  const j = await r.json().catch(() => ({})); if (!r.ok) return console.log('pairing failed: ' + (j.error || r.status));
137
139
  const c = load(); c.server = httpOf(server); c.token = j.token; c.machineId = j.machineId; save(c); console.log('paired. Next: cypher add <folder>, then cypher connect');
138
140
  },
@@ -159,10 +161,10 @@ const cmds = {
159
161
  c = { ...c, ...load() };
160
162
  const ws = new WebSocket(`${wsOf(c.server)}/agent?token=${encodeURIComponent(c.token)}`);
161
163
  const tx = m => ws.readyState === 1 && ws.send(JSON.stringify(m));
162
- ws.on('open', () => { ws.opened = true; retry = 1000; tx({ type: 'hello', name: os.hostname(), projects: announce() }); console.log(`connected as ${os.hostname()} with Root + ${readRegistry().length} project(s). Ctrl+C to stop.`); });
164
+ ws.on('open', () => { ws.opened = true; retry = 1000; tx({ type: 'hello', name: devName(), projects: announce() }); console.log(`connected as ${devName()} with Root + ${readRegistry().length} project(s). Ctrl+C to stop.`); });
163
165
  // pick up `cypher add` / `remove` while running: re-announce projects when the config changes
164
166
  let sig = JSON.stringify(announce());
165
- const watch = setInterval(() => { const a = announce(), n = JSON.stringify(a); if (n !== sig) { sig = n; tx({ type: 'hello', name: os.hostname(), projects: a }); console.log(`projects updated: ${a.length - 1}`); } }, 2000);
167
+ const watch = setInterval(() => { const a = announce(), n = JSON.stringify(a); if (n !== sig) { sig = n; tx({ type: 'hello', name: devName(), projects: a }); console.log(`projects updated: ${a.length - 1}`); } }, 2000);
166
168
  ws.on('close', () => clearInterval(watch));
167
169
  ws.on('unexpected-response', (req, res) => { if (res.statusCode === 401) { dropToken('This computer is not linked (removed from the app). Run cypher init to link again.'); process.exit(0); } else { req.destroy(); } });
168
170
  ws.on('message', raw => {
@@ -170,6 +172,8 @@ const cmds = {
170
172
  if (m.type === 'revoked') { runs.forEach(r => r.kill('SIGINT')); dropToken('Disconnected from the app. Run cypher init to link again.'); process.exit(0); }
171
173
  const proj = m.projectId === 'root' ? { id: 'root', path: ROOT_DIR, root: true } : readRegistry().find(p => p.id === m.projectId); const ctx = { reqId: m.reqId, projectId: m.projectId };
172
174
  if (m.type === 'stop') return runs.get(m.projectId)?.kill('SIGINT');
175
+ if (m.type === 'rmproject') { if (m.projectId !== 'root') writeRegistry(readRegistry().filter(x => x.id !== m.projectId)); return; }
176
+ if (m.type === 'reset') { const k = load(); if (k.sessions) { delete k.sessions[m.projectId]; save(k); } return; }
173
177
  if (m.type !== 'prompt') return;
174
178
  if (!proj) return tx({ ...ctx, type: 'error', message: 'unknown project' });
175
179
  if (runs.has(proj.id)) return tx({ ...ctx, type: 'error', message: 'Claude is still working on this project.' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-cypher",
3
- "version": "0.9.0",
3
+ "version": "0.11.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"