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.
- package/bin/cypher.js +12 -8
- 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(
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.' });
|