@1presence/bridge 0.40.0 → 0.42.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 +407 -491
- package/dist/config.js +6 -10
- package/dist/index.js +62 -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) {
|
|
@@ -195,8 +195,8 @@ async function writeSetupFiles(auth, agentSlug) {
|
|
|
195
195
|
// state. writeFileSync's mode only takes effect on file creation — chmodSync
|
|
196
196
|
// covers the overwrite case so a legacy 0644 file gets tightened on next run.
|
|
197
197
|
function writeRestricted(path, data) {
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
writeFileSync(path, data, { mode: 0o600 });
|
|
199
|
+
chmodSync(path, 0o600);
|
|
200
200
|
}
|
|
201
201
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
202
202
|
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 +208,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
208
208
|
// Refresh JWT if <10 min remaining before spawning Claude
|
|
209
209
|
let activeAuth = auth;
|
|
210
210
|
try {
|
|
211
|
-
const freshAuth = await
|
|
211
|
+
const freshAuth = await ensureFreshToken(auth);
|
|
212
212
|
if (freshAuth.token !== auth.token) {
|
|
213
213
|
currentAuth = freshAuth;
|
|
214
214
|
activeAuth = freshAuth;
|
|
@@ -218,11 +218,11 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
218
218
|
catch (err) {
|
|
219
219
|
// If the cached token still has time, proceed — refresh was preemptive.
|
|
220
220
|
// If it's already invalid, MCP calls will 401 mid-turn — fail fast instead.
|
|
221
|
-
if (!
|
|
221
|
+
if (!isTokenValid(auth.token)) {
|
|
222
222
|
const message = 'Authentication expired and refresh failed — please restart the bridge to sign in again.';
|
|
223
|
-
|
|
223
|
+
stopTurnTimer();
|
|
224
224
|
console.error(`[bridge] ${message} (${err.message})`);
|
|
225
|
-
if (currentWs?.readyState ===
|
|
225
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
226
226
|
currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
|
|
227
227
|
}
|
|
228
228
|
return;
|
|
@@ -241,15 +241,15 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
241
241
|
}
|
|
242
242
|
catch (err) {
|
|
243
243
|
const message = `System prompt refresh failed: ${err.message}`;
|
|
244
|
-
|
|
244
|
+
stopTurnTimer();
|
|
245
245
|
console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message}`);
|
|
246
|
-
if (currentWs?.readyState ===
|
|
246
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
247
247
|
currentWs.send(JSON.stringify({ type: 'error', conversationId, message }));
|
|
248
248
|
}
|
|
249
249
|
return;
|
|
250
250
|
}
|
|
251
251
|
let responding = false;
|
|
252
|
-
const accumulator =
|
|
252
|
+
const accumulator = makeBridgeAccumulator();
|
|
253
253
|
const startedAt = Date.now();
|
|
254
254
|
const turnSessionId = sessionId ?? conversationId; // gateway always supplies one; defensive fallback
|
|
255
255
|
// The CLI's `--session-id` is treated as a "claim this new session ID"
|
|
@@ -283,14 +283,14 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
283
283
|
// ack is recoverable by drain-on-startup. The gateway dedupes on
|
|
284
284
|
// conversationId, so a replay is idempotent.
|
|
285
285
|
try {
|
|
286
|
-
|
|
286
|
+
writeSpool(record);
|
|
287
287
|
}
|
|
288
288
|
catch (err) {
|
|
289
289
|
console.warn(`[bridge] spool write failed: ${err.message}`);
|
|
290
290
|
}
|
|
291
|
-
const result = await
|
|
291
|
+
const result = await postSaveTurn(GATEWAY_HTTP, activeAuth.token, record);
|
|
292
292
|
if (result.ok) {
|
|
293
|
-
|
|
293
|
+
deleteSpool(record.conversationId);
|
|
294
294
|
}
|
|
295
295
|
else {
|
|
296
296
|
// Leave the spool file in place — next startup or next successful
|
|
@@ -298,7 +298,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
298
298
|
console.warn(`[bridge] save-turn POST failed (${result.status}): ${result.error ?? 'unknown'} — kept on disk for retry`);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
-
|
|
301
|
+
spawnClaude({
|
|
302
302
|
conversationId,
|
|
303
303
|
presenceSessionId: claudePinnedSessionId,
|
|
304
304
|
text,
|
|
@@ -313,7 +313,7 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
313
313
|
responding = true;
|
|
314
314
|
console.log(`[${new Date().toLocaleTimeString()}] ◐ responding…`);
|
|
315
315
|
}
|
|
316
|
-
if (currentWs?.readyState ===
|
|
316
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
317
317
|
currentWs.send(JSON.stringify({ type: 'stream', conversationId, event }));
|
|
318
318
|
}
|
|
319
319
|
},
|
|
@@ -321,13 +321,13 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
321
321
|
// Ephemeral, non-persisted thread notice (admin-only Local Mode). Relayed
|
|
322
322
|
// by the gateway to the PWA SSE stream as a `notice` AgentEvent; it does
|
|
323
323
|
// NOT go through the turn accumulator, so it never lands in history.
|
|
324
|
-
if (currentWs?.readyState ===
|
|
324
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
325
325
|
currentWs.send(JSON.stringify({ type: 'notice', conversationId, message }));
|
|
326
326
|
}
|
|
327
327
|
},
|
|
328
328
|
onDone: (messageCount, costUsd, usage, model, contextTokens) => {
|
|
329
|
-
const elapsed =
|
|
330
|
-
const parts = [
|
|
329
|
+
const elapsed = stopTurnTimer();
|
|
330
|
+
const parts = [formatElapsed(elapsed)];
|
|
331
331
|
if (usage)
|
|
332
332
|
parts.push(`in:${usage.input_tokens} out:${usage.output_tokens}`);
|
|
333
333
|
const costStr = costUsd === 0 ? '$0.0000 (plan usage)' : `$${costUsd.toFixed(4)}`;
|
|
@@ -341,9 +341,9 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
341
341
|
sessionCostUsd += costUsd;
|
|
342
342
|
const ctxPct = Math.max(0, Math.min(100, Math.round((contextTokens / contextWindowFor(model)) * 100)));
|
|
343
343
|
const costSeg = sessionCostUsd > 0 ? `$${sessionCostUsd.toFixed(2)} session` : 'plan usage';
|
|
344
|
-
console.log(
|
|
344
|
+
console.log(paint('90', ` 🤖 ${friendlyModelName(model)} · 🧠 ${ctxPct}% · 💰 ${costSeg}`));
|
|
345
345
|
const mapped = toBridgeUsage(usage);
|
|
346
|
-
if (currentWs?.readyState ===
|
|
346
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
347
347
|
currentWs.send(JSON.stringify({
|
|
348
348
|
type: 'done',
|
|
349
349
|
conversationId,
|
|
@@ -357,10 +357,10 @@ async function handleMessage(conversationId, text, sessionId, history, auth, vau
|
|
|
357
357
|
void finalizeAndPost(buildSpoolRecord(mapped, model));
|
|
358
358
|
},
|
|
359
359
|
onError: (message, usage, model) => {
|
|
360
|
-
const elapsed =
|
|
361
|
-
console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message} (${
|
|
360
|
+
const elapsed = stopTurnTimer();
|
|
361
|
+
console.error(`[${new Date().toLocaleTimeString()}] ✗ ${message} (${formatElapsed(elapsed)})`);
|
|
362
362
|
const mapped = toBridgeUsage(usage);
|
|
363
|
-
if (currentWs?.readyState ===
|
|
363
|
+
if (currentWs?.readyState === WebSocket.OPEN) {
|
|
364
364
|
currentWs.send(JSON.stringify({
|
|
365
365
|
type: 'error',
|
|
366
366
|
conversationId,
|
|
@@ -391,14 +391,14 @@ function toBridgeUsage(usage) {
|
|
|
391
391
|
// dedupes on conversationId — if it already saved via the WS path, the
|
|
392
392
|
// reply is finalized=false and we still delete the spool.
|
|
393
393
|
async function drainOutbox(auth) {
|
|
394
|
-
const records =
|
|
394
|
+
const records = listSpool();
|
|
395
395
|
if (records.length === 0)
|
|
396
396
|
return;
|
|
397
397
|
console.log(`[bridge] draining ${records.length} pending save record${records.length === 1 ? '' : 's'}…`);
|
|
398
398
|
for (const record of records) {
|
|
399
|
-
const result = await
|
|
399
|
+
const result = await postSaveTurn(GATEWAY_HTTP, auth.token, record);
|
|
400
400
|
if (result.ok) {
|
|
401
|
-
|
|
401
|
+
deleteSpool(record.conversationId);
|
|
402
402
|
}
|
|
403
403
|
else {
|
|
404
404
|
console.warn(`[bridge] drain POST failed (${result.status}): ${result.error ?? 'unknown'} — leaving on disk`);
|
|
@@ -411,14 +411,14 @@ async function drainOutbox(auth) {
|
|
|
411
411
|
const PING_INTERVAL_MS = 30_000;
|
|
412
412
|
const PONG_TIMEOUT_MS = 10_000;
|
|
413
413
|
function connect(auth, retryDelay = 1000) {
|
|
414
|
-
const ws = new
|
|
414
|
+
const ws = new WebSocket(GATEWAY_WS, {
|
|
415
415
|
headers: { Authorization: `Bearer ${auth.token}` },
|
|
416
416
|
});
|
|
417
417
|
let pingTimer = null;
|
|
418
418
|
let pongTimer = null;
|
|
419
419
|
function startPing() {
|
|
420
420
|
pingTimer = setInterval(() => {
|
|
421
|
-
if (ws.readyState !==
|
|
421
|
+
if (ws.readyState !== WebSocket.OPEN)
|
|
422
422
|
return;
|
|
423
423
|
ws.send(JSON.stringify({ type: 'ping', ts: Date.now() }));
|
|
424
424
|
pongTimer = setTimeout(() => {
|
|
@@ -473,7 +473,7 @@ function connect(auth, retryDelay = 1000) {
|
|
|
473
473
|
// (PWA→gateway connection dropped). Kill the local Claude Code process for
|
|
474
474
|
// this conversation so it stops generating instead of running to the end.
|
|
475
475
|
if (msg.type === 'cancel' && msg.conversationId) {
|
|
476
|
-
const cancelled =
|
|
476
|
+
const cancelled = cancelConversation(msg.conversationId);
|
|
477
477
|
if (cancelled)
|
|
478
478
|
console.log(`[bridge] ✕ stopped conversation ${msg.conversationId}`);
|
|
479
479
|
return;
|
|
@@ -484,9 +484,9 @@ function connect(auth, retryDelay = 1000) {
|
|
|
484
484
|
const ts = new Date().toLocaleTimeString();
|
|
485
485
|
const hist = Array.isArray(history) ? history : [];
|
|
486
486
|
console.log(`[${ts}] ▶ ${text}${hist.length ? ` (history: ${hist.length} turn${hist.length === 1 ? '' : 's'})` : ''}`);
|
|
487
|
-
|
|
487
|
+
startTurnTimer();
|
|
488
488
|
handleMessage(conversationId, text, sessionId ?? null, hist, auth, vaultFileOpen, clientCapabilities, syncedFolders, agentSlug).catch((err) => {
|
|
489
|
-
|
|
489
|
+
stopTurnTimer();
|
|
490
490
|
console.error(`[bridge] handleMessage error: ${err.message}`);
|
|
491
491
|
});
|
|
492
492
|
});
|
|
@@ -529,24 +529,24 @@ function connect(auth, retryDelay = 1000) {
|
|
|
529
529
|
}
|
|
530
530
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
|
531
531
|
async function main() {
|
|
532
|
-
console.log(`1Presence Bridge v${
|
|
532
|
+
console.log(`1Presence Bridge v${version}\n`);
|
|
533
533
|
if (VERBOSE) {
|
|
534
|
-
|
|
534
|
+
setVerbose(true);
|
|
535
535
|
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
536
|
}
|
|
537
537
|
if (DEBUG) {
|
|
538
|
-
|
|
538
|
+
setDebug(true);
|
|
539
539
|
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
540
|
}
|
|
541
|
-
if (await
|
|
541
|
+
if (await checkAndUpdate())
|
|
542
542
|
return;
|
|
543
543
|
// Auth
|
|
544
|
-
const auth = await
|
|
544
|
+
const auth = await getValidAuth(GATEWAY_HTTP, PWA_URL);
|
|
545
545
|
currentAuth = auth;
|
|
546
546
|
// One-time interactive model choice (only prompts on first run; saved to
|
|
547
547
|
// ~/.1presence/config.json). In a non-TTY environment this is a no-op and
|
|
548
548
|
// Claude Code's own default is used.
|
|
549
|
-
await
|
|
549
|
+
await ensureModelChoice();
|
|
550
550
|
// Write system prompt + MCP config. If this fails the bridge is dead in the
|
|
551
551
|
// water — surface the underlying error rather than letting it bubble up as
|
|
552
552
|
// a generic "Fatal:" with no context.
|
|
@@ -566,7 +566,7 @@ async function main() {
|
|
|
566
566
|
// Graceful shutdown
|
|
567
567
|
const shutdown = () => {
|
|
568
568
|
console.log('\nShutting down…');
|
|
569
|
-
|
|
569
|
+
killAll();
|
|
570
570
|
process.exit(0);
|
|
571
571
|
};
|
|
572
572
|
process.on('SIGINT', shutdown);
|
|
@@ -588,7 +588,7 @@ async function main() {
|
|
|
588
588
|
});
|
|
589
589
|
}
|
|
590
590
|
main().catch((err) => {
|
|
591
|
-
if (err instanceof
|
|
591
|
+
if (err instanceof AuthCancelledError) {
|
|
592
592
|
console.error(`\n${err.message}`);
|
|
593
593
|
console.error('Run `npx @1presence/bridge` again when you are ready to sign in.');
|
|
594
594
|
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.42.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": {
|