compact-agent 1.31.2 → 1.32.1
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/command-palette.d.ts +21 -0
- package/dist/command-palette.js +106 -0
- package/dist/command-palette.js.map +1 -0
- package/dist/index.js +215 -6
- package/dist/index.js.map +1 -1
- package/dist/openrouter-models.d.ts +44 -0
- package/dist/openrouter-models.js +112 -0
- package/dist/openrouter-models.js.map +1 -0
- package/dist/picker.d.ts +34 -0
- package/dist/picker.js +225 -0
- package/dist/picker.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter model catalog fetcher.
|
|
3
|
+
*
|
|
4
|
+
* GET https://openrouter.ai/api/v1/models returns the full catalog
|
|
5
|
+
* (300+ entries) with per-token pricing in USD. We cache the result
|
|
6
|
+
* for the duration of the process — the catalog only changes when
|
|
7
|
+
* OpenRouter adds/removes models, which is rare enough that one
|
|
8
|
+
* fetch per REPL session is the right trade-off.
|
|
9
|
+
*
|
|
10
|
+
* Pricing in the response is per-token; we convert to per-1M tokens
|
|
11
|
+
* for display because that's how everyone quotes LLM costs.
|
|
12
|
+
*
|
|
13
|
+
* No auth required for /models (it's public). We hit it without the
|
|
14
|
+
* user's API key so it works even before the user has finished
|
|
15
|
+
* configuring their key.
|
|
16
|
+
*/
|
|
17
|
+
export interface OpenRouterModel {
|
|
18
|
+
/** Canonical model ID, e.g. "anthropic/claude-sonnet-4". */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Display name, e.g. "Claude Sonnet 4". */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Context window in tokens, or null if unknown. */
|
|
23
|
+
contextLength: number | null;
|
|
24
|
+
/** USD per million input tokens. */
|
|
25
|
+
promptPerM: number;
|
|
26
|
+
/** USD per million output tokens. */
|
|
27
|
+
completionPerM: number;
|
|
28
|
+
/** True when the model is free (both prompt + completion = 0). */
|
|
29
|
+
isFree: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Fetch the OpenRouter model catalog, normalized + sorted (free
|
|
33
|
+
* models first, then alphabetical by ID).
|
|
34
|
+
*
|
|
35
|
+
* Returns an empty array on any network / parse failure instead of
|
|
36
|
+
* throwing — the caller is typically the model-picker, which can
|
|
37
|
+
* gracefully say "couldn't fetch models" without aborting the REPL.
|
|
38
|
+
*/
|
|
39
|
+
export declare function fetchOpenRouterModels(): Promise<OpenRouterModel[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Format the per-million pricing as a compact column for use in
|
|
42
|
+
* the picker's hint field. "in:$0.14 out:$0.28 · 128k" style.
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatPricing(m: OpenRouterModel): string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter model catalog fetcher.
|
|
3
|
+
*
|
|
4
|
+
* GET https://openrouter.ai/api/v1/models returns the full catalog
|
|
5
|
+
* (300+ entries) with per-token pricing in USD. We cache the result
|
|
6
|
+
* for the duration of the process — the catalog only changes when
|
|
7
|
+
* OpenRouter adds/removes models, which is rare enough that one
|
|
8
|
+
* fetch per REPL session is the right trade-off.
|
|
9
|
+
*
|
|
10
|
+
* Pricing in the response is per-token; we convert to per-1M tokens
|
|
11
|
+
* for display because that's how everyone quotes LLM costs.
|
|
12
|
+
*
|
|
13
|
+
* No auth required for /models (it's public). We hit it without the
|
|
14
|
+
* user's API key so it works even before the user has finished
|
|
15
|
+
* configuring their key.
|
|
16
|
+
*/
|
|
17
|
+
let _cache = null;
|
|
18
|
+
let _cacheAt = 0;
|
|
19
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
20
|
+
/**
|
|
21
|
+
* Fetch the OpenRouter model catalog, normalized + sorted (free
|
|
22
|
+
* models first, then alphabetical by ID).
|
|
23
|
+
*
|
|
24
|
+
* Returns an empty array on any network / parse failure instead of
|
|
25
|
+
* throwing — the caller is typically the model-picker, which can
|
|
26
|
+
* gracefully say "couldn't fetch models" without aborting the REPL.
|
|
27
|
+
*/
|
|
28
|
+
export async function fetchOpenRouterModels() {
|
|
29
|
+
// Cache hit?
|
|
30
|
+
if (_cache && (Date.now() - _cacheAt) < CACHE_TTL_MS) {
|
|
31
|
+
return _cache;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const resp = await fetch('https://openrouter.ai/api/v1/models', {
|
|
35
|
+
// Quick timeout — picker is interactive, can't sit on a stuck
|
|
36
|
+
// request. If the network is slow the user can use /model <id>
|
|
37
|
+
// directly.
|
|
38
|
+
signal: AbortSignal.timeout(8000),
|
|
39
|
+
headers: { 'User-Agent': 'compact-agent/1.x' },
|
|
40
|
+
});
|
|
41
|
+
if (!resp.ok)
|
|
42
|
+
return [];
|
|
43
|
+
const json = await resp.json();
|
|
44
|
+
const raw = json.data ?? [];
|
|
45
|
+
const models = [];
|
|
46
|
+
for (const r of raw) {
|
|
47
|
+
if (!r.id)
|
|
48
|
+
continue;
|
|
49
|
+
const prompt = parsePrice(r.pricing?.prompt);
|
|
50
|
+
const completion = parsePrice(r.pricing?.completion);
|
|
51
|
+
models.push({
|
|
52
|
+
id: r.id,
|
|
53
|
+
name: r.name ?? r.id,
|
|
54
|
+
contextLength: typeof r.context_length === 'number' ? r.context_length : null,
|
|
55
|
+
promptPerM: prompt * 1_000_000,
|
|
56
|
+
completionPerM: completion * 1_000_000,
|
|
57
|
+
isFree: prompt === 0 && completion === 0,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Sort: free first, then alphabetical by ID. Cheaper paid
|
|
61
|
+
// models float to the top within each group implicitly because
|
|
62
|
+
// ID alphabetical happens to put well-known cheap-tier vendors
|
|
63
|
+
// (anthropic, deepseek, google) near the top.
|
|
64
|
+
models.sort((a, b) => {
|
|
65
|
+
if (a.isFree !== b.isFree)
|
|
66
|
+
return a.isFree ? -1 : 1;
|
|
67
|
+
return a.id.localeCompare(b.id);
|
|
68
|
+
});
|
|
69
|
+
_cache = models;
|
|
70
|
+
_cacheAt = Date.now();
|
|
71
|
+
return models;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function parsePrice(p) {
|
|
78
|
+
if (p === undefined || p === null)
|
|
79
|
+
return 0;
|
|
80
|
+
if (typeof p === 'number')
|
|
81
|
+
return p;
|
|
82
|
+
const n = parseFloat(p);
|
|
83
|
+
return Number.isFinite(n) ? n : 0;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Format the per-million pricing as a compact column for use in
|
|
87
|
+
* the picker's hint field. "in:$0.14 out:$0.28 · 128k" style.
|
|
88
|
+
*/
|
|
89
|
+
export function formatPricing(m) {
|
|
90
|
+
if (m.isFree) {
|
|
91
|
+
return `FREE · ${formatCtx(m.contextLength)}`;
|
|
92
|
+
}
|
|
93
|
+
return `in:$${formatPrice(m.promptPerM)} out:$${formatPrice(m.completionPerM)} · ${formatCtx(m.contextLength)}`;
|
|
94
|
+
}
|
|
95
|
+
function formatPrice(n) {
|
|
96
|
+
// Sub-$1: show two decimals. $1+: show one or whole-dollar.
|
|
97
|
+
if (n < 1)
|
|
98
|
+
return n.toFixed(2);
|
|
99
|
+
if (n < 10)
|
|
100
|
+
return n.toFixed(1);
|
|
101
|
+
return n.toFixed(0);
|
|
102
|
+
}
|
|
103
|
+
function formatCtx(ctx) {
|
|
104
|
+
if (!ctx)
|
|
105
|
+
return '?';
|
|
106
|
+
if (ctx >= 1_000_000)
|
|
107
|
+
return `${(ctx / 1_000_000).toFixed(1)}M`;
|
|
108
|
+
if (ctx >= 1_000)
|
|
109
|
+
return `${Math.round(ctx / 1_000)}k`;
|
|
110
|
+
return String(ctx);
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=openrouter-models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter-models.js","sourceRoot":"","sources":["../src/openrouter-models.ts"],"names":[],"mappings":"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"}
|
package/dist/picker.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface PickerItem<T = string> {
|
|
2
|
+
/** The line shown to the user. Plain text — no ANSI codes. */
|
|
3
|
+
label: string;
|
|
4
|
+
/** Optional right-aligned hint (e.g. pricing, key combo). */
|
|
5
|
+
hint?: string;
|
|
6
|
+
/** Optional second line under the label (e.g. description). */
|
|
7
|
+
description?: string;
|
|
8
|
+
/** The value returned when this item is selected. */
|
|
9
|
+
value: T;
|
|
10
|
+
}
|
|
11
|
+
export interface PickerOptions {
|
|
12
|
+
/** Title shown at the top of the picker. */
|
|
13
|
+
title?: string;
|
|
14
|
+
/** Footer hint about what's happening (overrides default). */
|
|
15
|
+
footer?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Filtering: when true (default), the user can type to narrow the
|
|
18
|
+
* list. Set to false for pickers where you want pure navigation
|
|
19
|
+
* (rare).
|
|
20
|
+
*/
|
|
21
|
+
filterable?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Pre-fill the filter with this string before showing the picker.
|
|
24
|
+
* Use case: triggering the picker via a specific key (`/`) and
|
|
25
|
+
* wanting that character already in the filter so the user can
|
|
26
|
+
* keep typing to narrow without re-typing the trigger.
|
|
27
|
+
*/
|
|
28
|
+
initialFilter?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Show the picker and resolve with the user's selection (or null
|
|
32
|
+
* on cancel). Restores terminal state cleanly on either path.
|
|
33
|
+
*/
|
|
34
|
+
export declare function pick<T>(items: PickerItem<T>[], opts?: PickerOptions): Promise<T | null>;
|
package/dist/picker.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal list picker — an interactive arrow-key navigation widget
|
|
3
|
+
* that takes over the screen briefly, lets the user filter + select
|
|
4
|
+
* from a list, and returns the chosen value (or null on cancel).
|
|
5
|
+
*
|
|
6
|
+
* Design choice: alt-screen, not inline.
|
|
7
|
+
*
|
|
8
|
+
* Inline pickers (write a list below the cursor, redraw on each
|
|
9
|
+
* keystroke) are simpler to implement but fragile — they break when
|
|
10
|
+
* the terminal scrolls, when the live-queue scroll-region is active,
|
|
11
|
+
* or when items overflow the visible rows. The alt-screen pattern
|
|
12
|
+
* (`\x1b[?1049h` / `\x1b[?1049l`) is what `git diff --interactive`,
|
|
13
|
+
* `less`, `vim`, and `fzf` all use: switch to a fresh screen buffer,
|
|
14
|
+
* render the picker as a full-screen widget, exit back to the normal
|
|
15
|
+
* screen with the original contents intact.
|
|
16
|
+
*
|
|
17
|
+
* Trade-off: the user briefly loses sight of the surrounding REPL
|
|
18
|
+
* output during selection. In exchange, the picker is robust against
|
|
19
|
+
* any terminal state — it can be invoked from any point in the chat
|
|
20
|
+
* without coordinating with the live queue, current prompt, scroll
|
|
21
|
+
* position, etc.
|
|
22
|
+
*
|
|
23
|
+
* Key handling: bytes parsed from raw stdin. Recognized:
|
|
24
|
+
* Arrow Up / Down move selection
|
|
25
|
+
* Page Up / Down move 10 at a time
|
|
26
|
+
* Home / End jump to first / last
|
|
27
|
+
* Enter select current item
|
|
28
|
+
* Esc cancel (returns null)
|
|
29
|
+
* Ctrl+C cancel (returns null)
|
|
30
|
+
* Backspace delete last filter char
|
|
31
|
+
* Printable ASCII append to filter, reset selection to 0
|
|
32
|
+
*
|
|
33
|
+
* Returns the `value` field of the chosen item, or null if cancelled.
|
|
34
|
+
*/
|
|
35
|
+
import { stdin, stdout } from 'node:process';
|
|
36
|
+
// ANSI control sequences. Centralized so the rendering loop stays
|
|
37
|
+
// readable.
|
|
38
|
+
const ANSI = {
|
|
39
|
+
altScreenOn: '\x1b[?1049h',
|
|
40
|
+
altScreenOff: '\x1b[?1049l',
|
|
41
|
+
cursorHide: '\x1b[?25l',
|
|
42
|
+
cursorShow: '\x1b[?25h',
|
|
43
|
+
clearScreen: '\x1b[2J\x1b[H',
|
|
44
|
+
reverse: '\x1b[7m',
|
|
45
|
+
dim: '\x1b[2m',
|
|
46
|
+
bold: '\x1b[1m',
|
|
47
|
+
reset: '\x1b[0m',
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Show the picker and resolve with the user's selection (or null
|
|
51
|
+
* on cancel). Restores terminal state cleanly on either path.
|
|
52
|
+
*/
|
|
53
|
+
export async function pick(items, opts = {}) {
|
|
54
|
+
if (items.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
let filter = opts.initialFilter ?? '';
|
|
58
|
+
let selected = 0;
|
|
59
|
+
const wasRaw = stdin.isRaw;
|
|
60
|
+
const filterable = opts.filterable !== false;
|
|
61
|
+
function visibleItems() {
|
|
62
|
+
if (!filter)
|
|
63
|
+
return items;
|
|
64
|
+
const f = filter.toLowerCase();
|
|
65
|
+
return items.filter((i) => i.label.toLowerCase().includes(f) ||
|
|
66
|
+
(i.description ?? '').toLowerCase().includes(f) ||
|
|
67
|
+
(i.hint ?? '').toLowerCase().includes(f));
|
|
68
|
+
}
|
|
69
|
+
function render() {
|
|
70
|
+
const visible = visibleItems();
|
|
71
|
+
if (visible.length === 0) {
|
|
72
|
+
selected = 0;
|
|
73
|
+
}
|
|
74
|
+
else if (selected >= visible.length) {
|
|
75
|
+
selected = visible.length - 1;
|
|
76
|
+
}
|
|
77
|
+
stdout.write(ANSI.clearScreen);
|
|
78
|
+
// Title row.
|
|
79
|
+
if (opts.title) {
|
|
80
|
+
stdout.write(`${ANSI.bold} ${opts.title}${ANSI.reset}\n`);
|
|
81
|
+
}
|
|
82
|
+
// Filter row. The trailing ▮ is a visible cursor since we hid
|
|
83
|
+
// the real one (reduces flicker on each render).
|
|
84
|
+
if (filterable) {
|
|
85
|
+
stdout.write(` ${ANSI.dim}filter:${ANSI.reset} ${filter}${ANSI.dim}▮${ANSI.reset}\n`);
|
|
86
|
+
}
|
|
87
|
+
stdout.write('\n');
|
|
88
|
+
// Item list. Window around the selection so we always show
|
|
89
|
+
// the selected row even with hundreds of items. Reserve ~5
|
|
90
|
+
// rows for header + footer.
|
|
91
|
+
const termRows = stdout.rows || 24;
|
|
92
|
+
const itemSlot = Math.max(5, termRows - 7);
|
|
93
|
+
const startIdx = Math.max(0, selected - Math.floor(itemSlot / 2));
|
|
94
|
+
const endIdx = Math.min(visible.length, startIdx + itemSlot);
|
|
95
|
+
for (let i = startIdx; i < endIdx; i++) {
|
|
96
|
+
const item = visible[i];
|
|
97
|
+
const isSel = i === selected;
|
|
98
|
+
const hint = item.hint ? ` ${ANSI.dim}${item.hint}${ANSI.reset}` : '';
|
|
99
|
+
const prefix = isSel ? `${ANSI.reverse} ▸ ` : ' ';
|
|
100
|
+
const suffix = isSel ? `${ANSI.reset}` : '';
|
|
101
|
+
stdout.write(`${prefix}${item.label}${suffix}${hint}\n`);
|
|
102
|
+
if (item.description) {
|
|
103
|
+
const descPrefix = isSel ? `${ANSI.reverse} ` : ' ';
|
|
104
|
+
stdout.write(`${descPrefix}${ANSI.dim}${item.description}${ANSI.reset}${suffix}\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (visible.length === 0) {
|
|
108
|
+
stdout.write(` ${ANSI.dim}(no matches — Backspace to clear filter, Esc to cancel)${ANSI.reset}\n`);
|
|
109
|
+
}
|
|
110
|
+
// Footer.
|
|
111
|
+
stdout.write('\n');
|
|
112
|
+
const footerText = opts.footer ??
|
|
113
|
+
`${visible.length}/${items.length} • ↑↓ navigate • Enter select • Esc cancel${filterable ? ' • type to filter' : ''}`;
|
|
114
|
+
stdout.write(` ${ANSI.dim}${footerText}${ANSI.reset}\n`);
|
|
115
|
+
}
|
|
116
|
+
function cleanup() {
|
|
117
|
+
stdin.removeListener('data', onData);
|
|
118
|
+
try {
|
|
119
|
+
stdin.setRawMode(wasRaw);
|
|
120
|
+
}
|
|
121
|
+
catch { /* noop */ }
|
|
122
|
+
stdout.write(ANSI.cursorShow);
|
|
123
|
+
stdout.write(ANSI.altScreenOff);
|
|
124
|
+
}
|
|
125
|
+
function onData(buf) {
|
|
126
|
+
const visible = visibleItems();
|
|
127
|
+
// Ctrl+C — cancel. Has to win over everything else.
|
|
128
|
+
if (buf.length === 1 && buf[0] === 0x03) {
|
|
129
|
+
cleanup();
|
|
130
|
+
resolve(null);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Esc — cancel. But Esc is also the start byte of ANSI escape
|
|
134
|
+
// sequences (arrows, function keys), so only bare Esc (single
|
|
135
|
+
// byte) counts as a cancel. Arrows arrive as a 3+ byte chunk.
|
|
136
|
+
if (buf.length === 1 && buf[0] === 0x1B) {
|
|
137
|
+
cleanup();
|
|
138
|
+
resolve(null);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Enter (CR or LF).
|
|
142
|
+
if (buf.length === 1 && (buf[0] === 0x0D || buf[0] === 0x0A)) {
|
|
143
|
+
if (visible.length === 0)
|
|
144
|
+
return;
|
|
145
|
+
const chosen = visible[selected];
|
|
146
|
+
cleanup();
|
|
147
|
+
resolve(chosen ? chosen.value : null);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Backspace (DEL 0x7F on POSIX, BS 0x08 on Windows).
|
|
151
|
+
if (buf.length === 1 && (buf[0] === 0x7F || buf[0] === 0x08)) {
|
|
152
|
+
if (filterable && filter.length > 0) {
|
|
153
|
+
filter = filter.slice(0, -1);
|
|
154
|
+
selected = 0;
|
|
155
|
+
render();
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Arrow keys arrive as `Esc [ <code>` (typically 3 bytes).
|
|
160
|
+
// Page Up / Down arrive as `Esc [ 5 ~` / `Esc [ 6 ~`.
|
|
161
|
+
// Home / End vary: `Esc [ H`, `Esc [ F`, or `Esc [ 1 ~` / `Esc [ 4 ~`.
|
|
162
|
+
if (buf.length >= 3 && buf[0] === 0x1B && buf[1] === 0x5B) {
|
|
163
|
+
const code = buf[2];
|
|
164
|
+
if (code === 0x41) { // Up
|
|
165
|
+
if (visible.length > 0)
|
|
166
|
+
selected = (selected - 1 + visible.length) % visible.length;
|
|
167
|
+
render();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (code === 0x42) { // Down
|
|
171
|
+
if (visible.length > 0)
|
|
172
|
+
selected = (selected + 1) % visible.length;
|
|
173
|
+
render();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (code === 0x48) { // Home
|
|
177
|
+
selected = 0;
|
|
178
|
+
render();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (code === 0x46) { // End
|
|
182
|
+
selected = Math.max(0, visible.length - 1);
|
|
183
|
+
render();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (buf.length >= 4 && (code === 0x35 || code === 0x36) && buf[3] === 0x7E) {
|
|
187
|
+
// Page Up (5~) / Page Down (6~) — move by 10.
|
|
188
|
+
const step = code === 0x35 ? -10 : 10;
|
|
189
|
+
if (visible.length > 0) {
|
|
190
|
+
selected = Math.max(0, Math.min(visible.length - 1, selected + step));
|
|
191
|
+
}
|
|
192
|
+
render();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Unknown escape sequence — ignore.
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// Otherwise: treat as filter input, but only printable ASCII.
|
|
199
|
+
// Multi-byte UTF-8 entry (e.g. paste) gets appended as the
|
|
200
|
+
// string it represents; the regex check keeps control bytes
|
|
201
|
+
// out.
|
|
202
|
+
if (filterable) {
|
|
203
|
+
const s = buf.toString('utf-8');
|
|
204
|
+
// Allow printable ASCII + extended ranges; reject control
|
|
205
|
+
// chars + the escape we already handled.
|
|
206
|
+
if (/^[\x20-\x7E -]+$/.test(s)) {
|
|
207
|
+
filter += s;
|
|
208
|
+
selected = 0;
|
|
209
|
+
render();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Enter the picker.
|
|
214
|
+
stdout.write(ANSI.altScreenOn);
|
|
215
|
+
stdout.write(ANSI.cursorHide);
|
|
216
|
+
try {
|
|
217
|
+
stdin.setRawMode(true);
|
|
218
|
+
}
|
|
219
|
+
catch { /* noop */ }
|
|
220
|
+
stdin.on('data', onData);
|
|
221
|
+
stdin.resume();
|
|
222
|
+
render();
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.js","sourceRoot":"","sources":["../src/picker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compact-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.32.1",
|
|
4
4
|
"description": "Terminal AI coding CLI. Speaks any OpenAI-compatible API (OpenRouter, OpenAI, NVIDIA, Ollama, LM Studio, DeepSeek). Modes, slash commands, multi-agent swarming, key-rotation pool, optional voice + screen-reader, sandbox + permission gates, persistent input box, bundled everything-claude-code skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|