@1presence/bridge 0.40.0 → 0.43.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/dist/accumulator.js +2 -6
- package/dist/auth.js +16 -23
- package/dist/claude.js +447 -491
- package/dist/config.js +6 -10
- package/dist/index.js +68 -62
- package/dist/outbox.js +13 -18
- package/dist/timer.js +3 -8
- package/dist/update.js +8 -8
- package/package.json +3 -1
package/dist/config.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.ensureModelChoice = ensureModelChoice;
|
|
4
|
-
exports.getBridgeModel = getBridgeModel;
|
|
5
|
-
const readline_1 = require("readline");
|
|
6
|
-
const child_process_1 = require("child_process");
|
|
1
|
+
import { emitKeypressEvents } from 'readline';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
7
3
|
// ─── In-memory model choice ───────────────────────────────────────────────────
|
|
8
4
|
//
|
|
9
5
|
// The bridge prompts for a model on every interactive startup. The choice is
|
|
@@ -27,7 +23,7 @@ function detectClaudeDefaultModel() {
|
|
|
27
23
|
} };
|
|
28
24
|
let proc;
|
|
29
25
|
try {
|
|
30
|
-
proc =
|
|
26
|
+
proc = spawn('claude', [
|
|
31
27
|
'-p',
|
|
32
28
|
'--input-format', 'stream-json',
|
|
33
29
|
'--output-format', 'stream-json',
|
|
@@ -111,7 +107,7 @@ function promptForModel(defaultModel) {
|
|
|
111
107
|
}
|
|
112
108
|
};
|
|
113
109
|
render();
|
|
114
|
-
|
|
110
|
+
emitKeypressEvents(process.stdin);
|
|
115
111
|
const wasRaw = process.stdin.isRaw;
|
|
116
112
|
if (process.stdin.isTTY)
|
|
117
113
|
process.stdin.setRawMode(true);
|
|
@@ -172,7 +168,7 @@ function promptForModel(defaultModel) {
|
|
|
172
168
|
* in memory only — every startup re-prompts. In a non-TTY environment the
|
|
173
169
|
* prompt is skipped and Claude Code's own default is used.
|
|
174
170
|
*/
|
|
175
|
-
async function ensureModelChoice() {
|
|
171
|
+
export async function ensureModelChoice() {
|
|
176
172
|
if (!process.stdin.isTTY) {
|
|
177
173
|
selectedModel = null;
|
|
178
174
|
return;
|
|
@@ -187,6 +183,6 @@ async function ensureModelChoice() {
|
|
|
187
183
|
}
|
|
188
184
|
}
|
|
189
185
|
/** Returns the model id chosen for this session, or null to defer to Claude Code's own default. */
|
|
190
|
-
function getBridgeModel() {
|
|
186
|
+
export function getBridgeModel() {
|
|
191
187
|
return selectedModel;
|
|
192
188
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
const
|
|
2
|
+
import WebSocket from 'ws';
|
|
3
|
+
import { writeFileSync, chmodSync, existsSync, statSync, readdirSync } from 'fs';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { join, dirname } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { createRequire } from 'module';
|
|
8
|
+
import { getValidAuth, ensureFreshToken, isTokenValid, AuthCancelledError } from './auth.js';
|
|
9
|
+
import { spawnClaude, killAll, cancelConversation, setVerbose, setDebug, paint, SECTION_COLORS } from './claude.js';
|
|
10
|
+
import { ensureModelChoice } from './config.js';
|
|
11
|
+
import { checkAndUpdate } from './update.js';
|
|
12
|
+
import { makeBridgeAccumulator, postSaveTurn } from './accumulator.js';
|
|
13
|
+
import { writeSpool, deleteSpool, listSpool } from './outbox.js';
|
|
14
|
+
import { startTurnTimer, stopTurnTimer, formatElapsed } from './timer.js';
|
|
15
|
+
// ESM has no __dirname; derive it. JSON version is read via createRequire to
|
|
16
|
+
// avoid version-sensitive import assertions on a published CLI bin.
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const { version } = createRequire(import.meta.url)('../package.json');
|
|
19
19
|
// Published tarballs don't ship src/, so this fires only when running the
|
|
20
20
|
// dist build from a live workspace checkout. Catches the trap where editing
|
|
21
21
|
// src/ without re-running tsc leaves you executing stale dist code — banner
|
|
22
22
|
// version matches package.json but behavior doesn't match the source.
|
|
23
23
|
if (__dirname.endsWith('dist')) {
|
|
24
|
-
const srcDir =
|
|
25
|
-
if (
|
|
26
|
-
const newest = (dir) => Math.max(...
|
|
24
|
+
const srcDir = join(__dirname, '..', 'src');
|
|
25
|
+
if (existsSync(srcDir)) {
|
|
26
|
+
const newest = (dir) => Math.max(...readdirSync(dir).map(f => statSync(join(dir, f)).mtimeMs));
|
|
27
27
|
if (newest(srcDir) > newest(__dirname)) {
|
|
28
28
|
console.error('Bridge dist is stale (src/ has been edited since last build). Run: npm run build');
|
|
29
29
|
process.exit(1);
|
|
@@ -157,7 +157,7 @@ async function fetchSystemPrompt(token, agentSlug) {
|
|
|
157
157
|
}
|
|
158
158
|
// ─── Setup files ──────────────────────────────────────────────────────────────
|
|
159
159
|
function tmpFile(name) {
|
|
160
|
-
return
|
|
160
|
+
return join(tmpdir(), name);
|
|
161
161
|
}
|
|
162
162
|
// Fetch the system prompt and write it to /tmp/agent-${uid}.md. The hosted
|
|
163
163
|
// runtime rebuilds buildSystemBlocks() per turn (dynamic context: vault state,
|
|
@@ -169,9 +169,9 @@ async function writeSystemPrompt(auth, agentSlug) {
|
|
|
169
169
|
const systemPrompt = await fetchSystemPrompt(token, agentSlug);
|
|
170
170
|
writeRestricted(tmpFile(`agent-${uid}.md`), systemPrompt);
|
|
171
171
|
if (VERBOSE) {
|
|
172
|
-
console.log(
|
|
173
|
-
console.log(
|
|
174
|
-
console.log(
|
|
172
|
+
console.log(paint(SECTION_COLORS.system, '\n[bridge:verbose] ─── system prompt ───────────────────────'));
|
|
173
|
+
console.log(paint(SECTION_COLORS.system, systemPrompt));
|
|
174
|
+
console.log(paint(SECTION_COLORS.system, '[bridge:verbose] ─── end system prompt ───────────────────\n'));
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
function writeMcpConfig(auth) {
|
|
@@ -182,6 +182,12 @@ function writeMcpConfig(auth) {
|
|
|
182
182
|
type: 'sse',
|
|
183
183
|
url: `${GATEWAY_HTTP}/mcp`,
|
|
184
184
|
headers: { Authorization: `Bearer ${token}` },
|
|
185
|
+
// Force every 1Presence tool into the prompt from turn 1. Without this the
|
|
186
|
+
// SDK defers MCP tools behind tool-search, so the model sees tool *names*
|
|
187
|
+
// (from the system prompt) but has nothing callable and confabulates the
|
|
188
|
+
// call + result as text. Also blocks the turn until the MCP server connects
|
|
189
|
+
// (5s cap) — a loud failure beats a silent tool-less run. See vault/Bugs.md.
|
|
190
|
+
alwaysLoad: true,
|
|
185
191
|
},
|
|
186
192
|
},
|
|
187
193
|
};
|
|
@@ -195,8 +201,8 @@ async function writeSetupFiles(auth, agentSlug) {
|
|
|
195
201
|
// state. writeFileSync's mode only takes effect on file creation — chmodSync
|
|
196
202
|
// covers the overwrite case so a legacy 0644 file gets tightened on next run.
|
|
197
203
|
function writeRestricted(path, data) {
|
|
198
|
-
|
|
199
|
-
|
|
204
|
+
writeFileSync(path, data, { mode: 0o600 });
|
|
205
|
+
chmodSync(path, 0o600);
|
|
200
206
|
}
|
|
201
207
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
202
208
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -208,7 +214,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
208
214
|
// Refresh JWT if <10 min remaining before spawning Claude
|
|
209
215
|
let activeAuth = auth;
|
|
210
216
|
try {
|
|
211
|
-
const freshAuth = await
|
|
217
|
+
const freshAuth = await ensureFreshToken(auth);
|
|
212
218
|
if (freshAuth.token !== auth.token) {
|
|
213
219
|
currentAuth = freshAuth;
|
|
214
220
|
activeAuth = freshAuth;
|
|
@@ -218,11 +224,11 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
218
224
|
catch (err) {
|
|
219
225
|
// If the cached token still has time, proceed — refresh was preemptive.
|
|
220
226
|
// If it's already invalid, MCP calls will 401 mid-turn — fail fast instead.
|
|
221
|
-
if (!
|
|
227
|
+
if (!isTokenValid(auth.token)) {
|
|
222
228
|
const message = 'Authentication expired and refresh failed — please restart the bridge to sign in again.';
|
|
223
|
-
|
|
229
|
+
stopTurnTimer();
|
|
224
230
|
console.error(`[bridge] ${message} (${err.message})`);
|
|
225
|
-
if (currentWs?.readyState ===
|
|
231
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
226
232
|
currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
|
|
227
233
|
}
|
|
228
234
|
return;
|
|
@@ -241,15 +247,15 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
241
247
|
}
|
|
242
248
|
catch (err) {
|
|
243
249
|
const message = `System prompt refresh failed: ${err.message}`;
|
|
244
|
-
|
|
250
|
+
stopTurnTimer();
|
|
245
251
|
console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message}`);
|
|
246
|
-
if (currentWs?.readyState ===
|
|
252
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
247
253
|
currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
|
|
248
254
|
}
|
|
249
255
|
return;
|
|
250
256
|
}
|
|
251
257
|
let responding = false;
|
|
252
|
-
const accumulator =
|
|
258
|
+
const accumulator = makeBridgeAccumulator();
|
|
253
259
|
const startedAt = Date.now();
|
|
254
260
|
const turnSessionId = sessionId ?? conversationId; // gateway always supplies one; defensive fallback
|
|
255
261
|
// The CLI's `--session-id` is treated as a "claim this new session ID"
|
|
@@ -283,14 +289,14 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
283
289
|
// ack is recoverable by drain-on-startup. The gateway dedupes on
|
|
284
290
|
// conversationId, so a replay is idempotent.
|
|
285
291
|
try {
|
|
286
|
-
|
|
292
|
+
writeSpool(record);
|
|
287
293
|
}
|
|
288
294
|
catch (err) {
|
|
289
295
|
console.warn(`[bridge] spool write failed: ${err.message}`);
|
|
290
296
|
}
|
|
291
|
-
const result = await
|
|
297
|
+
const result = await postSaveTurn(GATEWAY_HTTP, activeAuth.token, record);
|
|
292
298
|
if (result.ok) {
|
|
293
|
-
|
|
299
|
+
deleteSpool(record.conversationId);
|
|
294
300
|
}
|
|
295
301
|
else {
|
|
296
302
|
// Leave the spool file in place — next startup or next successful
|
|
@@ -298,7 +304,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
298
304
|
console.warn(`[bridge] save-turn POST failed (${result.status}): ${result.error ?? 'unknown'} — kept on disk for retry`);
|
|
299
305
|
}
|
|
300
306
|
}
|
|
301
|
-
|
|
307
|
+
spawnClaude({
|
|
302
308
|
conversationId,
|
|
303
309
|
presenceSessionId: claudePinnedSessionId,
|
|
304
310
|
text,
|
|
@@ -313,7 +319,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
313
319
|
responding = true;
|
|
314
320
|
console.log(`[${new Date().toLocaleTimeString()}] ◐ responding…`);
|
|
315
321
|
}
|
|
316
|
-
if (currentWs?.readyState ===
|
|
322
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
317
323
|
currentWs.send(JSON.stringify({ type: 'stream', conversationId, event }));
|
|
318
324
|
}
|
|
319
325
|
},
|
|
@@ -321,13 +327,13 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
321
327
|
// Ephemeral, non-persisted thread notice (admin-only Local Mode). Relayed
|
|
322
328
|
// by the gateway to the PWA SSE stream as a `notice` AgentEvent; it does
|
|
323
329
|
// NOT go through the turn accumulator, so it never lands in history.
|
|
324
|
-
if (currentWs?.readyState ===
|
|
330
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
325
331
|
currentWs.send(JSON.stringify({ type: 'notice', conversationId, message }));
|
|
326
332
|
}
|
|
327
333
|
},
|
|
328
334
|
onDone: (messageCount, costUsd, usage, model, contextTokens) => {
|
|
329
|
-
const elapsed =
|
|
330
|
-
const parts = [
|
|
335
|
+
const elapsed = stopTurnTimer();
|
|
336
|
+
const parts = [formatElapsed(elapsed)];
|
|
331
337
|
if (usage)
|
|
332
338
|
parts.push(`in:${usage.input_tokens} out:${usage.output_tokens}`);
|
|
333
339
|
const costStr = costUsd === 0 ? '$0.0000 (plan usage)' : `$${costUsd.toFixed(4)}`;
|
|
@@ -341,9 +347,9 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
341
347
|
sessionCostUsd += costUsd;
|
|
342
348
|
const ctxPct = Math.max(0, Math.min(100, Math.round((contextTokens / contextWindowFor(model)) * 100)));
|
|
343
349
|
const costSeg = sessionCostUsd > 0 ? `$${sessionCostUsd.toFixed(2)} session` : 'plan usage';
|
|
344
|
-
console.log(
|
|
350
|
+
console.log(paint('90', ` 🤖 ${friendlyModelName(model)} · 🧠 ${ctxPct}% · 💰 ${costSeg}`));
|
|
345
351
|
const mapped = toBridgeUsage(usage);
|
|
346
|
-
if (currentWs?.readyState ===
|
|
352
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
347
353
|
currentWs.send(JSON.stringify({
|
|
348
354
|
type: 'done',
|
|
349
355
|
conversationId,
|
|
@@ -357,10 +363,10 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
357
363
|
void finalizeAndPost(buildSpoolRecord(mapped, model));
|
|
358
364
|
},
|
|
359
365
|
onError: (message, usage, model) => {
|
|
360
|
-
const elapsed =
|
|
361
|
-
console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message} (${
|
|
366
|
+
const elapsed = stopTurnTimer();
|
|
367
|
+
console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message} (${formatElapsed(elapsed)})`);
|
|
362
368
|
const mapped = toBridgeUsage(usage);
|
|
363
|
-
if (currentWs?.readyState ===
|
|
369
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
364
370
|
currentWs.send(JSON.stringify({
|
|
365
371
|
type: 'error',
|
|
366
372
|
conversationId,
|
|
@@ -391,14 +397,14 @@ function toBridgeUsage(usage) {
|
|
|
391
397
|
// dedupes on conversationId — if it already saved via the WS path, the
|
|
392
398
|
// reply is finalized=false and we still delete the spool.
|
|
393
399
|
async function drainOutbox(auth) {
|
|
394
|
-
const records =
|
|
400
|
+
const records = listSpool();
|
|
395
401
|
if (records.length === 0)
|
|
396
402
|
return;
|
|
397
403
|
console.log(`[bridge] draining ${records.length} pending save record${records.length === 1 ? '' : 's'}…`);
|
|
398
404
|
for (const record of records) {
|
|
399
|
-
const result = await
|
|
405
|
+
const result = await postSaveTurn(GATEWAY_HTTP, auth.token, record);
|
|
400
406
|
if (result.ok) {
|
|
401
|
-
|
|
407
|
+
deleteSpool(record.conversationId);
|
|
402
408
|
}
|
|
403
409
|
else {
|
|
404
410
|
console.warn(`[bridge] drain POST failed (${result.status}): ${result.error ?? 'unknown'} — leaving on disk`);
|
|
@@ -411,14 +417,14 @@ async function drainOutbox(auth) {
|
|
|
411
417
|
const PING_INTERVAL_MS = 30_000;
|
|
412
418
|
const PONG_TIMEOUT_MS = 10_000;
|
|
413
419
|
function connect(auth, retryDelay = 1000) {
|
|
414
|
-
const ws = new
|
|
420
|
+
const ws = new WebSocket(GATEWAY_WS, {
|
|
415
421
|
headers: { Authorization: `Bearer ${auth.token}` },
|
|
416
422
|
});
|
|
417
423
|
let pingTimer = null;
|
|
418
424
|
let pongTimer = null;
|
|
419
425
|
function startPing() {
|
|
420
426
|
pingTimer = setInterval(() => {
|
|
421
|
-
if (ws.readyState !==
|
|
427
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
422
428
|
return;
|
|
423
429
|
ws.send(JSON.stringify({ type: 'ping', ts: Date.now() }));
|
|
424
430
|
pongTimer = setTimeout(() => {
|
|
@@ -473,7 +479,7 @@ function connect(auth, retryDelay = 1000) {
|
|
|
473
479
|
// (PWA→gateway connection dropped). Kill the local Claude Code process for
|
|
474
480
|
// this conversation so it stops generating instead of running to the end.
|
|
475
481
|
if (msg.type === 'cancel' && msg.conversationId) {
|
|
476
|
-
const cancelled =
|
|
482
|
+
const cancelled = cancelConversation(msg.conversationId);
|
|
477
483
|
if (cancelled)
|
|
478
484
|
console.log(`[bridge] ✕ stopped conversation ${msg.conversationId}`);
|
|
479
485
|
return;
|
|
@@ -484,9 +490,9 @@ function connect(auth, retryDelay = 1000) {
|
|
|
484
490
|
const ts = new Date().toLocaleTimeString();
|
|
485
491
|
const hist = Array.isArray(history) ? history : [];
|
|
486
492
|
console.log(`[${ts}] ▶ ${text}${hist.length ? ` (history: ${hist.length} turn${hist.length === 1 ? '' : 's'})` : ''}`);
|
|
487
|
-
|
|
493
|
+
startTurnTimer();
|
|
488
494
|
handleMessage(conversationId, text, sessionId ?? null, hist, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug).catch((err) => {
|
|
489
|
-
|
|
495
|
+
stopTurnTimer();
|
|
490
496
|
console.error(`[bridge] handleMessage error: ${err.message}`);
|
|
491
497
|
});
|
|
492
498
|
});
|
|
@@ -529,24 +535,24 @@ function connect(auth, retryDelay = 1000) {
|
|
|
529
535
|
}
|
|
530
536
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
531
537
|
async function main() {
|
|
532
|
-
console.log(`1Presence Bridge v${
|
|
538
|
+
console.log(`1Presence Bridge v${version}\n`);
|
|
533
539
|
if (VERBOSE) {
|
|
534
|
-
|
|
540
|
+
setVerbose(true);
|
|
535
541
|
console.log('[bridge:verbose] verbose logging enabled — system prompts (magenta), user prompts (blue), assistant text (green), tool inputs (cyan), and tool outputs (yellow) will be printed, colour-coded by kind.\n');
|
|
536
542
|
}
|
|
537
543
|
if (DEBUG) {
|
|
538
|
-
|
|
544
|
+
setDebug(true);
|
|
539
545
|
console.log('[bridge:debug] debug transcript enabled — user prompts, assistant text, tool inputs, and tool outputs will be printed (system prompt omitted; use --verbose for that).\n');
|
|
540
546
|
}
|
|
541
|
-
if (await
|
|
547
|
+
if (await checkAndUpdate())
|
|
542
548
|
return;
|
|
543
549
|
// Auth
|
|
544
|
-
const auth = await
|
|
550
|
+
const auth = await getValidAuth(GATEWAY_HTTP, PWA_URL);
|
|
545
551
|
currentAuth = auth;
|
|
546
552
|
// One-time interactive model choice (only prompts on first run; saved to
|
|
547
553
|
// ~/.1presence/config.json). In a non-TTY environment this is a no-op and
|
|
548
554
|
// Claude Code's own default is used.
|
|
549
|
-
await
|
|
555
|
+
await ensureModelChoice();
|
|
550
556
|
// Write system prompt + MCP config. If this fails the bridge is dead in the
|
|
551
557
|
// water — surface the underlying error rather than letting it bubble up as
|
|
552
558
|
// a generic "Fatal:" with no context.
|
|
@@ -566,7 +572,7 @@ async function main() {
|
|
|
566
572
|
// Graceful shutdown
|
|
567
573
|
const shutdown = () => {
|
|
568
574
|
console.log('\nShutting down…');
|
|
569
|
-
|
|
575
|
+
killAll();
|
|
570
576
|
process.exit(0);
|
|
571
577
|
};
|
|
572
578
|
process.on('SIGINT', shutdown);
|
|
@@ -588,7 +594,7 @@ async function main() {
|
|
|
588
594
|
});
|
|
589
595
|
}
|
|
590
596
|
main().catch((err) => {
|
|
591
|
-
if (err instanceof
|
|
597
|
+
if (err instanceof AuthCancelledError) {
|
|
592
598
|
console.error(`\n${err.message}`);
|
|
593
599
|
console.error('Run `npx @1presence/bridge` again when you are ready to sign in.');
|
|
594
600
|
process.exit(0);
|
package/dist/outbox.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
exports.deleteSpool = deleteSpool;
|
|
5
|
-
exports.listSpool = listSpool;
|
|
6
|
-
const fs_1 = require("fs");
|
|
7
|
-
const os_1 = require("os");
|
|
8
|
-
const path_1 = require("path");
|
|
1
|
+
import { mkdirSync, writeFileSync, readdirSync, readFileSync, unlinkSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
9
4
|
// ─── On-disk turn spool ───────────────────────────────────────────────────────
|
|
10
5
|
//
|
|
11
6
|
// Each in-flight bridge turn writes a record to ~/.1presence/outbox/. The file
|
|
@@ -20,31 +15,31 @@ const path_1 = require("path");
|
|
|
20
15
|
//
|
|
21
16
|
// Payload mode 0600 — the file contains the user's assistant transcript and
|
|
22
17
|
// tool inputs. Tightened on every write to handle legacy world-readable files.
|
|
23
|
-
const OUTBOX_DIR =
|
|
18
|
+
const OUTBOX_DIR = join(homedir(), '.1presence', 'outbox');
|
|
24
19
|
function ensureDir() {
|
|
25
|
-
|
|
20
|
+
mkdirSync(OUTBOX_DIR, { recursive: true });
|
|
26
21
|
}
|
|
27
22
|
function pathFor(conversationId) {
|
|
28
|
-
return
|
|
23
|
+
return join(OUTBOX_DIR, `${conversationId}.json`);
|
|
29
24
|
}
|
|
30
|
-
function writeSpool(record) {
|
|
25
|
+
export function writeSpool(record) {
|
|
31
26
|
ensureDir();
|
|
32
|
-
|
|
27
|
+
writeFileSync(pathFor(record.conversationId), JSON.stringify(record), { mode: 0o600 });
|
|
33
28
|
}
|
|
34
|
-
function deleteSpool(conversationId) {
|
|
29
|
+
export function deleteSpool(conversationId) {
|
|
35
30
|
try {
|
|
36
|
-
|
|
31
|
+
unlinkSync(pathFor(conversationId));
|
|
37
32
|
}
|
|
38
33
|
catch { /* already gone — fine */ }
|
|
39
34
|
}
|
|
40
|
-
function listSpool() {
|
|
35
|
+
export function listSpool() {
|
|
41
36
|
ensureDir();
|
|
42
37
|
const out = [];
|
|
43
|
-
for (const file of
|
|
38
|
+
for (const file of readdirSync(OUTBOX_DIR)) {
|
|
44
39
|
if (!file.endsWith('.json'))
|
|
45
40
|
continue;
|
|
46
41
|
try {
|
|
47
|
-
const raw =
|
|
42
|
+
const raw = readFileSync(join(OUTBOX_DIR, file), 'utf-8');
|
|
48
43
|
out.push(JSON.parse(raw));
|
|
49
44
|
}
|
|
50
45
|
catch {
|
package/dist/timer.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Live elapsed-time indicator for the active turn. Writes `\r\x1b[K⏱ Xs`
|
|
3
2
|
// once per second; wraps console.log/error/warn so that any other output
|
|
4
3
|
// clears the timer line before printing, then redraws the timer on the new
|
|
5
4
|
// bottom line. Idempotent — stop is safe to call multiple times.
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.formatElapsed = formatElapsed;
|
|
8
|
-
exports.startTurnTimer = startTurnTimer;
|
|
9
|
-
exports.stopTurnTimer = stopTurnTimer;
|
|
10
5
|
let intervalId = null;
|
|
11
6
|
let startedAt = 0;
|
|
12
7
|
let originalLog = null;
|
|
@@ -19,14 +14,14 @@ function draw() {
|
|
|
19
14
|
const elapsedSec = Math.floor((Date.now() - startedAt) / 1000);
|
|
20
15
|
process.stdout.write(`\r\x1b[K⏱ ${formatElapsed(elapsedSec)}`);
|
|
21
16
|
}
|
|
22
|
-
function formatElapsed(seconds) {
|
|
17
|
+
export function formatElapsed(seconds) {
|
|
23
18
|
if (seconds < 60)
|
|
24
19
|
return `${seconds}s`;
|
|
25
20
|
const m = Math.floor(seconds / 60);
|
|
26
21
|
const s = seconds % 60;
|
|
27
22
|
return `${m}m ${s.toString().padStart(2, '0')}s`;
|
|
28
23
|
}
|
|
29
|
-
function startTurnTimer() {
|
|
24
|
+
export function startTurnTimer() {
|
|
30
25
|
if (intervalId !== null)
|
|
31
26
|
return;
|
|
32
27
|
startedAt = Date.now();
|
|
@@ -39,7 +34,7 @@ function startTurnTimer() {
|
|
|
39
34
|
draw();
|
|
40
35
|
intervalId = setInterval(draw, 1000);
|
|
41
36
|
}
|
|
42
|
-
function stopTurnTimer() {
|
|
37
|
+
export function stopTurnTimer() {
|
|
43
38
|
if (intervalId === null)
|
|
44
39
|
return 0;
|
|
45
40
|
clearInterval(intervalId);
|
package/dist/update.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
// ESM JSON imports need version-sensitive import assertions; createRequire reads
|
|
4
|
+
// the manifest synchronously on every Node 18+ without that fragility.
|
|
5
|
+
const { version } = createRequire(import.meta.url)('../package.json');
|
|
6
6
|
function isNewer(a, b) {
|
|
7
7
|
const pa = a.split('.').map(Number);
|
|
8
8
|
const pb = b.split('.').map(Number);
|
|
@@ -14,7 +14,7 @@ function isNewer(a, b) {
|
|
|
14
14
|
}
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
|
-
async function checkAndUpdate() {
|
|
17
|
+
export async function checkAndUpdate() {
|
|
18
18
|
try {
|
|
19
19
|
const res = await fetch('https://registry.npmjs.org/@1presence/bridge/latest', {
|
|
20
20
|
signal: AbortSignal.timeout(3000),
|
|
@@ -23,10 +23,10 @@ async function checkAndUpdate() {
|
|
|
23
23
|
return false;
|
|
24
24
|
const data = await res.json();
|
|
25
25
|
const latest = data.version;
|
|
26
|
-
if (!isNewer(latest,
|
|
26
|
+
if (!isNewer(latest, version))
|
|
27
27
|
return false;
|
|
28
28
|
console.log(`Updating to v${latest}…\n`);
|
|
29
|
-
const child =
|
|
29
|
+
const child = spawn('npx', ['--yes', `@1presence/bridge@${latest}`], {
|
|
30
30
|
stdio: 'inherit',
|
|
31
31
|
env: process.env,
|
|
32
32
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1presence/bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"bin": {
|
|
6
7
|
"1presence-bridge": "dist/index.js"
|
|
7
8
|
},
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
"start": "node dist/index.js"
|
|
19
20
|
},
|
|
20
21
|
"dependencies": {
|
|
22
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.153",
|
|
21
23
|
"ws": "^8.20.0"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|