@crouton-kit/crouter 0.3.70 → 0.3.79
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 +1 -1
- package/dist/build-root.d.ts +12 -4
- package/dist/build-root.js +25 -6
- package/dist/builtin-memory/00-runtime-base.md +3 -10
- package/dist/builtin-memory/04-base-worker.md +18 -0
- package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +82 -5
- package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +13 -34
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +1115 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +234 -71
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +2 -9
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +2 -516
- package/dist/builtin-pi-packages/pi-crtr-extensions/package-lock.json +2 -2
- package/dist/builtin-pi-packages/pi-mode-switch/README.md +11 -39
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +20 -15
- package/dist/builtin-pi-packages/pi-mode-switch/package.json +1 -1
- package/dist/builtin-views/canvas/tui.mjs +8 -9
- package/dist/builtin-views/chat/tui.mjs +12 -12
- package/dist/builtin-views/git-pr/tui.mjs +7 -8
- package/dist/builtin-views/inbox/tui.mjs +27 -41
- package/dist/builtin-views/linkedin/tui.mjs +23 -37
- package/dist/builtin-views/prompt-review/tui.mjs +11 -11
- package/dist/builtin-views/settings/tui.mjs +11 -11
- package/dist/builtin-views/workspace-sidebar/tui.mjs +8 -9
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +11 -6
- package/dist/clients/attach/__tests__/attach-keybindings.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/attach-keybindings.test.js +113 -0
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +1 -1
- package/dist/clients/attach/__tests__/mermaid-render.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/mermaid-render.test.js +28 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +110 -0
- package/dist/clients/attach/attach-cmd.d.ts +9 -1
- package/dist/clients/attach/attach-cmd.js +841 -792
- package/dist/clients/attach/auth-pickers.d.ts +0 -12
- package/dist/clients/attach/auth-pickers.js +64 -15
- package/dist/clients/attach/canvas-panels.js +2 -3
- package/dist/clients/attach/chat-view.d.ts +7 -8
- package/dist/clients/attach/chat-view.js +119 -79
- package/dist/clients/attach/edit-diff-render.d.ts +6 -0
- package/dist/clients/attach/edit-diff-render.js +263 -0
- package/dist/clients/attach/graph-overlay.d.ts +12 -2
- package/dist/clients/attach/graph-overlay.js +83 -33
- package/dist/clients/attach/input-controller.d.ts +28 -2
- package/dist/clients/attach/input-controller.js +38 -5
- package/dist/clients/attach/mermaid-render.js +5 -2
- package/dist/clients/attach/pickers.d.ts +8 -7
- package/dist/clients/attach/pickers.js +11 -17
- package/dist/clients/attach/slash-commands.d.ts +9 -0
- package/dist/clients/attach/slash-commands.js +127 -7
- package/dist/clients/attach/titled-editor.d.ts +12 -1
- package/dist/clients/attach/titled-editor.js +103 -8
- package/dist/commands/canvas-browse.js +2 -2
- package/dist/commands/human/queue.js +3 -4
- package/dist/commands/memory/lint.js +39 -5
- package/dist/commands/memory/write.js +1 -0
- package/dist/commands/node.js +9 -2
- package/dist/commands/pkg/plugin-inspect.js +22 -1
- package/dist/commands/pkg/plugin-manage.js +31 -9
- package/dist/commands/surface-tmux-spread.js +1 -3
- package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-keybindings.test.js +55 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/config-model-ladders.test.js +121 -0
- package/dist/commands/sys/__tests__/setup-core.test.js +158 -1
- package/dist/commands/sys/config.js +18 -21
- package/dist/commands/sys/doctor.js +42 -4
- package/dist/commands/sys/setup-core.d.ts +49 -1
- package/dist/commands/sys/setup-core.js +161 -4
- package/dist/commands/sys/setup.d.ts +88 -0
- package/dist/commands/sys/setup.js +943 -169
- package/dist/commands/view-pick.d.ts +4 -0
- package/dist/commands/view-pick.js +17 -7
- package/dist/core/__tests__/base-worker-prompt.test.d.ts +1 -0
- package/dist/core/__tests__/base-worker-prompt.test.js +24 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +232 -1
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +34 -9
- package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +1 -0
- package/dist/core/__tests__/command-plugins-surfaces.test.js +298 -0
- package/dist/core/__tests__/command-plugins.test.d.ts +1 -0
- package/dist/core/__tests__/command-plugins.test.js +444 -0
- package/dist/core/__tests__/fault-classifier.test.js +15 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +6 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +9 -1
- package/dist/core/__tests__/full/broker-dialogs.test.js +7 -2
- package/dist/core/__tests__/host-teardown-process-group.test.js +15 -4
- package/dist/core/__tests__/preview-registry-sync.test.js +30 -1
- package/dist/core/__tests__/scope-crouter-home-fence.test.d.ts +1 -0
- package/dist/core/__tests__/scope-crouter-home-fence.test.js +55 -0
- package/dist/core/__tests__/stream-watchdog.test.d.ts +1 -0
- package/dist/core/__tests__/stream-watchdog.test.js +70 -0
- package/dist/core/__tests__/tmux-surface.test.js +72 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +23 -5
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.d.ts +6 -0
- package/dist/core/canvas/browse/app.js +230 -41
- package/dist/core/canvas/browse/model.d.ts +10 -6
- package/dist/core/canvas/browse/model.js +20 -12
- package/dist/core/canvas/browse/pins.d.ts +4 -0
- package/dist/core/canvas/browse/pins.js +29 -0
- package/dist/core/canvas/browse/render.d.ts +13 -1
- package/dist/core/canvas/browse/render.js +72 -15
- package/dist/core/canvas/nav-model.js +5 -11
- package/dist/core/canvas/paths.d.ts +5 -0
- package/dist/core/canvas/paths.js +7 -0
- package/dist/core/command-plugins/adapter.d.ts +15 -0
- package/dist/core/command-plugins/adapter.js +145 -0
- package/dist/core/command-plugins/compose.d.ts +5 -0
- package/dist/core/command-plugins/compose.js +56 -0
- package/dist/core/command-plugins/discovery.d.ts +104 -0
- package/dist/core/command-plugins/discovery.js +565 -0
- package/dist/core/config.d.ts +48 -0
- package/dist/core/config.js +228 -110
- package/dist/core/fault-classifier.js +1 -1
- package/dist/core/fs-utils.d.ts +1 -0
- package/dist/core/fs-utils.js +15 -1
- package/dist/core/keybindings/__tests__/bespoke-consumers.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/bespoke-consumers.test.js +40 -0
- package/dist/core/keybindings/__tests__/resolve.test.d.ts +1 -0
- package/dist/core/keybindings/__tests__/resolve.test.js +224 -0
- package/dist/core/keybindings/catalog.d.ts +14 -0
- package/dist/core/keybindings/catalog.js +257 -0
- package/dist/core/keybindings/index.d.ts +5 -0
- package/dist/core/keybindings/index.js +4 -0
- package/dist/core/keybindings/match.d.ts +29 -0
- package/dist/core/keybindings/match.js +99 -0
- package/dist/core/keybindings/persistence.d.ts +14 -0
- package/dist/core/keybindings/persistence.js +31 -0
- package/dist/core/keybindings/resolve.d.ts +14 -0
- package/dist/core/keybindings/resolve.js +305 -0
- package/dist/core/keybindings/types.d.ts +37 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/memory-resolver.d.ts +1 -1
- package/dist/core/memory-resolver.js +10 -3
- package/dist/core/predicate.d.ts +5 -3
- package/dist/core/predicate.js +5 -3
- package/dist/core/profiles/select.d.ts +6 -0
- package/dist/core/profiles/select.js +86 -52
- package/dist/core/provider-management.d.ts +12 -0
- package/dist/core/provider-management.js +24 -0
- package/dist/core/runtime/broker-protocol.d.ts +33 -5
- package/dist/core/runtime/broker.js +164 -14
- package/dist/core/runtime/launch.d.ts +39 -6
- package/dist/core/runtime/launch.js +78 -20
- package/dist/core/runtime/naming.js +3 -3
- package/dist/core/runtime/pi-cli.d.ts +6 -0
- package/dist/core/runtime/pi-cli.js +16 -2
- package/dist/core/runtime/pi-vendored.d.ts +8 -0
- package/dist/core/runtime/pi-vendored.js +14 -0
- package/dist/core/runtime/placement.d.ts +2 -2
- package/dist/core/runtime/placement.js +4 -1
- package/dist/core/runtime/promote.js +4 -0
- package/dist/core/runtime/recap.d.ts +3 -3
- package/dist/core/runtime/recap.js +75 -47
- package/dist/core/runtime/recycle.js +6 -1
- package/dist/core/runtime/reset.js +5 -0
- package/dist/core/runtime/session-list-cache.d.ts +33 -0
- package/dist/core/runtime/session-list-cache.js +338 -0
- package/dist/core/runtime/spawn.js +52 -45
- package/dist/core/runtime/stream-watchdog.d.ts +26 -0
- package/dist/core/runtime/stream-watchdog.js +75 -0
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +2 -2
- package/dist/core/runtime/tmux.d.ts +18 -15
- package/dist/core/runtime/tmux.js +217 -110
- package/dist/core/scope.js +27 -4
- package/dist/core/subscription-state.d.ts +90 -0
- package/dist/core/subscription-state.js +762 -0
- package/dist/core/tui/__tests__/host-keybindings.test.d.ts +1 -0
- package/dist/core/tui/__tests__/host-keybindings.test.js +112 -0
- package/dist/core/tui/host.d.ts +30 -1
- package/dist/core/tui/host.js +148 -34
- package/dist/core/view/contract.d.ts +17 -3
- package/dist/daemon/crtrd.js +253 -12
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +41 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +121 -0
- package/dist/pi-extensions/canvas-inbox-watcher.d.ts +1 -1
- package/dist/pi-extensions/canvas-inbox-watcher.js +203 -25
- package/dist/pi-extensions/canvas-recap.d.ts +4 -0
- package/dist/pi-extensions/canvas-recap.js +73 -46
- package/dist/pi-extensions/canvas-stophook.d.ts +11 -0
- package/dist/pi-extensions/canvas-stophook.js +33 -4
- package/dist/types.d.ts +9 -26
- package/dist/types.js +1 -28
- package/dist/web-client/assets/{index-DiFuLcp6.js → index--md2ylfi.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/dist/web-client/sw.js +1 -1
- package/package.json +8 -4
- package/dist/pi-extensions/canvas-nav.d.ts +0 -43
- package/dist/pi-extensions/canvas-nav.js +0 -640
- package/dist/pi-extensions/widget-order-bus.d.ts +0 -6
- package/dist/pi-extensions/widget-order-bus.js +0 -34
|
@@ -15,6 +15,37 @@ import { emitKeypressEvents } from 'node:readline';
|
|
|
15
15
|
import { listProfiles, loadProfileManifest, updateProfileLastUsed, createProfile, addProfileProject, ensureRootProfile, ROOT_PROFILE_ID, } from './manifest.js';
|
|
16
16
|
import { getDefaultProfileId, setDefaultProfileId, clearDefaultProfile, } from './default-binding.js';
|
|
17
17
|
import { stdoutColor } from '../output.js';
|
|
18
|
+
import { formatBinding, matchesTerminalInput, resolveUserKeybindings, } from '../keybindings/index.js';
|
|
19
|
+
const PROFILE_MENU_ACTIONS = [
|
|
20
|
+
'crtr.profile.menu.abort', 'crtr.profile.menu.up', 'crtr.profile.menu.down',
|
|
21
|
+
'crtr.profile.menu.select', 'crtr.profile.menu.accept-default',
|
|
22
|
+
'crtr.profile.menu.slot.1', 'crtr.profile.menu.slot.2', 'crtr.profile.menu.slot.3',
|
|
23
|
+
'crtr.profile.menu.slot.4', 'crtr.profile.menu.slot.5', 'crtr.profile.menu.slot.6',
|
|
24
|
+
'crtr.profile.menu.slot.7', 'crtr.profile.menu.slot.8', 'crtr.profile.menu.slot.9',
|
|
25
|
+
'crtr.profile.menu.create', 'crtr.profile.menu.search-existing', 'crtr.profile.menu.use-root',
|
|
26
|
+
'crtr.profile.menu.set-default', 'crtr.profile.menu.add-directory', 'crtr.profile.menu.unpin',
|
|
27
|
+
];
|
|
28
|
+
const PROFILE_SEARCH_ACTIONS = [
|
|
29
|
+
'crtr.profile.search.abort', 'crtr.profile.search.back', 'crtr.profile.search.pick',
|
|
30
|
+
'crtr.profile.search.up', 'crtr.profile.search.down', 'crtr.profile.search.delete',
|
|
31
|
+
];
|
|
32
|
+
function profileRawInput(str, k) {
|
|
33
|
+
return {
|
|
34
|
+
input: k.ctrl === true || k.meta === true ? (k.name ?? str) : str,
|
|
35
|
+
upArrow: k.name === 'up', downArrow: k.name === 'down',
|
|
36
|
+
return: k.name === 'return' || k.name === 'enter', escape: k.name === 'escape',
|
|
37
|
+
backspace: k.name === 'backspace', ctrl: k.ctrl, meta: k.meta, shift: k.shift,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function firstProfileMatch(bindings, ids, raw) {
|
|
41
|
+
return ids.find((id) => matchesTerminalInput(bindings, id, raw)) ?? null;
|
|
42
|
+
}
|
|
43
|
+
export function resolveProfileMenuAction(bindings, raw) {
|
|
44
|
+
return firstProfileMatch(bindings, PROFILE_MENU_ACTIONS, raw);
|
|
45
|
+
}
|
|
46
|
+
export function resolveProfileSearchAction(bindings, raw) {
|
|
47
|
+
return firstProfileMatch(bindings, PROFILE_SEARCH_ACTIONS, raw);
|
|
48
|
+
}
|
|
18
49
|
/** Resolve cwd to an absolute, realpath'd form so it compares against the
|
|
19
50
|
* realpath'd project dirs `createProfile`/`addProfileProject` already store
|
|
20
51
|
* (manifest.ts resolves every project dir through `realpathSync`). Falls back
|
|
@@ -154,14 +185,17 @@ function blockRows(block) {
|
|
|
154
185
|
}
|
|
155
186
|
return newlines + extra;
|
|
156
187
|
}
|
|
157
|
-
function renderMenu(title, subtitle, rows, sel, hint) {
|
|
188
|
+
function renderMenu(bindings, title, subtitle, rows, sel, hint) {
|
|
158
189
|
let out = `\n ${bold(title)}\n`;
|
|
159
190
|
if (subtitle !== undefined)
|
|
160
191
|
out += ` ${dim(subtitle)}\n`;
|
|
161
192
|
out += '\n';
|
|
162
193
|
let num = 0;
|
|
163
194
|
for (const [i, r] of rows.entries()) {
|
|
164
|
-
const
|
|
195
|
+
const slot = r.bindingId === undefined ? ++num : 0;
|
|
196
|
+
const tag = r.bindingId !== undefined
|
|
197
|
+
? formatBinding(bindings, r.bindingId)
|
|
198
|
+
: slot <= 9 ? formatBinding(bindings, `crtr.profile.menu.slot.${slot}`) : '';
|
|
165
199
|
const mark = i === sel ? HILITE : ' ';
|
|
166
200
|
const det = r.detail !== '' ? ' ' + dim(r.detail) : '';
|
|
167
201
|
const def = r.isDefault ? ' ' + accent('\u00b7 ' + (r.defaultText ?? 'default')) : '';
|
|
@@ -169,23 +203,22 @@ function renderMenu(title, subtitle, rows, sel, hint) {
|
|
|
169
203
|
}
|
|
170
204
|
if (hint !== undefined)
|
|
171
205
|
out += `\n ${dim(hint)}\n`;
|
|
172
|
-
out += `\n ${dim('
|
|
206
|
+
out += `\n ${dim(`${formatBinding(bindings, 'crtr.profile.menu.up')}/${formatBinding(bindings, 'crtr.profile.menu.down')} move ${formatBinding(bindings, 'crtr.profile.menu.select')} select ${formatBinding(bindings, 'crtr.profile.menu.accept-default')} default ${formatBinding(bindings, 'crtr.profile.menu.abort')} quit`)}\n`;
|
|
173
207
|
return out;
|
|
174
208
|
}
|
|
175
209
|
/** Drive a highlighted keypress menu to a resolution. Returns the selected row
|
|
176
|
-
* index, plus the action
|
|
177
|
-
|
|
178
|
-
function runMenu(title, subtitle, rows, opts = {}) {
|
|
210
|
+
* index, plus the semantic action when a row-scoped action was invoked. */
|
|
211
|
+
function runMenu(bindings, title, subtitle, rows, opts = {}) {
|
|
179
212
|
const numbered = [];
|
|
180
213
|
rows.forEach((r, i) => {
|
|
181
|
-
if (r.
|
|
214
|
+
if (r.bindingId === undefined)
|
|
182
215
|
numbered.push(i);
|
|
183
216
|
});
|
|
184
217
|
let sel = opts.initialIndex ?? 0;
|
|
185
218
|
return new Promise((resolve) => {
|
|
186
219
|
let prevLines = 0;
|
|
187
220
|
const draw = () => {
|
|
188
|
-
const block = renderMenu(title, subtitle, rows, sel, opts.hint);
|
|
221
|
+
const block = renderMenu(bindings, title, subtitle, rows, sel, opts.hint);
|
|
189
222
|
process.stdout.write((prevLines > 0 ? cursorUp(prevLines) + CLEAR_DOWN : '') + block);
|
|
190
223
|
prevLines = blockRows(block);
|
|
191
224
|
};
|
|
@@ -196,22 +229,23 @@ function runMenu(title, subtitle, rows, opts = {}) {
|
|
|
196
229
|
process.stdin.pause();
|
|
197
230
|
};
|
|
198
231
|
const onKey = (str, k) => {
|
|
199
|
-
|
|
232
|
+
const action = resolveProfileMenuAction(bindings, profileRawInput(str, k));
|
|
233
|
+
if (action === 'crtr.profile.menu.abort') {
|
|
200
234
|
cleanup();
|
|
201
235
|
process.stdout.write('\n');
|
|
202
236
|
process.exit(130);
|
|
203
237
|
}
|
|
204
|
-
if (
|
|
238
|
+
if (action === 'crtr.profile.menu.up') {
|
|
205
239
|
sel = (sel - 1 + rows.length) % rows.length;
|
|
206
240
|
draw();
|
|
207
241
|
return;
|
|
208
242
|
}
|
|
209
|
-
if (
|
|
243
|
+
if (action === 'crtr.profile.menu.down') {
|
|
210
244
|
sel = (sel + 1) % rows.length;
|
|
211
245
|
draw();
|
|
212
246
|
return;
|
|
213
247
|
}
|
|
214
|
-
if (
|
|
248
|
+
if (action === 'crtr.profile.menu.select') {
|
|
215
249
|
cleanup();
|
|
216
250
|
resolve({ index: sel });
|
|
217
251
|
return;
|
|
@@ -219,28 +253,28 @@ function runMenu(title, subtitle, rows, opts = {}) {
|
|
|
219
253
|
// Esc cancels navigation and accepts the default row (the MRU/pinned
|
|
220
254
|
// profile, or the root row on the no-coverage menu) — a safe exit, never
|
|
221
255
|
// a dead key. Ctrl-C still aborts crtr entirely.
|
|
222
|
-
if (
|
|
256
|
+
if (action === 'crtr.profile.menu.accept-default') {
|
|
223
257
|
cleanup();
|
|
224
258
|
resolve({ index: opts.initialIndex ?? 0 });
|
|
225
259
|
return;
|
|
226
260
|
}
|
|
227
|
-
if (
|
|
228
|
-
const d = Number.parseInt(
|
|
261
|
+
if (action?.startsWith('crtr.profile.menu.slot.')) {
|
|
262
|
+
const d = Number.parseInt(action.slice(-1), 10);
|
|
229
263
|
if (d >= 1 && d <= numbered.length) {
|
|
230
264
|
sel = numbered[d - 1];
|
|
231
265
|
draw();
|
|
232
266
|
}
|
|
233
267
|
return;
|
|
234
268
|
}
|
|
235
|
-
const hk = rows.findIndex((r) => r.
|
|
269
|
+
const hk = rows.findIndex((r) => r.bindingId !== undefined && r.bindingId === action);
|
|
236
270
|
if (hk >= 0) {
|
|
237
271
|
cleanup();
|
|
238
272
|
resolve({ index: hk });
|
|
239
273
|
return;
|
|
240
274
|
}
|
|
241
|
-
if (
|
|
275
|
+
if (action !== null && opts.actionBindingIds?.includes(action) === true) {
|
|
242
276
|
cleanup();
|
|
243
|
-
resolve({ index: sel, action
|
|
277
|
+
resolve({ index: sel, action });
|
|
244
278
|
}
|
|
245
279
|
};
|
|
246
280
|
emitKeypressEvents(process.stdin);
|
|
@@ -308,7 +342,7 @@ async function offerAddDirToProfile(rl, entry, cwd) {
|
|
|
308
342
|
* out (returns null → caller falls back to root). Because typing filters,
|
|
309
343
|
* navigation here is arrows-only — j/k are literal filter characters (fzf
|
|
310
344
|
* convention), unlike the non-search menus where j/k move. */
|
|
311
|
-
async function promptSelectExistingProfile() {
|
|
345
|
+
async function promptSelectExistingProfile(bindings) {
|
|
312
346
|
const all = [...listProfiles()]
|
|
313
347
|
.filter((e) => e.profileId !== ROOT_PROFILE_ID)
|
|
314
348
|
.sort((a, b) => (isMoreRecent(a.manifest.last_used_at, b.manifest.last_used_at) ? -1 : 1));
|
|
@@ -341,7 +375,7 @@ async function promptSelectExistingProfile() {
|
|
|
341
375
|
});
|
|
342
376
|
}
|
|
343
377
|
block += `\n ${accent('\u203a')} ${filter}${dim('\u2588')}\n`;
|
|
344
|
-
block += ` ${dim(
|
|
378
|
+
block += ` ${dim(`type to filter ${formatBinding(bindings, 'crtr.profile.search.up')}/${formatBinding(bindings, 'crtr.profile.search.down')} move ${formatBinding(bindings, 'crtr.profile.search.pick')} pick ${formatBinding(bindings, 'crtr.profile.search.back')} back`)}\n`;
|
|
345
379
|
process.stdout.write((prevLines > 0 ? cursorUp(prevLines) + CLEAR_DOWN : '') + block);
|
|
346
380
|
prevLines = blockRows(block);
|
|
347
381
|
};
|
|
@@ -352,39 +386,40 @@ async function promptSelectExistingProfile() {
|
|
|
352
386
|
process.stdin.pause();
|
|
353
387
|
};
|
|
354
388
|
const onKey = (str, k) => {
|
|
355
|
-
|
|
389
|
+
const action = resolveProfileSearchAction(bindings, profileRawInput(str, k));
|
|
390
|
+
if (action === 'crtr.profile.search.abort') {
|
|
356
391
|
cleanup();
|
|
357
392
|
process.stdout.write('\n');
|
|
358
393
|
process.exit(130);
|
|
359
394
|
}
|
|
360
|
-
if (
|
|
395
|
+
if (action === 'crtr.profile.search.back') {
|
|
361
396
|
cleanup();
|
|
362
397
|
resolve(null);
|
|
363
398
|
return;
|
|
364
399
|
}
|
|
365
400
|
const matches = matchesOf();
|
|
366
|
-
if (
|
|
401
|
+
if (action === 'crtr.profile.search.pick') {
|
|
367
402
|
if (matches.length > 0) {
|
|
368
403
|
cleanup();
|
|
369
404
|
resolve(matches[sel].profileId);
|
|
370
405
|
}
|
|
371
406
|
return;
|
|
372
407
|
}
|
|
373
|
-
if (
|
|
408
|
+
if (action === 'crtr.profile.search.up') {
|
|
374
409
|
if (matches.length > 0) {
|
|
375
410
|
sel = (sel - 1 + matches.length) % matches.length;
|
|
376
411
|
draw();
|
|
377
412
|
}
|
|
378
413
|
return;
|
|
379
414
|
}
|
|
380
|
-
if (
|
|
415
|
+
if (action === 'crtr.profile.search.down') {
|
|
381
416
|
if (matches.length > 0) {
|
|
382
417
|
sel = (sel + 1) % matches.length;
|
|
383
418
|
draw();
|
|
384
419
|
}
|
|
385
420
|
return;
|
|
386
421
|
}
|
|
387
|
-
if (
|
|
422
|
+
if (action === 'crtr.profile.search.delete') {
|
|
388
423
|
filter = filter.slice(0, -1);
|
|
389
424
|
sel = 0;
|
|
390
425
|
draw();
|
|
@@ -416,18 +451,18 @@ async function createProfileHere(rl, cwd) {
|
|
|
416
451
|
process.stdout.write(` ${accent('\u2713')} ${dim(`created profile "${name}"`)}\n`);
|
|
417
452
|
return profileId;
|
|
418
453
|
}
|
|
419
|
-
async function promptCreateOrRoot(cwd) {
|
|
454
|
+
async function promptCreateOrRoot(cwd, bindings) {
|
|
420
455
|
// Root is the highlighted default so a bare Enter (or Esc) still lands on the
|
|
421
456
|
// safe no-op, exactly as the old `[c/s/r]` prompt defaulted. Looped so Esc
|
|
422
457
|
// out of the search sub-list returns HERE rather than silently dropping to
|
|
423
458
|
// root — the create/select/root choice is always the fallback, never skipped.
|
|
424
459
|
const rows = [
|
|
425
|
-
{ label: 'create a profile here', detail: '',
|
|
426
|
-
{ label: 'select an existing profile', detail: '',
|
|
427
|
-
{ label: dim('use the root profile'), detail: '',
|
|
460
|
+
{ label: 'create a profile here', detail: '', bindingId: 'crtr.profile.menu.create' },
|
|
461
|
+
{ label: 'select an existing profile', detail: '', bindingId: 'crtr.profile.menu.search-existing' },
|
|
462
|
+
{ label: dim('use the root profile'), detail: '', bindingId: 'crtr.profile.menu.use-root', isDefault: true },
|
|
428
463
|
];
|
|
429
464
|
for (;;) {
|
|
430
|
-
const res = await runMenu('No profile covers this directory', undefined, rows, {
|
|
465
|
+
const res = await runMenu(bindings, 'No profile covers this directory', undefined, rows, {
|
|
431
466
|
initialIndex: 2,
|
|
432
467
|
});
|
|
433
468
|
if (res.index === 0) {
|
|
@@ -440,7 +475,7 @@ async function promptCreateOrRoot(cwd) {
|
|
|
440
475
|
}
|
|
441
476
|
}
|
|
442
477
|
if (res.index === 1) {
|
|
443
|
-
const selected = await promptSelectExistingProfile();
|
|
478
|
+
const selected = await promptSelectExistingProfile(bindings);
|
|
444
479
|
// Esc/back out of the search list (or no profiles to pick) → re-show this
|
|
445
480
|
// create/select/root menu, not a silent fall-through to root.
|
|
446
481
|
if (selected === null)
|
|
@@ -469,7 +504,7 @@ async function promptCreateOrRoot(cwd) {
|
|
|
469
504
|
* here: a claimed/covered directory always has at least a covering profile as a
|
|
470
505
|
* sane identity, so `[r]oot` belongs only to the no-coverage prompt
|
|
471
506
|
* (`promptCreateOrRoot`). Same raw-readline / boot-gating rationale. */
|
|
472
|
-
async function promptPickProfileOrCreate(candidates, cwd, exact, pinnedId) {
|
|
507
|
+
async function promptPickProfileOrCreate(candidates, cwd, exact, pinnedId, bindings) {
|
|
473
508
|
// MRU-first so the most-recently-used candidate is the bare-Enter default —
|
|
474
509
|
// the same profile the non-interactive path auto-picks. A per-cwd pin (set
|
|
475
510
|
// here with `d`) overrides that: it sorts to the top and becomes the
|
|
@@ -501,24 +536,22 @@ async function promptPickProfileOrCreate(candidates, cwd, exact, pinnedId) {
|
|
|
501
536
|
defaultText,
|
|
502
537
|
};
|
|
503
538
|
});
|
|
504
|
-
rows.push({ label: dim('create a new profile here'), detail: '',
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
// an existing pin.
|
|
509
|
-
const actionKeys = ['d'];
|
|
539
|
+
rows.push({ label: dim('create a new profile here'), detail: '', bindingId: 'crtr.profile.menu.create' });
|
|
540
|
+
// Row-scoped actions pin the highlighted profile, widen its purview (hidden
|
|
541
|
+
// when candidates already claim cwd exactly), or clear an existing pin.
|
|
542
|
+
const actionBindingIds = ['crtr.profile.menu.set-default'];
|
|
510
543
|
if (!exact)
|
|
511
|
-
|
|
544
|
+
actionBindingIds.push('crtr.profile.menu.add-directory');
|
|
512
545
|
if (pinned !== null)
|
|
513
|
-
|
|
514
|
-
const hintParts = ['
|
|
546
|
+
actionBindingIds.push('crtr.profile.menu.unpin');
|
|
547
|
+
const hintParts = [`${formatBinding(bindings, 'crtr.profile.menu.set-default')}=set default`];
|
|
515
548
|
if (!exact)
|
|
516
|
-
hintParts.push('
|
|
549
|
+
hintParts.push(`${formatBinding(bindings, 'crtr.profile.menu.add-directory')}=add dir here`);
|
|
517
550
|
if (pinned !== null)
|
|
518
|
-
hintParts.push('
|
|
519
|
-
const res = await runMenu(exact ? 'Choose a profile' : 'Select a profile', exact
|
|
551
|
+
hintParts.push(`${formatBinding(bindings, 'crtr.profile.menu.unpin')}=unpin`);
|
|
552
|
+
const res = await runMenu(bindings, exact ? 'Choose a profile' : 'Select a profile', exact
|
|
520
553
|
? 'Several profiles claim this directory as their project'
|
|
521
|
-
: 'This directory falls under existing profile(s)', rows, {
|
|
554
|
+
: 'This directory falls under existing profile(s)', rows, { actionBindingIds, hint: hintParts.join(' ') });
|
|
522
555
|
// The create row is last; selecting it (Enter on it, or `c`) creates here.
|
|
523
556
|
if (res.index >= ordered.length) {
|
|
524
557
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -530,16 +563,16 @@ async function promptPickProfileOrCreate(candidates, cwd, exact, pinnedId) {
|
|
|
530
563
|
}
|
|
531
564
|
}
|
|
532
565
|
const picked = ordered[res.index];
|
|
533
|
-
if (res.action === '
|
|
566
|
+
if (res.action === 'crtr.profile.menu.unpin') {
|
|
534
567
|
clearDefaultProfile(cwd);
|
|
535
568
|
process.stdout.write(` ${accent('\u2713')} ${dim('cleared the default for this directory')}\n`);
|
|
536
569
|
return picked.profileId;
|
|
537
570
|
}
|
|
538
|
-
if (res.action === '
|
|
571
|
+
if (res.action === 'crtr.profile.menu.set-default') {
|
|
539
572
|
setDefaultProfileId(cwd, picked.profileId);
|
|
540
573
|
process.stdout.write(` ${accent('\u2713')} ${dim(`"${picked.manifest.name}" is now the default here`)}\n`);
|
|
541
574
|
}
|
|
542
|
-
else if (res.action === '
|
|
575
|
+
else if (res.action === 'crtr.profile.menu.add-directory') {
|
|
543
576
|
addProfileProject(picked.profileId, cwd);
|
|
544
577
|
process.stdout.write(` ${accent('\u2713')} ${dim(`added this directory to "${picked.manifest.name}"`)}\n`);
|
|
545
578
|
}
|
|
@@ -576,6 +609,7 @@ async function promptPickProfileOrCreate(candidates, cwd, exact, pinnedId) {
|
|
|
576
609
|
* never stdout, which the caller may be piping. */
|
|
577
610
|
export async function selectProfileForCwd(cwd, explicitProfile, forcePicker = false) {
|
|
578
611
|
const resolvedCwd = resolveCwd(cwd);
|
|
612
|
+
const bindings = resolveUserKeybindings();
|
|
579
613
|
if (explicitProfile !== undefined && explicitProfile !== null && explicitProfile !== '') {
|
|
580
614
|
const entry = loadProfileManifest(explicitProfile);
|
|
581
615
|
// Selecting a profile from a directory it does not yet cover: offer to
|
|
@@ -633,10 +667,10 @@ export async function selectProfileForCwd(cwd, explicitProfile, forcePicker = fa
|
|
|
633
667
|
// the create-or-root prompt (root is only ever offered there).
|
|
634
668
|
const validPinnedId = pinned !== null ? pinned.profileId : null;
|
|
635
669
|
const chosen = exact.length > 1
|
|
636
|
-
? await promptPickProfileOrCreate(exact, resolvedCwd, true, validPinnedId)
|
|
670
|
+
? await promptPickProfileOrCreate(exact, resolvedCwd, true, validPinnedId, bindings)
|
|
637
671
|
: covering.length > 0
|
|
638
|
-
? await promptPickProfileOrCreate(covering, resolvedCwd, false, validPinnedId)
|
|
639
|
-
: await promptCreateOrRoot(resolvedCwd);
|
|
672
|
+
? await promptPickProfileOrCreate(covering, resolvedCwd, false, validPinnedId, bindings)
|
|
673
|
+
: await promptCreateOrRoot(resolvedCwd, bindings);
|
|
640
674
|
updateProfileLastUsed(chosen);
|
|
641
675
|
return chosen;
|
|
642
676
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The minimal shape of a Providers-panel row the removal router needs. */
|
|
2
|
+
export interface RemovableProviderRow {
|
|
3
|
+
providerId: string;
|
|
4
|
+
name: string;
|
|
5
|
+
managed: boolean;
|
|
6
|
+
configured?: boolean;
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Route a row's removal to the coordinated service (managed) or AuthStorage (non-managed) and
|
|
10
|
+
* return the truthful panel notice. May throw (e.g. an unknown-subscription error) — callers
|
|
11
|
+
* surface that as the notice. */
|
|
12
|
+
export declare function removeProviderRow(agentDir: string, row: RemovableProviderRow): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Removal routing for a Providers-panel row, kept in core (out of the TUI module) so both the
|
|
2
|
+
// `crtr sys setup` panel and the regression suite exercise the exact same guard.
|
|
3
|
+
//
|
|
4
|
+
// Final-review finding #1: a MANAGED provider — whether an account row (has a label) or the
|
|
5
|
+
// provider HEADER row (no label, the default cursor position) — MUST go through the coordinated
|
|
6
|
+
// removeManagedProvider so auth.json AND the rotation pool are torn down together (a header removes
|
|
7
|
+
// every account). Only a NON-managed configured provider uses the raw AuthStorage.logout(). Routing
|
|
8
|
+
// a managed header to logout() (the original bug) strips only auth.json, leaves the pool configured,
|
|
9
|
+
// and makes the panel claim "Removed" while a reload still shows the provider — the split-ownership
|
|
10
|
+
// bug this feature exists to eliminate.
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { AuthStorage } from '@earendil-works/pi-coding-agent';
|
|
13
|
+
import { removeManagedProvider } from './subscription-state.js';
|
|
14
|
+
/** Route a row's removal to the coordinated service (managed) or AuthStorage (non-managed) and
|
|
15
|
+
* return the truthful panel notice. May throw (e.g. an unknown-subscription error) — callers
|
|
16
|
+
* surface that as the notice. */
|
|
17
|
+
export function removeProviderRow(agentDir, row) {
|
|
18
|
+
if (row.managed) {
|
|
19
|
+
removeManagedProvider(row.providerId, row.label === undefined ? undefined : row.label === 'Default account' ? row.providerId : row.label);
|
|
20
|
+
return row.label === undefined ? `Removed ${row.name}` : `Removed ${row.name} account`;
|
|
21
|
+
}
|
|
22
|
+
AuthStorage.create(join(agentDir, 'auth.json')).logout(row.providerId);
|
|
23
|
+
return `Removed ${row.name}`;
|
|
24
|
+
}
|
|
@@ -169,6 +169,15 @@ export interface CycleModelFrame {
|
|
|
169
169
|
type: 'cycle_model';
|
|
170
170
|
direction?: 'forward' | 'backward';
|
|
171
171
|
}
|
|
172
|
+
/** `cycleLadder(direction)` — alt+m forward / alt+shift+m backward. Walks the INTERLEAVED model ladder
|
|
173
|
+
* (anthropic/ultra → openai/ultra → anthropic/strong → openai/strong → …),
|
|
174
|
+
* starting from the current model, applying each rung's own thinking level.
|
|
175
|
+
* Distinct from `cycle_model`, which steps the flat registry list. `direction`
|
|
176
|
+
* defaults to `'forward'` when absent. */
|
|
177
|
+
export interface CycleLadderFrame {
|
|
178
|
+
type: 'cycle_ladder';
|
|
179
|
+
direction?: 'forward' | 'backward';
|
|
180
|
+
}
|
|
172
181
|
/** `cycleThinkingLevel()` — shift+tab. Cycles to the next thinking level; the new
|
|
173
182
|
* level reaches viewers via the relayed `thinking_level_changed` event, so the
|
|
174
183
|
* broker replies a plain `ack` (no payload). Distinct from `set_thinking_level`,
|
|
@@ -303,7 +312,7 @@ export interface ShareFrame {
|
|
|
303
312
|
export interface ReloadAuthFrame {
|
|
304
313
|
type: 'reload_auth';
|
|
305
314
|
}
|
|
306
|
-
export type ClientToBroker = HelloFrame | PromptFrame | SteerFrame | FollowUpFrame | AbortFrame | BashFrame | RequestControlFrame | ReleaseControlFrame | ByeFrame | ShutdownFrame | SetModelFrame | CycleModelFrame | CycleThinkingFrame | SetThinkingLevelFrame | DequeueFrame | SetAutoRetryFrame | SetAutoCompactionFrame | CompactFrame | NewSessionFrame | SwitchSessionFrame | ForkFrame | SetSessionNameFrame | GetCommandsFrame | NavigateTreeFrame | ReloadFrame | ExportFrame | ListModelsFrame | ListSessionsFrame | GetTreeFrame | GetSettingsFrame | ListScopedModelsFrame | ExtensionUIResponseFrame | CloneFrame | ShareFrame | ReloadAuthFrame;
|
|
315
|
+
export type ClientToBroker = HelloFrame | PromptFrame | SteerFrame | FollowUpFrame | AbortFrame | BashFrame | RequestControlFrame | ReleaseControlFrame | ByeFrame | ShutdownFrame | SetModelFrame | CycleModelFrame | CycleLadderFrame | CycleThinkingFrame | SetThinkingLevelFrame | DequeueFrame | SetAutoRetryFrame | SetAutoCompactionFrame | CompactFrame | NewSessionFrame | SwitchSessionFrame | ForkFrame | SetSessionNameFrame | GetCommandsFrame | NavigateTreeFrame | ReloadFrame | ExportFrame | ListModelsFrame | ListSessionsFrame | GetTreeFrame | GetSettingsFrame | ListScopedModelsFrame | ExtensionUIResponseFrame | CloneFrame | ShareFrame | ReloadAuthFrame;
|
|
307
316
|
export interface WelcomeFrame {
|
|
308
317
|
type: 'welcome';
|
|
309
318
|
snapshot: BrokerSnapshot;
|
|
@@ -330,6 +339,13 @@ export interface ControlChangedFrame {
|
|
|
330
339
|
export interface ModelChangedFrame {
|
|
331
340
|
type: 'model_changed';
|
|
332
341
|
model: string | undefined;
|
|
342
|
+
/** The full resolved `provider/id[:thinking]` spec (`formatModelSpec`) — what
|
|
343
|
+
* the broker persists into the node's own launch recipe. Viewers use it to
|
|
344
|
+
* persist a non-portable explicit pick as a kind DEFAULT
|
|
345
|
+
* (`persistDefaultKindModel`) without guessing the provider from the bare
|
|
346
|
+
* id. Null when the engine has no model. Optional so older brokers'
|
|
347
|
+
* frames still parse. */
|
|
348
|
+
spec?: string | null;
|
|
333
349
|
}
|
|
334
350
|
export interface ErrorFrame {
|
|
335
351
|
type: 'error';
|
|
@@ -380,9 +396,10 @@ export interface WireScopedModel {
|
|
|
380
396
|
model: Model<Api>;
|
|
381
397
|
thinkingLevel?: AgentSession['thinkingLevel'];
|
|
382
398
|
}
|
|
383
|
-
/** Wire form of pi's `SessionInfo`:
|
|
384
|
-
*
|
|
385
|
-
*
|
|
399
|
+
/** Wire form of pi's `SessionInfo`: `created`/`modified` are ISO strings (pi's
|
|
400
|
+
* are `Date`, which JSON cannot round-trip), and `allMessagesText` is a bounded
|
|
401
|
+
* opening/latest search excerpt rather than the unbounded transcript text. The
|
|
402
|
+
* viewer revives the dates before handing them to `SessionSelectorComponent`. */
|
|
386
403
|
export interface WireSessionInfo extends Omit<SessionInfo, 'created' | 'modified'> {
|
|
387
404
|
created: string;
|
|
388
405
|
modified: string;
|
|
@@ -494,10 +511,21 @@ export interface DisplayTitleFrame {
|
|
|
494
511
|
}
|
|
495
512
|
/** A blocking dialog routed to the controller — pi's public RPC request type. */
|
|
496
513
|
export type ExtensionUIRequestFrame = RpcExtensionUIRequest;
|
|
514
|
+
/** Broker → client: dismiss a previously-forwarded blocking dialog by request id,
|
|
515
|
+
* WITHOUT a client answer. The broker sends this exactly when it resolves a pending
|
|
516
|
+
* dialog ITSELF rather than the client answering it — the extension aborted the
|
|
517
|
+
* request out-of-band (e.g. a local OAuth loopback callback won the race against a
|
|
518
|
+
* still-open manual-paste dialog) or the broker-side timeout fired. The client tears
|
|
519
|
+
* down ONLY the overlay whose `id` matches; every other dialog stays exactly as it
|
|
520
|
+
* was, so an unrelated request can never dismiss an unrelated blocking dialog. */
|
|
521
|
+
export interface ExtensionUIDismissFrame {
|
|
522
|
+
type: 'extension_ui_dismiss';
|
|
523
|
+
id: string;
|
|
524
|
+
}
|
|
497
525
|
/** Everything the broker can send. Live `AgentSessionEvent`s are relayed
|
|
498
526
|
* verbatim (the broker adds nothing); the broker's own control frames carry
|
|
499
527
|
* non-colliding `type` discriminants. */
|
|
500
|
-
export type BrokerToClient = WelcomeFrame | ControlChangedFrame | ModelChangedFrame | ErrorFrame | AckFrame | BrokerDataFrame | DisplayStatusFrame | DisplayWidgetFrame | DisplayTitleFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | AgentSessionEvent;
|
|
528
|
+
export type BrokerToClient = WelcomeFrame | ControlChangedFrame | ModelChangedFrame | ErrorFrame | AckFrame | BrokerDataFrame | DisplayStatusFrame | DisplayWidgetFrame | DisplayTitleFrame | BashStartFrame | BashOutputFrame | BashEndFrame | ExtensionUIRequestFrame | ExtensionUIDismissFrame | AgentSessionEvent;
|
|
501
529
|
/** Encode one frame as a single newline-terminated JSON line. */
|
|
502
530
|
export declare function encodeFrame(frame: ClientToBroker | BrokerToClient): string;
|
|
503
531
|
/** Byte bounds for a {@link FrameDecoder} (C5). */
|