@crouton-kit/crouter 0.3.78 → 0.3.80
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/build-root.d.ts +12 -4
- package/dist/build-root.js +25 -6
- package/dist/builtin-memory/crouter-development/plugins.md +82 -5
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/provider-rotation.test.ts +1228 -12
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +3 -3
- package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +2 -787
- package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +8 -3
- 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__/mermaid-render.test.js +9 -1
- package/dist/clients/attach/attach-cmd.d.ts +9 -1
- package/dist/clients/attach/attach-cmd.js +847 -801
- package/dist/clients/attach/auth-pickers.d.ts +0 -12
- package/dist/clients/attach/auth-pickers.js +64 -15
- package/dist/clients/attach/chat-view.d.ts +4 -0
- package/dist/clients/attach/chat-view.js +10 -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 +19 -0
- package/dist/clients/attach/input-controller.js +55 -11
- package/dist/clients/attach/titled-editor.d.ts +21 -0
- package/dist/clients/attach/titled-editor.js +101 -0
- package/dist/commands/human/queue.js +3 -4
- package/dist/commands/pkg/plugin-inspect.js +22 -1
- package/dist/commands/pkg/plugin-manage.js +31 -9
- package/dist/commands/sys/__tests__/setup-core.test.js +158 -1
- package/dist/commands/sys/doctor.js +42 -4
- package/dist/commands/sys/setup-core.d.ts +37 -0
- package/dist/commands/sys/setup-core.js +138 -1
- package/dist/commands/sys/setup.d.ts +88 -0
- package/dist/commands/sys/setup.js +915 -171
- package/dist/commands/view-pick.d.ts +4 -0
- package/dist/commands/view-pick.js +17 -7
- 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__/fixtures/fake-engine.d.ts +6 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +9 -1
- 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/canvas/browse/app.d.ts +6 -0
- package/dist/core/canvas/browse/app.js +71 -41
- 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 +2 -5
- 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.js +2 -2
- package/dist/core/keybindings/catalog.d.ts +3 -3
- package/dist/core/keybindings/catalog.js +2 -1
- package/dist/core/profiles/select.d.ts +6 -0
- package/dist/core/profiles/select.js +90 -59
- package/dist/core/provider-management.d.ts +12 -0
- package/dist/core/provider-management.js +24 -0
- package/dist/core/runtime/banner.d.ts +13 -0
- package/dist/core/runtime/banner.js +51 -0
- package/dist/core/runtime/broker.js +7 -6
- package/dist/core/runtime/pi-vendored.d.ts +8 -0
- package/dist/core/runtime/pi-vendored.js +14 -0
- package/dist/core/runtime/recap.d.ts +1 -1
- package/dist/core/runtime/recap.js +50 -25
- package/dist/core/runtime/session-list-cache.d.ts +10 -0
- package/dist/core/runtime/session-list-cache.js +94 -26
- package/dist/core/runtime/spawn.js +5 -17
- package/dist/core/runtime/tmux.js +2 -1
- 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/daemon/crtrd.js +253 -12
- package/dist/pi-extensions/canvas-recap.js +43 -17
- package/dist/types.d.ts +6 -13
- package/dist/types.js +0 -3
- package/package.json +7 -3
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { BindingDefinition, SafetyRequirement } from './types.js';
|
|
2
|
-
type TmuxMenuAction = 'open' | 'promote' | 'resume' | 'demote' | 'detach' | 'close-subtree' | 'context' | 'graph' | 'focus-manager' | 'issues';
|
|
2
|
+
type TmuxMenuAction = 'open' | 'promote' | 'resume' | 'demote' | 'detach' | 'close-subtree' | 'context' | 'providers' | 'graph' | 'focus-manager' | 'issues';
|
|
3
3
|
type GraphAction = 'down' | 'up' | 'first' | 'last' | 'collapse-or-manager' | 'expand-or-child' | 'focus' | 'focus-manager' | 'close-subtree' | 'dismiss';
|
|
4
4
|
type InboxAction = 'down' | 'up' | 'open' | 'reply' | 'react' | 'filter' | 'refresh' | 'connect-linkedin' | 'connect-gmail' | 'quit' | 'submit-reply' | 'cancel' | 'reaction-previous' | 'reaction-next' | 'submit-reaction';
|
|
5
5
|
type LinkedinAction = Exclude<InboxAction, 'filter' | 'connect-linkedin' | 'connect-gmail'>;
|
|
6
|
-
type BrowseAction = 'quit' | 'down' | 'up' | 'first' | 'last' | 'expand-or-child' | 'collapse-or-parent' | 'toggle-fold' | 'tab-next' | 'tab-previous' | `tab.${1 | 2 | 3 | 4}` | 'close-subtree' | 'confirm-close' | 'sort' | 'scope' | 'residents' | 'preview' | 'search' | 'revive-now' | 'focus';
|
|
6
|
+
type BrowseAction = 'quit' | 'down' | 'up' | 'first' | 'last' | 'expand-or-child' | 'collapse-or-parent' | 'toggle-fold' | 'tab-next' | 'tab-previous' | `tab.${1 | 2 | 3 | 4 | 5}` | 'close-subtree' | 'confirm-close' | 'sort' | 'scope' | 'residents' | 'preview' | 'search' | 'revive-now' | 'focus';
|
|
7
7
|
type ProfileMenuAction = 'up' | 'down' | 'select' | 'accept-default' | 'abort' | `slot.${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9}` | 'create' | 'search-existing' | 'use-root' | 'set-default' | 'add-directory' | 'unpin';
|
|
8
8
|
type SetupAction = 'cancel' | 'tab-next' | 'tab-previous' | 'open' | 'list-down' | 'list-up' | 'toggle' | 'details' | 'advance';
|
|
9
9
|
type SetupKeybindingAction = 'edit' | 'commit-edit' | 'cancel-edit' | 'unbind' | 'reset-row' | 'reset-all' | 'save' | 'discard' | 'page-up' | 'page-down' | 'filter' | 'filter-delete' | 'filter-clear';
|
|
10
10
|
export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | `crtr.tmux.node.${'next' | 'previous'}` | `crtr.tmux.view.${'next' | 'previous'}` | `crtr.attach.${'detach' | 'clear-or-detach' | 'graph.toggle' | 'model-ladder.next'}` | 'crtr.mode.cycle' | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.view.host.${'force-quit' | 'editor.backspace'}` | `crtr.view.canvas.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.chat.${'submit' | 'contextual-cancel' | 'reconnect' | 'request-control' | 'release-control' | 'quit'}` | `crtr.view.git-pr.${'down' | 'up' | 'refresh' | 'quit'}` | `crtr.view.inbox.${InboxAction}` | `crtr.view.linkedin.${LinkedinAction}` | `crtr.view.prompt-review.${'down' | 'up' | 'compare-previous' | 'compare-next' | 'toggle-mode' | 'expand' | 'export' | 'refresh' | 'quit'}` | `crtr.view.settings.${'down' | 'up' | 'cell-previous' | 'cell-next' | 'refresh' | 'quit' | 'edit-or-submit' | 'cancel-edit'}` | `crtr.view.workspace-sidebar.${'down' | 'up' | 'open' | 'refresh' | 'quit'}` | `crtr.view-picker.${'down' | 'up' | 'run' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}`;
|
|
11
|
-
export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.promote">, BindingDefinition<"crtr.tmux.menu.resume">, BindingDefinition<"crtr.tmux.menu.demote">, BindingDefinition<"crtr.tmux.menu.detach">, BindingDefinition<"crtr.tmux.menu.close-subtree">, BindingDefinition<"crtr.tmux.menu.context">, BindingDefinition<"crtr.tmux.menu.graph">, BindingDefinition<"crtr.tmux.menu.focus-manager">, BindingDefinition<"crtr.tmux.menu.issues">, BindingDefinition<"crtr.tmux.menu.issue.idea">, BindingDefinition<"crtr.tmux.menu.issue.bug">, BindingDefinition<"crtr.tmux.node.next">, BindingDefinition<"crtr.tmux.node.previous">, BindingDefinition<"crtr.tmux.view.next">, BindingDefinition<"crtr.tmux.view.previous">, BindingDefinition<"crtr.attach.detach">, BindingDefinition<"crtr.attach.clear-or-detach">, BindingDefinition<"crtr.attach.graph.toggle">, BindingDefinition<"crtr.attach.model-ladder.next">, BindingDefinition<"crtr.mode.cycle">, BindingDefinition<"crtr.graph.down">, BindingDefinition<"crtr.graph.up">, BindingDefinition<"crtr.graph.first">, BindingDefinition<"crtr.graph.last">, BindingDefinition<"crtr.graph.collapse-or-manager">, BindingDefinition<"crtr.graph.expand-or-child">, BindingDefinition<"crtr.graph.focus">, BindingDefinition<"crtr.graph.focus-manager">, BindingDefinition<"crtr.graph.close-subtree">, BindingDefinition<"crtr.graph.dismiss">, BindingDefinition<"crtr.graph.confirm.accept">, BindingDefinition<"crtr.graph.confirm.cancel">, BindingDefinition<"crtr.view.host.force-quit">, BindingDefinition<"crtr.view.host.editor.backspace">, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<"crtr.view.chat.submit">, BindingDefinition<"crtr.view.chat.contextual-cancel">, BindingDefinition<"crtr.view.chat.reconnect">, BindingDefinition<"crtr.view.chat.request-control">, BindingDefinition<"crtr.view.chat.release-control">, BindingDefinition<"crtr.view.chat.quit">, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.view.prompt-review.down"> | BindingDefinition<"crtr.view.prompt-review.up"> | BindingDefinition<"crtr.view.prompt-review.compare-previous"> | BindingDefinition<"crtr.view.prompt-review.compare-next"> | BindingDefinition<"crtr.view.prompt-review.toggle-mode"> | BindingDefinition<"crtr.view.prompt-review.expand"> | BindingDefinition<"crtr.view.prompt-review.export"> | BindingDefinition<"crtr.view.prompt-review.refresh"> | BindingDefinition<"crtr.view.prompt-review.quit"> | BindingDefinition<"crtr.view.settings.down"> | BindingDefinition<"crtr.view.settings.up"> | BindingDefinition<"crtr.view.settings.cell-previous"> | BindingDefinition<"crtr.view.settings.cell-next"> | BindingDefinition<"crtr.view.settings.refresh"> | BindingDefinition<"crtr.view.settings.quit"> | BindingDefinition<"crtr.view.settings.edit-or-submit"> | BindingDefinition<"crtr.view.settings.cancel-edit"> | BindingDefinition<"crtr.view.workspace-sidebar.down"> | BindingDefinition<"crtr.view.workspace-sidebar.up"> | BindingDefinition<"crtr.view.workspace-sidebar.open"> | BindingDefinition<"crtr.view.workspace-sidebar.refresh"> | BindingDefinition<"crtr.view.workspace-sidebar.quit"> | BindingDefinition<"crtr.view-picker.down"> | BindingDefinition<"crtr.view-picker.up"> | BindingDefinition<"crtr.view-picker.run"> | BindingDefinition<"crtr.view-picker.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.9">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">];
|
|
11
|
+
export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.promote">, BindingDefinition<"crtr.tmux.menu.resume">, BindingDefinition<"crtr.tmux.menu.demote">, BindingDefinition<"crtr.tmux.menu.detach">, BindingDefinition<"crtr.tmux.menu.close-subtree">, BindingDefinition<"crtr.tmux.menu.context">, BindingDefinition<"crtr.tmux.menu.providers">, BindingDefinition<"crtr.tmux.menu.graph">, BindingDefinition<"crtr.tmux.menu.focus-manager">, BindingDefinition<"crtr.tmux.menu.issues">, BindingDefinition<"crtr.tmux.menu.issue.idea">, BindingDefinition<"crtr.tmux.menu.issue.bug">, BindingDefinition<"crtr.tmux.node.next">, BindingDefinition<"crtr.tmux.node.previous">, BindingDefinition<"crtr.tmux.view.next">, BindingDefinition<"crtr.tmux.view.previous">, BindingDefinition<"crtr.attach.detach">, BindingDefinition<"crtr.attach.clear-or-detach">, BindingDefinition<"crtr.attach.graph.toggle">, BindingDefinition<"crtr.attach.model-ladder.next">, BindingDefinition<"crtr.mode.cycle">, BindingDefinition<"crtr.graph.down">, BindingDefinition<"crtr.graph.up">, BindingDefinition<"crtr.graph.first">, BindingDefinition<"crtr.graph.last">, BindingDefinition<"crtr.graph.collapse-or-manager">, BindingDefinition<"crtr.graph.expand-or-child">, BindingDefinition<"crtr.graph.focus">, BindingDefinition<"crtr.graph.focus-manager">, BindingDefinition<"crtr.graph.close-subtree">, BindingDefinition<"crtr.graph.dismiss">, BindingDefinition<"crtr.graph.confirm.accept">, BindingDefinition<"crtr.graph.confirm.cancel">, BindingDefinition<"crtr.view.host.force-quit">, BindingDefinition<"crtr.view.host.editor.backspace">, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<"crtr.view.chat.submit">, BindingDefinition<"crtr.view.chat.contextual-cancel">, BindingDefinition<"crtr.view.chat.reconnect">, BindingDefinition<"crtr.view.chat.request-control">, BindingDefinition<"crtr.view.chat.release-control">, BindingDefinition<"crtr.view.chat.quit">, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, BindingDefinition<BindingId>, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.view.prompt-review.down"> | BindingDefinition<"crtr.view.prompt-review.up"> | BindingDefinition<"crtr.view.prompt-review.compare-previous"> | BindingDefinition<"crtr.view.prompt-review.compare-next"> | BindingDefinition<"crtr.view.prompt-review.toggle-mode"> | BindingDefinition<"crtr.view.prompt-review.expand"> | BindingDefinition<"crtr.view.prompt-review.export"> | BindingDefinition<"crtr.view.prompt-review.refresh"> | BindingDefinition<"crtr.view.prompt-review.quit"> | BindingDefinition<"crtr.view.settings.down"> | BindingDefinition<"crtr.view.settings.up"> | BindingDefinition<"crtr.view.settings.cell-previous"> | BindingDefinition<"crtr.view.settings.cell-next"> | BindingDefinition<"crtr.view.settings.refresh"> | BindingDefinition<"crtr.view.settings.quit"> | BindingDefinition<"crtr.view.settings.edit-or-submit"> | BindingDefinition<"crtr.view.settings.cancel-edit"> | BindingDefinition<"crtr.view.workspace-sidebar.down"> | BindingDefinition<"crtr.view.workspace-sidebar.up"> | BindingDefinition<"crtr.view.workspace-sidebar.open"> | BindingDefinition<"crtr.view.workspace-sidebar.refresh"> | BindingDefinition<"crtr.view.workspace-sidebar.quit"> | BindingDefinition<"crtr.view-picker.down"> | BindingDefinition<"crtr.view-picker.up"> | BindingDefinition<"crtr.view-picker.run"> | BindingDefinition<"crtr.view-picker.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.9">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">];
|
|
12
12
|
export declare const BINDING_IDS: ReadonlySet<string>;
|
|
13
13
|
export declare const SAFETY_REQUIREMENTS: readonly SafetyRequirement[];
|
|
14
14
|
export {};
|
|
@@ -32,6 +32,7 @@ export const BINDING_CATALOG = Object.freeze([
|
|
|
32
32
|
b('crtr.tmux.menu.detach', 'Global tmux', 'Detach node', 'Detach the focused node to the background.', ['shift+d'], 'tmux-menu-selector', ['tmux.menu']),
|
|
33
33
|
b('crtr.tmux.menu.close-subtree', 'Global tmux', 'Close subtree', 'Close the focused node and its subtree.', ['x'], 'tmux-menu-selector', ['tmux.menu']),
|
|
34
34
|
b('crtr.tmux.menu.context', 'Global tmux', 'Browse context', 'Browse the focused node context.', ['c'], 'tmux-menu-selector', ['tmux.menu']),
|
|
35
|
+
b('crtr.tmux.menu.providers', 'Global tmux', 'Manage providers', 'Open the provider management panel.', ['p'], 'tmux-menu-selector', ['tmux.menu']),
|
|
35
36
|
b('crtr.tmux.menu.graph', 'Global tmux', 'Open graph', 'Open the canvas graph.', ['g'], 'tmux-menu-selector', ['tmux.menu']),
|
|
36
37
|
b('crtr.tmux.menu.focus-manager', 'Global tmux', 'Focus manager', 'Focus the node manager.', ['m'], 'tmux-menu-selector', ['tmux.menu']),
|
|
37
38
|
b('crtr.tmux.menu.issues', 'Global tmux', 'Open issue menu', 'Open the issue type submenu.', ['i'], 'tmux-menu-selector', ['tmux.menu']),
|
|
@@ -155,7 +156,7 @@ function browseBindings() {
|
|
|
155
156
|
b('crtr.browse.toggle-fold', 'Browse and pickers', 'Toggle fold', 'Toggle the selected fold.', ['space'], 'terminal', [nav]),
|
|
156
157
|
b('crtr.browse.tab-next', 'Browse and pickers', 'Next tab', 'Select the next browse tab.', ['tab', ']'], 'terminal', [nav]),
|
|
157
158
|
b('crtr.browse.tab-previous', 'Browse and pickers', 'Previous tab', 'Select the previous browse tab.', ['shift+tab', '['], 'terminal', [nav]),
|
|
158
|
-
...[1, 2, 3, 4].map((slot) => b(`crtr.browse.tab.${slot}`, 'Browse and pickers', `Tab ${slot}`, `Select browse tab ${slot}.`, [`${slot}`], 'terminal', [nav])),
|
|
159
|
+
...[1, 2, 3, 4, 5].map((slot) => b(`crtr.browse.tab.${slot}`, 'Browse and pickers', `Tab ${slot}`, `Select browse tab ${slot}.`, [`${slot}`], 'terminal', [nav])),
|
|
159
160
|
b('crtr.browse.close-subtree', 'Browse and pickers', 'Close subtree', 'Ask to close the selected subtree.', ['x'], 'terminal', [nav]),
|
|
160
161
|
b('crtr.browse.confirm-close', 'Browse and pickers', 'Confirm close', 'Confirm closing the selected subtree.', ['y'], 'terminal', ['browse.confirm']),
|
|
161
162
|
b('crtr.browse.sort', 'Browse and pickers', 'Change sort', 'Change the browse sort.', ['s'], 'terminal', [nav]),
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { type ProfileEntry } from './manifest.js';
|
|
2
|
+
import { type BindingId, type BindingResolution, type RawTerminalInput } from '../keybindings/index.js';
|
|
3
|
+
declare const PROFILE_MENU_ACTIONS: readonly ["crtr.profile.menu.abort", "crtr.profile.menu.up", "crtr.profile.menu.down", "crtr.profile.menu.select", "crtr.profile.menu.accept-default", "crtr.profile.menu.slot.1", "crtr.profile.menu.slot.2", "crtr.profile.menu.slot.3", "crtr.profile.menu.slot.4", "crtr.profile.menu.slot.5", "crtr.profile.menu.slot.6", "crtr.profile.menu.slot.7", "crtr.profile.menu.slot.8", "crtr.profile.menu.slot.9", "crtr.profile.menu.create", "crtr.profile.menu.search-existing", "crtr.profile.menu.use-root", "crtr.profile.menu.set-default", "crtr.profile.menu.add-directory", "crtr.profile.menu.unpin"];
|
|
4
|
+
declare const PROFILE_SEARCH_ACTIONS: readonly ["crtr.profile.search.abort", "crtr.profile.search.back", "crtr.profile.search.pick", "crtr.profile.search.up", "crtr.profile.search.down", "crtr.profile.search.delete"];
|
|
5
|
+
export declare function resolveProfileMenuAction(bindings: BindingResolution<BindingId>, raw: RawTerminalInput): (typeof PROFILE_MENU_ACTIONS)[number] | null;
|
|
6
|
+
export declare function resolveProfileSearchAction(bindings: BindingResolution<BindingId>, raw: RawTerminalInput): (typeof PROFILE_SEARCH_ACTIONS)[number] | null;
|
|
2
7
|
/** Whether a profile's purview covers `cwd` (cwd at/under one of its project
|
|
3
8
|
* dirs, or a project dir under cwd — the workspace-root case). Exported for
|
|
4
9
|
* `crtr profile default set`, which rejects pinning a profile that doesn't
|
|
@@ -34,3 +39,4 @@ export declare function profileCoversCwd(entry: ProfileEntry, cwd: string): bool
|
|
|
34
39
|
* default to the root profile and print the recovery instruction to STDERR —
|
|
35
40
|
* never stdout, which the caller may be piping. */
|
|
36
41
|
export declare function selectProfileForCwd(cwd: string, explicitProfile?: string | null, forcePicker?: boolean): Promise<string>;
|
|
42
|
+
export {};
|
|
@@ -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
|
|
@@ -597,15 +631,12 @@ export async function selectProfileForCwd(cwd, explicitProfile, forcePicker = fa
|
|
|
597
631
|
if (pinnedId !== null && pinned === null)
|
|
598
632
|
clearDefaultProfile(resolvedCwd);
|
|
599
633
|
// A valid pin decides it outright and STOPS ASKING — the whole point of
|
|
600
|
-
// setting a default. Auto-pick it
|
|
601
|
-
//
|
|
602
|
-
//
|
|
634
|
+
// setting a default. Auto-pick it silently: the crouton banner the viewer
|
|
635
|
+
// paints a beat later shows the active profile and the `crtr --pick-profile`
|
|
636
|
+
// hint, so a boot-time breadcrumb here would just be redundant.
|
|
637
|
+
// `crtr --pick-profile` (forcePicker) re-opens the menu to re-pin/unpin.
|
|
603
638
|
if (pinned !== null && !forcePicker) {
|
|
604
639
|
updateProfileLastUsed(pinned.profileId);
|
|
605
|
-
if (isInteractive()) {
|
|
606
|
-
process.stdout.write(` ${accent('\u2713')} ${bold(pinned.manifest.name)} ${dim('\u00b7 default for this directory')}` +
|
|
607
|
-
` ${dim('(crtr --pick-profile to change)')}\n`);
|
|
608
|
-
}
|
|
609
640
|
return pinned.profileId;
|
|
610
641
|
}
|
|
611
642
|
// Exactly one profile's project dir IS cwd — unambiguous project root, use it
|
|
@@ -633,10 +664,10 @@ export async function selectProfileForCwd(cwd, explicitProfile, forcePicker = fa
|
|
|
633
664
|
// the create-or-root prompt (root is only ever offered there).
|
|
634
665
|
const validPinnedId = pinned !== null ? pinned.profileId : null;
|
|
635
666
|
const chosen = exact.length > 1
|
|
636
|
-
? await promptPickProfileOrCreate(exact, resolvedCwd, true, validPinnedId)
|
|
667
|
+
? await promptPickProfileOrCreate(exact, resolvedCwd, true, validPinnedId, bindings)
|
|
637
668
|
: covering.length > 0
|
|
638
|
-
? await promptPickProfileOrCreate(covering, resolvedCwd, false, validPinnedId)
|
|
639
|
-
: await promptCreateOrRoot(resolvedCwd);
|
|
669
|
+
? await promptPickProfileOrCreate(covering, resolvedCwd, false, validPinnedId, bindings)
|
|
670
|
+
: await promptCreateOrRoot(resolvedCwd, bindings);
|
|
640
671
|
updateProfileLastUsed(chosen);
|
|
641
672
|
return chosen;
|
|
642
673
|
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface BannerInfo {
|
|
2
|
+
/** crtr version, e.g. "0.3.78" → shown as "v0.3.78". */
|
|
3
|
+
version?: string;
|
|
4
|
+
/** Active profile name, e.g. "northlight". */
|
|
5
|
+
profile?: string;
|
|
6
|
+
/** Launch directory (already home-abbreviated if desired). */
|
|
7
|
+
cwd?: string;
|
|
8
|
+
}
|
|
9
|
+
/** The crouton banner as pre-colored terminal lines: the cube on the left with
|
|
10
|
+
* name/version, tagline, profile, and launch path stacked beside rows 1–4.
|
|
11
|
+
* Missing facts are simply omitted (their row shows just the cube). No
|
|
12
|
+
* leading/trailing blank lines — the caller frames it. */
|
|
13
|
+
export declare function croutonBannerLines(info?: BannerInfo): string[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// The crouton welcome banner — a little crouton rendered at the very top of a
|
|
2
|
+
// node's transcript in the attach viewer (the way Claude Code opens with its
|
|
3
|
+
// logo + session facts), with the launch facts stacked to its right: name +
|
|
4
|
+
// version, tagline, profile, and the launch path. It scrolls up into history as
|
|
5
|
+
// the conversation grows. Purely cosmetic.
|
|
6
|
+
//
|
|
7
|
+
// This lives in the VIEWER, not the front-door stdout: the attach TUI takes over
|
|
8
|
+
// the whole terminal, so anything printed to stdout before it starts is wiped.
|
|
9
|
+
// ChatView pins these lines as the first children of its chat container.
|
|
10
|
+
// Clean line-art isometric cube in a single warm crust tone, with a few subtly
|
|
11
|
+
// darker toast flecks. No background fills (they read as detached color slabs in
|
|
12
|
+
// a dark terminal) and no inverted-brightness speckle.
|
|
13
|
+
const RESET = '\u001b[0m';
|
|
14
|
+
const CRUST = '\u001b[38;5;180m'; // warm tan — cube outline + accents
|
|
15
|
+
const FLECK = '\u001b[38;5;137m'; // slightly darker toast dots
|
|
16
|
+
const BOLD = '\u001b[1m';
|
|
17
|
+
const DIM = '\u001b[2m';
|
|
18
|
+
// Darker toast flecks on the cube faces; `.` glyphs recolor a touch darker.
|
|
19
|
+
function crumb(text) {
|
|
20
|
+
return CRUST + text.replace(/\./g, `${FLECK}.${CRUST}`) + RESET;
|
|
21
|
+
}
|
|
22
|
+
// Rows 1–4 all share the same visible width (15 cells), so a fixed 3-space
|
|
23
|
+
// gutter lands every info line in the same column — the stacked-facts layout.
|
|
24
|
+
const CROUTON = [
|
|
25
|
+
crumb(' ________'),
|
|
26
|
+
crumb(' / /|'),
|
|
27
|
+
crumb(' / . . / |'),
|
|
28
|
+
crumb(' /_______/ |'),
|
|
29
|
+
crumb(' | . . | |'),
|
|
30
|
+
crumb(' | . . | /'),
|
|
31
|
+
crumb(' |_______|/'),
|
|
32
|
+
];
|
|
33
|
+
/** The crouton banner as pre-colored terminal lines: the cube on the left with
|
|
34
|
+
* name/version, tagline, profile, and launch path stacked beside rows 1–4.
|
|
35
|
+
* Missing facts are simply omitted (their row shows just the cube). No
|
|
36
|
+
* leading/trailing blank lines — the caller frames it. */
|
|
37
|
+
export function croutonBannerLines(info = {}) {
|
|
38
|
+
const name = `${BOLD}${CRUST}crouter${RESET}${info.version ? `${DIM} v${info.version}${RESET}` : ''}`;
|
|
39
|
+
const facts = [name, `${DIM}the agentic runtime${RESET}`];
|
|
40
|
+
if (info.profile)
|
|
41
|
+
facts.push(`${DIM}profile: ${RESET}${CRUST}${info.profile}${RESET}`);
|
|
42
|
+
if (info.cwd)
|
|
43
|
+
facts.push(`${DIM}${info.cwd}${RESET}`);
|
|
44
|
+
const out = [];
|
|
45
|
+
for (let i = 0; i < CROUTON.length; i++) {
|
|
46
|
+
// Facts sit beside rows 1..N (the top of the cube), one per row.
|
|
47
|
+
const fact = i >= 1 && i - 1 < facts.length ? ` ${facts[i - 1]}` : '';
|
|
48
|
+
out.push(CROUTON[i] + fact);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
@@ -19,7 +19,7 @@ import { createServer } from 'node:net';
|
|
|
19
19
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
20
20
|
import { execFile } from 'node:child_process';
|
|
21
21
|
import { tmpdir } from 'node:os';
|
|
22
|
-
import {
|
|
22
|
+
import { join } from 'node:path';
|
|
23
23
|
import { randomUUID } from 'node:crypto';
|
|
24
24
|
import { getAgentDir, initTheme, } from '@earendil-works/pi-coding-agent';
|
|
25
25
|
import { jobDir, nodeDir, sessionListCachePath, viewSocketPath } from '../canvas/paths.js';
|
|
@@ -35,7 +35,7 @@ import { CONNECTION_FAULT_RECOVERY_BODY, PROVIDER_FAULT_RECOVERY_BODY } from './
|
|
|
35
35
|
import { markFatalOnExhaust, nextFaultRetry, policyFor } from './fault-recovery.js';
|
|
36
36
|
import { probeOnline as probeOnlineReal } from './connectivity.js';
|
|
37
37
|
import { CRTR_CYCLE_CUSTOM_TYPE, cycleAwareMessages } from './session-cycles.js';
|
|
38
|
-
import { BUILTIN_SLASH_COMMANDS } from './pi-vendored.js';
|
|
38
|
+
import { BUILTIN_SLASH_COMMANDS, piSessionsRoot } from './pi-vendored.js';
|
|
39
39
|
import { StreamWatchdog, resolveStreamWatchdogMs } from './stream-watchdog.js';
|
|
40
40
|
import { encodeFrame, FrameDecoder, FrameOverflowError, BROKER_READ_CAPS, } from './broker-protocol.js';
|
|
41
41
|
const THINKING_LEVELS = new Set(['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
@@ -1197,15 +1197,16 @@ export async function runBroker(nodeId) {
|
|
|
1197
1197
|
return { type: 'data', id, kind: 'get_settings', settings };
|
|
1198
1198
|
};
|
|
1199
1199
|
// Cache per-session wire info keyed by (path, mtime, size) so reopening the
|
|
1200
|
-
// picker doesn't re-read the whole corpus (see session-list-cache.ts).
|
|
1201
|
-
// sessions root is `dirname` of any cwd's session dir — no need to resolve
|
|
1202
|
-
// pi's private path config.
|
|
1200
|
+
// picker doesn't re-read the whole corpus (see session-list-cache.ts).
|
|
1203
1201
|
const sessionListCache = new SessionListCache(sessionListCachePath());
|
|
1202
|
+
// Pre-warm the cwd scope (the picker's default tab) in the background so the
|
|
1203
|
+
// first /resume open on this fresh broker is already warm.
|
|
1204
|
+
sessionListCache.prewarm(session.sessionManager.getSessionDir());
|
|
1204
1205
|
const buildSessionsData = async (id, scope) => {
|
|
1205
1206
|
const sm = session.sessionManager;
|
|
1206
1207
|
const sessionDir = sm.getSessionDir();
|
|
1207
1208
|
const sessions = scope === 'all'
|
|
1208
|
-
? await sessionListCache.listAll(
|
|
1209
|
+
? await sessionListCache.listAll(piSessionsRoot())
|
|
1209
1210
|
: await sessionListCache.listDir(sessionDir);
|
|
1210
1211
|
return {
|
|
1211
1212
|
type: 'data',
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/** pi's sessions root, VENDORED from pi `config.getSessionsDir()` (= `<agentDir>/
|
|
2
|
+
* sessions`). pi's package `exports` map is `.`-only, so config.js can't be
|
|
3
|
+
* deep-imported, and a ROOT import of `getAgentDir` would eager-load the entire
|
|
4
|
+
* heavy pi SDK index on crtr's front-door hot path (the reason broker-sdk.ts
|
|
5
|
+
* dynamic-imports the engine). Mirrors pi: `PI_CODING_AGENT_DIR` env, else
|
|
6
|
+
* `~/.pi/agent` (APP_NAME defaults to 'pi'). Re-sync on a pi SDK bump that moves
|
|
7
|
+
* the sessions dir — same vendoring rationale as the slash-command list below. */
|
|
8
|
+
export declare function piSessionsRoot(): string;
|
|
1
9
|
/**
|
|
2
10
|
* pi's builtin slash commands — vendored verbatim from pi `core/slash-commands.js`
|
|
3
11
|
* `BUILTIN_SLASH_COMMANDS` (review C1). **21 entries at 0.78.1** (review n1 — NOT 23).
|