@indianic/dialout 1.9.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-adapters/claude.d.ts.map +1 -1
- package/dist/ai-adapters/claude.js +18 -1
- package/dist/ai-adapters/claude.js.map +1 -1
- package/dist/ai-attachments.d.ts +18 -0
- package/dist/ai-attachments.d.ts.map +1 -0
- package/dist/ai-attachments.js +110 -0
- package/dist/ai-attachments.js.map +1 -0
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +14 -0
- package/dist/websocket.js.map +1 -1
- package/node_modules/@indianic/dialout-shared/dist/ai-chat-prefs.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/ai-chat-prefs.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/ai-chat-prefs.test.js +38 -0
- package/node_modules/@indianic/dialout-shared/dist/attachments.d.ts +33 -0
- package/node_modules/@indianic/dialout-shared/dist/attachments.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/attachments.js +84 -0
- package/node_modules/@indianic/dialout-shared/dist/attachments.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/attachments.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/attachments.test.js +31 -0
- package/node_modules/@indianic/dialout-shared/dist/chat-blocks.d.ts +1 -0
- package/node_modules/@indianic/dialout-shared/dist/chat-blocks.d.ts.map +1 -1
- package/node_modules/@indianic/dialout-shared/dist/chat-blocks.js +1 -1
- package/node_modules/@indianic/dialout-shared/dist/chat-blocks.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/chat-blocks.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/chat-blocks.test.js +67 -0
- package/node_modules/@indianic/dialout-shared/dist/command-filter.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/command-filter.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/command-filter.test.js +63 -0
- package/node_modules/@indianic/dialout-shared/dist/composer-behaviour.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/composer-behaviour.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/composer-behaviour.test.js +27 -0
- package/node_modules/@indianic/dialout-shared/dist/event-merge.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/event-merge.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/event-merge.test.js +42 -0
- package/node_modules/@indianic/dialout-shared/dist/events.d.ts +1 -0
- package/node_modules/@indianic/dialout-shared/dist/events.d.ts.map +1 -1
- package/node_modules/@indianic/dialout-shared/dist/index.d.ts +4 -1
- package/node_modules/@indianic/dialout-shared/dist/index.d.ts.map +1 -1
- package/node_modules/@indianic/dialout-shared/dist/index.js +15 -1
- package/node_modules/@indianic/dialout-shared/dist/notify.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/notify.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/notify.test.js +51 -0
- package/node_modules/@indianic/dialout-shared/dist/pane-prompt.d.ts +17 -0
- package/node_modules/@indianic/dialout-shared/dist/pane-prompt.d.ts.map +1 -1
- package/node_modules/@indianic/dialout-shared/dist/pane-prompt.js +27 -0
- package/node_modules/@indianic/dialout-shared/dist/pane-prompt.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/pane-prompt.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/pane-prompt.test.js +184 -0
- package/node_modules/@indianic/dialout-shared/dist/scroll-pin.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/scroll-pin.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/scroll-pin.test.js +21 -0
- package/node_modules/@indianic/dialout-shared/dist/thinking.d.ts +9 -0
- package/node_modules/@indianic/dialout-shared/dist/thinking.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/thinking.js +38 -0
- package/node_modules/@indianic/dialout-shared/dist/thinking.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/thinking.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/thinking.test.js +30 -0
- package/node_modules/@indianic/dialout-shared/dist/tmux-prefs.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/tmux-prefs.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/tmux-prefs.test.js +95 -0
- package/node_modules/@indianic/dialout-shared/dist/tool-appearance.test.d.ts +2 -0
- package/node_modules/@indianic/dialout-shared/dist/tool-appearance.test.d.ts.map +1 -0
- package/node_modules/@indianic/dialout-shared/dist/tool-appearance.test.js +38 -0
- package/node_modules/@indianic/dialout-shared/package.json +2 -2
- package/node_modules/@indianic/dialout-shared/src/attachments.test.ts +33 -0
- package/node_modules/@indianic/dialout-shared/src/attachments.ts +82 -0
- package/node_modules/@indianic/dialout-shared/src/chat-blocks.ts +2 -2
- package/node_modules/@indianic/dialout-shared/src/events.ts +5 -1
- package/node_modules/@indianic/dialout-shared/src/index.ts +8 -1
- package/node_modules/@indianic/dialout-shared/src/pane-prompt.test.ts +23 -1
- package/node_modules/@indianic/dialout-shared/src/pane-prompt.ts +27 -0
- package/node_modules/@indianic/dialout-shared/src/thinking.test.ts +32 -0
- package/node_modules/@indianic/dialout-shared/src/thinking.ts +32 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-filter.test.d.ts","sourceRoot":"","sources":["../src/command-filter.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const command_filter_1 = require("./command-filter");
|
|
5
|
+
const cmd = (name, source, description = '') => ({ name, source, description });
|
|
6
|
+
(0, vitest_1.describe)('commandQuery', () => {
|
|
7
|
+
(0, vitest_1.it)('opens on a lone slash', () => {
|
|
8
|
+
(0, vitest_1.expect)((0, command_filter_1.commandQuery)('/')).toBe('');
|
|
9
|
+
});
|
|
10
|
+
(0, vitest_1.it)('returns the text after the slash', () => {
|
|
11
|
+
(0, vitest_1.expect)((0, command_filter_1.commandQuery)('/comp')).toBe('comp');
|
|
12
|
+
});
|
|
13
|
+
// A slash mid-sentence is a path or a date, not a command.
|
|
14
|
+
(0, vitest_1.it)('does not open for a slash that is not first', () => {
|
|
15
|
+
(0, vitest_1.expect)((0, command_filter_1.commandQuery)('what about /tmp')).toBe(null);
|
|
16
|
+
(0, vitest_1.expect)((0, command_filter_1.commandQuery)(' /leading-space')).toBe(null);
|
|
17
|
+
});
|
|
18
|
+
(0, vitest_1.it)('closes once the command is complete', () => {
|
|
19
|
+
(0, vitest_1.expect)((0, command_filter_1.commandQuery)('/compact now')).toBe(null);
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)('is closed for ordinary text and for empty input', () => {
|
|
22
|
+
(0, vitest_1.expect)((0, command_filter_1.commandQuery)('')).toBe(null);
|
|
23
|
+
(0, vitest_1.expect)((0, command_filter_1.commandQuery)('hello')).toBe(null);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
(0, vitest_1.describe)('rankCommands', () => {
|
|
27
|
+
const all = [
|
|
28
|
+
cmd('code-review:code-review', 'plugin', 'Review a PR'),
|
|
29
|
+
cmd('compact', 'builtin', 'Compact conversation history'),
|
|
30
|
+
cmd('commit-commands:clean_gone', 'plugin', 'Delete merged branches'),
|
|
31
|
+
cmd('seo', 'user', 'SEO Machine'),
|
|
32
|
+
cmd('deploy', 'project', 'Ship it'),
|
|
33
|
+
];
|
|
34
|
+
(0, vitest_1.it)('puts a name prefix match above a name substring above a description match', () => {
|
|
35
|
+
const names = (0, command_filter_1.rankCommands)(all, 'co').map((c) => c.name);
|
|
36
|
+
(0, vitest_1.expect)(names[0]).toBe('compact'); // prefix on the bare name
|
|
37
|
+
(0, vitest_1.expect)(names).toContain('code-review:code-review');
|
|
38
|
+
(0, vitest_1.expect)(names).not.toContain('seo');
|
|
39
|
+
});
|
|
40
|
+
// 52 of 81 commands are plugin-namespaced, so the prefix has to be searchable
|
|
41
|
+
// or half the list is unreachable by typing.
|
|
42
|
+
(0, vitest_1.it)('matches the plugin namespace as well as the command name', () => {
|
|
43
|
+
const names = (0, command_filter_1.rankCommands)(all, 'commit').map((c) => c.name);
|
|
44
|
+
(0, vitest_1.expect)(names).toContain('commit-commands:clean_gone');
|
|
45
|
+
});
|
|
46
|
+
(0, vitest_1.it)('matches a word in the description', () => {
|
|
47
|
+
const names = (0, command_filter_1.rankCommands)(all, 'branches').map((c) => c.name);
|
|
48
|
+
(0, vitest_1.expect)(names).toEqual(['commit-commands:clean_gone']);
|
|
49
|
+
});
|
|
50
|
+
// Plugins outnumber the user's own commands 52:29, so a purely alphabetical
|
|
51
|
+
// sort buries the ones they wrote.
|
|
52
|
+
(0, vitest_1.it)('breaks ties by source, user first and plugin last', () => {
|
|
53
|
+
const tie = [cmd('zzz', 'plugin'), cmd('zzz2', 'user'), cmd('zzz3', 'project')];
|
|
54
|
+
(0, vitest_1.expect)((0, command_filter_1.rankCommands)(tie, 'zzz').map((c) => c.source)).toEqual(['user', 'project', 'plugin']);
|
|
55
|
+
});
|
|
56
|
+
(0, vitest_1.it)('an empty query returns everything, still ranked by source', () => {
|
|
57
|
+
(0, vitest_1.expect)((0, command_filter_1.rankCommands)(all, '')).toHaveLength(5);
|
|
58
|
+
(0, vitest_1.expect)((0, command_filter_1.rankCommands)(all, '')[0].source).toBe('user');
|
|
59
|
+
});
|
|
60
|
+
(0, vitest_1.it)('is case insensitive', () => {
|
|
61
|
+
(0, vitest_1.expect)((0, command_filter_1.rankCommands)(all, 'SEO').map((c) => c.name)).toEqual(['seo']);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composer-behaviour.test.d.ts","sourceRoot":"","sources":["../src/composer-behaviour.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const composer_behaviour_1 = require("./composer-behaviour");
|
|
5
|
+
const k = (key, shiftKey = false) => ({ key, shiftKey });
|
|
6
|
+
(0, vitest_1.describe)('shouldSubmitOnEnter', () => {
|
|
7
|
+
(0, vitest_1.it)('sends on Enter where there is a real keyboard', () => {
|
|
8
|
+
(0, vitest_1.expect)((0, composer_behaviour_1.shouldSubmitOnEnter)(k('Enter'), false)).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
// On a touch device the on-screen Enter is conventionally a newline, and a
|
|
11
|
+
// stray one would fire a half-typed message.
|
|
12
|
+
(0, vitest_1.it)('never sends on Enter on a coarse pointer', () => {
|
|
13
|
+
(0, vitest_1.expect)((0, composer_behaviour_1.shouldSubmitOnEnter)(k('Enter'), true)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
(0, vitest_1.it)('Shift+Enter is always a newline', () => {
|
|
16
|
+
(0, vitest_1.expect)((0, composer_behaviour_1.shouldSubmitOnEnter)(k('Enter', true), false)).toBe(false);
|
|
17
|
+
(0, vitest_1.expect)((0, composer_behaviour_1.shouldSubmitOnEnter)(k('Enter', true), true)).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.it)('ignores every other key', () => {
|
|
20
|
+
for (const key of ['a', 'Escape', 'Tab', 'NumpadEnter']) {
|
|
21
|
+
(0, vitest_1.expect)((0, composer_behaviour_1.shouldSubmitOnEnter)(k(key), false)).toBe(false);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
(0, vitest_1.it)('exposes the media query it is paired with', () => {
|
|
25
|
+
(0, vitest_1.expect)(composer_behaviour_1.COARSE_POINTER_QUERY).toBe('(pointer: coarse)');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-merge.test.d.ts","sourceRoot":"","sources":["../src/event-merge.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const event_merge_1 = require("./event-merge");
|
|
5
|
+
const ev = (n) => ({ kind: 'message', role: 'user', text: `m${n}`, id: `m${n}` });
|
|
6
|
+
const range = (a, b) => Array.from({ length: b - a }, (_, i) => ev(a + i));
|
|
7
|
+
const texts = (s) => s.events.map((e) => e.text);
|
|
8
|
+
(0, vitest_1.describe)('mergeEvents', () => {
|
|
9
|
+
(0, vitest_1.it)('replay replaces, so a reconnect never duplicates history', () => {
|
|
10
|
+
let s = (0, event_merge_1.mergeEvents)(event_merge_1.EMPTY_TRANSCRIPT, range(250, 450), { replay: true, firstSeq: 250, earlier: 250 });
|
|
11
|
+
s = (0, event_merge_1.mergeEvents)(s, range(250, 450), { replay: true, firstSeq: 250, earlier: 250 });
|
|
12
|
+
(0, vitest_1.expect)(s.events).toHaveLength(200);
|
|
13
|
+
(0, vitest_1.expect)(s).toMatchObject({ firstSeq: 250, earlier: 250 });
|
|
14
|
+
});
|
|
15
|
+
(0, vitest_1.it)('history prepends only what is not held', () => {
|
|
16
|
+
let s = (0, event_merge_1.mergeEvents)(event_merge_1.EMPTY_TRANSCRIPT, range(250, 450), { replay: true, firstSeq: 250, earlier: 250 });
|
|
17
|
+
s = (0, event_merge_1.mergeEvents)(s, range(50, 250), { history: true, firstSeq: 50, earlier: 50 });
|
|
18
|
+
(0, vitest_1.expect)(texts(s)[0]).toBe('m50');
|
|
19
|
+
(0, vitest_1.expect)(s.events).toHaveLength(400);
|
|
20
|
+
// The same page again (double tap, or another device) changes nothing.
|
|
21
|
+
(0, vitest_1.expect)((0, event_merge_1.mergeEvents)(s, range(50, 250), { history: true, firstSeq: 50, earlier: 50 })).toBe(s);
|
|
22
|
+
// An overlapping page keeps only the older part.
|
|
23
|
+
s = (0, event_merge_1.mergeEvents)(s, range(0, 100), { history: true, firstSeq: 0, earlier: 0 });
|
|
24
|
+
(0, vitest_1.expect)(texts(s).slice(0, 51)).toEqual(range(0, 51).map((e) => e.text));
|
|
25
|
+
(0, vitest_1.expect)(s.events).toHaveLength(450);
|
|
26
|
+
(0, vitest_1.expect)(s.earlier).toBe(0);
|
|
27
|
+
});
|
|
28
|
+
(0, vitest_1.it)('live appends and trims overlap with the tail', () => {
|
|
29
|
+
let s = (0, event_merge_1.mergeEvents)(event_merge_1.EMPTY_TRANSCRIPT, range(0, 3), { replay: true, firstSeq: 0, earlier: 0 });
|
|
30
|
+
s = (0, event_merge_1.mergeEvents)(s, range(2, 5), { firstSeq: 2 });
|
|
31
|
+
(0, vitest_1.expect)(texts(s)).toEqual(['m0', 'm1', 'm2', 'm3', 'm4']);
|
|
32
|
+
(0, vitest_1.expect)((0, event_merge_1.mergeEvents)(s, range(3, 5), { firstSeq: 3 })).toBe(s);
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.it)('an agent without paging is a plain append', () => {
|
|
35
|
+
let s = (0, event_merge_1.mergeEvents)(event_merge_1.EMPTY_TRANSCRIPT, range(0, 2));
|
|
36
|
+
s = (0, event_merge_1.mergeEvents)(s, range(2, 3));
|
|
37
|
+
(0, vitest_1.expect)(texts(s)).toEqual(['m0', 'm1', 'm2']);
|
|
38
|
+
(0, vitest_1.expect)(s.firstSeq).toBeNull();
|
|
39
|
+
// A history page cannot be placed without numbering, so it is ignored.
|
|
40
|
+
(0, vitest_1.expect)((0, event_merge_1.mergeEvents)(s, range(0, 1), { history: true, firstSeq: 0 })).toBe(s);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,eAAe,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAEjF,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,SAAS,CAAC;IAAK,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,WAAW,CAAC;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GACjG;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,eAAe,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAEjF,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,SAAS,CAAC;IAAK,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,WAAW,CAAC;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GACjG;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAKjF;IAAE,IAAI,EAAE,UAAU,CAAC;IAAI,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,IAAI,EAAE,OAAO,CAAC;IAAO,MAAM,EAAE,QAAQ,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,MAAM,WAAW,SAAS;IAExB,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,EAAE,CAAC;IAErC,KAAK,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;CACvC;AAKD,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C"}
|
|
@@ -23,6 +23,9 @@ export type { TmuxPrefs, TmuxStatusPrefs } from './tmux-prefs';
|
|
|
23
23
|
export { DEFAULT_TMUX_PREFS, STATUS_TOKENS, MAX_STATUS_TEMPLATE, compileStatusTemplate, isValidStatusTemplate, isValidTmuxColor, resolveTmuxPrefs, } from './tmux-prefs';
|
|
24
24
|
export type { TranscriptState, EventsMeta } from './event-merge';
|
|
25
25
|
export { EMPTY_TRANSCRIPT, mergeEvents } from './event-merge';
|
|
26
|
+
export { thinkingDuration, thinkingLabel } from './thinking';
|
|
26
27
|
export type { PanePrompt, PanePromptOption, PromptKey } from './pane-prompt';
|
|
27
|
-
export { PROMPT_KEYS, detectPanePrompt, isSafeKeyList, promptKeys } from './pane-prompt';
|
|
28
|
+
export { PROMPT_KEYS, detectPanePrompt, freeTextRow, isFreeTextOption, isSafeKeyList, promptKeys, promptMoveKeys } from './pane-prompt';
|
|
29
|
+
export type { AttachmentRef } from './attachments';
|
|
30
|
+
export { ATTACHMENT_DIR, ATTACHMENT_MAX_BYTES, ATTACHMENT_MAX_FILES, composeWithAttachments, isImageName, safeAttachmentName, splitAttachments, } from './attachments';
|
|
28
31
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAElD,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAElE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE9F,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAElF,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE9D,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAErG,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAExH,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EACL,kBAAkB,EAAE,aAAa,EAAE,mBAAmB,EACtD,qBAAqB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,GACjF,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE9D,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAElD,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAElE,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE9F,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAElF,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE9D,YAAY,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE9D,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEjF,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAErG,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAExH,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EACL,kBAAkB,EAAE,aAAa,EAAE,mBAAmB,EACtD,qBAAqB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,GACjF,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7D,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,WAAW,EAAE,gBAAgB,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAExI,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EACL,cAAc,EAAE,oBAAoB,EAAE,oBAAoB,EAC1D,sBAAsB,EAAE,WAAW,EAAE,kBAAkB,EAAE,gBAAgB,GAC1E,MAAM,eAAe,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.promptKeys = exports.isSafeKeyList = exports.detectPanePrompt = exports.PROMPT_KEYS = exports.mergeEvents = exports.EMPTY_TRANSCRIPT = exports.resolveTmuxPrefs = exports.isValidTmuxColor = exports.isValidStatusTemplate = exports.compileStatusTemplate = exports.MAX_STATUS_TEMPLATE = exports.STATUS_TOKENS = exports.DEFAULT_TMUX_PREFS = exports.renderTerminalName = exports.factsFromSession = exports.TERMINAL_NAME_TOKENS = exports.DEFAULT_TERMINAL_TEMPLATE = exports.setFunctionKeysVisible = exports.getFunctionKeysVisible = exports.FUNCTION_KEYS_PREF = exports.shouldSubmitOnEnter = exports.COARSE_POINTER_QUERY = exports.rankCommands = exports.commandQuery = exports.toolAppearance = exports.toolClass = exports.shouldFollow = exports.PIN_THRESHOLD_PX = exports.toolGroupStartsOpen = exports.groupEvents = exports.shouldNotifyPrompt = exports.shouldNotifyAi = exports.NOTIFY_COOLDOWN_MS = exports.REPLAY_LIMIT = exports.isPermissionMode = exports.PERMISSION_MODES = exports.preview = exports.PREVIEW_LIMIT = void 0;
|
|
3
|
+
exports.splitAttachments = exports.safeAttachmentName = exports.isImageName = exports.composeWithAttachments = exports.ATTACHMENT_MAX_FILES = exports.ATTACHMENT_MAX_BYTES = exports.ATTACHMENT_DIR = exports.promptMoveKeys = exports.promptKeys = exports.isSafeKeyList = exports.isFreeTextOption = exports.freeTextRow = exports.detectPanePrompt = exports.PROMPT_KEYS = exports.thinkingLabel = exports.thinkingDuration = exports.mergeEvents = exports.EMPTY_TRANSCRIPT = exports.resolveTmuxPrefs = exports.isValidTmuxColor = exports.isValidStatusTemplate = exports.compileStatusTemplate = exports.MAX_STATUS_TEMPLATE = exports.STATUS_TOKENS = exports.DEFAULT_TMUX_PREFS = exports.renderTerminalName = exports.factsFromSession = exports.TERMINAL_NAME_TOKENS = exports.DEFAULT_TERMINAL_TEMPLATE = exports.setFunctionKeysVisible = exports.getFunctionKeysVisible = exports.FUNCTION_KEYS_PREF = exports.shouldSubmitOnEnter = exports.COARSE_POINTER_QUERY = exports.rankCommands = exports.commandQuery = exports.toolAppearance = exports.toolClass = exports.shouldFollow = exports.PIN_THRESHOLD_PX = exports.toolGroupStartsOpen = exports.groupEvents = exports.shouldNotifyPrompt = exports.shouldNotifyAi = exports.NOTIFY_COOLDOWN_MS = exports.REPLAY_LIMIT = exports.isPermissionMode = exports.PERMISSION_MODES = exports.preview = exports.PREVIEW_LIMIT = void 0;
|
|
4
4
|
var events_1 = require("./events");
|
|
5
5
|
Object.defineProperty(exports, "PREVIEW_LIMIT", { enumerable: true, get: function () { return events_1.PREVIEW_LIMIT; } });
|
|
6
6
|
Object.defineProperty(exports, "preview", { enumerable: true, get: function () { return events_1.preview; } });
|
|
@@ -48,8 +48,22 @@ Object.defineProperty(exports, "resolveTmuxPrefs", { enumerable: true, get: func
|
|
|
48
48
|
var event_merge_1 = require("./event-merge");
|
|
49
49
|
Object.defineProperty(exports, "EMPTY_TRANSCRIPT", { enumerable: true, get: function () { return event_merge_1.EMPTY_TRANSCRIPT; } });
|
|
50
50
|
Object.defineProperty(exports, "mergeEvents", { enumerable: true, get: function () { return event_merge_1.mergeEvents; } });
|
|
51
|
+
var thinking_1 = require("./thinking");
|
|
52
|
+
Object.defineProperty(exports, "thinkingDuration", { enumerable: true, get: function () { return thinking_1.thinkingDuration; } });
|
|
53
|
+
Object.defineProperty(exports, "thinkingLabel", { enumerable: true, get: function () { return thinking_1.thinkingLabel; } });
|
|
51
54
|
var pane_prompt_1 = require("./pane-prompt");
|
|
52
55
|
Object.defineProperty(exports, "PROMPT_KEYS", { enumerable: true, get: function () { return pane_prompt_1.PROMPT_KEYS; } });
|
|
53
56
|
Object.defineProperty(exports, "detectPanePrompt", { enumerable: true, get: function () { return pane_prompt_1.detectPanePrompt; } });
|
|
57
|
+
Object.defineProperty(exports, "freeTextRow", { enumerable: true, get: function () { return pane_prompt_1.freeTextRow; } });
|
|
58
|
+
Object.defineProperty(exports, "isFreeTextOption", { enumerable: true, get: function () { return pane_prompt_1.isFreeTextOption; } });
|
|
54
59
|
Object.defineProperty(exports, "isSafeKeyList", { enumerable: true, get: function () { return pane_prompt_1.isSafeKeyList; } });
|
|
55
60
|
Object.defineProperty(exports, "promptKeys", { enumerable: true, get: function () { return pane_prompt_1.promptKeys; } });
|
|
61
|
+
Object.defineProperty(exports, "promptMoveKeys", { enumerable: true, get: function () { return pane_prompt_1.promptMoveKeys; } });
|
|
62
|
+
var attachments_1 = require("./attachments");
|
|
63
|
+
Object.defineProperty(exports, "ATTACHMENT_DIR", { enumerable: true, get: function () { return attachments_1.ATTACHMENT_DIR; } });
|
|
64
|
+
Object.defineProperty(exports, "ATTACHMENT_MAX_BYTES", { enumerable: true, get: function () { return attachments_1.ATTACHMENT_MAX_BYTES; } });
|
|
65
|
+
Object.defineProperty(exports, "ATTACHMENT_MAX_FILES", { enumerable: true, get: function () { return attachments_1.ATTACHMENT_MAX_FILES; } });
|
|
66
|
+
Object.defineProperty(exports, "composeWithAttachments", { enumerable: true, get: function () { return attachments_1.composeWithAttachments; } });
|
|
67
|
+
Object.defineProperty(exports, "isImageName", { enumerable: true, get: function () { return attachments_1.isImageName; } });
|
|
68
|
+
Object.defineProperty(exports, "safeAttachmentName", { enumerable: true, get: function () { return attachments_1.safeAttachmentName; } });
|
|
69
|
+
Object.defineProperty(exports, "splitAttachments", { enumerable: true, get: function () { return attachments_1.splitAttachments; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.test.d.ts","sourceRoot":"","sources":["../src/notify.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const notify_1 = require("./notify");
|
|
5
|
+
// This is the logic most capable of annoying someone into turning
|
|
6
|
+
// notifications off, so every rule gets a case.
|
|
7
|
+
(0, vitest_1.describe)('shouldNotifyAi', () => {
|
|
8
|
+
const NOW = 1_000_000;
|
|
9
|
+
(0, vitest_1.it)('fires when a working session starts waiting for input', () => {
|
|
10
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyAi)('working', 'waiting_input', undefined, NOW)).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
(0, vitest_1.it)('fires when a working session starts waiting for approval', () => {
|
|
13
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyAi)('working', 'waiting_approval', undefined, NOW)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
(0, vitest_1.it)('stays silent while the status is unchanged', () => {
|
|
16
|
+
// Without this the agent's five-second poll would re-notify forever.
|
|
17
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyAi)('waiting_input', 'waiting_input', undefined, NOW)).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
(0, vitest_1.it)('stays silent for a session seen for the first time', () => {
|
|
20
|
+
// It was probably already sitting there before anyone subscribed;
|
|
21
|
+
// announcing it would mean a burst on every reconnect.
|
|
22
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyAi)(undefined, 'waiting_input', undefined, NOW)).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
(0, vitest_1.it)('stays silent when a session goes back to working', () => {
|
|
25
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyAi)('waiting_input', 'working', undefined, NOW)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
(0, vitest_1.it)('stays silent when a session merely goes idle', () => {
|
|
28
|
+
// Idle is not "your turn" — nobody needs waking for it.
|
|
29
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyAi)('working', 'idle', undefined, NOW)).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
(0, vitest_1.it)('does not fire on idle -> waiting_input', () => {
|
|
32
|
+
// Only work that was actually running is worth being pulled back to.
|
|
33
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyAi)('idle', 'waiting_input', undefined, NOW)).toBe(false);
|
|
34
|
+
});
|
|
35
|
+
(0, vitest_1.it)('respects the cooldown so a run of short tool calls cannot spam', () => {
|
|
36
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyAi)('working', 'waiting_input', NOW - 30_000, NOW)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.it)('fires again once the cooldown has passed', () => {
|
|
39
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyAi)('working', 'waiting_input', NOW - 200_000, NOW)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.describe)('shouldNotifyPrompt', () => {
|
|
43
|
+
(0, vitest_1.it)('fires for a new question, not on first sight, not twice, not inside the cooldown', () => {
|
|
44
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyPrompt)(undefined, 'a', undefined, 1000)).toBe(false);
|
|
45
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyPrompt)(null, 'a', undefined, 1000)).toBe(true);
|
|
46
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyPrompt)('a', 'a', undefined, 1000)).toBe(false);
|
|
47
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyPrompt)('a', null, undefined, 1000)).toBe(false);
|
|
48
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyPrompt)('a', 'b', 1000, 1000 + notify_1.NOTIFY_COOLDOWN_MS - 1)).toBe(false);
|
|
49
|
+
(0, vitest_1.expect)((0, notify_1.shouldNotifyPrompt)('a', 'b', 1000, 1000 + notify_1.NOTIFY_COOLDOWN_MS)).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -43,4 +43,21 @@ export declare function isSafeKeyList(keys: unknown): keys is string[];
|
|
|
43
43
|
* multi-select row Enter toggles it; the menu stays up and is re-read.
|
|
44
44
|
*/
|
|
45
45
|
export declare function promptKeys(p: PanePrompt, n: number): string[];
|
|
46
|
+
/**
|
|
47
|
+
* A row that takes typed text rather than being an answer itself: Claude
|
|
48
|
+
* Code's "Type something." / an "Other" row. Picking it with Enter answers
|
|
49
|
+
* with nothing, so the card asks for the text first.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isFreeTextOption(label: string): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Keys that put the cursor on row `n` without choosing it. For a free-text
|
|
54
|
+
* row: move there, then the text is typed and Enter pressed (as one message).
|
|
55
|
+
*/
|
|
56
|
+
export declare function promptMoveKeys(p: PanePrompt, n: number): string[];
|
|
57
|
+
/**
|
|
58
|
+
* The free-text row's number, or 0 if the menu has none. A TUI echoes what is
|
|
59
|
+
* typed in place of that row's label, so a row that already holds text no
|
|
60
|
+
* longer matches — which is correct: it is mid-answer, not an empty invitation.
|
|
61
|
+
*/
|
|
62
|
+
export declare function freeTextRow(p: PanePrompt): number;
|
|
46
63
|
//# sourceMappingURL=pane-prompt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pane-prompt.d.ts","sourceRoot":"","sources":["../src/pane-prompt.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,mFAAmF;IACnF,EAAE,EAAE,MAAM,CAAC;IACX,mFAAmF;IACnF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,QAAQ,EAAE,OAAO,CAAC;CACnB;AA6GD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAuChE;AAED,yEAAyE;AACzE,eAAO,MAAM,WAAW,sGAAuG,CAAC;AAChI,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,EAAE,CAG7D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAK7D"}
|
|
1
|
+
{"version":3,"file":"pane-prompt.d.ts","sourceRoot":"","sources":["../src/pane-prompt.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,kEAAkE;IAClE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,mFAAmF;IACnF,EAAE,EAAE,MAAM,CAAC;IACX,mFAAmF;IACnF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,mEAAmE;IACnE,KAAK,EAAE,OAAO,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,QAAQ,EAAE,OAAO,CAAC;CACnB;AA6GD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAuChE;AAED,yEAAyE;AACzE,eAAO,MAAM,WAAW,sGAAuG,CAAC;AAChI,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,kGAAkG;AAClG,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,EAAE,CAG7D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAK7D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAEjE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAGjD"}
|
|
@@ -17,6 +17,9 @@ exports.PROMPT_KEYS = void 0;
|
|
|
17
17
|
exports.detectPanePrompt = detectPanePrompt;
|
|
18
18
|
exports.isSafeKeyList = isSafeKeyList;
|
|
19
19
|
exports.promptKeys = promptKeys;
|
|
20
|
+
exports.isFreeTextOption = isFreeTextOption;
|
|
21
|
+
exports.promptMoveKeys = promptMoveKeys;
|
|
22
|
+
exports.freeTextRow = freeTextRow;
|
|
20
23
|
// Box-drawing borders some CLIs draw around a prompt: stripped before parsing,
|
|
21
24
|
// keeping the indentation inside the box (it tells options from their hints).
|
|
22
25
|
const BORDER_LEFT = /^\s*[│┃║] ?/u;
|
|
@@ -225,3 +228,27 @@ function promptKeys(p, n) {
|
|
|
225
228
|
const move = to > from ? Array(to - from).fill('Down') : Array(from - to).fill('Up');
|
|
226
229
|
return [...move, 'Enter'];
|
|
227
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* A row that takes typed text rather than being an answer itself: Claude
|
|
233
|
+
* Code's "Type something." / an "Other" row. Picking it with Enter answers
|
|
234
|
+
* with nothing, so the card asks for the text first.
|
|
235
|
+
*/
|
|
236
|
+
function isFreeTextOption(label) {
|
|
237
|
+
return /^(type something|type your own|other|something else)\.?(\s*\(.*\))?$/i.test(label.trim());
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Keys that put the cursor on row `n` without choosing it. For a free-text
|
|
241
|
+
* row: move there, then the text is typed and Enter pressed (as one message).
|
|
242
|
+
*/
|
|
243
|
+
function promptMoveKeys(p, n) {
|
|
244
|
+
return promptKeys(p, n).slice(0, -1);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The free-text row's number, or 0 if the menu has none. A TUI echoes what is
|
|
248
|
+
* typed in place of that row's label, so a row that already holds text no
|
|
249
|
+
* longer matches — which is correct: it is mid-answer, not an empty invitation.
|
|
250
|
+
*/
|
|
251
|
+
function freeTextRow(p) {
|
|
252
|
+
const row = p.options.find((o) => o.checked === null && isFreeTextOption(o.label));
|
|
253
|
+
return row ? row.n : 0;
|
|
254
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pane-prompt.test.d.ts","sourceRoot":"","sources":["../src/pane-prompt.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const pane_prompt_1 = require("./pane-prompt");
|
|
5
|
+
// Shapes as the CLIs draw them (spacing kept), plus the noise around them.
|
|
6
|
+
const CLAUDE_BASH = `
|
|
7
|
+
⏺ I'll run the tests.
|
|
8
|
+
|
|
9
|
+
╭──────────────────────────────────────────────────────────────╮
|
|
10
|
+
│ Bash command │
|
|
11
|
+
│ │
|
|
12
|
+
│ npm test │
|
|
13
|
+
│ Run the test suite │
|
|
14
|
+
│ │
|
|
15
|
+
│ Do you want to proceed? │
|
|
16
|
+
│ ❯ 1. Yes │
|
|
17
|
+
│ 2. Yes, and don't ask again for npm test commands in /app │
|
|
18
|
+
│ 3. No, and tell Claude what to do differently (esc) │
|
|
19
|
+
╰──────────────────────────────────────────────────────────────╯
|
|
20
|
+
`;
|
|
21
|
+
const CLAUDE_QUESTION = `
|
|
22
|
+
⏺ Let me ask a couple of things first.
|
|
23
|
+
|
|
24
|
+
────────────────────────────────────────────────────────────────
|
|
25
|
+
☐ Colour ☐ Pet ✔ Submit →
|
|
26
|
+
|
|
27
|
+
Which colour do you prefer?
|
|
28
|
+
|
|
29
|
+
❯ 1. Red
|
|
30
|
+
Warm and loud
|
|
31
|
+
2. Green
|
|
32
|
+
Calm
|
|
33
|
+
3. Type something.
|
|
34
|
+
────────────────────────────────────────────────────────────────
|
|
35
|
+
4. Chat about this
|
|
36
|
+
|
|
37
|
+
Enter to select · Tab/Arrow keys to navigate · Esc to cancel
|
|
38
|
+
`;
|
|
39
|
+
const CLAUDE_TRUST = `
|
|
40
|
+
Accessing workspace:
|
|
41
|
+
|
|
42
|
+
/Users/me/code/app
|
|
43
|
+
|
|
44
|
+
Quick safety check: Is this a project you created or one you trust?
|
|
45
|
+
|
|
46
|
+
❯ No, exit
|
|
47
|
+
Yes, I trust this folder
|
|
48
|
+
|
|
49
|
+
Enter to confirm · Esc to cancel
|
|
50
|
+
`;
|
|
51
|
+
const CODEX_APPROVAL = `
|
|
52
|
+
• Running tests
|
|
53
|
+
|
|
54
|
+
Would you like to run the following command?
|
|
55
|
+
|
|
56
|
+
$ npm test
|
|
57
|
+
|
|
58
|
+
› 1. Yes, proceed (y)
|
|
59
|
+
2. Yes, and don't ask again for this command (a)
|
|
60
|
+
3. No, and tell Codex what to do differently (esc)
|
|
61
|
+
|
|
62
|
+
Press enter to confirm or esc to cancel
|
|
63
|
+
`;
|
|
64
|
+
const MULTI = `
|
|
65
|
+
Which features should I add?
|
|
66
|
+
|
|
67
|
+
❯ 1. [ ] Dark mode
|
|
68
|
+
2. [✔] Search
|
|
69
|
+
3. [ ] Export
|
|
70
|
+
4. Submit
|
|
71
|
+
|
|
72
|
+
Enter to select · ↑/↓ to navigate · Esc to cancel
|
|
73
|
+
`;
|
|
74
|
+
const IDLE_CLAUDE = `
|
|
75
|
+
⏺ Done. Here is what changed:
|
|
76
|
+
|
|
77
|
+
1. Fixed the parser
|
|
78
|
+
2. Added tests
|
|
79
|
+
3. Updated docs
|
|
80
|
+
|
|
81
|
+
────────────────────────────────────────────────────────────────
|
|
82
|
+
❯
|
|
83
|
+
────────────────────────────────────────────────────────────────
|
|
84
|
+
⏵⏵ auto mode on (shift+tab to cycle)
|
|
85
|
+
`;
|
|
86
|
+
const SHELL = `
|
|
87
|
+
~/code/app main
|
|
88
|
+
❯ ls
|
|
89
|
+
README.md package.json src
|
|
90
|
+
~/code/app main
|
|
91
|
+
❯
|
|
92
|
+
`;
|
|
93
|
+
(0, vitest_1.describe)('detectPanePrompt', () => {
|
|
94
|
+
(0, vitest_1.it)('reads a Claude permission prompt inside a box', () => {
|
|
95
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(CLAUDE_BASH);
|
|
96
|
+
(0, vitest_1.expect)(p.question).toBe('Do you want to proceed?');
|
|
97
|
+
(0, vitest_1.expect)(p.detail).toEqual(['Bash command', 'npm test', 'Run the test suite']);
|
|
98
|
+
(0, vitest_1.expect)(p.options.map((o) => o.label)).toEqual([
|
|
99
|
+
'Yes', "Yes, and don't ask again for npm test commands in /app", 'No, and tell Claude what to do differently (esc)',
|
|
100
|
+
]);
|
|
101
|
+
(0, vitest_1.expect)(p.options[0].selected).toBe(true);
|
|
102
|
+
(0, vitest_1.expect)(p.approval).toBe(true);
|
|
103
|
+
(0, vitest_1.expect)(p.multi).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.it)('reads a question menu with hints, a separator and a footer', () => {
|
|
106
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(CLAUDE_QUESTION);
|
|
107
|
+
(0, vitest_1.expect)(p.question).toBe('Which colour do you prefer?');
|
|
108
|
+
(0, vitest_1.expect)(p.options.map((o) => o.label)).toEqual(['Red', 'Green', 'Type something.', 'Chat about this']);
|
|
109
|
+
(0, vitest_1.expect)(p.options[0].hint).toBe('Warm and loud');
|
|
110
|
+
(0, vitest_1.expect)(p.options[1].hint).toBe('Calm');
|
|
111
|
+
(0, vitest_1.expect)(p.footer).toMatch(/Enter to select/);
|
|
112
|
+
(0, vitest_1.expect)(p.approval).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
(0, vitest_1.it)('reads an unnumbered menu when a footer vouches for it', () => {
|
|
115
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(CLAUDE_TRUST);
|
|
116
|
+
(0, vitest_1.expect)(p.options.map((o) => [o.n, o.label, o.selected])).toEqual([[1, 'No, exit', true], [2, 'Yes, I trust this folder', false]]);
|
|
117
|
+
(0, vitest_1.expect)(p.question).toMatch(/one you trust\?$/);
|
|
118
|
+
});
|
|
119
|
+
(0, vitest_1.it)('reads a Codex approval', () => {
|
|
120
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(CODEX_APPROVAL);
|
|
121
|
+
(0, vitest_1.expect)(p.question).toBe('Would you like to run the following command?');
|
|
122
|
+
(0, vitest_1.expect)(p.detail).toContain('$ npm test');
|
|
123
|
+
(0, vitest_1.expect)(p.options).toHaveLength(3);
|
|
124
|
+
(0, vitest_1.expect)(p.options[0].selected).toBe(true);
|
|
125
|
+
});
|
|
126
|
+
(0, vitest_1.it)('marks multi-select rows', () => {
|
|
127
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(MULTI);
|
|
128
|
+
(0, vitest_1.expect)(p.multi).toBe(true);
|
|
129
|
+
(0, vitest_1.expect)(p.options.map((o) => o.checked)).toEqual([false, true, false, null]);
|
|
130
|
+
(0, vitest_1.expect)(p.options[1].label).toBe('Search');
|
|
131
|
+
});
|
|
132
|
+
(0, vitest_1.it)('finds a prompt under several status lines', () => {
|
|
133
|
+
const status = '\n [Opus 5.5] │ app │ main\n Context ░░░░ 12%\n Usage ok\n ⏵⏵ auto mode on (shift+tab to cycle)\n';
|
|
134
|
+
const noFooter = CLAUDE_BASH.trimEnd() + status;
|
|
135
|
+
(0, vitest_1.expect)((0, pane_prompt_1.detectPanePrompt)(noFooter)?.question).toBe('Do you want to proceed?');
|
|
136
|
+
(0, vitest_1.expect)((0, pane_prompt_1.detectPanePrompt)(CLAUDE_TRUST.trimEnd() + status)?.options).toHaveLength(2);
|
|
137
|
+
});
|
|
138
|
+
(0, vitest_1.it)('ignores an idle agent, a numbered reply and a shell prompt', () => {
|
|
139
|
+
(0, vitest_1.expect)((0, pane_prompt_1.detectPanePrompt)(IDLE_CLAUDE)).toBeNull();
|
|
140
|
+
(0, vitest_1.expect)((0, pane_prompt_1.detectPanePrompt)(SHELL)).toBeNull();
|
|
141
|
+
(0, vitest_1.expect)((0, pane_prompt_1.detectPanePrompt)('')).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
(0, vitest_1.it)('keeps its id while the cursor moves, changes it for a new question', () => {
|
|
144
|
+
const a = (0, pane_prompt_1.detectPanePrompt)(CLAUDE_BASH);
|
|
145
|
+
(0, vitest_1.expect)((0, pane_prompt_1.detectPanePrompt)(CLAUDE_BASH.replace('❯ 1. Yes', ' 1. Yes').replace('│ 2. Yes,', '│ ❯ 2. Yes,')).id).toBe(a.id);
|
|
146
|
+
(0, vitest_1.expect)((0, pane_prompt_1.detectPanePrompt)(CLAUDE_BASH.replace('npm test ', 'npm run x')).id).not.toBe(a.id);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
(0, vitest_1.describe)('promptKeys', () => {
|
|
150
|
+
(0, vitest_1.it)('moves from the cursor to the target, then Enter', () => {
|
|
151
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(CLAUDE_BASH);
|
|
152
|
+
(0, vitest_1.expect)((0, pane_prompt_1.promptKeys)(p, 1)).toEqual(['Enter']);
|
|
153
|
+
(0, vitest_1.expect)((0, pane_prompt_1.promptKeys)(p, 3)).toEqual(['Down', 'Down', 'Enter']);
|
|
154
|
+
const moved = (0, pane_prompt_1.detectPanePrompt)(CLAUDE_BASH.replace('❯ 1. Yes', ' 1. Yes').replace('│ 2. Yes,', '│ ❯ 2. Yes,'));
|
|
155
|
+
(0, vitest_1.expect)((0, pane_prompt_1.promptKeys)(moved, 1)).toEqual(['Up', 'Enter']);
|
|
156
|
+
});
|
|
157
|
+
(0, vitest_1.it)('only lets named keys and single characters through', () => {
|
|
158
|
+
(0, vitest_1.expect)((0, pane_prompt_1.isSafeKeyList)(['Down', 'Enter', 'y', '1'])).toBe(true);
|
|
159
|
+
(0, vitest_1.expect)((0, pane_prompt_1.isSafeKeyList)(['rm -rf /'])).toBe(false);
|
|
160
|
+
(0, vitest_1.expect)((0, pane_prompt_1.isSafeKeyList)([';', 'Enter'])).toBe(false);
|
|
161
|
+
(0, vitest_1.expect)((0, pane_prompt_1.isSafeKeyList)([])).toBe(false);
|
|
162
|
+
(0, vitest_1.expect)((0, pane_prompt_1.isSafeKeyList)('Enter')).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
(0, vitest_1.describe)('free-text rows', () => {
|
|
166
|
+
(0, vitest_1.it)('knows which rows want typing, and moves there without choosing', () => {
|
|
167
|
+
(0, vitest_1.expect)((0, pane_prompt_1.isFreeTextOption)('Type something.')).toBe(true);
|
|
168
|
+
(0, vitest_1.expect)((0, pane_prompt_1.isFreeTextOption)('Other')).toBe(true);
|
|
169
|
+
(0, vitest_1.expect)((0, pane_prompt_1.isFreeTextOption)('No, and tell Claude what to do differently (esc)')).toBe(false);
|
|
170
|
+
(0, vitest_1.expect)((0, pane_prompt_1.isFreeTextOption)('Yes')).toBe(false);
|
|
171
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(CLAUDE_QUESTION);
|
|
172
|
+
(0, vitest_1.expect)((0, pane_prompt_1.promptMoveKeys)(p, 3)).toEqual(['Down', 'Down']);
|
|
173
|
+
(0, vitest_1.expect)((0, pane_prompt_1.promptMoveKeys)(p, 1)).toEqual([]);
|
|
174
|
+
});
|
|
175
|
+
(0, vitest_1.it)('finds the row that wants typing, and stops once it holds text', () => {
|
|
176
|
+
const p = (0, pane_prompt_1.detectPanePrompt)(CLAUDE_QUESTION);
|
|
177
|
+
(0, vitest_1.expect)((0, pane_prompt_1.freeTextRow)(p)).toBe(3);
|
|
178
|
+
// The TUI echoes what is typed in place of the label; the row is then
|
|
179
|
+
// mid-answer, not an empty invitation, so the card must not reopen on it.
|
|
180
|
+
const typed = (0, pane_prompt_1.detectPanePrompt)(CLAUDE_QUESTION.replace('3. Type something.', '3. Teal like the sea'));
|
|
181
|
+
(0, vitest_1.expect)((0, pane_prompt_1.freeTextRow)(typed)).toBe(0);
|
|
182
|
+
(0, vitest_1.expect)((0, pane_prompt_1.freeTextRow)((0, pane_prompt_1.detectPanePrompt)(CLAUDE_BASH))).toBe(0);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-pin.test.d.ts","sourceRoot":"","sources":["../src/scroll-pin.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const scroll_pin_1 = require("./scroll-pin");
|
|
5
|
+
(0, vitest_1.describe)('shouldFollow', () => {
|
|
6
|
+
(0, vitest_1.it)('follows when parked at the bottom', () => {
|
|
7
|
+
(0, vitest_1.expect)((0, scroll_pin_1.shouldFollow)(900, 1400, 500)).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
(0, vitest_1.it)('follows at exactly the threshold, and stops one pixel past it', () => {
|
|
10
|
+
(0, vitest_1.expect)((0, scroll_pin_1.shouldFollow)(900 - scroll_pin_1.PIN_THRESHOLD_PX, 1400, 500)).toBe(true);
|
|
11
|
+
(0, vitest_1.expect)((0, scroll_pin_1.shouldFollow)(900 - scroll_pin_1.PIN_THRESHOLD_PX - 1, 1400, 500)).toBe(false);
|
|
12
|
+
});
|
|
13
|
+
(0, vitest_1.it)('does not follow when the reader has scrolled up', () => {
|
|
14
|
+
(0, vitest_1.expect)((0, scroll_pin_1.shouldFollow)(0, 1400, 500)).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
// Content shorter than the viewport is always "at the bottom": there is
|
|
17
|
+
// nowhere to scroll, so a new event must still land in view.
|
|
18
|
+
(0, vitest_1.it)('follows when content is shorter than the viewport', () => {
|
|
19
|
+
(0, vitest_1.expect)((0, scroll_pin_1.shouldFollow)(0, 300, 500)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** "1.4s", "12s", "2m 5s" — the duration Claude Code reports for a turn. */
|
|
2
|
+
export declare function thinkingDuration(ms: number): string;
|
|
3
|
+
/**
|
|
4
|
+
* The label for a thinking row. "Thought for 1.4s" when the vendor said how
|
|
5
|
+
* long, and the vaguer wording only when it did not — never a bare
|
|
6
|
+
* "thinking…", which read as though something were still in flight.
|
|
7
|
+
*/
|
|
8
|
+
export declare function thinkingLabel(ms?: number): string;
|
|
9
|
+
//# sourceMappingURL=thinking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thinking.d.ts","sourceRoot":"","sources":["../src/thinking.ts"],"names":[],"mappings":"AAWA,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CASnD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAGjD"}
|