@1presence/bridge 0.6.0 → 0.9.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 +6 -0
- package/dist/config.js +131 -14
- package/dist/index.js +4 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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
|
-
|
|
172
|
+
if (!('model' in c))
|
|
173
|
+
persist({ ...c, model: null });
|
|
58
174
|
return;
|
|
59
175
|
}
|
|
60
|
-
const
|
|
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/dist/index.js
CHANGED
|
@@ -249,6 +249,10 @@ function connect(auth, retryDelay = 1000) {
|
|
|
249
249
|
(0, auth_1.clearAuth)();
|
|
250
250
|
process.exit(1);
|
|
251
251
|
}
|
|
252
|
+
if (code === 4003) {
|
|
253
|
+
console.error('Local Claude Code is not enabled for your account. To request access, email hello@1presence.com.');
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
252
256
|
const delay = Math.min(retryDelay, 30_000);
|
|
253
257
|
console.log(`Bridge disconnected (${code}). Reconnecting in ${delay / 1000}s…`);
|
|
254
258
|
setTimeout(async () => {
|