@dreb/coding-agent 2.29.0 → 2.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/modes/interactive/components/copy-selector.d.ts +3 -3
- package/dist/modes/interactive/components/copy-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/copy-selector.js +4 -5
- package/dist/modes/interactive/components/copy-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +25 -13
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +21 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +74 -11
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +47 -10
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/message-text.d.ts +12 -0
- package/dist/utils/message-text.d.ts.map +1 -1
- package/dist/utils/message-text.js +31 -19
- package/dist/utils/message-text.js.map +1 -1
- package/docs/rpc.md +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -173,7 +173,7 @@ Type `/` in the editor to trigger commands. [Extensions](#extensions) can regist
|
|
|
173
173
|
| `/tree` | Jump to any point in the session and continue from there |
|
|
174
174
|
| `/fork` | Create a new session from the current branch |
|
|
175
175
|
| `/compact [prompt]` | Manually compact context, optional custom instructions |
|
|
176
|
-
| `/copy` | Open multi-select message picker to copy any messages to clipboard |
|
|
176
|
+
| `/copy` | Open multi-select message picker to copy any messages to clipboard. Assistant reasoning is excluded by default and offered as a separate, selectable `Thinking` row. |
|
|
177
177
|
| `/dream` | Consolidate and prune memories — backs up, merges duplicates, scans sessions for patterns |
|
|
178
178
|
| `/export [file]` | Export session to HTML file |
|
|
179
179
|
| `/buddy` | Terminal companion — hatch, pet, reroll, set model, or hide. See [docs/buddy.md](docs/buddy.md) |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Component, Container } from "@dreb/tui";
|
|
2
2
|
export interface CopyMessageItem {
|
|
3
|
-
index: number;
|
|
4
3
|
roleLabel: string;
|
|
5
4
|
preview: string;
|
|
5
|
+
text: string;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Inner list component with multi-select support.
|
|
@@ -12,7 +12,7 @@ declare class CopyMessageList implements Component {
|
|
|
12
12
|
private selectedIndex;
|
|
13
13
|
private selected;
|
|
14
14
|
private maxVisible;
|
|
15
|
-
onCopy?: (
|
|
15
|
+
onCopy?: (selectedPositions: number[]) => void | Promise<void>;
|
|
16
16
|
onCancel?: () => void;
|
|
17
17
|
constructor(items: CopyMessageItem[], maxVisible?: number);
|
|
18
18
|
getMaxVisible(): number;
|
|
@@ -25,7 +25,7 @@ declare class CopyMessageList implements Component {
|
|
|
25
25
|
*/
|
|
26
26
|
export declare class CopySelectorComponent extends Container {
|
|
27
27
|
private messageList;
|
|
28
|
-
constructor(items: CopyMessageItem[], onCopy: (
|
|
28
|
+
constructor(items: CopyMessageItem[], onCopy: (selectedPositions: number[]) => void | Promise<void>, onCancel: () => void, maxVisible?: number);
|
|
29
29
|
getMessageList(): CopyMessageList;
|
|
30
30
|
}
|
|
31
31
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/copy-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAA6D,MAAM,WAAW,CAAC;AAIjH,MAAM,WAAW,eAAe;IAC/B,
|
|
1
|
+
{"version":3,"file":"copy-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/copy-selector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAA6D,MAAM,WAAW,CAAC;AAIjH,MAAM,WAAW,eAAe;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,cAAM,eAAgB,YAAW,SAAS;IACzC,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,UAAU,CAAS;IAEpB,MAAM,CAAC,EAAE,CAAC,iBAAiB,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAE7B,YAAY,KAAK,EAAE,eAAe,EAAE,EAAE,UAAU,GAAE,MAAW,EAM5D;IAED,aAAa,IAAI,MAAM,CAEtB;IAED,UAAU,IAAI,IAAI,CAAG;IAErB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA+C9B;IAED,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CA2DjC;CACD;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,SAAS;IACnD,OAAO,CAAC,WAAW,CAAkB;IAErC,YACC,KAAK,EAAE,eAAe,EAAE,EACxB,MAAM,EAAE,CAAC,iBAAiB,EAAE,MAAM,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC7D,QAAQ,EAAE,MAAM,IAAI,EACpB,UAAU,CAAC,EAAE,MAAM,EA4BnB;IAED,cAAc,IAAI,eAAe,CAEhC;CACD","sourcesContent":["import { type Component, Container, getKeybindings, matchesKey, Spacer, Text, truncateToWidth } from \"@dreb/tui\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\nexport interface CopyMessageItem {\n\troleLabel: string; // e.g. \"You\", \"Assistant\", \"Thinking\", \"Tool: read\", \"Bash\"\n\tpreview: string; // Single-line preview text (no ANSI)\n\ttext: string; // Pre-extracted copyable text for this row (joined on copy in item order)\n}\n\n/**\n * Inner list component with multi-select support.\n */\nclass CopyMessageList implements Component {\n\tprivate items: CopyMessageItem[];\n\tprivate selectedIndex: number; // cursor position\n\tprivate selected: Set<number>; // set of selected item array positions\n\tprivate maxVisible: number;\n\n\tpublic onCopy?: (selectedPositions: number[]) => void | Promise<void>;\n\tpublic onCancel?: () => void;\n\n\tconstructor(items: CopyMessageItem[], maxVisible: number = 15) {\n\t\tthis.items = items;\n\t\tthis.maxVisible = maxVisible;\n\t\t// Bottom-anchored: start at the last (most recent) item\n\t\tthis.selectedIndex = Math.max(0, items.length - 1);\n\t\tthis.selected = new Set();\n\t}\n\n\tgetMaxVisible(): number {\n\t\treturn this.maxVisible;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\tif (this.items.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No messages found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate consistent role label width\n\t\tconst minRoleLabelWidth = 10;\n\t\tconst maxRoleLabelWidth = Math.max(minRoleLabelWidth, ...this.items.map((item) => item.roleLabel.length));\n\n\t\t// Calculate visible range centered on selectedIndex\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.items.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.items.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.items[i];\n\t\t\tconst isCursor = i === this.selectedIndex;\n\t\t\tconst isSelected = this.selected.has(i);\n\n\t\t\t// Build line: {cursor}{checkbox} {roleLabel} {preview}\n\t\t\tconst cursor = isCursor ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst checkbox = isSelected ? theme.fg(\"accent\", \"[×]\") : theme.fg(\"muted\", \"[ ]\");\n\t\t\tconst paddedRole = item.roleLabel.padEnd(maxRoleLabelWidth);\n\t\t\tconst roleStr = theme.fg(\"muted\", paddedRole);\n\n\t\t\t// Calculate remaining width for preview\n\t\t\t// cursor(2) + checkbox(3) + space(1) + role(maxRoleLabelWidth) + gap(2)\n\t\t\tconst prefixWidth = 2 + 3 + 1 + maxRoleLabelWidth + 2;\n\t\t\tconst previewWidth = Math.max(10, width - prefixWidth);\n\t\t\tconst truncatedPreview = truncateToWidth(item.preview, previewWidth);\n\t\t\tconst previewStr = isCursor ? theme.bold(truncatedPreview) : truncatedPreview;\n\n\t\t\tlines.push(`${cursor}${checkbox} ${roleStr} ${previewStr}`);\n\t\t}\n\n\t\t// Scroll indicator if items overflow viewport\n\t\tif (startIndex > 0 || endIndex < this.items.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.items.length})`);\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\t// Up arrow - move cursor up, wrap at top to bottom\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.items.length - 1 : this.selectedIndex - 1;\n\t\t}\n\t\t// Down arrow - move cursor down, wrap at bottom to top\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tthis.selectedIndex = this.selectedIndex === this.items.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t}\n\t\t// Page Up - jump up by maxVisible\n\t\telse if (kb.matches(keyData, \"tui.select.pageUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t}\n\t\t// Page Down - jump down by maxVisible\n\t\telse if (kb.matches(keyData, \"tui.select.pageDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.items.length - 1, this.selectedIndex + this.maxVisible);\n\t\t}\n\t\t// Space - toggle selection on current item\n\t\telse if (matchesKey(keyData, \"space\")) {\n\t\t\tif (this.selected.has(this.selectedIndex)) {\n\t\t\t\tthis.selected.delete(this.selectedIndex);\n\t\t\t} else {\n\t\t\t\tthis.selected.add(this.selectedIndex);\n\t\t\t}\n\t\t}\n\t\t// Ctrl+A - toggle all\n\t\telse if (matchesKey(keyData, \"ctrl+a\")) {\n\t\t\tif (this.selected.size === this.items.length) {\n\t\t\t\t// All selected → deselect all\n\t\t\t\tthis.selected.clear();\n\t\t\t} else {\n\t\t\t\t// Select all\n\t\t\t\tfor (let i = 0; i < this.items.length; i++) {\n\t\t\t\t\tthis.selected.add(i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Enter - confirm copy\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tif (this.onCopy) {\n\t\t\t\t// Return selected item positions in chronological (list) order.\n\t\t\t\t// Items are built in chronological order, so position order is correct.\n\t\t\t\tconst selectedPositions = Array.from(this.selected).sort((a, b) => a - b);\n\t\t\t\tconst result = this.onCopy(selectedPositions);\n\t\t\t\tif (result instanceof Promise) {\n\t\t\t\t\tresult.catch(() => {\n\t\t\t\t\t\t// Errors from the async copy callback are handled by the caller\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Escape/Ctrl+C - cancel\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Outer container that wraps the list with header and controls.\n */\nexport class CopySelectorComponent extends Container {\n\tprivate messageList: CopyMessageList;\n\n\tconstructor(\n\t\titems: CopyMessageItem[],\n\t\tonCopy: (selectedPositions: number[]) => void | Promise<void>,\n\t\tonCancel: () => void,\n\t\tmaxVisible?: number,\n\t) {\n\t\tsuper();\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.bold(\"Copy Messages\"), 1, 0));\n\t\tthis.addChild(\n\t\t\tnew Text(theme.fg(\"muted\", \"↑↓ Navigate · Space Toggle · Ctrl+A All · Enter Copy · Esc Cancel\"), 1, 0),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create message list\n\t\tthis.messageList = new CopyMessageList(items, maxVisible);\n\t\tthis.messageList.onCopy = onCopy;\n\t\tthis.messageList.onCancel = onCancel;\n\t\tthis.addChild(this.messageList);\n\n\t\t// Bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Auto-cancel if no items\n\t\tif (items.length === 0) {\n\t\t\tsetTimeout(() => onCancel(), 100);\n\t\t}\n\t}\n\n\tgetMessageList(): CopyMessageList {\n\t\treturn this.messageList;\n\t}\n}\n"]}
|
|
@@ -101,11 +101,10 @@ class CopyMessageList {
|
|
|
101
101
|
// Enter - confirm copy
|
|
102
102
|
else if (kb.matches(keyData, "tui.select.confirm")) {
|
|
103
103
|
if (this.onCopy) {
|
|
104
|
-
// Return
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const result = this.onCopy(selectedIndices);
|
|
104
|
+
// Return selected item positions in chronological (list) order.
|
|
105
|
+
// Items are built in chronological order, so position order is correct.
|
|
106
|
+
const selectedPositions = Array.from(this.selected).sort((a, b) => a - b);
|
|
107
|
+
const result = this.onCopy(selectedPositions);
|
|
109
108
|
if (result instanceof Promise) {
|
|
110
109
|
result.catch(() => {
|
|
111
110
|
// Errors from the async copy callback are handled by the caller
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/copy-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, Container, getKeybindings, matchesKey, Spacer, Text, truncateToWidth } from \"@dreb/tui\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\nexport interface CopyMessageItem {\n\tindex: number; // Original index in messages array (for chronological ordering on copy)\n\troleLabel: string; // e.g. \"You\", \"Assistant\", \"Tool: read\", \"Bash\"\n\tpreview: string; // Single-line preview text (no ANSI)\n}\n\n/**\n * Inner list component with multi-select support.\n */\nclass CopyMessageList implements Component {\n\tprivate items: CopyMessageItem[];\n\tprivate selectedIndex: number; // cursor position\n\tprivate selected: Set<number>; // set of selected item array positions\n\tprivate maxVisible: number;\n\n\tpublic onCopy?: (selectedIndices: number[]) => void | Promise<void>;\n\tpublic onCancel?: () => void;\n\n\tconstructor(items: CopyMessageItem[], maxVisible: number = 15) {\n\t\tthis.items = items;\n\t\tthis.maxVisible = maxVisible;\n\t\t// Bottom-anchored: start at the last (most recent) item\n\t\tthis.selectedIndex = Math.max(0, items.length - 1);\n\t\tthis.selected = new Set();\n\t}\n\n\tgetMaxVisible(): number {\n\t\treturn this.maxVisible;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\tif (this.items.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No messages found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate consistent role label width\n\t\tconst minRoleLabelWidth = 10;\n\t\tconst maxRoleLabelWidth = Math.max(minRoleLabelWidth, ...this.items.map((item) => item.roleLabel.length));\n\n\t\t// Calculate visible range centered on selectedIndex\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.items.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.items.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.items[i];\n\t\t\tconst isCursor = i === this.selectedIndex;\n\t\t\tconst isSelected = this.selected.has(i);\n\n\t\t\t// Build line: {cursor}{checkbox} {roleLabel} {preview}\n\t\t\tconst cursor = isCursor ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst checkbox = isSelected ? theme.fg(\"accent\", \"[×]\") : theme.fg(\"muted\", \"[ ]\");\n\t\t\tconst paddedRole = item.roleLabel.padEnd(maxRoleLabelWidth);\n\t\t\tconst roleStr = theme.fg(\"muted\", paddedRole);\n\n\t\t\t// Calculate remaining width for preview\n\t\t\t// cursor(2) + checkbox(3) + space(1) + role(maxRoleLabelWidth) + gap(2)\n\t\t\tconst prefixWidth = 2 + 3 + 1 + maxRoleLabelWidth + 2;\n\t\t\tconst previewWidth = Math.max(10, width - prefixWidth);\n\t\t\tconst truncatedPreview = truncateToWidth(item.preview, previewWidth);\n\t\t\tconst previewStr = isCursor ? theme.bold(truncatedPreview) : truncatedPreview;\n\n\t\t\tlines.push(`${cursor}${checkbox} ${roleStr} ${previewStr}`);\n\t\t}\n\n\t\t// Scroll indicator if items overflow viewport\n\t\tif (startIndex > 0 || endIndex < this.items.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.items.length})`);\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\t// Up arrow - move cursor up, wrap at top to bottom\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.items.length - 1 : this.selectedIndex - 1;\n\t\t}\n\t\t// Down arrow - move cursor down, wrap at bottom to top\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tthis.selectedIndex = this.selectedIndex === this.items.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t}\n\t\t// Page Up - jump up by maxVisible\n\t\telse if (kb.matches(keyData, \"tui.select.pageUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t}\n\t\t// Page Down - jump down by maxVisible\n\t\telse if (kb.matches(keyData, \"tui.select.pageDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.items.length - 1, this.selectedIndex + this.maxVisible);\n\t\t}\n\t\t// Space - toggle selection on current item\n\t\telse if (matchesKey(keyData, \"space\")) {\n\t\t\tif (this.selected.has(this.selectedIndex)) {\n\t\t\t\tthis.selected.delete(this.selectedIndex);\n\t\t\t} else {\n\t\t\t\tthis.selected.add(this.selectedIndex);\n\t\t\t}\n\t\t}\n\t\t// Ctrl+A - toggle all\n\t\telse if (matchesKey(keyData, \"ctrl+a\")) {\n\t\t\tif (this.selected.size === this.items.length) {\n\t\t\t\t// All selected → deselect all\n\t\t\t\tthis.selected.clear();\n\t\t\t} else {\n\t\t\t\t// Select all\n\t\t\t\tfor (let i = 0; i < this.items.length; i++) {\n\t\t\t\t\tthis.selected.add(i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Enter - confirm copy\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tif (this.onCopy) {\n\t\t\t\t// Return original indices sorted chronologically\n\t\t\t\tconst selectedIndices = Array.from(this.selected)\n\t\t\t\t\t.map((i) => this.items[i].index)\n\t\t\t\t\t.sort((a, b) => a - b);\n\t\t\t\tconst result = this.onCopy(selectedIndices);\n\t\t\t\tif (result instanceof Promise) {\n\t\t\t\t\tresult.catch(() => {\n\t\t\t\t\t\t// Errors from the async copy callback are handled by the caller\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Escape/Ctrl+C - cancel\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Outer container that wraps the list with header and controls.\n */\nexport class CopySelectorComponent extends Container {\n\tprivate messageList: CopyMessageList;\n\n\tconstructor(\n\t\titems: CopyMessageItem[],\n\t\tonCopy: (selectedIndices: number[]) => void | Promise<void>,\n\t\tonCancel: () => void,\n\t\tmaxVisible?: number,\n\t) {\n\t\tsuper();\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.bold(\"Copy Messages\"), 1, 0));\n\t\tthis.addChild(\n\t\t\tnew Text(theme.fg(\"muted\", \"↑↓ Navigate · Space Toggle · Ctrl+A All · Enter Copy · Esc Cancel\"), 1, 0),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create message list\n\t\tthis.messageList = new CopyMessageList(items, maxVisible);\n\t\tthis.messageList.onCopy = onCopy;\n\t\tthis.messageList.onCancel = onCancel;\n\t\tthis.addChild(this.messageList);\n\n\t\t// Bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Auto-cancel if no items\n\t\tif (items.length === 0) {\n\t\t\tsetTimeout(() => onCancel(), 100);\n\t\t}\n\t}\n\n\tgetMessageList(): CopyMessageList {\n\t\treturn this.messageList;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"copy-selector.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/copy-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, Container, getKeybindings, matchesKey, Spacer, Text, truncateToWidth } from \"@dreb/tui\";\nimport { theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\n\nexport interface CopyMessageItem {\n\troleLabel: string; // e.g. \"You\", \"Assistant\", \"Thinking\", \"Tool: read\", \"Bash\"\n\tpreview: string; // Single-line preview text (no ANSI)\n\ttext: string; // Pre-extracted copyable text for this row (joined on copy in item order)\n}\n\n/**\n * Inner list component with multi-select support.\n */\nclass CopyMessageList implements Component {\n\tprivate items: CopyMessageItem[];\n\tprivate selectedIndex: number; // cursor position\n\tprivate selected: Set<number>; // set of selected item array positions\n\tprivate maxVisible: number;\n\n\tpublic onCopy?: (selectedPositions: number[]) => void | Promise<void>;\n\tpublic onCancel?: () => void;\n\n\tconstructor(items: CopyMessageItem[], maxVisible: number = 15) {\n\t\tthis.items = items;\n\t\tthis.maxVisible = maxVisible;\n\t\t// Bottom-anchored: start at the last (most recent) item\n\t\tthis.selectedIndex = Math.max(0, items.length - 1);\n\t\tthis.selected = new Set();\n\t}\n\n\tgetMaxVisible(): number {\n\t\treturn this.maxVisible;\n\t}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\n\t\tif (this.items.length === 0) {\n\t\t\tlines.push(theme.fg(\"muted\", \" No messages found\"));\n\t\t\treturn lines;\n\t\t}\n\n\t\t// Calculate consistent role label width\n\t\tconst minRoleLabelWidth = 10;\n\t\tconst maxRoleLabelWidth = Math.max(minRoleLabelWidth, ...this.items.map((item) => item.roleLabel.length));\n\n\t\t// Calculate visible range centered on selectedIndex\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(this.maxVisible / 2), this.items.length - this.maxVisible),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.items.length);\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.items[i];\n\t\t\tconst isCursor = i === this.selectedIndex;\n\t\t\tconst isSelected = this.selected.has(i);\n\n\t\t\t// Build line: {cursor}{checkbox} {roleLabel} {preview}\n\t\t\tconst cursor = isCursor ? theme.fg(\"accent\", \"› \") : \" \";\n\t\t\tconst checkbox = isSelected ? theme.fg(\"accent\", \"[×]\") : theme.fg(\"muted\", \"[ ]\");\n\t\t\tconst paddedRole = item.roleLabel.padEnd(maxRoleLabelWidth);\n\t\t\tconst roleStr = theme.fg(\"muted\", paddedRole);\n\n\t\t\t// Calculate remaining width for preview\n\t\t\t// cursor(2) + checkbox(3) + space(1) + role(maxRoleLabelWidth) + gap(2)\n\t\t\tconst prefixWidth = 2 + 3 + 1 + maxRoleLabelWidth + 2;\n\t\t\tconst previewWidth = Math.max(10, width - prefixWidth);\n\t\t\tconst truncatedPreview = truncateToWidth(item.preview, previewWidth);\n\t\t\tconst previewStr = isCursor ? theme.bold(truncatedPreview) : truncatedPreview;\n\n\t\t\tlines.push(`${cursor}${checkbox} ${roleStr} ${previewStr}`);\n\t\t}\n\n\t\t// Scroll indicator if items overflow viewport\n\t\tif (startIndex > 0 || endIndex < this.items.length) {\n\t\t\tconst scrollInfo = theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.items.length})`);\n\t\t\tlines.push(scrollInfo);\n\t\t}\n\n\t\treturn lines;\n\t}\n\n\thandleInput(keyData: string): void {\n\t\tconst kb = getKeybindings();\n\n\t\t// Up arrow - move cursor up, wrap at top to bottom\n\t\tif (kb.matches(keyData, \"tui.select.up\")) {\n\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.items.length - 1 : this.selectedIndex - 1;\n\t\t}\n\t\t// Down arrow - move cursor down, wrap at bottom to top\n\t\telse if (kb.matches(keyData, \"tui.select.down\")) {\n\t\t\tthis.selectedIndex = this.selectedIndex === this.items.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t}\n\t\t// Page Up - jump up by maxVisible\n\t\telse if (kb.matches(keyData, \"tui.select.pageUp\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - this.maxVisible);\n\t\t}\n\t\t// Page Down - jump down by maxVisible\n\t\telse if (kb.matches(keyData, \"tui.select.pageDown\")) {\n\t\t\tthis.selectedIndex = Math.min(this.items.length - 1, this.selectedIndex + this.maxVisible);\n\t\t}\n\t\t// Space - toggle selection on current item\n\t\telse if (matchesKey(keyData, \"space\")) {\n\t\t\tif (this.selected.has(this.selectedIndex)) {\n\t\t\t\tthis.selected.delete(this.selectedIndex);\n\t\t\t} else {\n\t\t\t\tthis.selected.add(this.selectedIndex);\n\t\t\t}\n\t\t}\n\t\t// Ctrl+A - toggle all\n\t\telse if (matchesKey(keyData, \"ctrl+a\")) {\n\t\t\tif (this.selected.size === this.items.length) {\n\t\t\t\t// All selected → deselect all\n\t\t\t\tthis.selected.clear();\n\t\t\t} else {\n\t\t\t\t// Select all\n\t\t\t\tfor (let i = 0; i < this.items.length; i++) {\n\t\t\t\t\tthis.selected.add(i);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Enter - confirm copy\n\t\telse if (kb.matches(keyData, \"tui.select.confirm\")) {\n\t\t\tif (this.onCopy) {\n\t\t\t\t// Return selected item positions in chronological (list) order.\n\t\t\t\t// Items are built in chronological order, so position order is correct.\n\t\t\t\tconst selectedPositions = Array.from(this.selected).sort((a, b) => a - b);\n\t\t\t\tconst result = this.onCopy(selectedPositions);\n\t\t\t\tif (result instanceof Promise) {\n\t\t\t\t\tresult.catch(() => {\n\t\t\t\t\t\t// Errors from the async copy callback are handled by the caller\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Escape/Ctrl+C - cancel\n\t\telse if (kb.matches(keyData, \"tui.select.cancel\")) {\n\t\t\tif (this.onCancel) {\n\t\t\t\tthis.onCancel();\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Outer container that wraps the list with header and controls.\n */\nexport class CopySelectorComponent extends Container {\n\tprivate messageList: CopyMessageList;\n\n\tconstructor(\n\t\titems: CopyMessageItem[],\n\t\tonCopy: (selectedPositions: number[]) => void | Promise<void>,\n\t\tonCancel: () => void,\n\t\tmaxVisible?: number,\n\t) {\n\t\tsuper();\n\n\t\t// Add header\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.bold(\"Copy Messages\"), 1, 0));\n\t\tthis.addChild(\n\t\t\tnew Text(theme.fg(\"muted\", \"↑↓ Navigate · Space Toggle · Ctrl+A All · Enter Copy · Esc Cancel\"), 1, 0),\n\t\t);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Create message list\n\t\tthis.messageList = new CopyMessageList(items, maxVisible);\n\t\tthis.messageList.onCopy = onCopy;\n\t\tthis.messageList.onCancel = onCancel;\n\t\tthis.addChild(this.messageList);\n\n\t\t// Bottom border\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Auto-cancel if no items\n\t\tif (items.length === 0) {\n\t\t\tsetTimeout(() => onCancel(), 100);\n\t\t}\n\t}\n\n\tgetMessageList(): CopyMessageList {\n\t\treturn this.messageList;\n\t}\n}\n"]}
|