@openeryc/pi-coding-agent 0.75.26 → 0.75.28
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/CHANGELOG.md +9 -0
- package/dist/cli/config-selector.d.ts +1 -13
- package/dist/cli/config-selector.d.ts.map +1 -1
- package/dist/cli/config-selector.js +4 -29
- package/dist/cli/config-selector.js.map +1 -1
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +1 -1
- package/dist/cli/list-models.js.map +1 -1
- package/dist/core/agent-session.d.ts +0 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +12 -20
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
- package/dist/core/export-html/tool-renderer.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +1 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +1 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +1 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/keybindings.d.ts +7 -135
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +1 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/mcp/client.d.ts +30 -11
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +55 -110
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/index.d.ts +1 -3
- package/dist/core/mcp/index.d.ts.map +1 -1
- package/dist/core/mcp/index.js.map +1 -1
- package/dist/core/mcp/manager.d.ts +3 -9
- package/dist/core/mcp/manager.d.ts.map +1 -1
- package/dist/core/mcp/manager.js +71 -105
- package/dist/core/mcp/manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +1 -1
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +3 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +27 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +1 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/web-fetch.d.ts +15 -0
- package/dist/core/tools/web-fetch.d.ts.map +1 -0
- package/dist/core/tools/web-fetch.js +52 -0
- package/dist/core/tools/web-fetch.js.map +1 -0
- package/dist/core/tools/web-search.d.ts +18 -0
- package/dist/core/tools/web-search.d.ts.map +1 -0
- package/dist/core/tools/web-search.js +83 -0
- package/dist/core/tools/web-search.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/tui-stubs.d.ts +95 -0
- package/dist/core/tui-stubs.d.ts.map +1 -0
- package/dist/core/tui-stubs.js +90 -0
- package/dist/core/tui-stubs.js.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +92 -106
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +0 -4
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +0 -4
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/diff.d.ts +1 -8
- package/dist/modes/interactive/components/diff.d.ts.map +1 -1
- package/dist/modes/interactive/components/diff.js +2 -131
- package/dist/modes/interactive/components/diff.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts +2 -12
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +4 -33
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.d.ts +1 -19
- package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.js +4 -31
- package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +27 -89
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +44 -992
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +1 -1
- package/dist/package-manager-cli.js.map +1 -1
- package/npm-shrinkwrap.json +240 -246
- package/package.json +6 -7
- package/dist/cli/session-picker.d.ts +0 -9
- package/dist/cli/session-picker.d.ts.map +0 -1
- package/dist/cli/session-picker.js +0 -35
- package/dist/cli/session-picker.js.map +0 -1
- package/dist/core/mcp/types.d.ts +0 -86
- package/dist/core/mcp/types.d.ts.map +0 -1
- package/dist/core/mcp/types.js +0 -8
- package/dist/core/mcp/types.js.map +0 -1
- package/dist/modes/interactive/assets/clankolas.png +0 -0
- package/dist/modes/interactive/components/armin.d.ts +0 -34
- package/dist/modes/interactive/components/armin.d.ts.map +0 -1
- package/dist/modes/interactive/components/armin.js +0 -333
- package/dist/modes/interactive/components/armin.js.map +0 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +0 -20
- package/dist/modes/interactive/components/assistant-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/assistant-message.js +0 -121
- package/dist/modes/interactive/components/assistant-message.js.map +0 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +0 -34
- package/dist/modes/interactive/components/bash-execution.d.ts.map +0 -1
- package/dist/modes/interactive/components/bash-execution.js +0 -175
- package/dist/modes/interactive/components/bash-execution.js.map +0 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +0 -16
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +0 -1
- package/dist/modes/interactive/components/bordered-loader.js +0 -54
- package/dist/modes/interactive/components/bordered-loader.js.map +0 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +0 -16
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/branch-summary-message.js +0 -44
- package/dist/modes/interactive/components/branch-summary-message.js.map +0 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +0 -16
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +0 -45
- package/dist/modes/interactive/components/compaction-summary-message.js.map +0 -1
- package/dist/modes/interactive/components/config-selector.d.ts +0 -71
- package/dist/modes/interactive/components/config-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/config-selector.js +0 -506
- package/dist/modes/interactive/components/config-selector.js.map +0 -1
- package/dist/modes/interactive/components/countdown-timer.d.ts +0 -14
- package/dist/modes/interactive/components/countdown-timer.d.ts.map +0 -1
- package/dist/modes/interactive/components/countdown-timer.js +0 -33
- package/dist/modes/interactive/components/countdown-timer.js.map +0 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +0 -21
- package/dist/modes/interactive/components/custom-editor.d.ts.map +0 -1
- package/dist/modes/interactive/components/custom-editor.js +0 -70
- package/dist/modes/interactive/components/custom-editor.js.map +0 -1
- package/dist/modes/interactive/components/custom-message.d.ts +0 -20
- package/dist/modes/interactive/components/custom-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/custom-message.js +0 -79
- package/dist/modes/interactive/components/custom-message.js.map +0 -1
- package/dist/modes/interactive/components/daxnuts.d.ts +0 -23
- package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
- package/dist/modes/interactive/components/daxnuts.js +0 -140
- package/dist/modes/interactive/components/daxnuts.js.map +0 -1
- package/dist/modes/interactive/components/dynamic-border.d.ts +0 -15
- package/dist/modes/interactive/components/dynamic-border.d.ts.map +0 -1
- package/dist/modes/interactive/components/dynamic-border.js +0 -21
- package/dist/modes/interactive/components/dynamic-border.js.map +0 -1
- package/dist/modes/interactive/components/earendil-announcement.d.ts +0 -5
- package/dist/modes/interactive/components/earendil-announcement.d.ts.map +0 -1
- package/dist/modes/interactive/components/earendil-announcement.js +0 -40
- package/dist/modes/interactive/components/earendil-announcement.js.map +0 -1
- package/dist/modes/interactive/components/extension-editor.d.ts +0 -20
- package/dist/modes/interactive/components/extension-editor.d.ts.map +0 -1
- package/dist/modes/interactive/components/extension-editor.js +0 -119
- package/dist/modes/interactive/components/extension-editor.js.map +0 -1
- package/dist/modes/interactive/components/extension-input.d.ts +0 -23
- package/dist/modes/interactive/components/extension-input.d.ts.map +0 -1
- package/dist/modes/interactive/components/extension-input.js +0 -61
- package/dist/modes/interactive/components/extension-input.js.map +0 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +0 -26
- package/dist/modes/interactive/components/extension-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/extension-selector.js +0 -83
- package/dist/modes/interactive/components/extension-selector.js.map +0 -1
- package/dist/modes/interactive/components/footer.d.ts +0 -27
- package/dist/modes/interactive/components/footer.d.ts.map +0 -1
- package/dist/modes/interactive/components/footer.js +0 -201
- package/dist/modes/interactive/components/footer.js.map +0 -1
- package/dist/modes/interactive/components/index.d.ts +0 -32
- package/dist/modes/interactive/components/index.d.ts.map +0 -1
- package/dist/modes/interactive/components/index.js +0 -33
- package/dist/modes/interactive/components/index.js.map +0 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +0 -46
- package/dist/modes/interactive/components/login-dialog.d.ts.map +0 -1
- package/dist/modes/interactive/components/login-dialog.js +0 -160
- package/dist/modes/interactive/components/login-dialog.js.map +0 -1
- package/dist/modes/interactive/components/model-selector.d.ts +0 -47
- package/dist/modes/interactive/components/model-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/model-selector.js +0 -278
- package/dist/modes/interactive/components/model-selector.js.map +0 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts +0 -31
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/oauth-selector.js +0 -165
- package/dist/modes/interactive/components/oauth-selector.js.map +0 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +0 -42
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +0 -290
- package/dist/modes/interactive/components/scoped-models-selector.js.map +0 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts +0 -23
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +0 -1
- package/dist/modes/interactive/components/session-selector-search.js +0 -155
- package/dist/modes/interactive/components/session-selector-search.js.map +0 -1
- package/dist/modes/interactive/components/session-selector.d.ts +0 -96
- package/dist/modes/interactive/components/session-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/session-selector.js +0 -861
- package/dist/modes/interactive/components/session-selector.js.map +0 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +0 -69
- package/dist/modes/interactive/components/settings-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/settings-selector.js +0 -390
- package/dist/modes/interactive/components/settings-selector.js.map +0 -1
- package/dist/modes/interactive/components/show-images-selector.d.ts +0 -10
- package/dist/modes/interactive/components/show-images-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/show-images-selector.js +0 -39
- package/dist/modes/interactive/components/show-images-selector.js.map +0 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +0 -17
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +0 -47
- package/dist/modes/interactive/components/skill-invocation-message.js.map +0 -1
- package/dist/modes/interactive/components/theme-selector.d.ts +0 -11
- package/dist/modes/interactive/components/theme-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/theme-selector.js +0 -50
- package/dist/modes/interactive/components/theme-selector.js.map +0 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts +0 -11
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/thinking-selector.js +0 -51
- package/dist/modes/interactive/components/thinking-selector.js.map +0 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +0 -63
- package/dist/modes/interactive/components/tool-execution.d.ts.map +0 -1
- package/dist/modes/interactive/components/tool-execution.js +0 -295
- package/dist/modes/interactive/components/tool-execution.js.map +0 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +0 -89
- package/dist/modes/interactive/components/tree-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/tree-selector.js +0 -1093
- package/dist/modes/interactive/components/tree-selector.js.map +0 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts +0 -30
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +0 -1
- package/dist/modes/interactive/components/user-message-selector.js +0 -114
- package/dist/modes/interactive/components/user-message-selector.js.map +0 -1
- package/dist/modes/interactive/components/user-message.d.ts +0 -10
- package/dist/modes/interactive/components/user-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/user-message.js +0 -29
- package/dist/modes/interactive/components/user-message.js.map +0 -1
- package/dist/modes/interactive/interactive-mode.d.ts +0 -375
- package/dist/modes/interactive/interactive-mode.d.ts.map +0 -1
- package/dist/modes/interactive/interactive-mode.js +0 -4799
- package/dist/modes/interactive/interactive-mode.js.map +0 -1
- package/dist/modes/interactive/theme/dark.json +0 -86
- package/dist/modes/interactive/theme/light.json +0 -85
- package/dist/modes/interactive/theme/theme-schema.json +0 -335
- package/examples/extensions/border-status-editor.ts +0 -150
- package/examples/extensions/built-in-tool-renderer.ts +0 -249
- package/examples/extensions/custom-footer.ts +0 -64
- package/examples/extensions/custom-header.ts +0 -73
- package/examples/extensions/custom-provider-anthropic/index.ts +0 -604
- package/examples/extensions/custom-provider-anthropic/package-lock.json +0 -24
- package/examples/extensions/custom-provider-anthropic/package.json +0 -19
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +0 -349
- package/examples/extensions/custom-provider-gitlab-duo/package.json +0 -16
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +0 -82
- package/examples/extensions/doom-overlay/README.md +0 -46
- package/examples/extensions/doom-overlay/doom/build/doom.js +0 -21
- package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
- package/examples/extensions/doom-overlay/doom/build.sh +0 -152
- package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +0 -72
- package/examples/extensions/doom-overlay/doom-component.ts +0 -132
- package/examples/extensions/doom-overlay/doom-engine.ts +0 -173
- package/examples/extensions/doom-overlay/doom-keys.ts +0 -104
- package/examples/extensions/doom-overlay/index.ts +0 -74
- package/examples/extensions/doom-overlay/wad-finder.ts +0 -51
- package/examples/extensions/github-issue-autocomplete.ts +0 -185
- package/examples/extensions/handoff.ts +0 -191
- package/examples/extensions/interactive-shell.ts +0 -196
- package/examples/extensions/message-renderer.ts +0 -59
- package/examples/extensions/minimal-mode.ts +0 -426
- package/examples/extensions/modal-editor.ts +0 -85
- package/examples/extensions/overlay-qa-tests.ts +0 -1379
- package/examples/extensions/overlay-test.ts +0 -153
- package/examples/extensions/plan-mode/index.ts +0 -340
- package/examples/extensions/preset.ts +0 -430
- package/examples/extensions/qna.ts +0 -122
- package/examples/extensions/question.ts +0 -264
- package/examples/extensions/questionnaire.ts +0 -427
- package/examples/extensions/rainbow-editor.ts +0 -88
- package/examples/extensions/sandbox/index.ts +0 -321
- package/examples/extensions/sandbox/package-lock.json +0 -92
- package/examples/extensions/sandbox/package.json +0 -19
- package/examples/extensions/snake.ts +0 -343
- package/examples/extensions/space-invaders.ts +0 -560
- package/examples/extensions/ssh.ts +0 -220
- package/examples/extensions/status-line.ts +0 -32
- package/examples/extensions/structured-output.ts +0 -65
- package/examples/extensions/subagent/index.ts +0 -1009
- package/examples/extensions/summarize.ts +0 -206
- package/examples/extensions/tic-tac-toe.ts +0 -1008
- package/examples/extensions/todo.ts +0 -297
- package/examples/extensions/tools.ts +0 -141
- package/examples/extensions/truncated-tool.ts +0 -195
- package/examples/extensions/with-deps/index.ts +0 -32
- package/examples/extensions/with-deps/package-lock.json +0 -31
- package/examples/extensions/with-deps/package.json +0 -22
- package/examples/extensions/working-indicator.ts +0 -123
- package/examples/rpc-extension-ui.ts +0 -632
|
@@ -1,861 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { unlink } from "node:fs/promises";
|
|
4
|
-
import * as os from "node:os";
|
|
5
|
-
import { Container, getKeybindings, Input, Spacer, Text, truncateToWidth, visibleWidth, } from "@openeryc/pi-tui";
|
|
6
|
-
import { KeybindingsManager } from "../../../core/keybindings.js";
|
|
7
|
-
import { canonicalizePath as _canonicalizePath } from "../../../utils/paths.js";
|
|
8
|
-
import { theme } from "../theme/theme.js";
|
|
9
|
-
import { DynamicBorder } from "./dynamic-border.js";
|
|
10
|
-
import { keyHint, keyText } from "./keybinding-hints.js";
|
|
11
|
-
import { filterAndSortSessions, hasSessionName } from "./session-selector-search.js";
|
|
12
|
-
function shortenPath(path) {
|
|
13
|
-
const home = os.homedir();
|
|
14
|
-
if (!path)
|
|
15
|
-
return path;
|
|
16
|
-
if (path.startsWith(home)) {
|
|
17
|
-
return `~${path.slice(home.length)}`;
|
|
18
|
-
}
|
|
19
|
-
return path;
|
|
20
|
-
}
|
|
21
|
-
function formatSessionDate(date) {
|
|
22
|
-
const now = new Date();
|
|
23
|
-
const diffMs = now.getTime() - date.getTime();
|
|
24
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
25
|
-
const diffHours = Math.floor(diffMs / 3600000);
|
|
26
|
-
const diffDays = Math.floor(diffMs / 86400000);
|
|
27
|
-
if (diffMins < 1)
|
|
28
|
-
return "now";
|
|
29
|
-
if (diffMins < 60)
|
|
30
|
-
return `${diffMins}m`;
|
|
31
|
-
if (diffHours < 24)
|
|
32
|
-
return `${diffHours}h`;
|
|
33
|
-
if (diffDays < 7)
|
|
34
|
-
return `${diffDays}d`;
|
|
35
|
-
if (diffDays < 30)
|
|
36
|
-
return `${Math.floor(diffDays / 7)}w`;
|
|
37
|
-
if (diffDays < 365)
|
|
38
|
-
return `${Math.floor(diffDays / 30)}mo`;
|
|
39
|
-
return `${Math.floor(diffDays / 365)}y`;
|
|
40
|
-
}
|
|
41
|
-
function canonicalizePath(path) {
|
|
42
|
-
if (!path)
|
|
43
|
-
return path;
|
|
44
|
-
return _canonicalizePath(path);
|
|
45
|
-
}
|
|
46
|
-
class SessionSelectorHeader {
|
|
47
|
-
scope;
|
|
48
|
-
sortMode;
|
|
49
|
-
nameFilter;
|
|
50
|
-
requestRender;
|
|
51
|
-
loading = false;
|
|
52
|
-
loadProgress = null;
|
|
53
|
-
showPath = false;
|
|
54
|
-
confirmingDeletePath = null;
|
|
55
|
-
statusMessage = null;
|
|
56
|
-
statusTimeout = null;
|
|
57
|
-
showRenameHint = false;
|
|
58
|
-
constructor(scope, sortMode, nameFilter, requestRender) {
|
|
59
|
-
this.scope = scope;
|
|
60
|
-
this.sortMode = sortMode;
|
|
61
|
-
this.nameFilter = nameFilter;
|
|
62
|
-
this.requestRender = requestRender;
|
|
63
|
-
}
|
|
64
|
-
setScope(scope) {
|
|
65
|
-
this.scope = scope;
|
|
66
|
-
}
|
|
67
|
-
setSortMode(sortMode) {
|
|
68
|
-
this.sortMode = sortMode;
|
|
69
|
-
}
|
|
70
|
-
setNameFilter(nameFilter) {
|
|
71
|
-
this.nameFilter = nameFilter;
|
|
72
|
-
}
|
|
73
|
-
setLoading(loading) {
|
|
74
|
-
this.loading = loading;
|
|
75
|
-
// Progress is scoped to the current load; clear whenever the loading state is set
|
|
76
|
-
this.loadProgress = null;
|
|
77
|
-
}
|
|
78
|
-
setProgress(loaded, total) {
|
|
79
|
-
this.loadProgress = { loaded, total };
|
|
80
|
-
}
|
|
81
|
-
setShowPath(showPath) {
|
|
82
|
-
this.showPath = showPath;
|
|
83
|
-
}
|
|
84
|
-
setShowRenameHint(show) {
|
|
85
|
-
this.showRenameHint = show;
|
|
86
|
-
}
|
|
87
|
-
setConfirmingDeletePath(path) {
|
|
88
|
-
this.confirmingDeletePath = path;
|
|
89
|
-
}
|
|
90
|
-
clearStatusTimeout() {
|
|
91
|
-
if (!this.statusTimeout)
|
|
92
|
-
return;
|
|
93
|
-
clearTimeout(this.statusTimeout);
|
|
94
|
-
this.statusTimeout = null;
|
|
95
|
-
}
|
|
96
|
-
setStatusMessage(msg, autoHideMs) {
|
|
97
|
-
this.clearStatusTimeout();
|
|
98
|
-
this.statusMessage = msg;
|
|
99
|
-
if (!msg || !autoHideMs)
|
|
100
|
-
return;
|
|
101
|
-
this.statusTimeout = setTimeout(() => {
|
|
102
|
-
this.statusMessage = null;
|
|
103
|
-
this.statusTimeout = null;
|
|
104
|
-
this.requestRender();
|
|
105
|
-
}, autoHideMs);
|
|
106
|
-
}
|
|
107
|
-
invalidate() { }
|
|
108
|
-
render(width) {
|
|
109
|
-
const title = this.scope === "current" ? "Resume Session (Current Folder)" : "Resume Session (All)";
|
|
110
|
-
const leftText = theme.bold(title);
|
|
111
|
-
const sortLabel = this.sortMode === "threaded" ? "Threaded" : this.sortMode === "recent" ? "Recent" : "Fuzzy";
|
|
112
|
-
const sortText = theme.fg("muted", "Sort: ") + theme.fg("accent", sortLabel);
|
|
113
|
-
const nameLabel = this.nameFilter === "all" ? "All" : "Named";
|
|
114
|
-
const nameText = theme.fg("muted", "Name: ") + theme.fg("accent", nameLabel);
|
|
115
|
-
let scopeText;
|
|
116
|
-
if (this.loading) {
|
|
117
|
-
const progressText = this.loadProgress ? `${this.loadProgress.loaded}/${this.loadProgress.total}` : "...";
|
|
118
|
-
scopeText = `${theme.fg("muted", "○ Current Folder | ")}${theme.fg("accent", `Loading ${progressText}`)}`;
|
|
119
|
-
}
|
|
120
|
-
else if (this.scope === "current") {
|
|
121
|
-
scopeText = `${theme.fg("accent", "◉ Current Folder")}${theme.fg("muted", " | ○ All")}`;
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
scopeText = `${theme.fg("muted", "○ Current Folder | ")}${theme.fg("accent", "◉ All")}`;
|
|
125
|
-
}
|
|
126
|
-
const rightText = truncateToWidth(`${scopeText} ${nameText} ${sortText}`, width, "");
|
|
127
|
-
const availableLeft = Math.max(0, width - visibleWidth(rightText) - 1);
|
|
128
|
-
const left = truncateToWidth(leftText, availableLeft, "");
|
|
129
|
-
const spacing = Math.max(0, width - visibleWidth(left) - visibleWidth(rightText));
|
|
130
|
-
// Build hint lines - changes based on state (all branches truncate to width)
|
|
131
|
-
let hintLine1;
|
|
132
|
-
let hintLine2;
|
|
133
|
-
if (this.confirmingDeletePath !== null) {
|
|
134
|
-
const confirmHint = `Delete session? ${keyHint("tui.select.confirm", "confirm")} · ${keyHint("tui.select.cancel", "cancel")}`;
|
|
135
|
-
hintLine1 = theme.fg("error", truncateToWidth(confirmHint, width, "…"));
|
|
136
|
-
hintLine2 = "";
|
|
137
|
-
}
|
|
138
|
-
else if (this.statusMessage) {
|
|
139
|
-
const color = this.statusMessage.type === "error" ? "error" : "accent";
|
|
140
|
-
hintLine1 = theme.fg(color, truncateToWidth(this.statusMessage.message, width, "…"));
|
|
141
|
-
hintLine2 = "";
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
const pathState = this.showPath ? "(on)" : "(off)";
|
|
145
|
-
const sep = theme.fg("muted", " · ");
|
|
146
|
-
const hint1 = keyHint("tui.input.tab", "scope") + sep + theme.fg("muted", 're:<pattern> regex · "phrase" exact');
|
|
147
|
-
const hint2Parts = [
|
|
148
|
-
keyHint("app.session.toggleSort", "sort"),
|
|
149
|
-
keyHint("app.session.toggleNamedFilter", "named"),
|
|
150
|
-
keyHint("app.session.delete", "delete"),
|
|
151
|
-
keyHint("app.session.togglePath", `path ${pathState}`),
|
|
152
|
-
];
|
|
153
|
-
if (this.showRenameHint) {
|
|
154
|
-
hint2Parts.push(keyHint("app.session.rename", "rename"));
|
|
155
|
-
}
|
|
156
|
-
const hint2 = hint2Parts.join(sep);
|
|
157
|
-
hintLine1 = truncateToWidth(hint1, width, "…");
|
|
158
|
-
hintLine2 = truncateToWidth(hint2, width, "…");
|
|
159
|
-
}
|
|
160
|
-
return [`${left}${" ".repeat(spacing)}${rightText}`, hintLine1, hintLine2];
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Build a tree structure from sessions based on parentSessionPath.
|
|
165
|
-
* Returns root nodes sorted by modified date (descending).
|
|
166
|
-
*/
|
|
167
|
-
function buildSessionTree(sessions) {
|
|
168
|
-
const byPath = new Map();
|
|
169
|
-
for (const session of sessions) {
|
|
170
|
-
const sessionPath = canonicalizePath(session.path) ?? session.path;
|
|
171
|
-
byPath.set(sessionPath, { session, children: [] });
|
|
172
|
-
}
|
|
173
|
-
const roots = [];
|
|
174
|
-
for (const session of sessions) {
|
|
175
|
-
const sessionPath = canonicalizePath(session.path) ?? session.path;
|
|
176
|
-
const node = byPath.get(sessionPath);
|
|
177
|
-
const parentPath = canonicalizePath(session.parentSessionPath);
|
|
178
|
-
if (parentPath && byPath.has(parentPath)) {
|
|
179
|
-
byPath.get(parentPath).children.push(node);
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
roots.push(node);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// Sort children and roots by modified date (descending)
|
|
186
|
-
const sortNodes = (nodes) => {
|
|
187
|
-
nodes.sort((a, b) => b.session.modified.getTime() - a.session.modified.getTime());
|
|
188
|
-
for (const node of nodes) {
|
|
189
|
-
sortNodes(node.children);
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
sortNodes(roots);
|
|
193
|
-
return roots;
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Flatten tree into display list with tree structure metadata.
|
|
197
|
-
*/
|
|
198
|
-
function flattenSessionTree(roots) {
|
|
199
|
-
const result = [];
|
|
200
|
-
const walk = (node, depth, ancestorContinues, isLast) => {
|
|
201
|
-
result.push({ session: node.session, depth, isLast, ancestorContinues });
|
|
202
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
203
|
-
const childIsLast = i === node.children.length - 1;
|
|
204
|
-
// Only show continuation line for non-root ancestors
|
|
205
|
-
const continues = depth > 0 ? !isLast : false;
|
|
206
|
-
walk(node.children[i], depth + 1, [...ancestorContinues, continues], childIsLast);
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
for (let i = 0; i < roots.length; i++) {
|
|
210
|
-
walk(roots[i], 0, [], i === roots.length - 1);
|
|
211
|
-
}
|
|
212
|
-
return result;
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Custom session list component with multi-line items and search
|
|
216
|
-
*/
|
|
217
|
-
class SessionList {
|
|
218
|
-
getSelectedSessionPath() {
|
|
219
|
-
const selected = this.filteredSessions[this.selectedIndex];
|
|
220
|
-
return selected?.session.path;
|
|
221
|
-
}
|
|
222
|
-
allSessions = [];
|
|
223
|
-
filteredSessions = [];
|
|
224
|
-
selectedIndex = 0;
|
|
225
|
-
searchInput;
|
|
226
|
-
showCwd = false;
|
|
227
|
-
sortMode = "threaded";
|
|
228
|
-
nameFilter = "all";
|
|
229
|
-
keybindings;
|
|
230
|
-
showPath = false;
|
|
231
|
-
confirmingDeletePath = null;
|
|
232
|
-
currentSessionCanonicalPath;
|
|
233
|
-
onSelect;
|
|
234
|
-
onCancel;
|
|
235
|
-
onExit = () => { };
|
|
236
|
-
onToggleScope;
|
|
237
|
-
onToggleSort;
|
|
238
|
-
onToggleNameFilter;
|
|
239
|
-
onTogglePath;
|
|
240
|
-
onDeleteConfirmationChange;
|
|
241
|
-
onDeleteSession;
|
|
242
|
-
onRenameSession;
|
|
243
|
-
onError;
|
|
244
|
-
maxVisible = 10; // Max sessions visible (one line each)
|
|
245
|
-
// Focusable implementation - propagate to searchInput for IME cursor positioning
|
|
246
|
-
_focused = false;
|
|
247
|
-
get focused() {
|
|
248
|
-
return this._focused;
|
|
249
|
-
}
|
|
250
|
-
set focused(value) {
|
|
251
|
-
this._focused = value;
|
|
252
|
-
this.searchInput.focused = value;
|
|
253
|
-
}
|
|
254
|
-
constructor(sessions, showCwd, sortMode, nameFilter, keybindings, currentSessionFilePath) {
|
|
255
|
-
this.allSessions = sessions;
|
|
256
|
-
this.filteredSessions = [];
|
|
257
|
-
this.searchInput = new Input();
|
|
258
|
-
this.showCwd = showCwd;
|
|
259
|
-
this.sortMode = sortMode;
|
|
260
|
-
this.nameFilter = nameFilter;
|
|
261
|
-
this.keybindings = keybindings;
|
|
262
|
-
this.currentSessionCanonicalPath = canonicalizePath(currentSessionFilePath);
|
|
263
|
-
this.filterSessions("");
|
|
264
|
-
// Handle Enter in search input - select current item
|
|
265
|
-
this.searchInput.onSubmit = () => {
|
|
266
|
-
if (this.filteredSessions[this.selectedIndex]) {
|
|
267
|
-
const selected = this.filteredSessions[this.selectedIndex];
|
|
268
|
-
if (this.onSelect) {
|
|
269
|
-
this.onSelect(selected.session.path);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
setSortMode(sortMode) {
|
|
275
|
-
this.sortMode = sortMode;
|
|
276
|
-
this.filterSessions(this.searchInput.getValue());
|
|
277
|
-
}
|
|
278
|
-
setNameFilter(nameFilter) {
|
|
279
|
-
this.nameFilter = nameFilter;
|
|
280
|
-
this.filterSessions(this.searchInput.getValue());
|
|
281
|
-
}
|
|
282
|
-
setSessions(sessions, showCwd) {
|
|
283
|
-
this.allSessions = sessions;
|
|
284
|
-
this.showCwd = showCwd;
|
|
285
|
-
this.filterSessions(this.searchInput.getValue());
|
|
286
|
-
}
|
|
287
|
-
filterSessions(query) {
|
|
288
|
-
const trimmed = query.trim();
|
|
289
|
-
const nameFiltered = this.nameFilter === "all" ? this.allSessions : this.allSessions.filter((session) => hasSessionName(session));
|
|
290
|
-
if (this.sortMode === "threaded" && !trimmed) {
|
|
291
|
-
// Threaded mode without search: show tree structure
|
|
292
|
-
const roots = buildSessionTree(nameFiltered);
|
|
293
|
-
this.filteredSessions = flattenSessionTree(roots);
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
// Other modes or with search: flat list
|
|
297
|
-
const filtered = filterAndSortSessions(nameFiltered, query, this.sortMode, "all");
|
|
298
|
-
this.filteredSessions = filtered.map((session) => ({
|
|
299
|
-
session,
|
|
300
|
-
depth: 0,
|
|
301
|
-
isLast: true,
|
|
302
|
-
ancestorContinues: [],
|
|
303
|
-
}));
|
|
304
|
-
}
|
|
305
|
-
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredSessions.length - 1));
|
|
306
|
-
}
|
|
307
|
-
setConfirmingDeletePath(path) {
|
|
308
|
-
this.confirmingDeletePath = path;
|
|
309
|
-
this.onDeleteConfirmationChange?.(path);
|
|
310
|
-
}
|
|
311
|
-
startDeleteConfirmationForSelectedSession() {
|
|
312
|
-
const selected = this.filteredSessions[this.selectedIndex];
|
|
313
|
-
if (!selected)
|
|
314
|
-
return;
|
|
315
|
-
// Prevent deleting current session
|
|
316
|
-
if (this.isCurrentSessionPath(selected.session.path)) {
|
|
317
|
-
this.onError?.("Cannot delete the currently active session");
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
this.setConfirmingDeletePath(selected.session.path);
|
|
321
|
-
}
|
|
322
|
-
isCurrentSessionPath(path) {
|
|
323
|
-
if (!this.currentSessionCanonicalPath)
|
|
324
|
-
return false;
|
|
325
|
-
return (canonicalizePath(path) ?? path) === this.currentSessionCanonicalPath;
|
|
326
|
-
}
|
|
327
|
-
invalidate() { }
|
|
328
|
-
render(width) {
|
|
329
|
-
const lines = [];
|
|
330
|
-
// Render search input
|
|
331
|
-
lines.push(...this.searchInput.render(width));
|
|
332
|
-
lines.push(""); // Blank line after search
|
|
333
|
-
if (this.filteredSessions.length === 0) {
|
|
334
|
-
let emptyMessage;
|
|
335
|
-
if (this.nameFilter === "named") {
|
|
336
|
-
const toggleKey = keyText("app.session.toggleNamedFilter");
|
|
337
|
-
if (this.showCwd) {
|
|
338
|
-
emptyMessage = ` No named sessions found. Press ${toggleKey} to show all.`;
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
emptyMessage = ` No named sessions in current folder. Press ${toggleKey} to show all, or Tab to view all.`;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
else if (this.showCwd) {
|
|
345
|
-
// "All" scope - no sessions anywhere that match filter
|
|
346
|
-
emptyMessage = " No sessions found";
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
// "Current folder" scope - hint to try "all"
|
|
350
|
-
emptyMessage = " No sessions in current folder. Press Tab to view all.";
|
|
351
|
-
}
|
|
352
|
-
lines.push(theme.fg("muted", truncateToWidth(emptyMessage, width, "…")));
|
|
353
|
-
return lines;
|
|
354
|
-
}
|
|
355
|
-
// Calculate visible range with scrolling
|
|
356
|
-
const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.filteredSessions.length - this.maxVisible));
|
|
357
|
-
const endIndex = Math.min(startIndex + this.maxVisible, this.filteredSessions.length);
|
|
358
|
-
// Render visible sessions (one line each with tree structure)
|
|
359
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
360
|
-
const node = this.filteredSessions[i];
|
|
361
|
-
const session = node.session;
|
|
362
|
-
const isSelected = i === this.selectedIndex;
|
|
363
|
-
const isConfirmingDelete = session.path === this.confirmingDeletePath;
|
|
364
|
-
const isCurrent = this.isCurrentSessionPath(session.path);
|
|
365
|
-
// Build tree prefix
|
|
366
|
-
const prefix = this.buildTreePrefix(node);
|
|
367
|
-
// Session display text (name or first message)
|
|
368
|
-
const hasName = !!session.name;
|
|
369
|
-
const displayText = session.name ?? session.firstMessage;
|
|
370
|
-
const normalizedMessage = displayText.replace(/[\x00-\x1f\x7f]/g, " ").trim();
|
|
371
|
-
// Right side: message count and age
|
|
372
|
-
const age = formatSessionDate(session.modified);
|
|
373
|
-
const msgCount = String(session.messageCount);
|
|
374
|
-
let rightPart = `${msgCount} ${age}`;
|
|
375
|
-
if (this.showCwd && session.cwd) {
|
|
376
|
-
rightPart = `${shortenPath(session.cwd)} ${rightPart}`;
|
|
377
|
-
}
|
|
378
|
-
if (this.showPath) {
|
|
379
|
-
rightPart = `${shortenPath(session.path)} ${rightPart}`;
|
|
380
|
-
}
|
|
381
|
-
// Cursor
|
|
382
|
-
const cursor = isSelected ? theme.fg("accent", "› ") : " ";
|
|
383
|
-
// Calculate available width for message
|
|
384
|
-
const prefixWidth = visibleWidth(prefix);
|
|
385
|
-
const rightWidth = visibleWidth(rightPart) + 2; // +2 for spacing
|
|
386
|
-
const availableForMsg = width - 2 - prefixWidth - rightWidth; // -2 for cursor
|
|
387
|
-
const truncatedMsg = truncateToWidth(normalizedMessage, Math.max(10, availableForMsg), "…");
|
|
388
|
-
// Style message
|
|
389
|
-
let messageColor = null;
|
|
390
|
-
if (isConfirmingDelete) {
|
|
391
|
-
messageColor = "error";
|
|
392
|
-
}
|
|
393
|
-
else if (isCurrent) {
|
|
394
|
-
messageColor = "accent";
|
|
395
|
-
}
|
|
396
|
-
else if (hasName) {
|
|
397
|
-
messageColor = "warning";
|
|
398
|
-
}
|
|
399
|
-
let styledMsg = messageColor ? theme.fg(messageColor, truncatedMsg) : truncatedMsg;
|
|
400
|
-
if (isSelected) {
|
|
401
|
-
styledMsg = theme.bold(styledMsg);
|
|
402
|
-
}
|
|
403
|
-
// Build line
|
|
404
|
-
const leftPart = cursor + theme.fg("dim", prefix) + styledMsg;
|
|
405
|
-
const leftWidth = visibleWidth(leftPart);
|
|
406
|
-
const spacing = Math.max(1, width - leftWidth - visibleWidth(rightPart));
|
|
407
|
-
const styledRight = theme.fg(isConfirmingDelete ? "error" : "dim", rightPart);
|
|
408
|
-
let line = leftPart + " ".repeat(spacing) + styledRight;
|
|
409
|
-
if (isSelected) {
|
|
410
|
-
line = theme.bg("selectedBg", line);
|
|
411
|
-
}
|
|
412
|
-
lines.push(truncateToWidth(line, width));
|
|
413
|
-
}
|
|
414
|
-
// Add scroll indicator if needed
|
|
415
|
-
if (startIndex > 0 || endIndex < this.filteredSessions.length) {
|
|
416
|
-
const scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;
|
|
417
|
-
const scrollInfo = theme.fg("muted", truncateToWidth(scrollText, width, ""));
|
|
418
|
-
lines.push(scrollInfo);
|
|
419
|
-
}
|
|
420
|
-
return lines;
|
|
421
|
-
}
|
|
422
|
-
buildTreePrefix(node) {
|
|
423
|
-
if (node.depth === 0) {
|
|
424
|
-
return "";
|
|
425
|
-
}
|
|
426
|
-
const parts = node.ancestorContinues.map((continues) => (continues ? "│ " : " "));
|
|
427
|
-
const branch = node.isLast ? "└─ " : "├─ ";
|
|
428
|
-
return parts.join("") + branch;
|
|
429
|
-
}
|
|
430
|
-
handleInput(keyData) {
|
|
431
|
-
const kb = getKeybindings();
|
|
432
|
-
// Handle delete confirmation state first - intercept all keys
|
|
433
|
-
if (this.confirmingDeletePath !== null) {
|
|
434
|
-
if (kb.matches(keyData, "tui.select.confirm")) {
|
|
435
|
-
const pathToDelete = this.confirmingDeletePath;
|
|
436
|
-
this.setConfirmingDeletePath(null);
|
|
437
|
-
void this.onDeleteSession?.(pathToDelete);
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
if (kb.matches(keyData, "tui.select.cancel")) {
|
|
441
|
-
this.setConfirmingDeletePath(null);
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
// Ignore all other keys while confirming
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (kb.matches(keyData, "tui.input.tab")) {
|
|
448
|
-
if (this.onToggleScope) {
|
|
449
|
-
this.onToggleScope();
|
|
450
|
-
}
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (kb.matches(keyData, "app.session.toggleSort")) {
|
|
454
|
-
this.onToggleSort?.();
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
if (this.keybindings.matches(keyData, "app.session.toggleNamedFilter")) {
|
|
458
|
-
this.onToggleNameFilter?.();
|
|
459
|
-
return;
|
|
460
|
-
}
|
|
461
|
-
// Ctrl+P: toggle path display
|
|
462
|
-
if (kb.matches(keyData, "app.session.togglePath")) {
|
|
463
|
-
this.showPath = !this.showPath;
|
|
464
|
-
this.onTogglePath?.(this.showPath);
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
// Ctrl+D: initiate delete confirmation (useful on terminals that don't distinguish Ctrl+Backspace from Backspace)
|
|
468
|
-
if (kb.matches(keyData, "app.session.delete")) {
|
|
469
|
-
this.startDeleteConfirmationForSelectedSession();
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
// Rename selected session
|
|
473
|
-
if (kb.matches(keyData, "app.session.rename")) {
|
|
474
|
-
const selected = this.filteredSessions[this.selectedIndex];
|
|
475
|
-
if (selected) {
|
|
476
|
-
this.onRenameSession?.(selected.session.path);
|
|
477
|
-
}
|
|
478
|
-
return;
|
|
479
|
-
}
|
|
480
|
-
// Ctrl+Backspace: non-invasive convenience alias for delete
|
|
481
|
-
// Only triggers deletion when the query is empty; otherwise it is forwarded to the input
|
|
482
|
-
if (kb.matches(keyData, "app.session.deleteNoninvasive")) {
|
|
483
|
-
if (this.searchInput.getValue().length > 0) {
|
|
484
|
-
this.searchInput.handleInput(keyData);
|
|
485
|
-
this.filterSessions(this.searchInput.getValue());
|
|
486
|
-
return;
|
|
487
|
-
}
|
|
488
|
-
this.startDeleteConfirmationForSelectedSession();
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
// Up arrow
|
|
492
|
-
if (kb.matches(keyData, "tui.select.up")) {
|
|
493
|
-
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
494
|
-
}
|
|
495
|
-
// Down arrow
|
|
496
|
-
else if (kb.matches(keyData, "tui.select.down")) {
|
|
497
|
-
this.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + 1);
|
|
498
|
-
}
|
|
499
|
-
// Page up - jump up by maxVisible items
|
|
500
|
-
else if (kb.matches(keyData, "tui.select.pageUp")) {
|
|
501
|
-
this.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);
|
|
502
|
-
}
|
|
503
|
-
// Page down - jump down by maxVisible items
|
|
504
|
-
else if (kb.matches(keyData, "tui.select.pageDown")) {
|
|
505
|
-
this.selectedIndex = Math.min(this.filteredSessions.length - 1, this.selectedIndex + this.maxVisible);
|
|
506
|
-
}
|
|
507
|
-
// Enter
|
|
508
|
-
else if (kb.matches(keyData, "tui.select.confirm")) {
|
|
509
|
-
const selected = this.filteredSessions[this.selectedIndex];
|
|
510
|
-
if (selected && this.onSelect) {
|
|
511
|
-
this.onSelect(selected.session.path);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
// Escape - cancel
|
|
515
|
-
else if (kb.matches(keyData, "tui.select.cancel")) {
|
|
516
|
-
if (this.onCancel) {
|
|
517
|
-
this.onCancel();
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
// Pass everything else to search input
|
|
521
|
-
else {
|
|
522
|
-
this.searchInput.handleInput(keyData);
|
|
523
|
-
this.filterSessions(this.searchInput.getValue());
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
/**
|
|
528
|
-
* Delete a session file, trying the `trash` CLI first, then falling back to unlink
|
|
529
|
-
*/
|
|
530
|
-
async function deleteSessionFile(sessionPath) {
|
|
531
|
-
// Try `trash` first (if installed)
|
|
532
|
-
const trashArgs = sessionPath.startsWith("-") ? ["--", sessionPath] : [sessionPath];
|
|
533
|
-
const trashResult = spawnSync("trash", trashArgs, { encoding: "utf-8" });
|
|
534
|
-
const getTrashErrorHint = () => {
|
|
535
|
-
const parts = [];
|
|
536
|
-
if (trashResult.error) {
|
|
537
|
-
parts.push(trashResult.error.message);
|
|
538
|
-
}
|
|
539
|
-
const stderr = trashResult.stderr?.trim();
|
|
540
|
-
if (stderr) {
|
|
541
|
-
parts.push(stderr.split("\n")[0] ?? stderr);
|
|
542
|
-
}
|
|
543
|
-
if (parts.length === 0)
|
|
544
|
-
return null;
|
|
545
|
-
return `trash: ${parts.join(" · ").slice(0, 200)}`;
|
|
546
|
-
};
|
|
547
|
-
// If trash reports success, or the file is gone afterwards, treat it as successful
|
|
548
|
-
if (trashResult.status === 0 || !existsSync(sessionPath)) {
|
|
549
|
-
return { ok: true, method: "trash" };
|
|
550
|
-
}
|
|
551
|
-
// Fallback to permanent deletion
|
|
552
|
-
try {
|
|
553
|
-
await unlink(sessionPath);
|
|
554
|
-
return { ok: true, method: "unlink" };
|
|
555
|
-
}
|
|
556
|
-
catch (err) {
|
|
557
|
-
const unlinkError = err instanceof Error ? err.message : String(err);
|
|
558
|
-
const trashErrorHint = getTrashErrorHint();
|
|
559
|
-
const error = trashErrorHint ? `${unlinkError} (${trashErrorHint})` : unlinkError;
|
|
560
|
-
return { ok: false, method: "unlink", error };
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
/**
|
|
564
|
-
* Component that renders a session selector
|
|
565
|
-
*/
|
|
566
|
-
export class SessionSelectorComponent extends Container {
|
|
567
|
-
handleInput(data) {
|
|
568
|
-
if (this.mode === "rename") {
|
|
569
|
-
const kb = getKeybindings();
|
|
570
|
-
if (kb.matches(data, "tui.select.cancel")) {
|
|
571
|
-
this.exitRenameMode();
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
this.renameInput.handleInput(data);
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
this.sessionList.handleInput(data);
|
|
578
|
-
}
|
|
579
|
-
canRename = true;
|
|
580
|
-
sessionList;
|
|
581
|
-
header;
|
|
582
|
-
keybindings;
|
|
583
|
-
scope = "current";
|
|
584
|
-
sortMode = "threaded";
|
|
585
|
-
nameFilter = "all";
|
|
586
|
-
currentSessions = null;
|
|
587
|
-
allSessions = null;
|
|
588
|
-
currentSessionsLoader;
|
|
589
|
-
allSessionsLoader;
|
|
590
|
-
onCancel;
|
|
591
|
-
requestRender;
|
|
592
|
-
renameSession;
|
|
593
|
-
currentLoading = false;
|
|
594
|
-
allLoading = false;
|
|
595
|
-
allLoadSeq = 0;
|
|
596
|
-
mode = "list";
|
|
597
|
-
renameInput = new Input();
|
|
598
|
-
renameTargetPath = null;
|
|
599
|
-
// Focusable implementation - propagate to sessionList for IME cursor positioning
|
|
600
|
-
_focused = false;
|
|
601
|
-
get focused() {
|
|
602
|
-
return this._focused;
|
|
603
|
-
}
|
|
604
|
-
set focused(value) {
|
|
605
|
-
this._focused = value;
|
|
606
|
-
this.sessionList.focused = value;
|
|
607
|
-
this.renameInput.focused = value;
|
|
608
|
-
if (value && this.mode === "rename") {
|
|
609
|
-
this.renameInput.focused = true;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
buildBaseLayout(content, options) {
|
|
613
|
-
this.clear();
|
|
614
|
-
this.addChild(new Spacer(1));
|
|
615
|
-
this.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
616
|
-
this.addChild(new Spacer(1));
|
|
617
|
-
if (options?.showHeader ?? true) {
|
|
618
|
-
this.addChild(this.header);
|
|
619
|
-
this.addChild(new Spacer(1));
|
|
620
|
-
}
|
|
621
|
-
this.addChild(content);
|
|
622
|
-
this.addChild(new Spacer(1));
|
|
623
|
-
this.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
624
|
-
}
|
|
625
|
-
constructor(currentSessionsLoader, allSessionsLoader, onSelect, onCancel, onExit, requestRender, options, currentSessionFilePath) {
|
|
626
|
-
super();
|
|
627
|
-
this.keybindings = options?.keybindings ?? KeybindingsManager.create();
|
|
628
|
-
this.currentSessionsLoader = currentSessionsLoader;
|
|
629
|
-
this.allSessionsLoader = allSessionsLoader;
|
|
630
|
-
this.onCancel = onCancel;
|
|
631
|
-
this.requestRender = requestRender;
|
|
632
|
-
this.header = new SessionSelectorHeader(this.scope, this.sortMode, this.nameFilter, this.requestRender);
|
|
633
|
-
const renameSession = options?.renameSession;
|
|
634
|
-
this.renameSession = renameSession;
|
|
635
|
-
this.canRename = !!renameSession;
|
|
636
|
-
this.header.setShowRenameHint(options?.showRenameHint ?? this.canRename);
|
|
637
|
-
// Create session list (starts empty, will be populated after load)
|
|
638
|
-
this.sessionList = new SessionList([], false, this.sortMode, this.nameFilter, this.keybindings, currentSessionFilePath);
|
|
639
|
-
this.buildBaseLayout(this.sessionList);
|
|
640
|
-
this.renameInput.onSubmit = (value) => {
|
|
641
|
-
void this.confirmRename(value);
|
|
642
|
-
};
|
|
643
|
-
// Ensure header status timeouts are cleared when leaving the selector
|
|
644
|
-
const clearStatusMessage = () => this.header.setStatusMessage(null);
|
|
645
|
-
this.sessionList.onSelect = (sessionPath) => {
|
|
646
|
-
clearStatusMessage();
|
|
647
|
-
onSelect(sessionPath);
|
|
648
|
-
};
|
|
649
|
-
this.sessionList.onCancel = () => {
|
|
650
|
-
clearStatusMessage();
|
|
651
|
-
onCancel();
|
|
652
|
-
};
|
|
653
|
-
this.sessionList.onExit = () => {
|
|
654
|
-
clearStatusMessage();
|
|
655
|
-
onExit();
|
|
656
|
-
};
|
|
657
|
-
this.sessionList.onToggleScope = () => this.toggleScope();
|
|
658
|
-
this.sessionList.onToggleSort = () => this.toggleSortMode();
|
|
659
|
-
this.sessionList.onToggleNameFilter = () => this.toggleNameFilter();
|
|
660
|
-
this.sessionList.onRenameSession = (sessionPath) => {
|
|
661
|
-
if (!renameSession)
|
|
662
|
-
return;
|
|
663
|
-
if (this.scope === "current" && this.currentLoading)
|
|
664
|
-
return;
|
|
665
|
-
if (this.scope === "all" && this.allLoading)
|
|
666
|
-
return;
|
|
667
|
-
const sessions = this.scope === "all" ? (this.allSessions ?? []) : (this.currentSessions ?? []);
|
|
668
|
-
const session = sessions.find((s) => s.path === sessionPath);
|
|
669
|
-
this.enterRenameMode(sessionPath, session?.name);
|
|
670
|
-
};
|
|
671
|
-
// Sync list events to header
|
|
672
|
-
this.sessionList.onTogglePath = (showPath) => {
|
|
673
|
-
this.header.setShowPath(showPath);
|
|
674
|
-
this.requestRender();
|
|
675
|
-
};
|
|
676
|
-
this.sessionList.onDeleteConfirmationChange = (path) => {
|
|
677
|
-
this.header.setConfirmingDeletePath(path);
|
|
678
|
-
this.requestRender();
|
|
679
|
-
};
|
|
680
|
-
this.sessionList.onError = (msg) => {
|
|
681
|
-
this.header.setStatusMessage({ type: "error", message: msg }, 3000);
|
|
682
|
-
this.requestRender();
|
|
683
|
-
};
|
|
684
|
-
// Handle session deletion
|
|
685
|
-
this.sessionList.onDeleteSession = async (sessionPath) => {
|
|
686
|
-
const result = await deleteSessionFile(sessionPath);
|
|
687
|
-
if (result.ok) {
|
|
688
|
-
if (this.currentSessions) {
|
|
689
|
-
this.currentSessions = this.currentSessions.filter((s) => s.path !== sessionPath);
|
|
690
|
-
}
|
|
691
|
-
if (this.allSessions) {
|
|
692
|
-
this.allSessions = this.allSessions.filter((s) => s.path !== sessionPath);
|
|
693
|
-
}
|
|
694
|
-
const sessions = this.scope === "all" ? (this.allSessions ?? []) : (this.currentSessions ?? []);
|
|
695
|
-
const showCwd = this.scope === "all";
|
|
696
|
-
this.sessionList.setSessions(sessions, showCwd);
|
|
697
|
-
const msg = result.method === "trash" ? "Session moved to trash" : "Session deleted";
|
|
698
|
-
this.header.setStatusMessage({ type: "info", message: msg }, 2000);
|
|
699
|
-
await this.refreshSessionsAfterMutation();
|
|
700
|
-
}
|
|
701
|
-
else {
|
|
702
|
-
const errorMessage = result.error ?? "Unknown error";
|
|
703
|
-
this.header.setStatusMessage({ type: "error", message: `Failed to delete: ${errorMessage}` }, 3000);
|
|
704
|
-
}
|
|
705
|
-
this.requestRender();
|
|
706
|
-
};
|
|
707
|
-
// Start loading current sessions immediately
|
|
708
|
-
this.loadCurrentSessions();
|
|
709
|
-
}
|
|
710
|
-
loadCurrentSessions() {
|
|
711
|
-
void this.loadScope("current", "initial");
|
|
712
|
-
}
|
|
713
|
-
enterRenameMode(sessionPath, currentName) {
|
|
714
|
-
this.mode = "rename";
|
|
715
|
-
this.renameTargetPath = sessionPath;
|
|
716
|
-
this.renameInput.setValue(currentName ?? "");
|
|
717
|
-
this.renameInput.focused = true;
|
|
718
|
-
const panel = new Container();
|
|
719
|
-
panel.addChild(new Text(theme.bold("Rename Session"), 1, 0));
|
|
720
|
-
panel.addChild(new Spacer(1));
|
|
721
|
-
panel.addChild(this.renameInput);
|
|
722
|
-
panel.addChild(new Spacer(1));
|
|
723
|
-
panel.addChild(new Text(theme.fg("muted", `${keyText("tui.select.confirm")} to save · ${keyText("tui.select.cancel")} to cancel`), 1, 0));
|
|
724
|
-
this.buildBaseLayout(panel, { showHeader: false });
|
|
725
|
-
this.requestRender();
|
|
726
|
-
}
|
|
727
|
-
exitRenameMode() {
|
|
728
|
-
this.mode = "list";
|
|
729
|
-
this.renameTargetPath = null;
|
|
730
|
-
this.buildBaseLayout(this.sessionList);
|
|
731
|
-
this.requestRender();
|
|
732
|
-
}
|
|
733
|
-
async confirmRename(value) {
|
|
734
|
-
const next = value.trim();
|
|
735
|
-
if (!next)
|
|
736
|
-
return;
|
|
737
|
-
const target = this.renameTargetPath;
|
|
738
|
-
if (!target) {
|
|
739
|
-
this.exitRenameMode();
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
// Find current name for callback
|
|
743
|
-
const renameSession = this.renameSession;
|
|
744
|
-
if (!renameSession) {
|
|
745
|
-
this.exitRenameMode();
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
try {
|
|
749
|
-
await renameSession(target, next);
|
|
750
|
-
await this.refreshSessionsAfterMutation();
|
|
751
|
-
}
|
|
752
|
-
finally {
|
|
753
|
-
this.exitRenameMode();
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
async loadScope(scope, reason) {
|
|
757
|
-
const showCwd = scope === "all";
|
|
758
|
-
// Mark loading
|
|
759
|
-
if (scope === "current") {
|
|
760
|
-
this.currentLoading = true;
|
|
761
|
-
}
|
|
762
|
-
else {
|
|
763
|
-
this.allLoading = true;
|
|
764
|
-
}
|
|
765
|
-
const seq = scope === "all" ? ++this.allLoadSeq : undefined;
|
|
766
|
-
this.header.setScope(scope);
|
|
767
|
-
this.header.setLoading(true);
|
|
768
|
-
this.requestRender();
|
|
769
|
-
const onProgress = (loaded, total) => {
|
|
770
|
-
if (scope !== this.scope)
|
|
771
|
-
return;
|
|
772
|
-
if (seq !== undefined && seq !== this.allLoadSeq)
|
|
773
|
-
return;
|
|
774
|
-
this.header.setProgress(loaded, total);
|
|
775
|
-
this.requestRender();
|
|
776
|
-
};
|
|
777
|
-
try {
|
|
778
|
-
const sessions = await (scope === "current"
|
|
779
|
-
? this.currentSessionsLoader(onProgress)
|
|
780
|
-
: this.allSessionsLoader(onProgress));
|
|
781
|
-
if (scope === "current") {
|
|
782
|
-
this.currentSessions = sessions;
|
|
783
|
-
this.currentLoading = false;
|
|
784
|
-
}
|
|
785
|
-
else {
|
|
786
|
-
this.allSessions = sessions;
|
|
787
|
-
this.allLoading = false;
|
|
788
|
-
}
|
|
789
|
-
if (scope !== this.scope)
|
|
790
|
-
return;
|
|
791
|
-
if (seq !== undefined && seq !== this.allLoadSeq)
|
|
792
|
-
return;
|
|
793
|
-
this.header.setLoading(false);
|
|
794
|
-
this.sessionList.setSessions(sessions, showCwd);
|
|
795
|
-
this.requestRender();
|
|
796
|
-
if (scope === "all" && sessions.length === 0 && (this.currentSessions?.length ?? 0) === 0) {
|
|
797
|
-
this.onCancel();
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
catch (err) {
|
|
801
|
-
if (scope === "current") {
|
|
802
|
-
this.currentLoading = false;
|
|
803
|
-
}
|
|
804
|
-
else {
|
|
805
|
-
this.allLoading = false;
|
|
806
|
-
}
|
|
807
|
-
if (scope !== this.scope)
|
|
808
|
-
return;
|
|
809
|
-
if (seq !== undefined && seq !== this.allLoadSeq)
|
|
810
|
-
return;
|
|
811
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
812
|
-
this.header.setLoading(false);
|
|
813
|
-
this.header.setStatusMessage({ type: "error", message: `Failed to load sessions: ${message}` }, 4000);
|
|
814
|
-
if (reason === "initial") {
|
|
815
|
-
this.sessionList.setSessions([], showCwd);
|
|
816
|
-
}
|
|
817
|
-
this.requestRender();
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
toggleSortMode() {
|
|
821
|
-
// Cycle: threaded -> recent -> relevance -> threaded
|
|
822
|
-
this.sortMode = this.sortMode === "threaded" ? "recent" : this.sortMode === "recent" ? "relevance" : "threaded";
|
|
823
|
-
this.header.setSortMode(this.sortMode);
|
|
824
|
-
this.sessionList.setSortMode(this.sortMode);
|
|
825
|
-
this.requestRender();
|
|
826
|
-
}
|
|
827
|
-
toggleNameFilter() {
|
|
828
|
-
this.nameFilter = this.nameFilter === "all" ? "named" : "all";
|
|
829
|
-
this.header.setNameFilter(this.nameFilter);
|
|
830
|
-
this.sessionList.setNameFilter(this.nameFilter);
|
|
831
|
-
this.requestRender();
|
|
832
|
-
}
|
|
833
|
-
async refreshSessionsAfterMutation() {
|
|
834
|
-
await this.loadScope(this.scope, "refresh");
|
|
835
|
-
}
|
|
836
|
-
toggleScope() {
|
|
837
|
-
if (this.scope === "current") {
|
|
838
|
-
this.scope = "all";
|
|
839
|
-
this.header.setScope(this.scope);
|
|
840
|
-
if (this.allSessions !== null) {
|
|
841
|
-
this.header.setLoading(false);
|
|
842
|
-
this.sessionList.setSessions(this.allSessions, true);
|
|
843
|
-
this.requestRender();
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
if (!this.allLoading) {
|
|
847
|
-
void this.loadScope("all", "toggle");
|
|
848
|
-
}
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
this.scope = "current";
|
|
852
|
-
this.header.setScope(this.scope);
|
|
853
|
-
this.header.setLoading(this.currentLoading);
|
|
854
|
-
this.sessionList.setSessions(this.currentSessions ?? [], false);
|
|
855
|
-
this.requestRender();
|
|
856
|
-
}
|
|
857
|
-
getSessionList() {
|
|
858
|
-
return this.sessionList;
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
//# sourceMappingURL=session-selector.js.map
|