@1presence/bridge 0.6.0 → 0.8.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/claude.js CHANGED
@@ -125,6 +125,12 @@ function spawnClaude(params) {
125
125
  const { conversationId, presenceSessionId, text, uid, vaultFileOpen, clientCapabilities, syncedFolders, onEvent, onDone, onError } = params;
126
126
  const systemPromptPath = (0, path_1.join)((0, os_1.tmpdir)(), `agent-${uid}.md`);
127
127
  const mcpConfigPath = (0, path_1.join)((0, os_1.tmpdir)(), `mcp-${uid}.json`);
128
+ if (verbose) {
129
+ process.stderr.write(`[bridge:verbose] cwd: ${BRIDGE_CWD}\n`);
130
+ process.stderr.write(`[bridge:verbose] override md: ${(0, path_1.join)(BRIDGE_CWD, 'CLAUDE.md')}\n`);
131
+ process.stderr.write(`[bridge:verbose] system prompt: ${systemPromptPath}\n`);
132
+ process.stderr.write(`[bridge:verbose] mcp config: ${mcpConfigPath}\n`);
133
+ }
128
134
  // If a prior process is still running for this conversation (user sent a
129
135
  // follow-up before the previous turn finished), supersede it. The latest
130
136
  // user intent wins; the orphan would otherwise keep streaming events.
package/dist/config.js CHANGED
@@ -6,6 +6,7 @@ const fs_1 = require("fs");
6
6
  const os_1 = require("os");
7
7
  const path_1 = require("path");
8
8
  const readline_1 = require("readline");
9
+ const child_process_1 = require("child_process");
9
10
  // ─── Storage ──────────────────────────────────────────────────────────────────
10
11
  //
11
12
  // `~/.1presence/config.json` holds bridge-wide preferences that should persist
@@ -30,41 +31,157 @@ function persist(c) {
30
31
  cached = c;
31
32
  }
32
33
  // ─── Model choice ─────────────────────────────────────────────────────────────
33
- function promptForModel() {
34
+ /**
35
+ * Asks the local `claude` CLI which model it would pick by default, by reading
36
+ * the `model` field of the `system/init` stream-json event and killing the
37
+ * process before any model call completes. Returns null on timeout or if the
38
+ * CLI isn't installed — we never want this auxiliary lookup to block startup.
39
+ */
40
+ function detectClaudeDefaultModel() {
41
+ return new Promise((resolve) => {
42
+ let settled = false;
43
+ const finish = (v) => { if (!settled) {
44
+ settled = true;
45
+ resolve(v);
46
+ } };
47
+ let proc;
48
+ try {
49
+ proc = (0, child_process_1.spawn)('claude', [
50
+ '-p',
51
+ '--input-format', 'stream-json',
52
+ '--output-format', 'stream-json',
53
+ '--verbose',
54
+ '--tools', '',
55
+ '--setting-sources', '',
56
+ ], { stdio: ['pipe', 'pipe', 'ignore'] });
57
+ }
58
+ catch {
59
+ finish(null);
60
+ return;
61
+ }
62
+ const timer = setTimeout(() => { try {
63
+ proc.kill('SIGKILL');
64
+ }
65
+ catch { } ; finish(null); }, 5000);
66
+ proc.on('error', () => { clearTimeout(timer); finish(null); });
67
+ proc.on('close', () => { clearTimeout(timer); finish(null); });
68
+ let buf = '';
69
+ proc.stdout?.on('data', (chunk) => {
70
+ buf += chunk.toString('utf-8');
71
+ const lines = buf.split('\n');
72
+ buf = lines.pop() ?? '';
73
+ for (const line of lines) {
74
+ const trimmed = line.trim();
75
+ if (!trimmed)
76
+ continue;
77
+ try {
78
+ const ev = JSON.parse(trimmed);
79
+ if (ev['type'] === 'system' && ev['subtype'] === 'init') {
80
+ const model = ev['model'];
81
+ clearTimeout(timer);
82
+ try {
83
+ proc.kill('SIGKILL');
84
+ }
85
+ catch { }
86
+ finish(typeof model === 'string' ? model : null);
87
+ return;
88
+ }
89
+ }
90
+ catch { /* ignore non-JSON lines */ }
91
+ }
92
+ });
93
+ // Send one empty user message so claude proceeds past startup and emits
94
+ // the init event. We kill before any assistant turn runs, so no tokens
95
+ // are billed.
96
+ proc.stdin?.end('{"type":"user","message":{"role":"user","content":""}}\n');
97
+ });
98
+ }
99
+ // The fixed menu — keep the option count small so the timeout default is easy
100
+ // to glance at. Option 1's `model: null` means "let Claude Code pick" (no
101
+ // `--model` flag passed to the subprocess).
102
+ const MODEL_OPTIONS = [
103
+ { num: 1, model: null, label: 'Use Claude Code default' },
104
+ { num: 2, model: 'claude-opus-4-7', label: 'claude-opus-4-7' },
105
+ { num: 3, model: 'claude-sonnet-4-6', label: 'claude-sonnet-4-6' },
106
+ { num: 4, model: 'claude-haiku-4-5', label: 'claude-haiku-4-5' },
107
+ ];
108
+ const PROMPT_TIMEOUT_MS = 10_000;
109
+ function defaultOptionNum(previous) {
110
+ if (typeof previous === 'string') {
111
+ const match = MODEL_OPTIONS.find((o) => o.model === previous);
112
+ if (match)
113
+ return match.num;
114
+ }
115
+ // Either never asked (undefined) or explicit no-override (null) → option 1.
116
+ return 1;
117
+ }
118
+ function promptForModel(defaultModel, previousChoice) {
34
119
  return new Promise((resolve) => {
35
120
  const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
121
+ const defaultNum = defaultOptionNum(previousChoice);
36
122
  process.stdout.write('\nWhich Claude model should the bridge use?\n');
37
- process.stdout.write(' Leave blank to use your local Claude Code default (recommended).\n');
38
- process.stdout.write(' Or enter a model id (e.g. claude-sonnet-4-5, claude-opus-4-7, claude-haiku-4-5).\n');
39
- rl.question(' model: ', (answer) => {
123
+ for (const opt of MODEL_OPTIONS) {
124
+ const isDefault = opt.num === defaultNum;
125
+ const marker = isDefault ? '*' : ' ';
126
+ const suffix = opt.num === 1 && defaultModel ? ` (${defaultModel})` : '';
127
+ process.stdout.write(` ${marker} ${opt.num}) ${opt.label}${suffix}\n`);
128
+ }
129
+ process.stdout.write(` (* = current; auto-selected in ${PROMPT_TIMEOUT_MS / 1000}s if nothing pressed)\n`);
130
+ let settled = false;
131
+ const finish = (model) => {
132
+ if (settled)
133
+ return;
134
+ settled = true;
135
+ clearTimeout(timer);
40
136
  rl.close();
137
+ resolve(model);
138
+ };
139
+ const timer = setTimeout(() => {
140
+ const def = MODEL_OPTIONS.find((o) => o.num === defaultNum);
141
+ process.stdout.write(`\n(timed out — using option ${defaultNum})\n`);
142
+ finish(def.model);
143
+ }, PROMPT_TIMEOUT_MS);
144
+ rl.question(' choice: ', (answer) => {
41
145
  const trimmed = answer.trim();
42
- resolve(trimmed || null);
146
+ if (!trimmed) {
147
+ const def = MODEL_OPTIONS.find((o) => o.num === defaultNum);
148
+ finish(def.model);
149
+ return;
150
+ }
151
+ const n = Number(trimmed);
152
+ const opt = MODEL_OPTIONS.find((o) => o.num === n);
153
+ if (opt) {
154
+ finish(opt.model);
155
+ return;
156
+ }
157
+ // Fall back to treating the input as a raw model id for power users.
158
+ finish(trimmed);
43
159
  });
44
160
  });
45
161
  }
46
162
  /**
47
- * Ensures the user has picked a model preference. On first interactive run,
48
- * prompts and persists. Subsequent runs return the saved value without asking.
49
- * In a non-TTY environment (background process, CI) the prompt is skipped and
50
- * we record an explicit "no override" so we don't keep trying.
163
+ * Asks the user which model to pin every time the bridge starts interactively.
164
+ * The current pick (or option 1 on first run) is the timeout default wait
165
+ * 10 seconds and the previous choice carries over. In a non-TTY environment
166
+ * the prompt is skipped: an existing pin is kept, and a first non-TTY start
167
+ * records "no override" so we don't keep trying.
51
168
  */
52
169
  async function ensureModelChoice() {
53
170
  const c = load();
54
- if ('model' in c)
55
- return; // already configured (string or explicit null)
56
171
  if (!process.stdin.isTTY) {
57
- persist({ ...c, model: null });
172
+ if (!('model' in c))
173
+ persist({ ...c, model: null });
58
174
  return;
59
175
  }
60
- const chosen = await promptForModel();
176
+ const defaultModel = await detectClaudeDefaultModel();
177
+ const chosen = await promptForModel(defaultModel, c.model);
61
178
  persist({ ...c, model: chosen });
62
179
  if (chosen) {
63
180
  console.log(`\nPinned model: ${chosen}`);
64
181
  console.log(`(Edit or delete ~/.1presence/config.json to change.)\n`);
65
182
  }
66
183
  else {
67
- console.log(`\nUsing your Claude Code default.`);
184
+ console.log(`\nUsing your Claude Code default${defaultModel ? ` (${defaultModel})` : ''}.`);
68
185
  console.log(`(Edit ~/.1presence/config.json to pin a model later.)\n`);
69
186
  }
70
187
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.6.0",
3
+ "version": "0.8.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"