dexto 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/agents/coding-agent/coding-agent.yml +3 -1
  2. package/dist/cli/assets/sounds/SOURCES.md +35 -0
  3. package/dist/cli/assets/sounds/boot.wav +0 -0
  4. package/dist/cli/assets/sounds/chime.wav +0 -0
  5. package/dist/cli/assets/sounds/coin.wav +0 -0
  6. package/dist/cli/assets/sounds/confirm.wav +0 -0
  7. package/dist/cli/assets/sounds/levelup.wav +0 -0
  8. package/dist/cli/assets/sounds/ping.wav +0 -0
  9. package/dist/cli/assets/sounds/powerup.wav +0 -0
  10. package/dist/cli/assets/sounds/startup.wav +0 -0
  11. package/dist/cli/assets/sounds/success.wav +0 -0
  12. package/dist/cli/assets/sounds/treasure.wav +0 -0
  13. package/dist/cli/assets/sounds/win.wav +0 -0
  14. package/dist/cli/commands/interactive-commands/exit-handler.d.ts +12 -0
  15. package/dist/cli/commands/interactive-commands/exit-handler.d.ts.map +1 -0
  16. package/dist/cli/commands/interactive-commands/exit-handler.js +20 -0
  17. package/dist/cli/commands/interactive-commands/exit-stats.d.ts +24 -0
  18. package/dist/cli/commands/interactive-commands/exit-stats.d.ts.map +1 -0
  19. package/dist/cli/commands/interactive-commands/exit-stats.js +17 -0
  20. package/dist/cli/commands/interactive-commands/general-commands.d.ts.map +1 -1
  21. package/dist/cli/commands/interactive-commands/general-commands.js +53 -3
  22. package/dist/cli/commands/interactive-commands/prompt-commands.d.ts.map +1 -1
  23. package/dist/cli/commands/interactive-commands/prompt-commands.js +12 -67
  24. package/dist/cli/commands/interactive-commands/session/session-commands.d.ts.map +1 -1
  25. package/dist/cli/commands/interactive-commands/session/session-commands.js +0 -2
  26. package/dist/cli/commands/interactive-commands/system/system-commands.d.ts +1 -13
  27. package/dist/cli/commands/interactive-commands/system/system-commands.d.ts.map +1 -1
  28. package/dist/cli/commands/interactive-commands/system/system-commands.js +45 -54
  29. package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
  30. package/dist/cli/ink-cli/InkCLIRefactored.js +132 -21
  31. package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts.map +1 -1
  32. package/dist/cli/ink-cli/components/ApprovalPrompt.js +74 -20
  33. package/dist/cli/ink-cli/components/ElicitationForm.d.ts +5 -3
  34. package/dist/cli/ink-cli/components/ElicitationForm.d.ts.map +1 -1
  35. package/dist/cli/ink-cli/components/ElicitationForm.js +414 -180
  36. package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
  37. package/dist/cli/ink-cli/components/ResourceAutocomplete.js +20 -11
  38. package/dist/cli/ink-cli/components/SlashCommandAutocomplete.d.ts.map +1 -1
  39. package/dist/cli/ink-cli/components/SlashCommandAutocomplete.js +47 -67
  40. package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
  41. package/dist/cli/ink-cli/components/StatusBar.js +10 -4
  42. package/dist/cli/ink-cli/components/base/BaseSelector.d.ts +2 -1
  43. package/dist/cli/ink-cli/components/base/BaseSelector.d.ts.map +1 -1
  44. package/dist/cli/ink-cli/components/base/BaseSelector.js +37 -27
  45. package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
  46. package/dist/cli/ink-cli/components/chat/Header.js +1 -1
  47. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
  48. package/dist/cli/ink-cli/components/chat/MessageItem.js +3 -1
  49. package/dist/cli/ink-cli/components/chat/ToolIcon.d.ts.map +1 -1
  50. package/dist/cli/ink-cli/components/chat/ToolIcon.js +5 -15
  51. package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.d.ts.map +1 -1
  52. package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +1 -1
  53. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  54. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +4 -2
  55. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  56. package/dist/cli/ink-cli/components/modes/StaticCLI.js +9 -2
  57. package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.d.ts +13 -0
  58. package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.d.ts.map +1 -0
  59. package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.js +60 -0
  60. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +1 -1
  61. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
  62. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +213 -100
  63. package/dist/cli/ink-cli/components/overlays/PromptList.d.ts.map +1 -1
  64. package/dist/cli/ink-cli/components/overlays/PromptList.js +12 -16
  65. package/dist/cli/ink-cli/components/overlays/SoundsSelector.d.ts +21 -0
  66. package/dist/cli/ink-cli/components/overlays/SoundsSelector.d.ts.map +1 -0
  67. package/dist/cli/ink-cli/components/overlays/SoundsSelector.js +566 -0
  68. package/dist/cli/ink-cli/components/overlays/ToolBrowser.d.ts.map +1 -1
  69. package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +94 -39
  70. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts.map +1 -1
  71. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.js +8 -13
  72. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts +3 -3
  73. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts.map +1 -1
  74. package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.js +6 -5
  75. package/dist/cli/ink-cli/components/renderers/FileRenderer.d.ts +3 -1
  76. package/dist/cli/ink-cli/components/renderers/FileRenderer.d.ts.map +1 -1
  77. package/dist/cli/ink-cli/components/renderers/FileRenderer.js +18 -7
  78. package/dist/cli/ink-cli/components/renderers/ShellRenderer.d.ts.map +1 -1
  79. package/dist/cli/ink-cli/components/renderers/ShellRenderer.js +7 -17
  80. package/dist/cli/ink-cli/components/renderers/index.d.ts.map +1 -1
  81. package/dist/cli/ink-cli/components/renderers/index.js +1 -1
  82. package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.d.ts +7 -0
  83. package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.d.ts.map +1 -0
  84. package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.js +8 -0
  85. package/dist/cli/ink-cli/components/shared/HintBar.d.ts +6 -0
  86. package/dist/cli/ink-cli/components/shared/HintBar.d.ts.map +1 -0
  87. package/dist/cli/ink-cli/components/shared/HintBar.js +6 -0
  88. package/dist/cli/ink-cli/constants/spinnerFrames.d.ts +2 -0
  89. package/dist/cli/ink-cli/constants/spinnerFrames.d.ts.map +1 -0
  90. package/dist/cli/ink-cli/constants/spinnerFrames.js +1 -0
  91. package/dist/cli/ink-cli/constants/tips.d.ts.map +1 -1
  92. package/dist/cli/ink-cli/constants/tips.js +1 -0
  93. package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
  94. package/dist/cli/ink-cli/containers/InputContainer.js +19 -15
  95. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  96. package/dist/cli/ink-cli/containers/OverlayContainer.js +21 -5
  97. package/dist/cli/ink-cli/hooks/useAnimationTick.d.ts +11 -0
  98. package/dist/cli/ink-cli/hooks/useAnimationTick.d.ts.map +1 -0
  99. package/dist/cli/ink-cli/hooks/useAnimationTick.js +54 -0
  100. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  101. package/dist/cli/ink-cli/hooks/useCLIState.js +1 -0
  102. package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
  103. package/dist/cli/ink-cli/services/processStream.js +17 -8
  104. package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
  105. package/dist/cli/ink-cli/state/initialState.js +1 -0
  106. package/dist/cli/ink-cli/state/types.d.ts +13 -1
  107. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  108. package/dist/cli/ink-cli/utils/commandOverlays.d.ts.map +1 -1
  109. package/dist/cli/ink-cli/utils/commandOverlays.js +1 -0
  110. package/dist/cli/ink-cli/utils/elicitationSchema.d.ts +11 -0
  111. package/dist/cli/ink-cli/utils/elicitationSchema.d.ts.map +1 -0
  112. package/dist/cli/ink-cli/utils/elicitationSchema.js +80 -0
  113. package/dist/cli/ink-cli/utils/index.d.ts +1 -1
  114. package/dist/cli/ink-cli/utils/index.d.ts.map +1 -1
  115. package/dist/cli/ink-cli/utils/index.js +1 -1
  116. package/dist/cli/ink-cli/utils/messageFormatting.d.ts +2 -17
  117. package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
  118. package/dist/cli/ink-cli/utils/messageFormatting.js +22 -128
  119. package/dist/cli/ink-cli/utils/overlayPresentation.d.ts +19 -0
  120. package/dist/cli/ink-cli/utils/overlayPresentation.d.ts.map +1 -0
  121. package/dist/cli/ink-cli/utils/overlayPresentation.js +33 -0
  122. package/dist/cli/ink-cli/utils/overlaySizing.d.ts +19 -0
  123. package/dist/cli/ink-cli/utils/overlaySizing.d.ts.map +1 -0
  124. package/dist/cli/ink-cli/utils/overlaySizing.js +11 -0
  125. package/dist/cli/ink-cli/utils/soundNotification.d.ts +19 -13
  126. package/dist/cli/ink-cli/utils/soundNotification.d.ts.map +1 -1
  127. package/dist/cli/ink-cli/utils/soundNotification.js +120 -97
  128. package/dist/utils/session-logger-factory.d.ts.map +1 -1
  129. package/dist/utils/session-logger-factory.js +17 -2
  130. package/dist/webui/assets/{index-DwtueA8l.js → index-CKhumsZA.js} +135 -135
  131. package/dist/webui/index.html +1 -1
  132. package/package.json +11 -11
@@ -1 +1 @@
1
- {"version":3,"file":"PromptList.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/overlays/PromptList.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAQN,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG1D,MAAM,MAAM,gBAAgB,GACtB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAEhC,UAAU,eAAe;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7C,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC7B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAuCD;;GAEG;AACH,QAAA,MAAM,UAAU,0FAqKd,CAAC;AAEH,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"PromptList.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/overlays/PromptList.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAQN,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG1D,MAAM,MAAM,gBAAgB,GACtB;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAEhC,UAAU,eAAe;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC7C,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC7B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAuCD;;GAEG;AACH,QAAA,MAAM,UAAU,0FAgKd,CAAC;AAEH,eAAe,UAAU,CAAC"}
@@ -66,7 +66,7 @@ const PromptList = forwardRef(function PromptList({ isVisible, onAction, onLoadI
66
66
  setIsLoading(false);
67
67
  })
68
68
  .catch((err) => {
69
- console.error(`PromptList: Failed to load prompts: ${err}`);
69
+ agent.logger.error(`PromptList: Failed to load prompts: ${err instanceof Error ? err.message : String(err)}`);
70
70
  setPrompts([]);
71
71
  setIsLoading(false);
72
72
  });
@@ -88,21 +88,17 @@ const PromptList = forwardRef(function PromptList({ isVisible, onAction, onLoadI
88
88
  }, [isVisible, refreshKey, loadPrompts]);
89
89
  // Build list items: prompts + Add/Delete actions
90
90
  const items = useMemo(() => {
91
- const list = prompts.map((prompt) => ({
92
- id: prompt.name,
93
- type: 'prompt',
94
- prompt,
95
- }));
96
- // Add action items at the end
97
- list.push({
98
- id: '__add__',
99
- type: 'add',
100
- });
101
- list.push({
102
- id: '__delete__',
103
- type: 'delete',
104
- });
105
- return list;
91
+ return [
92
+ // Action items at the top
93
+ { id: '__add__', type: 'add' },
94
+ { id: '__delete__', type: 'delete' },
95
+ // Prompts list
96
+ ...prompts.map((prompt) => ({
97
+ id: prompt.name,
98
+ type: 'prompt',
99
+ prompt,
100
+ })),
101
+ ];
106
102
  }, [prompts]);
107
103
  // Format item for display
108
104
  const formatItem = (item, isSelected) => {
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SoundsSelector Component
3
+ * Interactive overlay for configuring sound notifications and selecting built-in sounds.
4
+ *
5
+ * Built-in sounds are copied into the Dexto sounds directory (typically ~/.dexto/sounds/builtins/)
6
+ * and selected via preferences.yml using paths relative to that directory (e.g., builtins/coin.wav).
7
+ *
8
+ * This reuses the existing sound resolution logic in soundNotification.ts.
9
+ */
10
+ import React from 'react';
11
+ import type { Key } from '../../hooks/useInputOrchestrator.js';
12
+ interface SoundsSelectorProps {
13
+ isVisible: boolean;
14
+ onClose: () => void;
15
+ }
16
+ export interface SoundsSelectorHandle {
17
+ handleInput: (input: string, key: Key) => boolean;
18
+ }
19
+ declare const SoundsSelector: React.ForwardRefExoticComponent<SoundsSelectorProps & React.RefAttributes<SoundsSelectorHandle>>;
20
+ export default SoundsSelector;
21
+ //# sourceMappingURL=SoundsSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SoundsSelector.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/overlays/SoundsSelector.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAQN,MAAM,OAAO,CAAC;AAKf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAkB/D,UAAU,mBAAmB;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACjC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAuMD,QAAA,MAAM,cAAc,kGAsiBnB,CAAC;AAEF,eAAe,cAAc,CAAC"}
@@ -0,0 +1,566 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * SoundsSelector Component
4
+ * Interactive overlay for configuring sound notifications and selecting built-in sounds.
5
+ *
6
+ * Built-in sounds are copied into the Dexto sounds directory (typically ~/.dexto/sounds/builtins/)
7
+ * and selected via preferences.yml using paths relative to that directory (e.g., builtins/coin.wav).
8
+ *
9
+ * This reuses the existing sound resolution logic in soundNotification.ts.
10
+ */
11
+ import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, forwardRef, } from 'react';
12
+ import { Box, Text } from 'ink';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { promises as fs } from 'node:fs';
15
+ import path from 'node:path';
16
+ import { BaseSelector } from '../base/BaseSelector.js';
17
+ import { CUSTOM_SOUND_EXTENSIONS, getDefaultSoundSpec, playNotificationSound, playSoundFile, } from '../../utils/soundNotification.js';
18
+ import { useSoundService } from '../../contexts/index.js';
19
+ import { getDextoGlobalPath, globalPreferencesExist, loadGlobalPreferences, updateGlobalPreferences, } from '@dexto/agent-management';
20
+ const BUILTIN_SOUNDS = [
21
+ {
22
+ id: 'coin',
23
+ name: 'Coin',
24
+ filename: 'coin.wav',
25
+ },
26
+ {
27
+ id: 'confirm',
28
+ name: 'Confirm',
29
+ filename: 'confirm.wav',
30
+ },
31
+ {
32
+ id: 'ping',
33
+ name: 'Ping',
34
+ filename: 'ping.wav',
35
+ },
36
+ {
37
+ id: 'powerup',
38
+ name: 'Power Up',
39
+ filename: 'powerup.wav',
40
+ },
41
+ {
42
+ id: 'chime',
43
+ name: 'Chime',
44
+ filename: 'chime.wav',
45
+ },
46
+ {
47
+ id: 'levelup',
48
+ name: 'Level Up',
49
+ filename: 'levelup.wav',
50
+ },
51
+ {
52
+ id: 'boot',
53
+ name: 'Boot',
54
+ filename: 'boot.wav',
55
+ },
56
+ {
57
+ id: 'startup',
58
+ name: 'Startup',
59
+ filename: 'startup.wav',
60
+ },
61
+ {
62
+ id: 'success',
63
+ name: 'Success',
64
+ filename: 'success.wav',
65
+ },
66
+ {
67
+ id: 'win',
68
+ name: 'Win',
69
+ filename: 'win.wav',
70
+ },
71
+ {
72
+ id: 'treasure',
73
+ name: 'Treasure Chest',
74
+ filename: 'treasure.wav',
75
+ },
76
+ ];
77
+ const DEFAULT_CONFIG = {
78
+ enabled: true,
79
+ onStartup: false,
80
+ onApprovalRequired: true,
81
+ onTaskComplete: true,
82
+ };
83
+ function getSoundEnabledKey(soundType) {
84
+ switch (soundType) {
85
+ case 'startup':
86
+ return 'onStartup';
87
+ case 'approval':
88
+ return 'onApprovalRequired';
89
+ case 'complete':
90
+ return 'onTaskComplete';
91
+ }
92
+ }
93
+ function getSoundFileKey(soundType) {
94
+ switch (soundType) {
95
+ case 'startup':
96
+ return 'startupSoundFile';
97
+ case 'approval':
98
+ return 'approvalSoundFile';
99
+ case 'complete':
100
+ return 'completeSoundFile';
101
+ }
102
+ }
103
+ function selectionLabel(soundType, selection) {
104
+ if (selection.kind === 'system')
105
+ return soundType === 'startup' ? 'Startup' : 'System';
106
+ const normalizedRelative = selection.relativePath.replaceAll('\\', '/');
107
+ const builtinFilename = normalizedRelative.startsWith('builtins/')
108
+ ? normalizedRelative.slice('builtins/'.length)
109
+ : null;
110
+ if (builtinFilename) {
111
+ const builtin = BUILTIN_SOUNDS.find((s) => s.filename === builtinFilename);
112
+ if (builtin)
113
+ return builtin.name;
114
+ }
115
+ return normalizedRelative.replace(/\.[^/.]+$/, '');
116
+ }
117
+ function getAllowedCustomSoundExtensions() {
118
+ switch (process.platform) {
119
+ case 'win32':
120
+ return new Set(['.wav']);
121
+ case 'linux':
122
+ return new Set(['.wav', '.ogg', '.oga']);
123
+ default:
124
+ return new Set(CUSTOM_SOUND_EXTENSIONS);
125
+ }
126
+ }
127
+ async function listCustomSoundFiles(soundsDir) {
128
+ const allowedExtensions = getAllowedCustomSoundExtensions();
129
+ const results = [];
130
+ const walk = async (dir) => {
131
+ let entries;
132
+ try {
133
+ entries = await fs.readdir(dir, { withFileTypes: true });
134
+ }
135
+ catch (error) {
136
+ if (error.code === 'ENOENT')
137
+ return;
138
+ throw error;
139
+ }
140
+ for (const entry of entries) {
141
+ if (entry.name.startsWith('.'))
142
+ continue;
143
+ const absPath = path.join(dir, entry.name);
144
+ if (entry.isDirectory()) {
145
+ if (dir === soundsDir && entry.name === 'builtins')
146
+ continue;
147
+ await walk(absPath);
148
+ continue;
149
+ }
150
+ if (!entry.isFile())
151
+ continue;
152
+ const ext = path.extname(entry.name).toLowerCase();
153
+ if (!allowedExtensions.has(ext))
154
+ continue;
155
+ const relative = path.relative(soundsDir, absPath);
156
+ if (relative.startsWith('..') || path.isAbsolute(relative))
157
+ continue;
158
+ results.push(relative.split(path.sep).join('/'));
159
+ }
160
+ };
161
+ await walk(soundsDir);
162
+ return results.sort((a, b) => a.localeCompare(b));
163
+ }
164
+ function formatCustomSoundLabel(relativePath) {
165
+ return relativePath.replaceAll('\\', '/').replace(/\.[^/.]+$/, '');
166
+ }
167
+ function resolveSelection(soundType, config) {
168
+ const configuredRelativePath = config[getSoundFileKey(soundType)];
169
+ if (configuredRelativePath) {
170
+ return { kind: 'file', relativePath: configuredRelativePath };
171
+ }
172
+ return { kind: 'system' };
173
+ }
174
+ const SoundsSelector = forwardRef(function SoundsSelector({ isVisible, onClose }, ref) {
175
+ const soundService = useSoundService();
176
+ const baseSelectorRef = useRef(null);
177
+ const [viewMode, setViewMode] = useState('main');
178
+ const [selectedIndex, setSelectedIndex] = useState(0);
179
+ const [pickAction, setPickAction] = useState('listen');
180
+ const [isLoading, setIsLoading] = useState(false);
181
+ const [isApplying, setIsApplying] = useState(false);
182
+ const [error, setError] = useState(null);
183
+ const [canPersistPreferences, setCanPersistPreferences] = useState(false);
184
+ const [config, setConfig] = useState(DEFAULT_CONFIG);
185
+ const [customSoundFiles, setCustomSoundFiles] = useState([]);
186
+ const [approvalSelection, setApprovalSelection] = useState({
187
+ kind: 'system',
188
+ });
189
+ const [completeSelection, setCompleteSelection] = useState({
190
+ kind: 'system',
191
+ });
192
+ const [startupSelection, setStartupSelection] = useState({
193
+ kind: 'system',
194
+ });
195
+ const configRef = useRef(config);
196
+ configRef.current = config;
197
+ const closeOrBack = useCallback(() => {
198
+ if (viewMode === 'main') {
199
+ onClose();
200
+ return;
201
+ }
202
+ setViewMode('main');
203
+ setSelectedIndex(0);
204
+ setError(null);
205
+ }, [onClose, viewMode]);
206
+ useImperativeHandle(ref, () => ({
207
+ handleInput: (input, key) => {
208
+ if (!isVisible)
209
+ return false;
210
+ if (viewMode !== 'main') {
211
+ if (key.leftArrow) {
212
+ setPickAction('listen');
213
+ return true;
214
+ }
215
+ if (key.rightArrow) {
216
+ setPickAction('select');
217
+ return true;
218
+ }
219
+ }
220
+ return baseSelectorRef.current?.handleInput(input, key) ?? false;
221
+ },
222
+ }), [isVisible, viewMode]);
223
+ const builtinSoundPaths = useMemo(() => {
224
+ return BUILTIN_SOUNDS.map((sound) => ({
225
+ ...sound,
226
+ absPath: fileURLToPath(new URL(`../../../assets/sounds/${sound.filename}`, import.meta.url)),
227
+ }));
228
+ }, []);
229
+ const refreshSelections = useCallback((nextConfig) => {
230
+ setStartupSelection(resolveSelection('startup', nextConfig));
231
+ setApprovalSelection(resolveSelection('approval', nextConfig));
232
+ setCompleteSelection(resolveSelection('complete', nextConfig));
233
+ }, []);
234
+ // Load preferences + built-in sounds when becoming visible
235
+ useEffect(() => {
236
+ if (!isVisible)
237
+ return;
238
+ let cancelled = false;
239
+ setIsLoading(true);
240
+ setIsApplying(false);
241
+ setError(null);
242
+ setViewMode('main');
243
+ setSelectedIndex(0);
244
+ const run = async () => {
245
+ // Load preferences (optional)
246
+ const prefsExist = globalPreferencesExist();
247
+ setCanPersistPreferences(prefsExist);
248
+ let nextConfig = DEFAULT_CONFIG;
249
+ if (prefsExist) {
250
+ try {
251
+ const preferences = await loadGlobalPreferences();
252
+ nextConfig = {
253
+ enabled: preferences.sounds?.enabled ?? nextConfig.enabled,
254
+ onStartup: preferences.sounds?.onStartup ?? nextConfig.onStartup,
255
+ startupSoundFile: preferences.sounds?.startupSoundFile,
256
+ onApprovalRequired: preferences.sounds?.onApprovalRequired ??
257
+ nextConfig.onApprovalRequired,
258
+ approvalSoundFile: preferences.sounds?.approvalSoundFile,
259
+ onTaskComplete: preferences.sounds?.onTaskComplete ?? nextConfig.onTaskComplete,
260
+ completeSoundFile: preferences.sounds?.completeSoundFile,
261
+ };
262
+ }
263
+ catch (err) {
264
+ // Non-fatal: allow selection changes, but toggles won't persist
265
+ setCanPersistPreferences(false);
266
+ if (!cancelled) {
267
+ setError(`Failed to load preferences: ${err instanceof Error ? err.message : String(err)}`);
268
+ }
269
+ }
270
+ }
271
+ if (!cancelled) {
272
+ setConfig(nextConfig);
273
+ soundService?.setConfig(nextConfig);
274
+ refreshSelections(nextConfig);
275
+ }
276
+ const soundsDir = getDextoGlobalPath('sounds');
277
+ try {
278
+ const files = await listCustomSoundFiles(soundsDir);
279
+ if (!cancelled)
280
+ setCustomSoundFiles(files);
281
+ }
282
+ catch (err) {
283
+ if (!cancelled) {
284
+ setCustomSoundFiles([]);
285
+ setError(`Failed to load custom sounds: ${err instanceof Error ? err.message : String(err)}`);
286
+ }
287
+ }
288
+ };
289
+ void run()
290
+ .catch((err) => {
291
+ if (!cancelled) {
292
+ setError(err instanceof Error ? err.message : String(err));
293
+ }
294
+ })
295
+ .finally(() => {
296
+ if (!cancelled)
297
+ setIsLoading(false);
298
+ });
299
+ return () => {
300
+ cancelled = true;
301
+ };
302
+ }, [isVisible, refreshSelections, soundService]);
303
+ const mainItems = useMemo(() => [
304
+ { type: 'enabled', id: 'enabled' },
305
+ {
306
+ type: 'pick',
307
+ id: 'pick-startup',
308
+ soundType: 'startup',
309
+ label: 'startup sound',
310
+ },
311
+ {
312
+ type: 'pick',
313
+ id: 'pick-approval',
314
+ soundType: 'approval',
315
+ label: 'approval sound',
316
+ },
317
+ {
318
+ type: 'pick',
319
+ id: 'pick-complete',
320
+ soundType: 'complete',
321
+ label: 'completion sound',
322
+ },
323
+ ], []);
324
+ const pickSoundType = viewMode === 'pick-startup'
325
+ ? 'startup'
326
+ : viewMode === 'pick-approval'
327
+ ? 'approval'
328
+ : viewMode === 'pick-complete'
329
+ ? 'complete'
330
+ : null;
331
+ const pickItems = useMemo(() => {
332
+ if (!pickSoundType)
333
+ return [];
334
+ const current = pickSoundType === 'startup'
335
+ ? startupSelection
336
+ : pickSoundType === 'approval'
337
+ ? approvalSelection
338
+ : completeSelection;
339
+ const enabledKey = getSoundEnabledKey(pickSoundType);
340
+ const isEnabled = config[enabledKey];
341
+ const normalizedRelative = current.kind === 'file' ? current.relativePath.replaceAll('\\', '/') : null;
342
+ const items = [
343
+ {
344
+ type: 'off',
345
+ id: 'off',
346
+ label: 'Off',
347
+ isCurrent: !isEnabled,
348
+ },
349
+ {
350
+ type: 'default',
351
+ id: 'default',
352
+ label: 'Default',
353
+ isCurrent: isEnabled && current.kind === 'system',
354
+ },
355
+ ...builtinSoundPaths.map((sound) => ({
356
+ type: 'builtin',
357
+ id: sound.id,
358
+ label: sound.name,
359
+ isCurrent: isEnabled &&
360
+ normalizedRelative === path.posix.join('builtins', sound.filename),
361
+ })),
362
+ ...customSoundFiles.map((relativePath) => ({
363
+ type: 'file',
364
+ id: relativePath,
365
+ relativePath,
366
+ label: formatCustomSoundLabel(relativePath),
367
+ isCurrent: isEnabled && normalizedRelative === relativePath.replaceAll('\\', '/'),
368
+ })),
369
+ ];
370
+ return items;
371
+ }, [
372
+ approvalSelection,
373
+ builtinSoundPaths,
374
+ customSoundFiles,
375
+ completeSelection,
376
+ config,
377
+ pickSoundType,
378
+ startupSelection,
379
+ ]);
380
+ const items = viewMode === 'main' ? mainItems : pickItems;
381
+ const applyConfigUpdate = useCallback(async (partial) => {
382
+ const previousConfig = configRef.current;
383
+ const nextConfig = { ...previousConfig, ...partial };
384
+ setConfig(nextConfig);
385
+ soundService?.setConfig(partial);
386
+ refreshSelections(nextConfig);
387
+ if (!canPersistPreferences) {
388
+ return;
389
+ }
390
+ try {
391
+ await updateGlobalPreferences({ sounds: partial });
392
+ }
393
+ catch (err) {
394
+ setConfig(previousConfig);
395
+ soundService?.setConfig(previousConfig);
396
+ refreshSelections(previousConfig);
397
+ setError(err instanceof Error ? err.message : String(err));
398
+ }
399
+ }, [canPersistPreferences, refreshSelections, soundService]);
400
+ const ensureBuiltinSoundFile = useCallback(async (soundId) => {
401
+ const soundsDir = getDextoGlobalPath('sounds');
402
+ const builtinsDir = path.join(soundsDir, 'builtins');
403
+ await fs.mkdir(builtinsDir, { recursive: true });
404
+ const builtin = builtinSoundPaths.find((s) => s.id === soundId);
405
+ if (!builtin) {
406
+ throw new Error(`Unknown built-in sound: ${soundId}`);
407
+ }
408
+ const destPath = path.join(builtinsDir, builtin.filename);
409
+ await fs.copyFile(builtin.absPath, destPath);
410
+ return path.posix.join('builtins', builtin.filename);
411
+ }, [builtinSoundPaths]);
412
+ const previewPickItem = useCallback((item) => {
413
+ if (!pickSoundType)
414
+ return;
415
+ if (item.type === 'off') {
416
+ return;
417
+ }
418
+ if (item.type === 'default') {
419
+ const spec = getDefaultSoundSpec(pickSoundType);
420
+ if (spec)
421
+ playSoundFile(spec);
422
+ return;
423
+ }
424
+ if (item.type === 'builtin') {
425
+ const builtin = builtinSoundPaths.find((s) => s.id === item.id);
426
+ if (builtin) {
427
+ playSoundFile(builtin.absPath);
428
+ }
429
+ return;
430
+ }
431
+ if (item.type === 'file') {
432
+ const soundsDir = path.normalize(getDextoGlobalPath('sounds'));
433
+ const resolved = path.normalize(path.resolve(soundsDir, item.relativePath));
434
+ if (resolved === soundsDir || resolved.startsWith(soundsDir + path.sep)) {
435
+ playSoundFile(resolved);
436
+ }
437
+ }
438
+ }, [builtinSoundPaths, pickSoundType]);
439
+ const handleSelect = useCallback(async (item) => {
440
+ if (isApplying || isLoading)
441
+ return;
442
+ setIsApplying(true);
443
+ setError(null);
444
+ try {
445
+ if (item.type === 'enabled') {
446
+ await applyConfigUpdate({ enabled: !configRef.current.enabled });
447
+ return;
448
+ }
449
+ if (item.type === 'pick') {
450
+ setPickAction('listen');
451
+ setViewMode(item.id);
452
+ setSelectedIndex(0);
453
+ return;
454
+ }
455
+ if (item.type === 'off' ||
456
+ item.type === 'default' ||
457
+ item.type === 'builtin' ||
458
+ item.type === 'file') {
459
+ if (!pickSoundType)
460
+ return;
461
+ const enabledKey = getSoundEnabledKey(pickSoundType);
462
+ const fileKey = getSoundFileKey(pickSoundType);
463
+ if (pickAction === 'listen') {
464
+ previewPickItem(item);
465
+ return;
466
+ }
467
+ if (item.type === 'off') {
468
+ await applyConfigUpdate({ [enabledKey]: false });
469
+ setViewMode('main');
470
+ setSelectedIndex(0);
471
+ return;
472
+ }
473
+ if (item.type === 'default') {
474
+ const partial = {
475
+ [enabledKey]: true,
476
+ [fileKey]: undefined,
477
+ };
478
+ await applyConfigUpdate(partial);
479
+ playNotificationSound(pickSoundType, {
480
+ ...configRef.current,
481
+ ...partial,
482
+ });
483
+ }
484
+ else if (item.type === 'builtin') {
485
+ const relativePath = await ensureBuiltinSoundFile(item.id);
486
+ const partial = {
487
+ [enabledKey]: true,
488
+ [fileKey]: relativePath,
489
+ };
490
+ await applyConfigUpdate(partial);
491
+ playNotificationSound(pickSoundType, {
492
+ ...configRef.current,
493
+ ...partial,
494
+ });
495
+ }
496
+ else {
497
+ const partial = {
498
+ [enabledKey]: true,
499
+ [fileKey]: item.relativePath,
500
+ };
501
+ await applyConfigUpdate(partial);
502
+ playNotificationSound(pickSoundType, {
503
+ ...configRef.current,
504
+ ...partial,
505
+ });
506
+ }
507
+ setViewMode('main');
508
+ setSelectedIndex(0);
509
+ return;
510
+ }
511
+ }
512
+ catch (err) {
513
+ setError(err instanceof Error ? err.message : String(err));
514
+ }
515
+ finally {
516
+ setIsApplying(false);
517
+ }
518
+ }, [
519
+ applyConfigUpdate,
520
+ isApplying,
521
+ isLoading,
522
+ pickAction,
523
+ pickSoundType,
524
+ previewPickItem,
525
+ ensureBuiltinSoundFile,
526
+ ]);
527
+ const formatItem = useCallback((item, isSelected) => {
528
+ if (item.type === 'enabled') {
529
+ return (_jsxs(Text, { wrap: "truncate-end", children: [_jsxs(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, children: ["sounds:", ' '] }), _jsx(Text, { color: config.enabled ? 'green' : 'gray', bold: isSelected, children: config.enabled ? 'On' : 'Off' })] }));
530
+ }
531
+ if (item.type === 'pick') {
532
+ const enabled = item.soundType === 'startup'
533
+ ? config.onStartup
534
+ : item.soundType === 'approval'
535
+ ? config.onApprovalRequired
536
+ : config.onTaskComplete;
537
+ const selection = item.soundType === 'startup'
538
+ ? startupSelection
539
+ : item.soundType === 'approval'
540
+ ? approvalSelection
541
+ : completeSelection;
542
+ const currentLabel = enabled
543
+ ? selectionLabel(item.soundType, selection)
544
+ : 'Off';
545
+ return (_jsxs(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, wrap: "truncate-end", children: [item.label, ": ", currentLabel] }));
546
+ }
547
+ if (item.type === 'off' ||
548
+ item.type === 'default' ||
549
+ item.type === 'builtin' ||
550
+ item.type === 'file') {
551
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { flexGrow: 1, flexDirection: "row", children: [_jsx(Text, { color: "green", children: item.isCurrent ? '* ' : ' ' }), _jsx(Text, { color: isSelected ? 'cyan' : 'gray', bold: isSelected, wrap: "truncate-end", children: item.label })] }), isSelected && (_jsxs(Box, { flexDirection: "row", marginLeft: 1, children: [_jsxs(Text, { inverse: pickAction === 'listen', bold: pickAction === 'listen', children: [' ', "Listen", ' '] }), _jsx(Text, { children: " " }), _jsxs(Text, { inverse: pickAction === 'select', bold: pickAction === 'select', children: [' ', "Select", ' '] })] }))] }));
552
+ }
553
+ return null;
554
+ }, [approvalSelection, completeSelection, config, pickAction, startupSelection]);
555
+ const title = viewMode === 'main'
556
+ ? 'Sounds'
557
+ : pickSoundType === 'startup'
558
+ ? 'Select Startup Sound'
559
+ : pickSoundType === 'approval'
560
+ ? 'Select Approval Sound'
561
+ : 'Select Completion Sound';
562
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(BaseSelector, { ref: baseSelectorRef, items: items, isVisible: isVisible, isLoading: isLoading, loadingMessage: "Loading sound settings...", selectedIndex: selectedIndex, onSelectIndex: setSelectedIndex, onSelect: (item) => void handleSelect(item), onClose: closeOrBack, formatItem: formatItem, title: title, instructionsOverride: viewMode === 'main'
563
+ ? '↑↓ navigate • Enter toggle/select • Esc close'
564
+ : `↑↓ navigate • ←/→ Listen/Select • Enter ${pickAction === 'listen' ? 'preview' : 'select'} • Esc back`, borderColor: "magenta", emptyMessage: "No options available" }), error && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "red", wrap: "wrap", children: error }) }))] }));
565
+ });
566
+ export default SoundsSelector;
@@ -1 +1 @@
1
- {"version":3,"file":"ToolBrowser.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/overlays/ToolBrowser.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAI9C,UAAU,gBAAgB;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAC9B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAmBD;;GAEG;AACH,QAAA,MAAM,WAAW,4FAspBf,CAAC;AAyZH,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"ToolBrowser.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/overlays/ToolBrowser.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAON,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qCAAqC,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAM9C,UAAU,gBAAgB;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAC9B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAkBD;;GAEG;AACH,QAAA,MAAM,WAAW,4FA8rBf,CAAC;AAyZH,eAAe,WAAW,CAAC"}