@1presence/bridge 0.27.0 → 0.29.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/README.md CHANGED
@@ -15,6 +15,11 @@ npx @1presence/bridge
15
15
 
16
16
  On first run, a browser window will open to sign in to your 1Presence account. Your credentials are cached in `~/.1presence/auth.json` so subsequent runs start immediately.
17
17
 
18
+ ### Flags
19
+
20
+ - `--debug` (`-d`) — print a clean per-turn transcript: the user prompt, the assistant's text, and every tool call's input and output. This mirrors what an admin sees in the chat's debug view. Use this when you want to follow the conversation and inspect tool calls.
21
+ - `--verbose` (`-v`) — log tool inputs/outputs plus the full system prompt and setup paths on every turn. Use this when debugging the prompt itself; for following messages and tool calls without the prompt dump, prefer `--debug`.
22
+
18
23
  Once connected, your 1Presence app on any device automatically routes to your local Claude Code session. When you stop the bridge, 1Presence falls back to platform mode.
19
24
 
20
25
  ## How it works
package/dist/claude.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setVerbose = setVerbose;
4
+ exports.setDebug = setDebug;
4
5
  exports.spawnClaude = spawnClaude;
5
6
  exports.killAll = killAll;
6
7
  const child_process_1 = require("child_process");
@@ -36,9 +37,17 @@ const config_1 = require("./config");
36
37
  // Track whether we've already announced the model this process — printing it
37
38
  // per-spawn is noisy; once on startup is what the user actually wants to see.
38
39
  let modelAnnounced = false;
39
- // Verbose flag — when set via --verbose, log full tool inputs and outputs.
40
+ // Verbose flag — when set via --verbose, log full tool inputs and outputs
41
+ // PLUS the entire system prompt. Great for prompt debugging, noisy for
42
+ // message debugging (the prompt dump buries the conversation).
40
43
  let verbose = false;
41
44
  function setVerbose(v) { verbose = v; }
45
+ // Debug flag — when set via --debug, render a clean, sectioned transcript of
46
+ // the live turn: user prompt, assistant text, every tool input, every tool
47
+ // result. This is the bridge equivalent of the chat's admin debug view. It
48
+ // deliberately does NOT print the system prompt — that's what --verbose is for.
49
+ let debug = false;
50
+ function setDebug(v) { debug = v; }
42
51
  function formatPayload(value) {
43
52
  try {
44
53
  return JSON.stringify(value, null, 2);
@@ -47,6 +56,26 @@ function formatPayload(value) {
47
56
  return String(value);
48
57
  }
49
58
  }
59
+ // ─── Debug transcript rendering ─────────────────────────────────────────────
60
+ //
61
+ // A clean, scannable block per event — coloured header rule + body. Matches
62
+ // the shape of the chat's admin debug bubbles (user / assistant / tool input /
63
+ // tool result) so what you see locally mirrors what an admin sees in the app.
64
+ const USE_COLOR = process.stderr.isTTY === true && !process.env['NO_COLOR'];
65
+ function paint(code, s) {
66
+ return USE_COLOR ? `\x1b[${code}m${s}\x1b[0m` : s;
67
+ }
68
+ // ANSI colour codes per section, mirroring the admin debug palette.
69
+ const DEBUG_COLORS = {
70
+ user: '34', // blue
71
+ assistant: '32', // green
72
+ input: '36', // cyan
73
+ result: '33', // yellow
74
+ };
75
+ function debugBlock(label, colorCode, body) {
76
+ const rule = `── ${label} `.padEnd(64, '─');
77
+ process.stderr.write(`\n${paint(colorCode, rule)}\n${body.trimEnd()}\n`);
78
+ }
50
79
  // ─── Active processes ─────────────────────────────────────────────────────────
51
80
  const active = new Map();
52
81
  // ─── Spawn ────────────────────────────────────────────────────────────────────
@@ -61,6 +90,16 @@ function spawnClaude(params) {
61
90
  process.stderr.write(`[bridge:verbose] mcp config: ${mcpConfigPath}\n`);
62
91
  process.stderr.write(`[bridge:verbose] history turns: ${history.length}\n`);
63
92
  }
93
+ // Debug transcript: lead with the user prompt for this turn (the clean
94
+ // message, before the gateway's ephemeral-context prefix), plus a hint at
95
+ // how much prior context is being replayed.
96
+ if (debug) {
97
+ const histNote = history.length ? ` (replaying ${history.length} prior turn${history.length === 1 ? '' : 's'})` : '';
98
+ debugBlock(`user${histNote}`, DEBUG_COLORS.user, text);
99
+ }
100
+ // tool_use_id → tool name, so a tool_result block (which only carries the id)
101
+ // can be labelled with the tool it answers in the debug transcript.
102
+ const toolNames = new Map();
64
103
  // If a prior process is still running for this conversation (user sent a
65
104
  // follow-up before the previous turn finished), supersede it. The latest
66
105
  // user intent wins; the orphan would otherwise keep streaming events.
@@ -147,7 +186,16 @@ function spawnClaude(params) {
147
186
  throw new Error('claude stdin is null — spawn must use stdio[0]="pipe"');
148
187
  }
149
188
  for (const msg of history) {
150
- const wrapped = { type: msg.role, message: { role: msg.role, content: msg.content } };
189
+ // Normalise to array-of-blocks: Claude Code's stream-json input parser
190
+ // iterates `content` directly. A string slips into a `"tool_use_id" in
191
+ // <char>` check inside the CLI and aborts the process with `W is not an
192
+ // Object` (JSC) / exit 1 mid-turn. The gateway also normalises before
193
+ // sending, so a current gateway + any bridge version is safe; this guard
194
+ // covers older gateways and ad-hoc local replay tests.
195
+ const content = Array.isArray(msg.content)
196
+ ? msg.content
197
+ : [{ type: 'text', text: typeof msg.content === 'string' ? msg.content : '' }];
198
+ const wrapped = { type: msg.role, message: { role: msg.role, content } };
151
199
  stdin.write(JSON.stringify(wrapped) + '\n');
152
200
  }
153
201
  stdin.end();
@@ -221,16 +269,25 @@ function spawnClaude(params) {
221
269
  let wroteText = false;
222
270
  for (const block of content) {
223
271
  if (block['type'] === 'tool_use') {
224
- if (wroteText) {
225
- process.stderr.write('\n');
226
- wroteText = false;
227
- }
228
272
  const toolName = block['name'];
229
- const prefix = toolName.startsWith('mcp__') ? '[mcp]' : '[tool]';
230
- process.stderr.write(`[bridge] ${prefix} ${toolName}\n`);
231
- if (verbose) {
232
- const input = block['input'];
233
- process.stderr.write(`[bridge:verbose] ─── input ${toolName} ───\n${formatPayload(input)}\n[bridge:verbose] ─── end input ───\n`);
273
+ const toolId = block['id'];
274
+ if (toolId)
275
+ toolNames.set(toolId, toolName);
276
+ if (debug) {
277
+ // Clean transcript: a single coloured block with the full input.
278
+ debugBlock(`tool → ${toolName}`, DEBUG_COLORS.input, formatPayload(block['input']));
279
+ }
280
+ else {
281
+ if (wroteText) {
282
+ process.stderr.write('\n');
283
+ wroteText = false;
284
+ }
285
+ const prefix = toolName.startsWith('mcp__') ? '[mcp]' : '[tool]';
286
+ process.stderr.write(`[bridge] ${prefix} ${toolName}\n`);
287
+ if (verbose) {
288
+ const input = block['input'];
289
+ process.stderr.write(`[bridge:verbose] ─── input ${toolName} ───\n${formatPayload(input)}\n[bridge:verbose] ─── end input ───\n`);
290
+ }
234
291
  }
235
292
  // Defense-in-depth: CLI flags (--tools "", --allowedTools, --strict-mcp-config,
236
293
  // --setting-sources "") are supposed to make this unreachable. If we see a
@@ -260,8 +317,14 @@ function spawnClaude(params) {
260
317
  else if (block['type'] === 'text') {
261
318
  const text = block['text'];
262
319
  if (text) {
263
- process.stderr.write(text.replace(/\n+/g, ' '));
264
- wroteText = true;
320
+ if (debug) {
321
+ // Full text, newlines intact — the readable transcript.
322
+ debugBlock('assistant', DEBUG_COLORS.assistant, text);
323
+ }
324
+ else {
325
+ process.stderr.write(text.replace(/\n+/g, ' '));
326
+ wroteText = true;
327
+ }
265
328
  }
266
329
  }
267
330
  }
@@ -270,7 +333,7 @@ function spawnClaude(params) {
270
333
  }
271
334
  }
272
335
  // Tool results stream back as `user` events with tool_result blocks.
273
- if (verbose && type === 'user') {
336
+ if ((verbose || debug) && type === 'user') {
274
337
  const msg = event['message'];
275
338
  const content = msg?.['content'];
276
339
  if (Array.isArray(content)) {
@@ -278,7 +341,14 @@ function spawnClaude(params) {
278
341
  if (block['type'] === 'tool_result') {
279
342
  const id = block['tool_use_id'] ?? '';
280
343
  const out = block['content'];
281
- process.stderr.write(`[bridge:verbose] ─── output ${id} ───\n${formatPayload(out)}\n[bridge:verbose] ─── end output ───\n`);
344
+ if (debug) {
345
+ const name = toolNames.get(id) ?? id ?? 'result';
346
+ const errFlag = block['is_error'] ? ' [error]' : '';
347
+ debugBlock(`result ← ${name}${errFlag}`, DEBUG_COLORS.result, formatPayload(out));
348
+ }
349
+ else {
350
+ process.stderr.write(`[bridge:verbose] ─── output ${id} ───\n${formatPayload(out)}\n[bridge:verbose] ─── end output ───\n`);
351
+ }
282
352
  }
283
353
  }
284
354
  }
package/dist/index.js CHANGED
@@ -31,6 +31,10 @@ if (__dirname.endsWith('dist')) {
31
31
  }
32
32
  // ─── CLI args ─────────────────────────────────────────────────────────────────
33
33
  const VERBOSE = process.argv.includes('--verbose') || process.argv.includes('-v');
34
+ // --debug renders a clean per-turn transcript (user prompt, assistant text,
35
+ // tool inputs, tool outputs) — the bridge equivalent of the chat's admin
36
+ // debug view. Unlike --verbose it does NOT dump the system prompt.
37
+ const DEBUG = process.argv.includes('--debug') || process.argv.includes('-d');
34
38
  // ─── Config ───────────────────────────────────────────────────────────────────
35
39
  const GATEWAY_URL = process.env.BRIDGE_GATEWAY_URL ?? 'https://api.1presence.com';
36
40
  const GATEWAY_WS = GATEWAY_URL.replace(/^https?:/, 'wss:').replace(/\/$/, '') + '/bridge';
@@ -424,6 +428,10 @@ async function main() {
424
428
  (0, claude_1.setVerbose)(true);
425
429
  console.log('[bridge:verbose] verbose logging enabled — system prompts, tool inputs, and tool outputs will be printed.\n');
426
430
  }
431
+ if (DEBUG) {
432
+ (0, claude_1.setDebug)(true);
433
+ 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');
434
+ }
427
435
  if (await (0, update_1.checkAndUpdate)())
428
436
  return;
429
437
  // Auth
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
5
5
  "bin": {
6
6
  "1presence-bridge": "dist/index.js"