@itookit/dsht 0.2.4 → 0.3.2

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.
Files changed (129) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +54 -25
  3. package/README.zh.md +54 -25
  4. package/dist/catalog/controller.d.ts +32 -0
  5. package/dist/catalog/controller.js +88 -0
  6. package/dist/catalog/index.d.ts +2 -0
  7. package/dist/catalog/index.js +2 -0
  8. package/dist/{cli.js → cli/index.js} +40 -24
  9. package/dist/controller/connection.d.ts +80 -0
  10. package/dist/controller/connection.js +190 -0
  11. package/dist/controller/controller.d.ts +260 -0
  12. package/dist/controller/controller.js +362 -0
  13. package/dist/controller/index.d.ts +5 -0
  14. package/dist/controller/index.js +3 -0
  15. package/dist/controller/memory-log.d.ts +35 -0
  16. package/dist/controller/memory-log.js +95 -0
  17. package/dist/cost/controller.d.ts +34 -0
  18. package/dist/cost/controller.js +107 -0
  19. package/dist/cost/index.d.ts +9 -0
  20. package/dist/cost/index.js +7 -0
  21. package/dist/cost/ledger-files.d.ts +20 -0
  22. package/dist/cost/ledger-files.js +128 -0
  23. package/dist/cost/ledger.d.ts +65 -0
  24. package/dist/cost/ledger.js +136 -0
  25. package/dist/cost/pricing.d.ts +48 -0
  26. package/dist/cost/pricing.js +141 -0
  27. package/dist/cost/records.d.ts +17 -0
  28. package/dist/cost/records.js +66 -0
  29. package/dist/cost/scanner.d.ts +22 -0
  30. package/dist/cost/scanner.js +95 -0
  31. package/dist/cost/types.d.ts +69 -0
  32. package/dist/cost/types.js +3 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.js +2 -0
  35. package/dist/session/connection-view.d.ts +18 -0
  36. package/dist/session/connection-view.js +1 -0
  37. package/dist/{controller.d.ts → session/controller.d.ts} +119 -137
  38. package/dist/session/controller.js +616 -0
  39. package/dist/session/export-html.d.ts +9 -0
  40. package/dist/session/export-html.js +39 -0
  41. package/dist/session/export.d.ts +9 -0
  42. package/dist/session/export.js +38 -0
  43. package/dist/{history.d.ts → session/history.d.ts} +18 -1
  44. package/dist/session/history.js +338 -0
  45. package/dist/session/index.d.ts +17 -0
  46. package/dist/session/index.js +10 -0
  47. package/dist/session/markdown.d.ts +39 -0
  48. package/dist/session/markdown.js +255 -0
  49. package/dist/session/math.d.ts +11 -0
  50. package/dist/session/math.js +82 -0
  51. package/dist/{navigation.d.ts → session/navigation.d.ts} +1 -1
  52. package/dist/{navigation.js → session/navigation.js} +1 -1
  53. package/dist/{references.js → session/references.js} +1 -1
  54. package/dist/{telemetry.d.ts → session/telemetry.d.ts} +12 -1
  55. package/dist/{telemetry.js → session/telemetry.js} +18 -4
  56. package/dist/{transcript.d.ts → session/transcript.d.ts} +23 -1
  57. package/dist/{transcript.js → session/transcript.js} +36 -18
  58. package/dist/session/types.d.ts +18 -0
  59. package/dist/session/types.js +2 -0
  60. package/dist/state.d.ts +41 -0
  61. package/dist/state.js +9 -0
  62. package/dist/storage/directories.d.ts +14 -0
  63. package/dist/storage/directories.js +24 -0
  64. package/dist/storage/files.d.ts +43 -0
  65. package/dist/storage/files.js +135 -0
  66. package/dist/storage/index.d.ts +3 -0
  67. package/dist/storage/index.js +3 -0
  68. package/dist/transport/auth.js +65 -0
  69. package/dist/{client.d.ts → transport/client.d.ts} +8 -1
  70. package/dist/{client.js → transport/client.js} +21 -4
  71. package/dist/transport/host.d.ts +13 -0
  72. package/dist/transport/host.js +1 -0
  73. package/dist/ui/app.d.ts +11 -0
  74. package/dist/ui/app.js +726 -0
  75. package/dist/ui/chat/header.d.ts +11 -0
  76. package/dist/ui/chat/header.js +14 -0
  77. package/dist/{history-view.d.ts → ui/chat/history-view.d.ts} +1 -1
  78. package/dist/{history-view.js → ui/chat/history-view.js} +3 -3
  79. package/dist/ui/chat/status.d.ts +79 -0
  80. package/dist/ui/chat/status.js +311 -0
  81. package/dist/ui/chat/viewport.d.ts +17 -0
  82. package/dist/ui/chat/viewport.js +14 -0
  83. package/dist/ui/commands/parse.d.ts +96 -0
  84. package/dist/ui/commands/parse.js +121 -0
  85. package/dist/ui/commands/registry.d.ts +33 -0
  86. package/dist/ui/commands/registry.js +72 -0
  87. package/dist/ui/copy-mode.d.ts +4 -0
  88. package/dist/ui/copy-mode.js +6 -0
  89. package/dist/{cost-view.d.ts → ui/dialogs/cost.d.ts} +1 -1
  90. package/dist/{cost-view.js → ui/dialogs/cost.js} +3 -3
  91. package/dist/ui/dialogs/index.d.ts +120 -0
  92. package/dist/ui/dialogs/index.js +113 -0
  93. package/dist/ui/dialogs/picker.d.ts +18 -0
  94. package/dist/ui/dialogs/picker.js +38 -0
  95. package/dist/ui/frozen.d.ts +8 -0
  96. package/dist/ui/frozen.js +7 -0
  97. package/dist/ui/input/references.d.ts +12 -0
  98. package/dist/ui/input/references.js +15 -0
  99. package/dist/ui/mount.d.ts +6 -0
  100. package/dist/ui/mount.js +11 -0
  101. package/dist/{theme.d.ts → ui/theme/index.d.ts} +1 -1
  102. package/dsht-m.png +0 -0
  103. package/package.json +20 -14
  104. package/dist/app.d.ts +0 -21
  105. package/dist/app.js +0 -746
  106. package/dist/auth.js +0 -108
  107. package/dist/controller.js +0 -923
  108. package/dist/cost.d.ts +0 -119
  109. package/dist/cost.js +0 -313
  110. package/dist/history.js +0 -177
  111. package/dist/status.d.ts +0 -28
  112. package/dist/status.js +0 -157
  113. package/dsht.png +0 -0
  114. /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
  115. /package/dist/{memory.d.ts → session/memory.d.ts} +0 -0
  116. /package/dist/{memory.js → session/memory.js} +0 -0
  117. /package/dist/{references.d.ts → session/references.d.ts} +0 -0
  118. /package/dist/{auth.d.ts → transport/auth.d.ts} +0 -0
  119. /package/dist/{endpoint.d.ts → transport/endpoint.d.ts} +0 -0
  120. /package/dist/{endpoint.js → transport/endpoint.js} +0 -0
  121. /package/dist/{wire.d.ts → transport/wire.d.ts} +0 -0
  122. /package/dist/{wire.js → transport/wire.js} +0 -0
  123. /package/dist/{input-history.d.ts → ui/input/history.d.ts} +0 -0
  124. /package/dist/{input-history.js → ui/input/history.js} +0 -0
  125. /package/dist/{input.d.ts → ui/input/input.d.ts} +0 -0
  126. /package/dist/{input.js → ui/input/input.js} +0 -0
  127. /package/dist/{mouse.d.ts → ui/input/mouse.d.ts} +0 -0
  128. /package/dist/{mouse.js → ui/input/mouse.js} +0 -0
  129. /package/dist/{theme.js → ui/theme/index.js} +0 -0
@@ -0,0 +1,121 @@
1
+ /** Classify one submitted composer line into the action the terminal performs. */
2
+ import { navigationCommand } from "../../session/navigation.js";
3
+ /** Classify one composer draft without performing any of its effects.
4
+ *
5
+ * The order of the checks is the command precedence: reference completion, copy mode, the panels
6
+ * that toggle in place, navigation and removal, then the session and host commands, then answers,
7
+ * then a plain prompt.
8
+ * @param raw - Draft exactly as submitted.
9
+ * @param context - Current UI facts.
10
+ * @returns The action to perform.
11
+ */
12
+ export function classifySubmission(raw, context) {
13
+ if (context.referenceOpen)
14
+ return { kind: 'reference' };
15
+ if (context.copyMode)
16
+ return { kind: 'ignore' };
17
+ const value = raw.trim();
18
+ if (!value)
19
+ return { kind: 'ignore' };
20
+ if (value === '/copy')
21
+ return { kind: 'copy' };
22
+ if (value === '/quit')
23
+ return { kind: 'quit' };
24
+ if (value === '/cost')
25
+ return { kind: 'panel', panel: 'cost' };
26
+ if (value === '/status')
27
+ return { kind: 'panel', panel: 'status' };
28
+ if (value === '/help')
29
+ return { kind: 'panel', panel: 'help' };
30
+ const navigation = navigationCommand(value);
31
+ if (navigation && /^--(?:delete|archive)(?:\s|$)/.test(navigation.query ?? '')) {
32
+ const query = navigation.query.replace(/^--(?:delete|archive)\s*/, '');
33
+ if (!query)
34
+ return { kind: 'error', message: 'Specify the name or ID to remove' };
35
+ return { kind: 'remove', target: navigation.kind, query };
36
+ }
37
+ if (navigation)
38
+ return { kind: 'navigate', target: navigation.kind, query: navigation.query };
39
+ if (context.screen === 'path')
40
+ return { kind: 'path', value };
41
+ if (value === '/latest')
42
+ return { kind: 'latest' };
43
+ if (/^\/model(?: |$)/.test(value)) {
44
+ if (context.screen !== 'chat')
45
+ return { kind: 'error', message: 'Select a session first' };
46
+ const args = value.split(/\s+/).slice(1);
47
+ if (args.length && (args.length < 2 || args.length > 3))
48
+ return { kind: 'error', message: 'Use /model [provider model [effort]]' };
49
+ return { kind: 'models', args };
50
+ }
51
+ if (value === '/queue') {
52
+ if (context.screen !== 'chat')
53
+ return { kind: 'error', message: 'Select a session first' };
54
+ if (context.pending)
55
+ return { kind: 'error', message: 'Answer the pending question or approval first' };
56
+ return { kind: 'queue' };
57
+ }
58
+ if (value === '/new')
59
+ return { kind: 'newSession' };
60
+ if (value === '/history' || value.startsWith('/history ')) {
61
+ if (context.screen !== 'chat')
62
+ return { kind: 'error', message: 'Select a session first' };
63
+ return { kind: 'history', query: value.slice(8).trim() };
64
+ }
65
+ if (/^\/(?:search|ssearch|wsearch)(?: |$)/.test(value)) {
66
+ const [command, ...words] = value.split(' ');
67
+ const query = words.join(' ').trim();
68
+ if (!query)
69
+ return { kind: 'error', message: `Use ${command} <text>` };
70
+ return command === '/search' ? { kind: 'historySearch', query }
71
+ : { kind: 'sessionSearch', command: command, query };
72
+ }
73
+ if (/^\/think(?: |$)/.test(value)) {
74
+ if (context.screen !== 'chat')
75
+ return { kind: 'error', message: 'Select a session first' };
76
+ return { kind: 'think', target: value.slice(6).trim() };
77
+ }
78
+ if (value === '/older')
79
+ return { kind: 'older' };
80
+ if (/^\/compact(?: |$)/.test(value)) {
81
+ if (context.screen !== 'chat')
82
+ return { kind: 'error', message: 'Select a session first' };
83
+ if (value !== '/compact')
84
+ return { kind: 'error', message: 'Use /compact (no arguments)' };
85
+ return { kind: 'compact' };
86
+ }
87
+ if (value === '/cancel')
88
+ return { kind: 'cancel' };
89
+ if (value === '/allow')
90
+ return { kind: 'approval', allowed: true };
91
+ if (value === '/deny')
92
+ return { kind: 'approval', allowed: false };
93
+ if (/^\/(?:plan|goal|permission|feedback)(?:\s|$)/.test(value)) {
94
+ if (context.screen !== 'chat')
95
+ return { kind: 'error', message: 'Select a session first' };
96
+ if (context.pending)
97
+ return { kind: 'error', message: 'Answer the pending question or approval first' };
98
+ return { kind: 'hostCommand', line: value };
99
+ }
100
+ if (/^\/export(?:\s|$)/.test(value)) {
101
+ if (context.screen !== 'chat')
102
+ return { kind: 'error', message: 'Select a session first' };
103
+ const destination = value.slice(7).trim().replace(/^(["'])(.*)\1$/, '$2');
104
+ return { kind: 'export', ...(destination ? { destination } : {}) };
105
+ }
106
+ if (/^\/export-html(?:\s|$)/.test(value)) {
107
+ if (context.screen !== 'chat')
108
+ return { kind: 'error', message: 'Select a session first' };
109
+ const destination = value.slice(12).trim().replace(/^(["'])(.*)\1$/, '$2');
110
+ return { kind: 'exportHtml', ...(destination ? { destination } : {}) };
111
+ }
112
+ if (context.question)
113
+ return { kind: 'answer', text: value };
114
+ if (context.pending)
115
+ return { kind: 'error', message: 'Answer the approval with /allow or /deny' };
116
+ if (value.startsWith('/'))
117
+ return { kind: 'error', message: 'Unknown command. Use /help.' };
118
+ if (context.screen !== 'chat')
119
+ return { kind: 'error', message: 'Choose a session or type /ws or /resume' };
120
+ return { kind: 'prompt', text: value };
121
+ }
@@ -0,0 +1,33 @@
1
+ /** Slash-command catalog shared by completion, `/help` and the submission router. */
2
+ /** One slash command advertised by completion and `/help`. */
3
+ export interface CommandHint {
4
+ /** Slash command as typed without arguments. */
5
+ command: string;
6
+ /** Argument hint shown after the command; absent when it takes none. */
7
+ usage?: string;
8
+ /** One-line action description shown by `/help`. */
9
+ description: string;
10
+ }
11
+ /** Command discovery catalog shared by Tab completion and the `/help` panel. */
12
+ export declare const COMMAND_HINTS: readonly CommandHint[];
13
+ /** Command names only, in catalog order. */
14
+ export declare const COMMANDS: string[];
15
+ /** Command column text per hint, aligned in the `/help` panel. */
16
+ export declare const COMMAND_LABELS: string[];
17
+ /** Widest command column, so descriptions start on one column. */
18
+ export declare const COMMAND_LABEL_WIDTH: number;
19
+ /** Longest common prefix of the candidate commands, so Tab can extend an ambiguous draft.
20
+ * @param values - Command candidates.
21
+ * @returns The shared leading prefix.
22
+ */
23
+ export declare function commonPrefix(values: string[]): string;
24
+ /** Complete the leading slash command; an ambiguous draft extends to the shared prefix.
25
+ * @param input - Current composer draft.
26
+ * @returns The completed draft, or undefined when nothing can be completed.
27
+ */
28
+ export declare function completeCommand(input: string): string | undefined;
29
+ /** Commands matching the current draft, shown under the composer.
30
+ * @param input - Current composer draft.
31
+ * @returns Candidate command names.
32
+ */
33
+ export declare function suggestedCommands(input: string): string[];
@@ -0,0 +1,72 @@
1
+ /** Slash-command catalog shared by completion, `/help` and the submission router. */
2
+ /** Command discovery catalog shared by Tab completion and the `/help` panel. */
3
+ export const COMMAND_HINTS = [
4
+ { command: '/ws', usage: '[name or ID]', description: 'List/switch workspaces; --delete name removes registration' },
5
+ { command: '/resume', usage: '[title or ID]', description: 'List/switch sessions; --delete ID archives with confirmation' },
6
+ { command: '/model', usage: '[provider model [effort]]', description: 'Choose a model and reasoning effort for subsequent requests' },
7
+ { command: '/new', description: 'Create a session in the selected workspace' },
8
+ { command: '/copy', description: 'Freeze for native selection; Esc resumes (Ctrl+S shortcut)' },
9
+ { command: '/latest', description: 'Return to the live conversation' },
10
+ { command: '/older', description: 'Load earlier history' },
11
+ { command: '/history', usage: '[text]', description: 'List your prompts, optionally filtered' },
12
+ { command: '/search', usage: 'text', description: 'Search history page by page and open a match' },
13
+ { command: '/ssearch', usage: 'text', description: 'Search sessions in the current workspace' },
14
+ { command: '/wsearch', usage: 'text', description: 'Search sessions across all workspaces' },
15
+ { command: '/compact', description: 'Compact older history while the session is idle' },
16
+ { command: '/cancel', description: 'Cancel the active turn' },
17
+ { command: '/queue', description: 'View and remove pending input' },
18
+ { command: '/plan', usage: '[off|message]', description: 'Enter or leave host plan mode' },
19
+ { command: '/goal', usage: '[action|objective]', description: 'View or manage the host task goal' },
20
+ { command: '/permission', usage: '[preset]', description: 'View or switch the host permission preset' },
21
+ { command: '/feedback', usage: 'text', description: 'Record feedback about the session' },
22
+ { command: '/export', usage: '[local.zip]', description: 'Save the session log ZIP to a new local file' },
23
+ { command: '/export-html', usage: '[local.html]', description: 'Save loaded conversation with diagrams and math as offline HTML' },
24
+ { command: '/allow', description: 'Approve the pending request once' },
25
+ { command: '/deny', description: 'Reject the pending request' },
26
+ { command: '/status', description: 'Show full session status details' },
27
+ { command: '/cost', description: 'Show cost estimates and refresh usage' },
28
+ { command: '/think', usage: '[seq or live]', description: 'Inspect reasoning with user prompt summaries' },
29
+ { command: '/help', description: 'Show this command list' },
30
+ { command: '/quit', description: 'Exit dsht' },
31
+ ];
32
+ /** Command names only, in catalog order. */
33
+ export const COMMANDS = COMMAND_HINTS.map(hint => hint.command);
34
+ /** Command column text per hint, aligned in the `/help` panel. */
35
+ export const COMMAND_LABELS = COMMAND_HINTS.map(hint => hint.usage === undefined ? hint.command : `${hint.command} ${hint.usage}`);
36
+ /** Widest command column, so descriptions start on one column. */
37
+ export const COMMAND_LABEL_WIDTH = Math.max(...COMMAND_LABELS.map(label => label.length)) + 2;
38
+ /** Longest common prefix of the candidate commands, so Tab can extend an ambiguous draft.
39
+ * @param values - Command candidates.
40
+ * @returns The shared leading prefix.
41
+ */
42
+ export function commonPrefix(values) {
43
+ let prefix = values[0] ?? '';
44
+ for (const value of values) {
45
+ let index = 0;
46
+ while (index < prefix.length && index < value.length && prefix[index] === value[index])
47
+ index++;
48
+ prefix = prefix.slice(0, index);
49
+ }
50
+ return prefix;
51
+ }
52
+ /** Complete the leading slash command; an ambiguous draft extends to the shared prefix.
53
+ * @param input - Current composer draft.
54
+ * @returns The completed draft, or undefined when nothing can be completed.
55
+ */
56
+ export function completeCommand(input) {
57
+ if (!input.startsWith('/') || input.includes(' '))
58
+ return undefined;
59
+ const matches = COMMANDS.filter(command => command.startsWith(input));
60
+ const only = matches.length === 1 ? matches[0] : undefined;
61
+ if (only !== undefined)
62
+ return `${only} `;
63
+ const prefix = commonPrefix(matches);
64
+ return prefix.length > input.length ? prefix : undefined;
65
+ }
66
+ /** Commands matching the current draft, shown under the composer.
67
+ * @param input - Current composer draft.
68
+ * @returns Candidate command names.
69
+ */
70
+ export function suggestedCommands(input) {
71
+ return COMMANDS.filter(command => input.startsWith('/') && !input.includes(' ') && command.startsWith(input));
72
+ }
@@ -0,0 +1,4 @@
1
+ /** True while copy mode keeps pickers and the composer inactive. */
2
+ export declare const CopyMode: import("react").Context<boolean>;
3
+ /** @returns Whether the surrounding application is in copy mode. */
4
+ export declare function useCopyMode(): boolean;
@@ -0,0 +1,6 @@
1
+ /** Whether copy mode froze the display for native terminal selection. */
2
+ import { createContext, useContext } from 'react';
3
+ /** True while copy mode keeps pickers and the composer inactive. */
4
+ export const CopyMode = createContext(false);
5
+ /** @returns Whether the surrounding application is in copy mode. */
6
+ export function useCopyMode() { return useContext(CopyMode); }
@@ -1,4 +1,4 @@
1
- import type { Controller } from './controller.ts';
1
+ import type { Controller } from '../../controller/index.ts';
2
2
  /** Render cached totals while the independent HTTP cost scan refreshes.
3
3
  * @param controller - Selected session and origin-scoped cost ledger.
4
4
  * @returns Billing panel, including unpriced models and refresh errors.
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /** Billing estimates with explicit coverage, calendar and unknown-price indicators. */
3
- import { useTheme } from "./theme.js";
3
+ import { useTheme } from "../theme/index.js";
4
4
  import { Box, Text } from 'ink';
5
- import { costText } from "./cost.js";
6
- import { safeText } from "./wire.js";
5
+ import { costText } from "../../cost/index.js";
6
+ import { safeText } from "../../transport/wire.js";
7
7
  /** Render cached totals while the independent HTTP cost scan refreshes.
8
8
  * @param controller - Selected session and origin-scoped cost ledger.
9
9
  * @returns Billing panel, including unpriced models and refresh errors.
@@ -0,0 +1,120 @@
1
+ import { type ObjectValue } from '../../transport/wire.ts';
2
+ import { type Message } from '../../session/transcript.ts';
3
+ import type { HistorySearch, RemovalTarget } from '../../session/types.ts';
4
+ import type { QueuedInput } from '../../session/telemetry.ts';
5
+ import { type Choice } from './picker.tsx';
6
+ /** Pending host input with explicit removal.
7
+ * @param props - Queue contents, geometry and availability.
8
+ * @returns The pending-input picker.
9
+ */
10
+ export declare function QueueDialog({ queued, rows, width, unavailable, enabled, canSelect, onRemove }: {
11
+ queued: readonly QueuedInput[];
12
+ rows: number;
13
+ width: number;
14
+ unavailable: boolean;
15
+ enabled: boolean;
16
+ canSelect(): boolean;
17
+ onRemove(id: string): void;
18
+ }): import("react").JSX.Element;
19
+ /** Confirmation for workspace removal or session archival.
20
+ * @param props - Reviewed target and its confirmation actions.
21
+ * @returns The confirmation dialog.
22
+ */
23
+ export declare function RemovalDialog({ removal, enabled, canSelect, onCancel, onConfirm }: {
24
+ removal: RemovalTarget;
25
+ enabled: boolean;
26
+ canSelect(): boolean;
27
+ onCancel(): void;
28
+ onConfirm(): void;
29
+ }): import("react").JSX.Element;
30
+ /** Two-step model and reasoning-effort selector. */
31
+ export interface ModelState {
32
+ catalog: ObjectValue;
33
+ provider?: string;
34
+ model?: ObjectValue;
35
+ }
36
+ /** Model routes and adapter-owned reasoning efforts.
37
+ * @param props - Catalog, current step and its selection actions.
38
+ * @returns The model or effort picker.
39
+ */
40
+ export declare function ModelDialog({ models, rows, width, enabled, canSelect, onChoose, onOpen, onBack, onClose }: {
41
+ models: ModelState;
42
+ rows: number;
43
+ width: number;
44
+ enabled: boolean;
45
+ canSelect(): boolean;
46
+ onChoose(provider: string, model: string, effort?: string): void;
47
+ onOpen(provider: string, model: ObjectValue): void;
48
+ onBack(): void;
49
+ onClose(): void;
50
+ }): import("react").JSX.Element;
51
+ /** Bounded host session-search results.
52
+ * @param props - Query, results and their opening action.
53
+ * @returns The session-search picker.
54
+ */
55
+ export declare function SearchResultsDialog({ query, items, hasMore, width, enabled, canSelect, onOpen, onClose }: {
56
+ query: string;
57
+ items: readonly ObjectValue[];
58
+ hasMore: boolean;
59
+ width: number;
60
+ enabled: boolean;
61
+ canSelect(): boolean;
62
+ onOpen(sessionId: string): void;
63
+ onClose(): void;
64
+ }): import("react").JSX.Element;
65
+ /** Title, current selection and confirm action for a picker screen.
66
+ * @param props - Screen title, list identity and navigation state.
67
+ * @returns The picker screen.
68
+ */
69
+ export declare function PickerScreen({ title, identity, choices, enabled, canSelect }: {
70
+ title: string;
71
+ identity: string;
72
+ choices: Choice[];
73
+ enabled: boolean;
74
+ canSelect(): boolean;
75
+ }): import("react").JSX.Element;
76
+ /** Newest-first reasoning summaries with the preceding user prompt.
77
+ * @param props - Loaded entries and lazy paging action.
78
+ * @returns The reasoning list.
79
+ */
80
+ export declare function ThoughtsDialog({ identity, options, empty, rows, enabled, canSelect }: {
81
+ identity: string;
82
+ options: Choice[];
83
+ empty: boolean;
84
+ rows: number;
85
+ enabled: boolean;
86
+ canSelect(): boolean;
87
+ }): import("react").JSX.Element;
88
+ /** Loaded prompts or bounded content matches with a jump action.
89
+ * @param props - Query mode, matches and the selected record.
90
+ * @returns The history or search picker.
91
+ */
92
+ export declare function HistoryDialog({ identity, contentSearch, matches, query, messages, width, enabled, canSelect, onJump, onClose }: {
93
+ identity: string;
94
+ contentSearch: boolean;
95
+ matches?: HistorySearch;
96
+ query: string;
97
+ messages: readonly Message[];
98
+ width: number;
99
+ enabled: boolean;
100
+ canSelect(): boolean;
101
+ onJump(seq: number): void;
102
+ onClose(): void;
103
+ }): import("react").JSX.Element;
104
+ /** Paged slash-command reference.
105
+ * @param props - Current page, page count and rows per page.
106
+ * @returns The help panel.
107
+ */
108
+ export declare function HelpPanel({ page, pages, pageSize }: {
109
+ page: number;
110
+ pages: number;
111
+ pageSize: number;
112
+ }): import("react").JSX.Element;
113
+ /** Compact preview of host-owned pending input above the composer.
114
+ * @param props - Pending items and available columns.
115
+ * @returns Up to two pending-input rows.
116
+ */
117
+ export declare function QueuedPreview({ queued, width }: {
118
+ queued: readonly QueuedInput[];
119
+ width: number;
120
+ }): import("react").JSX.Element;
@@ -0,0 +1,113 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Modal panels and list screens rendered inside the shared composer frame. */
3
+ import { Box, Text } from 'ink';
4
+ import { array, object, safeText, string } from "../../transport/wire.js";
5
+ import { toolLine } from "../../session/transcript.js";
6
+ import { COMMAND_HINTS, COMMAND_LABELS, COMMAND_LABEL_WIDTH } from "../commands/registry.js";
7
+ import { useTheme } from "../theme/index.js";
8
+ import { Picker } from "./picker.js";
9
+ /** Pending host input with explicit removal.
10
+ * @param props - Queue contents, geometry and availability.
11
+ * @returns The pending-input picker.
12
+ */
13
+ export function QueueDialog({ queued, rows, width, unavailable, enabled, canSelect, onRemove }) {
14
+ return _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Pending input \u00B7 Esc close" }), !queued.length && _jsx(Text, { dimColor: true, children: unavailable ? 'Host queue unavailable' : 'No pending input' }), _jsx(Picker, { choices: queued.map(item => ({
15
+ key: item.id, label: toolLine(item.text, width - 6),
16
+ action: () => onRemove(item.id),
17
+ remove: () => onRemove(item.id),
18
+ })), pageSize: Math.max(1, Math.min(6, rows - 12)), hint: "\u2191 \u2193 select \u00B7 Enter / d / Delete remove \u00B7 Esc close", enabled: enabled, canSelect: canSelect })] });
19
+ }
20
+ /** Confirmation for workspace removal or session archival.
21
+ * @param props - Reviewed target and its confirmation actions.
22
+ * @returns The confirmation dialog.
23
+ */
24
+ export function RemovalDialog({ removal, enabled, canSelect, onCancel, onConfirm }) {
25
+ const theme = useTheme();
26
+ return _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, color: theme.colors.context, children: removal.kind === 'workspace' ? 'Remove workspace registration?' : 'Archive session?' }), _jsx(Text, { wrap: "truncate-end", children: safeText(removal.name) }), _jsxs(Text, { wrap: "truncate-end", children: ["ID: ", safeText(removal.id)] }), removal.path && _jsxs(Text, { wrap: "truncate-end", children: ["Path: ", safeText(removal.path)] }), _jsx(Text, { children: removal.kind === 'workspace' ? 'Removes the workspace from the list. Directory and sessions are kept.' : 'Hides the session from lists. History is kept; /resume ID can reopen it.' }), _jsx(Text, { dimColor: true, children: "Running tasks continue. Esc cancels this dialog." }), _jsx(Picker, { choices: [
27
+ { key: 'cancel', label: 'Cancel', action: onCancel },
28
+ { key: 'confirm', label: removal.kind === 'workspace' ? 'Remove workspace' : 'Archive session', action: onConfirm },
29
+ ], enabled: enabled, canSelect: canSelect }, `${removal.kind}:${removal.id}`)] });
30
+ }
31
+ /** Model routes and adapter-owned reasoning efforts.
32
+ * @param props - Catalog, current step and its selection actions.
33
+ * @returns The model or effort picker.
34
+ */
35
+ export function ModelDialog({ models, rows, width, enabled, canSelect, onChoose, onOpen, onBack, onClose }) {
36
+ const theme = useTheme();
37
+ return _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: models.model ? 'Choose reasoning effort' : 'Choose model' }), _jsx(Text, { dimColor: true, children: "Applies to subsequent requests; host also saves the default." }), array(models.catalog.failures).map(object).map(failure => _jsx(Text, { color: theme.colors.error, children: safeText(`${failure.name}: ${failure.message}`) }, string(failure.id))), _jsx(Picker, { pageSize: Math.max(1, Math.min(8, rows - 15)), choices: models.model ? [
38
+ { key: 'default', label: `Default effort${object(models.model.reasoning).defaultEffort ? ` · ${string(object(models.model.reasoning).defaultEffort)}` : ''}`,
39
+ action: () => onChoose(models.provider, string(models.model.id)) },
40
+ ...array(object(models.model.reasoning).efforts).map(object).map(effort => ({ key: string(effort.id), label: toolLine(`${effort.name} (${effort.id})${effort.description ? ` · ${effort.description}` : ''}`, width - 2),
41
+ action: () => onChoose(models.provider, string(models.model.id), string(effort.id)) })),
42
+ { key: 'back', label: '← Models', action: onBack },
43
+ ] : [
44
+ ...array(models.catalog.groups).map(object).flatMap(group => array(group.models).map(object).map(model => ({
45
+ key: `${group.id}:${model.id}`, label: toolLine(`${group.name} · ${model.name} (${model.id})`, width - 2),
46
+ action: () => {
47
+ if (array(object(model.reasoning ?? { efforts: [] }).efforts).length)
48
+ onOpen(string(group.id), model);
49
+ else
50
+ onChoose(string(group.id), string(model.id));
51
+ },
52
+ }))),
53
+ { key: 'close', label: '← Back to conversation', action: onClose },
54
+ ], enabled: enabled, canSelect: canSelect }, models.model ? `${models.provider}:${models.model.id}` : 'models')] });
55
+ }
56
+ /** Bounded host session-search results.
57
+ * @param props - Query, results and their opening action.
58
+ * @returns The session-search picker.
59
+ */
60
+ export function SearchResultsDialog({ query, items, hasMore, width, enabled, canSelect, onOpen, onClose }) {
61
+ const theme = useTheme();
62
+ return _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, children: ["Session search \u00B7 ", safeText(query)] }), hasMore && _jsx(Text, { color: theme.colors.context, children: "Host returned only the first 20 global matches; workspace results may be incomplete. Refine your query." }), !items.length && _jsx(Text, { children: "No sessions in the returned results" }), _jsx(Picker, { choices: [...items.map(item => ({ key: string(item.sessionId),
63
+ label: toolLine(`${item.sessionId} · ${item.snippet}`, width - 2),
64
+ action: () => onOpen(string(item.sessionId)) })), { key: 'close', label: '← Back', action: onClose }], enabled: enabled, canSelect: canSelect })] });
65
+ }
66
+ /** Title, current selection and confirm action for a picker screen.
67
+ * @param props - Screen title, list identity and navigation state.
68
+ * @returns The picker screen.
69
+ */
70
+ export function PickerScreen({ title, identity, choices, enabled, canSelect }) {
71
+ return _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, children: title }), _jsx(Picker, { choices: choices, enabled: enabled, canSelect: canSelect }, identity)] });
72
+ }
73
+ /** Newest-first reasoning summaries with the preceding user prompt.
74
+ * @param props - Loaded entries and lazy paging action.
75
+ * @returns The reasoning list.
76
+ */
77
+ export function ThoughtsDialog({ identity, options, empty, rows, enabled, canSelect }) {
78
+ const theme = useTheme();
79
+ return _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsx(Text, { bold: true, color: theme.colors.reasoning, children: "Reasoning history \u00B7 User prompts \u00B7 Esc close" }), empty && _jsx(Text, { dimColor: true, children: "No reasoning in loaded history" }), _jsx(Picker, { choices: options, pageSize: Math.max(1, Math.min(6, Math.floor((rows - 14) / 2))), enabled: enabled, canSelect: canSelect }, identity)] });
80
+ }
81
+ /** Loaded prompts or bounded content matches with a jump action.
82
+ * @param props - Query mode, matches and the selected record.
83
+ * @returns The history or search picker.
84
+ */
85
+ export function HistoryDialog({ identity, contentSearch, matches, query, messages, width, enabled, canSelect, onJump, onClose }) {
86
+ const theme = useTheme();
87
+ const needle = query.toLowerCase();
88
+ return _jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { bold: true, children: [contentSearch ? 'Search · session history' : 'History · loaded records', " \u00B7 Esc close"] }), contentSearch && matches?.truncated && _jsx(Text, { color: theme.colors.context, children: "Showing the first 200 matches \u00B7 Refine your search" }), _jsx(Picker, { choices: [
89
+ ...(contentSearch && matches ? matches.items.map(message => ({
90
+ key: String(message.seq), label: toolLine(`#${message.seq} ${message.role} · ${message.preview}`, width - 2),
91
+ action: () => onJump(message.seq),
92
+ })) : messages.filter(message => message.role === 'You' && `${message.seq} ${message.text}`.toLowerCase().includes(needle)).map(message => ({
93
+ key: String(message.seq), label: toolLine(`#${message.seq} ${message.role} · ${message.text}`, width - 2),
94
+ action: () => onJump(message.seq),
95
+ }))),
96
+ { key: 'close', label: '← Back to conversation', action: onClose },
97
+ ], enabled: enabled, canSelect: canSelect }, identity)] });
98
+ }
99
+ /** Paged slash-command reference.
100
+ * @param props - Current page, page count and rows per page.
101
+ * @returns The help panel.
102
+ */
103
+ export function HelpPanel({ page, pages, pageSize }) {
104
+ const theme = useTheme();
105
+ return _jsxs(Box, { flexDirection: "column", flexShrink: 1, minHeight: 0, overflowY: "hidden", children: [COMMAND_HINTS.slice(page * pageSize, (page + 1) * pageSize).map((hint, index) => _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [_jsx(Text, { color: theme.accent, children: COMMAND_LABELS[page * pageSize + index].padEnd(COMMAND_LABEL_WIDTH) }), hint.description] }, hint.command)), _jsxs(Text, { dimColor: true, children: ["Help ", page + 1, "/", pages, " \u00B7 PgUp/PgDn pages \u00B7 Esc close"] }), _jsx(Text, { dimColor: true, children: "Enter send \u00B7 Tab complete \u00B7 Esc cancel \u00B7 Wheel/PgUp/PgDn scroll \u00B7 Ctrl+C clear / stop / exit" }), _jsx(Text, { dimColor: true, children: "History: \u2191/\u2193 or Ctrl+P/N recall \u00B7 Editing: Ctrl+A/E start/end \u00B7 Ctrl+K/U kill right/left \u00B7 Ctrl+W kill word \u00B7 Ctrl+Y restore" })] });
106
+ }
107
+ /** Compact preview of host-owned pending input above the composer.
108
+ * @param props - Pending items and available columns.
109
+ * @returns Up to two pending-input rows.
110
+ */
111
+ export function QueuedPreview({ queued, width }) {
112
+ return _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsxs(Text, { dimColor: true, children: ["Waiting: ", queued.length, " \u00B7 /queue to remove"] }), queued.slice(0, 2).map(item => _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [item.placement === 'steering' ? '↳ ' : '· ', safeText(toolLine(item.text, width - 6))] }, item.id))] });
113
+ }
@@ -0,0 +1,18 @@
1
+ /** One selectable row; a `remove` action adds the archive/delete affordance. */
2
+ export interface Choice {
3
+ key: string;
4
+ label: string;
5
+ action(): void;
6
+ remove?(): void;
7
+ }
8
+ /** Keyboard-driven list with paging and optional removal.
9
+ * @param props - Choices, availability, page size and hint text.
10
+ * @returns The rendered list rows and its navigation hint.
11
+ */
12
+ export declare function Picker({ choices, enabled, canSelect, pageSize, hint }: {
13
+ choices: Choice[];
14
+ enabled: boolean;
15
+ canSelect(): boolean;
16
+ pageSize?: number;
17
+ hint?: string;
18
+ }): import("react").JSX.Element;
@@ -0,0 +1,38 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /** Shared list selector used by every picker and dialog. */
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { Box, Text, useInput, useStdin } from 'ink';
5
+ import { safeText } from "../../transport/wire.js";
6
+ import { useCopyMode } from "../copy-mode.js";
7
+ import { useTheme } from "../theme/index.js";
8
+ /** Keyboard-driven list with paging and optional removal.
9
+ * @param props - Choices, availability, page size and hint text.
10
+ * @returns The rendered list rows and its navigation hint.
11
+ */
12
+ export function Picker({ choices, enabled, canSelect, pageSize = 12, hint }) {
13
+ const theme = useTheme();
14
+ const copyMode = useCopyMode();
15
+ const [selected, setSelected] = useState(0);
16
+ const current = Math.min(selected, choices.length - 1);
17
+ const { internal_eventEmitter } = useStdin();
18
+ const rawKey = useRef('');
19
+ useEffect(() => {
20
+ const remember = (raw) => { rawKey.current = raw; };
21
+ internal_eventEmitter.prependListener('input', remember);
22
+ return () => { internal_eventEmitter.removeListener('input', remember); };
23
+ }, [internal_eventEmitter]);
24
+ useInput((_input, key) => {
25
+ if (!canSelect() || key.eventType === 'release')
26
+ return;
27
+ if (key.upArrow)
28
+ setSelected(Math.max(0, current - 1));
29
+ else if (key.downArrow)
30
+ setSelected(Math.min(choices.length - 1, current + 1));
31
+ else if (_input === 'd' && !key.ctrl && !key.meta || key.delete && /^\x1b\[3(?:;\d+)?~$/.test(rawKey.current))
32
+ choices[current]?.remove?.();
33
+ else if (key.return)
34
+ choices[current]?.action();
35
+ }, { isActive: enabled && !copyMode });
36
+ const start = Math.max(0, current - Math.max(0, pageSize - 1));
37
+ return _jsxs(Box, { flexDirection: "column", children: [choices.slice(start, start + pageSize).map((choice, index) => _jsxs(Text, { color: start + index === current ? theme.accent : undefined, children: [start + index === current ? '❯ ' : ' ', safeText(choice.label)] }, choice.key)), _jsx(Text, { dimColor: true, children: hint ?? `↑ ↓ select · Enter open${choices.some(choice => choice.remove) ? ' · d/Delete remove / archive' : ''} · Ctrl+C stop / exit` })] });
38
+ }
@@ -0,0 +1,8 @@
1
+ /** Retain rendered subtrees while the display is paused for reading or selection. */
2
+ import { type ReactNode } from 'react';
3
+ /** Retain rendered children while frozen; local dialog interactions remain outside this wrapper. */
4
+ export declare const Frozen: import("react").NamedExoticComponent<{
5
+ children: ReactNode;
6
+ frozen: boolean;
7
+ identity: string;
8
+ }>;
@@ -0,0 +1,7 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ /** Retain rendered subtrees while the display is paused for reading or selection. */
3
+ import { memo } from 'react';
4
+ /** Retain rendered children while frozen; local dialog interactions remain outside this wrapper. */
5
+ export const Frozen = memo(function Frozen({ children }) {
6
+ return _jsx(_Fragment, { children: children });
7
+ }, (previous, next) => previous.frozen && next.frozen && previous.identity === next.identity);
@@ -0,0 +1,12 @@
1
+ import type { FileReference } from '../../session/references.ts';
2
+ /** Candidates for the unfinished reference at the end of the draft.
3
+ * @param props - Lookup result and the highlighted candidate.
4
+ * @returns The completion menu.
5
+ */
6
+ export declare function ReferenceMenu({ matches, index }: {
7
+ matches?: {
8
+ items: FileReference[];
9
+ error?: string;
10
+ };
11
+ index: number;
12
+ }): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Trailing `@` host-path completion menu. */
3
+ import { Box, Text } from 'ink';
4
+ import { useTheme } from "../theme/index.js";
5
+ /** Candidates for the unfinished reference at the end of the draft.
6
+ * @param props - Lookup result and the highlighted candidate.
7
+ * @returns The completion menu.
8
+ */
9
+ export function ReferenceMenu({ matches, index }) {
10
+ const theme = useTheme();
11
+ const start = Math.max(0, index - 5);
12
+ return _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Host files \u00B7 \u2191 \u2193 select \u00B7 Tab/Enter insert \u00B7 Esc close" }), !matches ? _jsx(Text, { dimColor: true, children: "Searching\u2026" }) : matches.error ? _jsx(Text, { color: theme.colors.error, children: matches.error })
13
+ : matches.items.length === 0 ? _jsx(Text, { dimColor: true, children: "No matching host files" })
14
+ : matches.items.slice(start, start + 6).map((item, offset) => _jsxs(Text, { color: offset + start === index ? theme.accent : undefined, children: [offset + start === index ? '❯ ' : ' ', item.path, item.kind === 'directory' ? '/' : ''] }, `${item.kind}:${item.path}`))] });
15
+ }
@@ -0,0 +1,6 @@
1
+ import type { Controller } from '../controller/index.ts';
2
+ /** Render the application and return the Ink instance that owns its lifetime.
3
+ * @param controller - Application controller.
4
+ * @returns The mounted Ink instance.
5
+ */
6
+ export declare function mount(controller: Controller): import("ink").Instance;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /** The only module that touches Ink's renderer; the CLI mounts the interface through it. */
3
+ import { render } from 'ink';
4
+ import { App } from "./app.js";
5
+ /** Render the application and return the Ink instance that owns its lifetime.
6
+ * @param controller - Application controller.
7
+ * @returns The mounted Ink instance.
8
+ */
9
+ export function mount(controller) {
10
+ return render(_jsx(App, { controller: controller }), { exitOnCtrlC: false });
11
+ }
@@ -1,4 +1,4 @@
1
- import type { RowKind } from './history.ts';
1
+ import type { RowKind } from '../../session/history.ts';
2
2
  /** Themes provide semantic roles so components never depend on palette-specific color names. */
3
3
  export interface Theme {
4
4
  name: string;
package/dsht-m.png ADDED
Binary file