sonex-agent 0.1.0-alpha.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +15 -0
  3. package/bin/sonex.js +235 -0
  4. package/dist/App.js +1360 -0
  5. package/dist/activity.js +18 -0
  6. package/dist/chat-document.js +40 -0
  7. package/dist/chat-message.js +93 -0
  8. package/dist/chat-theme.js +18 -0
  9. package/dist/chat-window.js +104 -0
  10. package/dist/command-panel.js +45 -0
  11. package/dist/commands.js +70 -0
  12. package/dist/components.js +662 -0
  13. package/dist/confirm-choice.js +65 -0
  14. package/dist/constants.js +109 -0
  15. package/dist/conversation-flow.js +21 -0
  16. package/dist/cover-pattern.js +58 -0
  17. package/dist/cover-visual.js +158 -0
  18. package/dist/extension-panel.js +136 -0
  19. package/dist/format.js +99 -0
  20. package/dist/hooks.js +216 -0
  21. package/dist/i18n.js +291 -0
  22. package/dist/index.js +46 -0
  23. package/dist/info-banner.js +37 -0
  24. package/dist/input-cursor.js +6 -0
  25. package/dist/input-routing.js +41 -0
  26. package/dist/launch-preparing.js +30 -0
  27. package/dist/layout.js +134 -0
  28. package/dist/list.js +3 -0
  29. package/dist/login-navigation.js +7 -0
  30. package/dist/mini-progress-writer.js +122 -0
  31. package/dist/mini-progress.js +77 -0
  32. package/dist/model-selection.js +20 -0
  33. package/dist/model-status.js +34 -0
  34. package/dist/mouse-input.js +173 -0
  35. package/dist/panel-frame.js +86 -0
  36. package/dist/panel-lifecycle.js +17 -0
  37. package/dist/playback-keymap.js +59 -0
  38. package/dist/provider-state.js +67 -0
  39. package/dist/runtime-state.js +91 -0
  40. package/dist/shell-state.js +37 -0
  41. package/dist/sonex-logo.js +9 -0
  42. package/dist/terminal-clear.js +10 -0
  43. package/dist/terminal-frame-writer.js +133 -0
  44. package/dist/terminal-surface.js +80 -0
  45. package/dist/text-stream.js +17 -0
  46. package/dist/track-panel.js +95 -0
  47. package/dist/transcript.js +92 -0
  48. package/dist/types.js +1 -0
  49. package/dist/ui-settings.js +30 -0
  50. package/dist/usage-animation.js +14 -0
  51. package/package.json +57 -0
  52. package/vendor/requirements-linux-py312.txt +2659 -0
  53. package/vendor/sonex-0.1.0a1-py3-none-any.whl +0 -0
@@ -0,0 +1,18 @@
1
+ import { MAX_ACTIVITY_ITEMS } from './constants.js';
2
+ import { trimList } from './list.js';
3
+ /**
4
+ * Coordinates the upsert activity operation for the CLI UI runtime.
5
+ *
6
+ * @param items Input value used by the upsert activity operation.
7
+ * @param item Input value used by the upsert activity operation.
8
+ * @returns The computed result for the surrounding CLI UI flow.
9
+ */
10
+ export function upsertActivity(items, item) {
11
+ const index = items.findIndex((existing) => existing.id === item.id);
12
+ if (index === -1) {
13
+ return trimList([...items, item], MAX_ACTIVITY_ITEMS);
14
+ }
15
+ const next = [...items];
16
+ next[index] = { ...next[index], ...item };
17
+ return trimList(next, MAX_ACTIVITY_ITEMS);
18
+ }
@@ -0,0 +1,40 @@
1
+ export function chatDocumentSegments(document) {
2
+ const segments = [];
3
+ const append = (segment) => {
4
+ if (!segment.text)
5
+ return;
6
+ const previous = segments[segments.length - 1];
7
+ if (previous?.style === segment.style && previous.href === segment.href) {
8
+ previous.text += segment.text;
9
+ }
10
+ else {
11
+ segments.push({ ...segment });
12
+ }
13
+ };
14
+ document.blocks.forEach((block, index) => {
15
+ if (block.type === 'spacer') {
16
+ append({ text: '\n', style: 'plain' });
17
+ return;
18
+ }
19
+ if (block.type === 'code_block') {
20
+ append({ text: block.text, style: 'code' });
21
+ }
22
+ else {
23
+ if (block.type === 'list_item') {
24
+ const level = Math.max(0, Math.min(2, Math.floor(block.level ?? 0)));
25
+ append({ text: `${' '.repeat(level)}${block.marker} `, style: 'list_marker' });
26
+ }
27
+ for (const span of block.spans) {
28
+ append({
29
+ text: span.text,
30
+ style: block.type === 'heading' ? 'heading' : span.style,
31
+ ...(span.href ? { href: span.href } : {}),
32
+ });
33
+ }
34
+ }
35
+ if (index < document.blocks.length - 1) {
36
+ append({ text: '\n', style: 'plain' });
37
+ }
38
+ });
39
+ return segments;
40
+ }
@@ -0,0 +1,93 @@
1
+ import stringWidth from 'string-width';
2
+ import { BORDER_BLUE, SPOTIFY_GREEN } from './constants.js';
3
+ export const CHAT_USER_MARKER_COLOR = "#808791";
4
+ export const CHAT_SYSTEM_MARKER_COLOR = "#c8a6ff";
5
+ export const CHAT_WARNING_MARKER_COLOR = "#d4a72c";
6
+ export const CHAT_ERROR_MARKER_COLOR = "#ef4444";
7
+ export const CHAT_MESSAGE_TEXT_COLOR = "#ffffff";
8
+ export function wrapChatMessageContent(content, width) {
9
+ const boundedWidth = Math.max(1, Math.floor(width));
10
+ const physicalLines = [];
11
+ for (const logicalLine of content.split("\n")) {
12
+ if (logicalLine.length === 0) {
13
+ physicalLines.push("");
14
+ continue;
15
+ }
16
+ let line = "";
17
+ let lineWidth = 0;
18
+ for (const char of Array.from(logicalLine)) {
19
+ const charWidth = stringWidth(char);
20
+ if (line.length > 0 && lineWidth + charWidth > boundedWidth) {
21
+ physicalLines.push(line);
22
+ line = "";
23
+ lineWidth = 0;
24
+ }
25
+ if (line.length === 0 && charWidth > boundedWidth) {
26
+ physicalLines.push(char);
27
+ continue;
28
+ }
29
+ line += char;
30
+ lineWidth += charWidth;
31
+ }
32
+ if (line.length > 0) {
33
+ physicalLines.push(line);
34
+ }
35
+ }
36
+ return physicalLines.length > 0 ? physicalLines : [""];
37
+ }
38
+ export function wrapChatMessageSegments(segments, width) {
39
+ const boundedWidth = Math.max(1, Math.floor(width));
40
+ const lines = [[]];
41
+ let lineWidth = 0;
42
+ const append = (text, style) => {
43
+ if (!text)
44
+ return;
45
+ const line = lines[lines.length - 1];
46
+ const previous = line[line.length - 1];
47
+ if (previous?.style === style) {
48
+ previous.text += text;
49
+ }
50
+ else {
51
+ line.push({ text, style });
52
+ }
53
+ };
54
+ for (const segment of segments) {
55
+ for (const character of Array.from(segment.text)) {
56
+ if (character === "\n") {
57
+ lines.push([]);
58
+ lineWidth = 0;
59
+ continue;
60
+ }
61
+ const characterWidth = stringWidth(character);
62
+ if (lineWidth > 0 && lineWidth + characterWidth > boundedWidth) {
63
+ lines.push([]);
64
+ lineWidth = 0;
65
+ }
66
+ append(character, segment.style);
67
+ lineWidth += characterWidth;
68
+ }
69
+ }
70
+ return lines.length > 0 ? lines : [[]];
71
+ }
72
+ export function resolveChatMarkerColor(role, theme, tone) {
73
+ if (role === "user")
74
+ return CHAT_USER_MARKER_COLOR;
75
+ if (tone === "error")
76
+ return CHAT_ERROR_MARKER_COLOR;
77
+ if (tone === "warning")
78
+ return CHAT_WARNING_MARKER_COLOR;
79
+ if (tone === "system")
80
+ return CHAT_SYSTEM_MARKER_COLOR;
81
+ if (theme === "spotify")
82
+ return SPOTIFY_GREEN;
83
+ return BORDER_BLUE;
84
+ }
85
+ export function resolveChatContentColor(role, tone) {
86
+ if (role === "user")
87
+ return CHAT_MESSAGE_TEXT_COLOR;
88
+ if (tone === "error")
89
+ return CHAT_ERROR_MARKER_COLOR;
90
+ if (tone === "warning")
91
+ return CHAT_WARNING_MARKER_COLOR;
92
+ return CHAT_MESSAGE_TEXT_COLOR;
93
+ }
@@ -0,0 +1,18 @@
1
+ import { BORDER_BLUE, SPOTIFY_GREEN, TOOL_NAVY } from './constants.js';
2
+ const DEFAULT_AGENT_CHAT_THEME = {
3
+ accent: BORDER_BLUE,
4
+ strongText: BORDER_BLUE,
5
+ highlightBackground: TOOL_NAVY,
6
+ codeBackground: '#252933',
7
+ linkText: '#9fd9ff',
8
+ };
9
+ const SPOTIFY_AGENT_CHAT_THEME = {
10
+ accent: SPOTIFY_GREEN,
11
+ strongText: SPOTIFY_GREEN,
12
+ highlightBackground: '#0b3d20',
13
+ codeBackground: '#252933',
14
+ linkText: '#73d998',
15
+ };
16
+ export function resolveAgentChatTheme(theme) {
17
+ return theme === 'spotify' ? SPOTIFY_AGENT_CHAT_THEME : DEFAULT_AGENT_CHAT_THEME;
18
+ }
@@ -0,0 +1,104 @@
1
+ import { MAX_CHAT_ITEMS, MIN_CHAT_VIEWPORT_ROWS } from './constants.js';
2
+ import { wrapChatMessageContent } from './chat-message.js';
3
+ /**
4
+ * Coordinates the trim list operation for the CLI UI runtime.
5
+ *
6
+ * @param items Input value used by the trim list operation.
7
+ * @param limit Input value used by the trim list operation.
8
+ * @returns The computed result for the surrounding CLI UI flow.
9
+ */
10
+ export function trimList(items, limit) {
11
+ return items.slice(Math.max(0, items.length - limit));
12
+ }
13
+ /**
14
+ * Coordinates the clamp operation for the CLI UI runtime.
15
+ *
16
+ * @param value Input value used by the clamp operation.
17
+ * @param min Input value used by the clamp operation.
18
+ * @param max Input value used by the clamp operation.
19
+ * @returns The computed result for the surrounding CLI UI flow.
20
+ */
21
+ export function clamp(value, min, max) {
22
+ return Math.min(max, Math.max(min, value));
23
+ }
24
+ export function appendChatTimelineItems(state, appendedItems, limit) {
25
+ if (appendedItems.length === 0)
26
+ return state;
27
+ const items = trimList([...state.items, ...appendedItems], limit);
28
+ const scrollOffset = state.scrollOffset > 0
29
+ ? clamp(state.scrollOffset + appendedItems.length, 0, Math.max(0, items.length - 1))
30
+ : 0;
31
+ return { items, scrollOffset };
32
+ }
33
+ export function chatTimelineReducer(state, action) {
34
+ switch (action.type) {
35
+ case "append":
36
+ return appendChatTimelineItems(state, action.items, MAX_CHAT_ITEMS);
37
+ case "scroll":
38
+ return {
39
+ ...state,
40
+ scrollOffset: clamp(state.scrollOffset + action.delta, 0, action.maxScrollOffset),
41
+ };
42
+ case "clamp":
43
+ return {
44
+ ...state,
45
+ scrollOffset: clamp(state.scrollOffset, 0, action.maxScrollOffset),
46
+ };
47
+ case "resetScroll":
48
+ return { ...state, scrollOffset: 0 };
49
+ }
50
+ }
51
+ /**
52
+ * Coordinates the estimate chat item rows operation for the CLI UI runtime.
53
+ *
54
+ * @param item Input value used by the estimate chat item rows operation.
55
+ * @returns The computed result for the surrounding CLI UI flow.
56
+ */
57
+ function estimateChatItemRows(item, wrapWidth, headerVariant) {
58
+ if (item.type === "info_banner") {
59
+ return headerVariant === "compact" ? 6 : 9;
60
+ }
61
+ return wrapChatMessageContent(item.content, wrapWidth).length + 2;
62
+ }
63
+ export function getChatContentRows(items, wrapWidth, headerVariant = "full") {
64
+ return items.reduce((rows, item) => rows + estimateChatItemRows(item, wrapWidth, headerVariant), 0);
65
+ }
66
+ /**
67
+ * Coordinates the get visible chat window operation for the CLI UI runtime.
68
+ *
69
+ * @param items Input value used by the get visible chat window operation.
70
+ * @param viewportRows Input value used by the get visible chat window operation.
71
+ * @param scrollOffset Input value used by the get visible chat window operation.
72
+ * @returns The computed result for the surrounding CLI UI flow.
73
+ */
74
+ export function getVisibleChatWindow(items, viewportRows, scrollOffset, wrapWidth, headerVariant = "full") {
75
+ if (items.length === 0) {
76
+ return { items: [], hasHiddenAbove: false, hasHiddenBelow: false, maxScrollOffset: 0 };
77
+ }
78
+ const contentRows = Math.max(MIN_CHAT_VIEWPORT_ROWS, viewportRows);
79
+ const totalRows = getChatContentRows(items, wrapWidth, headerVariant);
80
+ const maxScrollOffset = totalRows > contentRows
81
+ ? Math.max(0, items.length - 1)
82
+ : 0;
83
+ const boundedOffset = clamp(scrollOffset, 0, maxScrollOffset);
84
+ const endExclusive = Math.max(1, items.length - boundedOffset);
85
+ let startIndex = endExclusive;
86
+ let usedRows = 0;
87
+ while (startIndex > 0) {
88
+ const nextRows = estimateChatItemRows(items[startIndex - 1], wrapWidth, headerVariant);
89
+ if (usedRows > 0 && usedRows + nextRows > contentRows) {
90
+ break;
91
+ }
92
+ usedRows += nextRows;
93
+ startIndex -= 1;
94
+ if (usedRows >= contentRows) {
95
+ break;
96
+ }
97
+ }
98
+ return {
99
+ items: items.slice(startIndex, endExclusive),
100
+ hasHiddenAbove: startIndex > 0,
101
+ hasHiddenBelow: endExclusive < items.length,
102
+ maxScrollOffset,
103
+ };
104
+ }
@@ -0,0 +1,45 @@
1
+ export const HELP_PANEL_VISIBLE_COMMANDS = 8;
2
+ /**
3
+ * Coordinates the visible command window operation for the CLI UI runtime.
4
+ *
5
+ * @param commands Input value used by the visible command window operation.
6
+ * @param selectedIndex Input value used by the visible command window operation.
7
+ * @param visibleLimit Input value used by the visible command window operation.
8
+ * @returns The computed result for the surrounding CLI UI flow.
9
+ */
10
+ export function visibleCommandWindow(commands, selectedIndex, visibleLimit) {
11
+ if (commands.length === 0) {
12
+ return { items: [], boundedIndex: 0, startIndex: 0 };
13
+ }
14
+ const boundedIndex = Math.min(Math.max(selectedIndex, 0), commands.length - 1);
15
+ const limit = Math.min(Math.max(1, visibleLimit), commands.length);
16
+ const maxStart = Math.max(0, commands.length - limit);
17
+ const startIndex = Math.min(Math.max(0, boundedIndex - limit + 1), maxStart);
18
+ return {
19
+ items: commands.slice(startIndex, startIndex + limit),
20
+ boundedIndex,
21
+ startIndex,
22
+ };
23
+ }
24
+ export function selectedHelpPanelCommand(commands, selectedIndex) {
25
+ if (commands.length === 0)
26
+ return null;
27
+ const boundedIndex = Math.min(Math.max(selectedIndex, 0), commands.length - 1);
28
+ return commands[boundedIndex] ?? null;
29
+ }
30
+ /**
31
+ * Coordinates the help panel commands operation for the CLI UI runtime.
32
+ *
33
+ * @param commands Input value used by the help panel commands operation.
34
+ * @returns The computed result for the surrounding CLI UI flow.
35
+ */
36
+ export function helpPanelCommands(commands) {
37
+ return [...commands]
38
+ .filter((command) => command.enabled !== false)
39
+ .sort((a, b) => a.name.localeCompare(b.name))
40
+ .map((command) => ({
41
+ name: command.name,
42
+ usage: command.usage,
43
+ description: command.description,
44
+ }));
45
+ }
@@ -0,0 +1,70 @@
1
+ import { SLASH_COMMANDS } from './constants.js';
2
+ import { localizeSlashCommands } from './i18n.js';
3
+ export const SPOTIFY_MODE_COMMAND_NAMES = ["bye", "extension", "exit", "info", "lang", "login", "logout", "memory", "model", "playlist", "queue", "random", "recommend", "settings", "spotify"];
4
+ const SPOTIFY_MODE_COMMANDS = SPOTIFY_MODE_COMMAND_NAMES.map((name) => (SLASH_COMMANDS.find((command) => command.name === name))).filter((command) => Boolean(command?.enabled !== false));
5
+ function commandSuggestionsFrom(commands, input, language) {
6
+ const trimmed = input.trimStart();
7
+ if (!trimmed.startsWith("/"))
8
+ return [];
9
+ const token = trimmed.slice(1).split(/\s+/, 1)[0]?.toLowerCase() ?? "";
10
+ return localizeSlashCommands(commands.filter((command) => command.enabled !== false && command.name.startsWith(token)), language);
11
+ }
12
+ /**
13
+ * Coordinates the slash command suggestions operation for the CLI UI runtime.
14
+ *
15
+ * @param input Input value used by the slash command suggestions operation.
16
+ * @returns The computed result for the surrounding CLI UI flow.
17
+ */
18
+ export function slashCommandSuggestions(input, language = "en") {
19
+ return commandSuggestionsFrom(SLASH_COMMANDS, input, language);
20
+ }
21
+ export function spotifyModeSlashCommands(input = "/", language = "en") {
22
+ return commandSuggestionsFrom(SPOTIFY_MODE_COMMANDS, input, language);
23
+ }
24
+ /**
25
+ * Coordinates the slash command token operation for the CLI UI runtime.
26
+ *
27
+ * @param input Input value used by the slash command token operation.
28
+ * @returns The computed result for the surrounding CLI UI flow.
29
+ */
30
+ export function slashCommandToken(input) {
31
+ const trimmed = input.trimStart();
32
+ if (!trimmed.startsWith("/"))
33
+ return "";
34
+ return trimmed.slice(1).split(/\s+/, 1)[0]?.toLowerCase() ?? "";
35
+ }
36
+ /**
37
+ * Coordinates the matching slash command operation for the CLI UI runtime.
38
+ *
39
+ * @param input Input value used by the matching slash command operation.
40
+ * @returns The computed result for the surrounding CLI UI flow.
41
+ */
42
+ export function matchingSlashCommand(input) {
43
+ const token = slashCommandToken(input);
44
+ return SLASH_COMMANDS.find((command) => command.enabled !== false
45
+ && (command.name === token || command.aliases?.includes(token)));
46
+ }
47
+ /**
48
+ * Coordinates the has slash command arguments operation for the CLI UI runtime.
49
+ *
50
+ * @param input Input value used by the has slash command arguments operation.
51
+ * @returns The computed result for the surrounding CLI UI flow.
52
+ */
53
+ export function hasSlashCommandArguments(input) {
54
+ const trimmed = input.trimStart();
55
+ const spaceIndex = trimmed.indexOf(" ");
56
+ return spaceIndex !== -1 && trimmed.slice(spaceIndex + 1).trim().length > 0;
57
+ }
58
+ /**
59
+ * Coordinates the complete slash command operation for the CLI UI runtime.
60
+ *
61
+ * @param command Input value used by the complete slash command operation.
62
+ * @returns The computed result for the surrounding CLI UI flow.
63
+ */
64
+ export function completeSlashCommand(command) {
65
+ return command.needsArgument ? `/${command.name} ` : `/${command.name}`;
66
+ }
67
+ export function unknownSlashCommandMessage(input) {
68
+ const command = input.trimStart().split(/\s+/, 1)[0] || "/";
69
+ return `Unknown command: ${command}. Type /help to view available commands.`;
70
+ }