@1presence/bridge 0.90.0 → 0.92.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 +2 -0
- package/dist/claude.js +8 -5
- package/dist/config.js +146 -50
- package/dist/hotkeys.js +78 -0
- package/dist/index.js +11 -2
- package/dist/keys.js +86 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -57,6 +57,8 @@ On startup the bridge makes one request to the public npm registry (`registry.np
|
|
|
57
57
|
|
|
58
58
|
**Every start** the bridge asks which Claude model to use — the choice is held in memory for that run only and nothing is written to disk, so restarting always asks again. Pick "Use Claude Code default" to defer to your local Claude Code default, or pin one of the latest models per family: `claude-opus-5`, `claude-fable-5`, `claude-sonnet-5`, `claude-haiku-4-5`. If the prompt times out it auto-selects "Use Claude Code default", which tracks whatever model Claude Code serves for your plan.
|
|
59
59
|
|
|
60
|
+
**Press `Esc` at any time** to change model without restarting — no signing in again, and nothing in flight is lost. The new model applies to your next message; a reply already being written finishes on the one it started with. `Esc` again closes the menu without changing anything. While the bridge is running it also takes `Ctrl+C` to stop and `Ctrl+Z` to suspend.
|
|
61
|
+
|
|
60
62
|
Superseded versions (`claude-opus-4-8`, `claude-sonnet-4-6`, …) are deliberately not listed. If you need something other than the latest, use "Use Claude Code default" and set the model in Claude Code itself.
|
|
61
63
|
|
|
62
64
|
Every reply prints the model, the account it ran for, and your context/cost usage, so you can always see what is running — and if the model that answered isn't the one you pinned, the bridge says so explicitly. Note that only **one bridge per account** serves your turns: start a second one and the older process exits, because otherwise your turns could be answered by a terminal you aren't watching, on the model *it* pinned.
|
package/dist/claude.js
CHANGED
|
@@ -17,6 +17,9 @@ mkdirSync(BRIDGE_CWD, { recursive: true });
|
|
|
17
17
|
writeFileSync(join(BRIDGE_CWD, 'CLAUDE.md'), BRIDGE_CLAUDE_MD, 'utf-8');
|
|
18
18
|
import { getBridgeModel } from './config.js';
|
|
19
19
|
let modelAnnounced = false;
|
|
20
|
+
export function resetModelAnnouncement() {
|
|
21
|
+
modelAnnounced = false;
|
|
22
|
+
}
|
|
20
23
|
let cliVersion = null;
|
|
21
24
|
export function getCliVersion() { return cliVersion; }
|
|
22
25
|
const rateLimitWindows = new Map();
|
|
@@ -311,19 +314,19 @@ export function spawnClaude(params) {
|
|
|
311
314
|
extractedModel = model;
|
|
312
315
|
if (ccv)
|
|
313
316
|
cliVersion = ccv;
|
|
314
|
-
const
|
|
315
|
-
const pinHonoured = !!
|
|
317
|
+
const activePin = getBridgeModel();
|
|
318
|
+
const pinHonoured = !!activePin && !!model && model === activePin;
|
|
316
319
|
if (!modelAnnounced) {
|
|
317
320
|
const source = keySource === 'none' || !keySource ? 'claude.ai subscription' : keySource;
|
|
318
|
-
const pin = pinHonoured ? ' (
|
|
321
|
+
const pin = pinHonoured ? ' (your selection)' : '';
|
|
319
322
|
process.stdout.write(`\n model: ${model ?? 'unknown'}${pin}\n auth: ${source}\n\n`);
|
|
320
323
|
modelAnnounced = true;
|
|
321
324
|
}
|
|
322
325
|
else {
|
|
323
326
|
process.stderr.write(`[bridge] model: ${model ?? 'unknown'} apiKeySource: ${keySource ?? 'none'}\n`);
|
|
324
327
|
}
|
|
325
|
-
if (
|
|
326
|
-
process.stderr.write(`[bridge] WARNING: ${
|
|
328
|
+
if (activePin && model && model !== activePin) {
|
|
329
|
+
process.stderr.write(`[bridge] WARNING: ${activePin} is the selected model, but this turn ran on ${model}.\n`);
|
|
327
330
|
}
|
|
328
331
|
sessionIdExtracted = true;
|
|
329
332
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { emitKeypressEvents } from 'readline';
|
|
2
1
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
import { decodeKeys } from './keys.js';
|
|
3
3
|
let selectedModel = null;
|
|
4
4
|
function detectClaudeDefaultModel() {
|
|
5
5
|
return new Promise((resolve) => {
|
|
@@ -61,83 +61,103 @@ const MODEL_OPTIONS = [
|
|
|
61
61
|
];
|
|
62
62
|
const PROMPT_TIMEOUT_MS = 10_000;
|
|
63
63
|
const DEFAULT_OPTION_NUM = 1;
|
|
64
|
-
|
|
64
|
+
let cachedDefaultModel = null;
|
|
65
|
+
function promptForModel(defaultModel, opts) {
|
|
65
66
|
return new Promise((resolve) => {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
const currentIdx = MODEL_OPTIONS.findIndex((o) => o.model === opts.current);
|
|
68
|
+
const fallbackIdx = Math.max(0, MODEL_OPTIONS.findIndex((o) => o.num === DEFAULT_OPTION_NUM));
|
|
69
|
+
let idx = currentIdx >= 0 ? currentIdx : fallbackIdx;
|
|
70
|
+
const keys = [
|
|
71
|
+
'↑/↓ to move',
|
|
72
|
+
'Enter to select',
|
|
73
|
+
`1–${MODEL_OPTIONS.length} to jump`,
|
|
74
|
+
opts.escapeCancels ? 'Esc to cancel' : null,
|
|
75
|
+
opts.timeoutMs !== null ? `auto-selects in ${opts.timeoutMs / 1000}s` : null,
|
|
76
|
+
].filter((s) => s !== null).join(', ');
|
|
77
|
+
opts.write(`\n${opts.heading}\n`);
|
|
78
|
+
opts.write(` (${keys})\n`);
|
|
70
79
|
let rendered = 0;
|
|
71
80
|
const render = () => {
|
|
72
81
|
if (rendered > 0)
|
|
73
|
-
|
|
82
|
+
opts.write(`\x1b[${rendered}A`);
|
|
74
83
|
rendered = 0;
|
|
75
84
|
for (let i = 0; i < MODEL_OPTIONS.length; i++) {
|
|
76
85
|
const opt = MODEL_OPTIONS[i];
|
|
77
86
|
const active = i === idx;
|
|
78
87
|
const marker = active ? '›' : ' ';
|
|
79
|
-
const
|
|
80
|
-
const
|
|
88
|
+
const probed = opt.num === 1 && defaultModel ? ` (${defaultModel})` : '';
|
|
89
|
+
const inUse = opts.markCurrent && i === currentIdx ? ' ← in use' : '';
|
|
90
|
+
const text = ` ${marker} ${opt.num}) ${opt.label}${probed}${inUse}`;
|
|
81
91
|
const line = active ? `\x1b[1;36m${text}\x1b[0m` : text;
|
|
82
|
-
|
|
92
|
+
opts.write(`\x1b[2K${line}\n`);
|
|
83
93
|
rendered++;
|
|
84
94
|
}
|
|
85
95
|
};
|
|
86
96
|
render();
|
|
87
|
-
emitKeypressEvents(process.stdin);
|
|
88
97
|
const wasRaw = process.stdin.isRaw;
|
|
89
98
|
if (process.stdin.isTTY)
|
|
90
99
|
process.stdin.setRawMode(true);
|
|
91
100
|
process.stdin.resume();
|
|
92
101
|
let settled = false;
|
|
93
102
|
const cleanup = () => {
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
if (timer)
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
process.stdin.removeListener('data', onData);
|
|
96
106
|
if (process.stdin.isTTY)
|
|
97
107
|
process.stdin.setRawMode(!!wasRaw);
|
|
98
108
|
process.stdin.pause();
|
|
99
109
|
};
|
|
100
|
-
const finish = (
|
|
110
|
+
const finish = (outcome) => {
|
|
101
111
|
if (settled)
|
|
102
112
|
return;
|
|
103
113
|
settled = true;
|
|
104
114
|
cleanup();
|
|
105
|
-
resolve(
|
|
115
|
+
resolve(outcome);
|
|
106
116
|
};
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
117
|
+
const onData = (chunk) => {
|
|
118
|
+
for (const key of decodeKeys(chunk)) {
|
|
119
|
+
if (settled)
|
|
120
|
+
return;
|
|
121
|
+
switch (key.type) {
|
|
122
|
+
case 'interrupt':
|
|
123
|
+
cleanup();
|
|
124
|
+
opts.onInterrupt();
|
|
125
|
+
return;
|
|
126
|
+
case 'escape':
|
|
127
|
+
if (opts.escapeCancels) {
|
|
128
|
+
finish({ cancelled: true });
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
case 'up':
|
|
133
|
+
idx = (idx - 1 + MODEL_OPTIONS.length) % MODEL_OPTIONS.length;
|
|
134
|
+
render();
|
|
135
|
+
break;
|
|
136
|
+
case 'down':
|
|
137
|
+
idx = (idx + 1) % MODEL_OPTIONS.length;
|
|
138
|
+
render();
|
|
139
|
+
break;
|
|
140
|
+
case 'enter':
|
|
141
|
+
finish({ cancelled: false, model: MODEL_OPTIONS[idx].model });
|
|
142
|
+
return;
|
|
143
|
+
case 'digit':
|
|
144
|
+
if (key.value <= MODEL_OPTIONS.length) {
|
|
145
|
+
idx = key.value - 1;
|
|
146
|
+
render();
|
|
147
|
+
finish({ cancelled: false, model: MODEL_OPTIONS[idx].model });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
default:
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
134
154
|
}
|
|
135
155
|
};
|
|
136
|
-
process.stdin.on('
|
|
137
|
-
const timer = setTimeout(() => {
|
|
138
|
-
|
|
139
|
-
finish(MODEL_OPTIONS[idx].model);
|
|
140
|
-
},
|
|
156
|
+
process.stdin.on('data', onData);
|
|
157
|
+
const timer = opts.timeoutMs === null ? null : setTimeout(() => {
|
|
158
|
+
opts.write(`\n(timed out — using option ${MODEL_OPTIONS[idx].num})\n`);
|
|
159
|
+
finish({ cancelled: false, model: MODEL_OPTIONS[idx].model });
|
|
160
|
+
}, opts.timeoutMs);
|
|
141
161
|
});
|
|
142
162
|
}
|
|
143
163
|
export async function ensureModelChoice() {
|
|
@@ -145,13 +165,89 @@ export async function ensureModelChoice() {
|
|
|
145
165
|
selectedModel = null;
|
|
146
166
|
return;
|
|
147
167
|
}
|
|
148
|
-
|
|
149
|
-
|
|
168
|
+
cachedDefaultModel = await detectClaudeDefaultModel();
|
|
169
|
+
const outcome = await promptForModel(cachedDefaultModel, {
|
|
170
|
+
heading: 'Which Claude model should the bridge use?',
|
|
171
|
+
current: null,
|
|
172
|
+
markCurrent: false,
|
|
173
|
+
timeoutMs: PROMPT_TIMEOUT_MS,
|
|
174
|
+
escapeCancels: false,
|
|
175
|
+
onInterrupt: () => { process.stdout.write('\n'); process.exit(130); },
|
|
176
|
+
write: (s) => { process.stdout.write(s); },
|
|
177
|
+
});
|
|
178
|
+
selectedModel = outcome.cancelled ? null : outcome.model;
|
|
150
179
|
if (selectedModel) {
|
|
151
180
|
console.log(`\nUsing model: ${selectedModel} (this session only)\n`);
|
|
152
181
|
}
|
|
153
182
|
else {
|
|
154
|
-
console.log(`\nUsing your Claude Code default${
|
|
183
|
+
console.log(`\nUsing your Claude Code default${cachedDefaultModel ? ` (${cachedDefaultModel})` : ''}.\n`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const HOLD_LIMIT_CHARS = 1_000_000;
|
|
187
|
+
function holdOutput() {
|
|
188
|
+
const out = process.stdout.write.bind(process.stdout);
|
|
189
|
+
const err = process.stderr.write.bind(process.stderr);
|
|
190
|
+
const held = [];
|
|
191
|
+
let heldChars = 0;
|
|
192
|
+
let dropped = 0;
|
|
193
|
+
const capture = (sink) => function (chunk, encoding, cb) {
|
|
194
|
+
const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf-8');
|
|
195
|
+
if (heldChars + text.length <= HOLD_LIMIT_CHARS) {
|
|
196
|
+
held.push([sink, text]);
|
|
197
|
+
heldChars += text.length;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
dropped += text.length;
|
|
201
|
+
}
|
|
202
|
+
const done = typeof encoding === 'function' ? encoding : cb;
|
|
203
|
+
if (typeof done === 'function')
|
|
204
|
+
done();
|
|
205
|
+
return true;
|
|
206
|
+
};
|
|
207
|
+
process.stdout.write = capture(out);
|
|
208
|
+
process.stderr.write = capture(err);
|
|
209
|
+
return {
|
|
210
|
+
passthrough: (s) => { out(s); },
|
|
211
|
+
release: () => {
|
|
212
|
+
process.stdout.write = out;
|
|
213
|
+
process.stderr.write = err;
|
|
214
|
+
for (const [sink, text] of held)
|
|
215
|
+
sink(text);
|
|
216
|
+
held.length = 0;
|
|
217
|
+
if (dropped > 0) {
|
|
218
|
+
err(`[bridge] ${dropped} characters of output were dropped while the model picker was open.\n`);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
export async function reopenModelPicker(onInterrupt) {
|
|
224
|
+
const hold = holdOutput();
|
|
225
|
+
const previous = selectedModel;
|
|
226
|
+
try {
|
|
227
|
+
const outcome = await promptForModel(cachedDefaultModel, {
|
|
228
|
+
heading: 'Switch model — applies to your next message.',
|
|
229
|
+
current: previous,
|
|
230
|
+
markCurrent: true,
|
|
231
|
+
timeoutMs: null,
|
|
232
|
+
escapeCancels: true,
|
|
233
|
+
onInterrupt,
|
|
234
|
+
write: hold.passthrough,
|
|
235
|
+
});
|
|
236
|
+
if (outcome.cancelled) {
|
|
237
|
+
hold.passthrough('\n\x1b[90m(unchanged)\x1b[0m\n\n');
|
|
238
|
+
return { changed: false, model: previous };
|
|
239
|
+
}
|
|
240
|
+
selectedModel = outcome.model;
|
|
241
|
+
const name = selectedModel ?? `Claude Code default${cachedDefaultModel ? ` (${cachedDefaultModel})` : ''}`;
|
|
242
|
+
if (selectedModel === previous) {
|
|
243
|
+
hold.passthrough(`\n\x1b[90m(unchanged — still ${name})\x1b[0m\n\n`);
|
|
244
|
+
return { changed: false, model: selectedModel };
|
|
245
|
+
}
|
|
246
|
+
hold.passthrough(`\nModel switched to ${name} — applies to your next message.\n\n`);
|
|
247
|
+
return { changed: true, model: selectedModel };
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
hold.release();
|
|
155
251
|
}
|
|
156
252
|
}
|
|
157
253
|
export function getBridgeModel() {
|
package/dist/hotkeys.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { reopenModelPicker } from './config.js';
|
|
2
|
+
import { decodeKeys } from './keys.js';
|
|
3
|
+
let installed = false;
|
|
4
|
+
let pickerOpen = false;
|
|
5
|
+
function cooked() {
|
|
6
|
+
try {
|
|
7
|
+
if (process.stdin.isTTY)
|
|
8
|
+
process.stdin.setRawMode(false);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function suspend() {
|
|
14
|
+
cooked();
|
|
15
|
+
process.stdin.pause();
|
|
16
|
+
process.once('SIGCONT', () => {
|
|
17
|
+
try {
|
|
18
|
+
if (process.stdin.isTTY)
|
|
19
|
+
process.stdin.setRawMode(true);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
}
|
|
23
|
+
process.stdin.resume();
|
|
24
|
+
});
|
|
25
|
+
process.kill(process.pid, 'SIGTSTP');
|
|
26
|
+
}
|
|
27
|
+
async function openPicker(h) {
|
|
28
|
+
pickerOpen = true;
|
|
29
|
+
try {
|
|
30
|
+
const { changed, model } = await reopenModelPicker(() => { cooked(); h.onQuit(); });
|
|
31
|
+
if (changed)
|
|
32
|
+
h.onModelChanged?.(model);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
process.stderr.write(`[bridge] model picker failed: ${err.message}\n`);
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
pickerOpen = false;
|
|
39
|
+
try {
|
|
40
|
+
if (process.stdin.isTTY)
|
|
41
|
+
process.stdin.setRawMode(true);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
}
|
|
45
|
+
process.stdin.resume();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function installHotkeys(h) {
|
|
49
|
+
if (installed || !process.stdin.isTTY)
|
|
50
|
+
return false;
|
|
51
|
+
installed = true;
|
|
52
|
+
process.stdin.setRawMode(true);
|
|
53
|
+
process.stdin.resume();
|
|
54
|
+
process.on('exit', cooked);
|
|
55
|
+
process.stdin.on('data', (chunk) => {
|
|
56
|
+
if (pickerOpen)
|
|
57
|
+
return;
|
|
58
|
+
for (const key of decodeKeys(chunk)) {
|
|
59
|
+
if (key.type === 'interrupt') {
|
|
60
|
+
cooked();
|
|
61
|
+
h.onQuit();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (key.type === 'suspend') {
|
|
65
|
+
suspend();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (key.type === 'escape') {
|
|
69
|
+
void openPicker(h);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
export function isPickerOpen() {
|
|
77
|
+
return pickerOpen;
|
|
78
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,9 @@ import { fileURLToPath } from 'url';
|
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
9
9
|
import { getValidAuth, ensureFreshToken, forceRefreshToken, isTokenValid, AuthCancelledError } from './auth.js';
|
|
10
|
-
import { spawnClaude, killAll, cancelConversation, setVerbose, setDebug, paint, SECTION_COLORS, getCliVersion, getRateLimitWindow, REMOTE_MCP_SERVER_NAME as BRIDGE_MCP_SERVER_NAME } from './claude.js';
|
|
10
|
+
import { spawnClaude, killAll, cancelConversation, setVerbose, setDebug, paint, SECTION_COLORS, getCliVersion, getRateLimitWindow, resetModelAnnouncement, REMOTE_MCP_SERVER_NAME as BRIDGE_MCP_SERVER_NAME } from './claude.js';
|
|
11
11
|
import { ensureModelChoice } from './config.js';
|
|
12
|
+
import { installHotkeys } from './hotkeys.js';
|
|
12
13
|
import { probeClaudeAuth, launchClaudeLogin, waitForClaudeLogin, promptYesNo } from './claudeAuth.js';
|
|
13
14
|
import { checkAndUpdate, warnIfRuntimeStale } from './update.js';
|
|
14
15
|
import { makeBridgeAccumulator, postSaveTurn } from './accumulator.js';
|
|
@@ -35,6 +36,7 @@ const PWA_URL = process.env.BRIDGE_PWA_URL ?? GATEWAY_HTTP.replace('://api.', ':
|
|
|
35
36
|
let currentAuth = null;
|
|
36
37
|
let currentWs = null;
|
|
37
38
|
let disconnectedAt = null;
|
|
39
|
+
let hotkeysActive = false;
|
|
38
40
|
let sessionCostUsd = 0;
|
|
39
41
|
let authRejections = 0;
|
|
40
42
|
const MAX_AUTH_REJECTIONS = 3;
|
|
@@ -496,7 +498,10 @@ function connect(auth, retryDelay = 1000) {
|
|
|
496
498
|
console.log(`✓ Bridge reconnected${who} after ${downFor} offline. Local Mode active on all your devices.\n`);
|
|
497
499
|
}
|
|
498
500
|
else {
|
|
499
|
-
console.log(`✓ Bridge connected${who}. Local Mode active on all your devices
|
|
501
|
+
console.log(`✓ Bridge connected${who}. Local Mode active on all your devices.`);
|
|
502
|
+
if (hotkeysActive)
|
|
503
|
+
console.log(paint('90', ' Press Esc to switch model, Ctrl+C to stop.'));
|
|
504
|
+
console.log('');
|
|
500
505
|
}
|
|
501
506
|
startPing();
|
|
502
507
|
if (currentAuth) {
|
|
@@ -702,6 +707,10 @@ async function main() {
|
|
|
702
707
|
};
|
|
703
708
|
process.on('SIGINT', shutdown);
|
|
704
709
|
process.on('SIGTERM', shutdown);
|
|
710
|
+
hotkeysActive = installHotkeys({
|
|
711
|
+
onQuit: shutdown,
|
|
712
|
+
onModelChanged: () => resetModelAnnouncement(),
|
|
713
|
+
});
|
|
705
714
|
process.on('uncaughtException', (err) => {
|
|
706
715
|
console.error(paint(SECTION_COLORS.error, `[bridge] uncaughtException: ${err.message}`));
|
|
707
716
|
if (err.stack)
|
package/dist/keys.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const ESC = 0x1b;
|
|
2
|
+
const isFinal = (b) => b >= 0x40 && b <= 0x7e;
|
|
3
|
+
const isParamOrInter = (b) => b >= 0x20 && b <= 0x3f;
|
|
4
|
+
export function decodeKeys(chunk) {
|
|
5
|
+
const buf = typeof chunk === 'string' ? Buffer.from(chunk, 'utf-8') : chunk;
|
|
6
|
+
const keys = [];
|
|
7
|
+
let i = 0;
|
|
8
|
+
while (i < buf.length) {
|
|
9
|
+
const b = buf[i];
|
|
10
|
+
if (b === ESC) {
|
|
11
|
+
if (i === buf.length - 1) {
|
|
12
|
+
keys.push({ type: 'escape' });
|
|
13
|
+
i++;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const next = buf[i + 1];
|
|
17
|
+
if (next === 0x5b) {
|
|
18
|
+
let j = i + 2;
|
|
19
|
+
while (j < buf.length && isParamOrInter(buf[j]))
|
|
20
|
+
j++;
|
|
21
|
+
if (j < buf.length && isFinal(buf[j])) {
|
|
22
|
+
const final = buf[j];
|
|
23
|
+
const bare = j === i + 2;
|
|
24
|
+
if (bare && final === 0x41)
|
|
25
|
+
keys.push({ type: 'up' });
|
|
26
|
+
else if (bare && final === 0x42)
|
|
27
|
+
keys.push({ type: 'down' });
|
|
28
|
+
else
|
|
29
|
+
keys.push({ type: 'other' });
|
|
30
|
+
i = j + 1;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
keys.push({ type: 'other' });
|
|
34
|
+
i = buf.length;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (next === 0x4f && i + 2 < buf.length) {
|
|
38
|
+
const final = buf[i + 2];
|
|
39
|
+
if (final === 0x41)
|
|
40
|
+
keys.push({ type: 'up' });
|
|
41
|
+
else if (final === 0x42)
|
|
42
|
+
keys.push({ type: 'down' });
|
|
43
|
+
else
|
|
44
|
+
keys.push({ type: 'other' });
|
|
45
|
+
i += 3;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
keys.push({ type: 'other' });
|
|
49
|
+
i += 2;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (b === 0x03) {
|
|
53
|
+
keys.push({ type: 'interrupt' });
|
|
54
|
+
i++;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (b === 0x1a) {
|
|
58
|
+
keys.push({ type: 'suspend' });
|
|
59
|
+
i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (b === 0x0d || b === 0x0a) {
|
|
63
|
+
keys.push({ type: 'enter' });
|
|
64
|
+
i++;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (b === 0x6b) {
|
|
68
|
+
keys.push({ type: 'up' });
|
|
69
|
+
i++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (b === 0x6a) {
|
|
73
|
+
keys.push({ type: 'down' });
|
|
74
|
+
i++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (b >= 0x31 && b <= 0x39) {
|
|
78
|
+
keys.push({ type: 'digit', value: b - 0x30 });
|
|
79
|
+
i++;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
keys.push({ type: 'other' });
|
|
83
|
+
i++;
|
|
84
|
+
}
|
|
85
|
+
return keys;
|
|
86
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1presence/bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.92.0",
|
|
4
4
|
"description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"start": "node dist/index.js"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
24
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.257",
|
|
25
25
|
"ws": "^8.20.0",
|
|
26
26
|
"zod": "^4.0.0"
|
|
27
27
|
},
|