auq-mcp-server 2.2.2 → 2.4.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.
Files changed (62) hide show
  1. package/README.md +82 -0
  2. package/dist/bin/auq.js +45 -39
  3. package/dist/bin/tui-app.js +78 -8
  4. package/dist/package.json +1 -1
  5. package/dist/src/__tests__/server.abort.test.js +214 -0
  6. package/dist/src/cli/commands/__tests__/answer.test.js +199 -0
  7. package/dist/src/cli/commands/__tests__/config.test.js +218 -0
  8. package/dist/src/cli/commands/__tests__/sessions.test.js +282 -0
  9. package/dist/src/cli/commands/answer.js +128 -0
  10. package/dist/src/cli/commands/config.js +263 -0
  11. package/dist/src/cli/commands/sessions.js +164 -0
  12. package/dist/src/cli/utils.js +95 -0
  13. package/dist/src/config/__tests__/ConfigLoader.test.js +41 -0
  14. package/dist/src/config/defaults.js +3 -0
  15. package/dist/src/config/types.js +4 -0
  16. package/dist/src/core/ask-user-questions.js +3 -2
  17. package/dist/src/i18n/locales/en.js +8 -1
  18. package/dist/src/i18n/locales/ko.js +8 -1
  19. package/dist/src/server.js +64 -11
  20. package/dist/src/session/SessionManager.js +69 -4
  21. package/dist/src/session/__tests__/SessionManager.test.js +65 -0
  22. package/dist/src/tui/ThemeProvider.js +2 -1
  23. package/dist/src/tui/__tests__/session-watcher.test.js +109 -0
  24. package/dist/src/tui/components/ConfirmationDialog.js +5 -4
  25. package/dist/src/tui/components/Footer.js +24 -23
  26. package/dist/src/tui/components/ReviewScreen.js +2 -1
  27. package/dist/src/tui/components/SessionDots.js +33 -4
  28. package/dist/src/tui/components/SessionPicker.js +27 -18
  29. package/dist/src/tui/components/Spinner.js +19 -0
  30. package/dist/src/tui/components/StepperView.js +71 -7
  31. package/dist/src/tui/components/WaitingScreen.js +2 -1
  32. package/dist/src/tui/components/__tests__/ConfirmationDialog.test.js +134 -0
  33. package/dist/src/tui/components/__tests__/Footer.test.js +121 -0
  34. package/dist/src/tui/components/__tests__/ReviewScreen.test.js +89 -0
  35. package/dist/src/tui/components/__tests__/SessionDots.test.js +160 -1
  36. package/dist/src/tui/components/__tests__/SessionPicker.test.js +43 -1
  37. package/dist/src/tui/components/__tests__/StepperView.abandoned.test.js +160 -0
  38. package/dist/src/tui/components/__tests__/StepperView.keyboard.test.js +135 -0
  39. package/dist/src/tui/components/__tests__/StepperView.state.test.js +1 -0
  40. package/dist/src/tui/components/__tests__/WaitingScreen.test.js +60 -0
  41. package/dist/src/tui/constants/keybindings.js +40 -0
  42. package/dist/src/tui/session-watcher.js +50 -0
  43. package/dist/src/tui/themes/catppuccin-latte.js +7 -0
  44. package/dist/src/tui/themes/catppuccin-mocha.js +7 -0
  45. package/dist/src/tui/themes/dark.js +7 -0
  46. package/dist/src/tui/themes/dracula.js +7 -0
  47. package/dist/src/tui/themes/github-dark.js +7 -0
  48. package/dist/src/tui/themes/github-light.js +7 -0
  49. package/dist/src/tui/themes/gruvbox-dark.js +7 -0
  50. package/dist/src/tui/themes/gruvbox-light.js +7 -0
  51. package/dist/src/tui/themes/light.js +7 -0
  52. package/dist/src/tui/themes/monokai.js +7 -0
  53. package/dist/src/tui/themes/nord.js +7 -0
  54. package/dist/src/tui/themes/one-dark.js +7 -0
  55. package/dist/src/tui/themes/rose-pine.js +7 -0
  56. package/dist/src/tui/themes/solarized-dark.js +7 -0
  57. package/dist/src/tui/themes/solarized-light.js +7 -0
  58. package/dist/src/tui/themes/tokyo-night.js +7 -0
  59. package/dist/src/tui/utils/__tests__/detectTheme.test.js +78 -0
  60. package/dist/src/tui/utils/__tests__/staleDetection.test.js +118 -0
  61. package/dist/src/tui/utils/staleDetection.js +51 -0
  62. package/package.json +1 -1
@@ -0,0 +1,160 @@
1
+ import React from "react";
2
+ import { cleanup, render } from "ink-testing-library";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import { ConfigProvider } from "../../ConfigContext.js";
5
+ import { ThemeContext } from "../../ThemeContext.js";
6
+ import { darkTheme } from "../../themes/dark.js";
7
+ import { StepperView } from "../StepperView.js";
8
+ const mockThemeValue = {
9
+ theme: darkTheme,
10
+ themeName: "AUQ dark",
11
+ cycleTheme: () => { },
12
+ };
13
+ const sessionRequest = {
14
+ sessionId: "abandoned-test-session",
15
+ status: "pending",
16
+ timestamp: new Date().toISOString(),
17
+ callId: "call-abandoned-1",
18
+ questions: [
19
+ {
20
+ title: "Language",
21
+ prompt: "Pick a language",
22
+ options: [{ label: "TypeScript" }, { label: "Python" }],
23
+ },
24
+ {
25
+ title: "Framework",
26
+ prompt: "Pick a framework",
27
+ options: [{ label: "React" }, { label: "Vue" }],
28
+ },
29
+ ],
30
+ };
31
+ function renderStepper(props = {}) {
32
+ return render(React.createElement(ThemeContext.Provider, { value: mockThemeValue },
33
+ React.createElement(ConfigProvider, null,
34
+ React.createElement(StepperView, { sessionId: sessionRequest.sessionId, sessionRequest: sessionRequest, ...props }))));
35
+ }
36
+ function getOutput(frame) {
37
+ return (frame ?? "").replace(/\x1b\[[0-9;]*m/g, "").replace(/\r/g, "");
38
+ }
39
+ afterEach(() => {
40
+ cleanup();
41
+ vi.restoreAllMocks();
42
+ });
43
+ describe("StepperView abandoned session confirmation", () => {
44
+ it("shows abandoned confirmation dialog when isAbandoned is true", async () => {
45
+ const instance = renderStepper({ isAbandoned: true });
46
+ await vi.waitFor(() => {
47
+ const output = getOutput(instance.lastFrame());
48
+ expect(output).toContain("AI Disconnected");
49
+ });
50
+ const output = getOutput(instance.lastFrame());
51
+ expect(output).toContain("Answer anyway");
52
+ expect(output).toContain("Cancel");
53
+ // Should NOT show question content behind the dialog
54
+ expect(output).not.toContain("Pick a language");
55
+ });
56
+ it("does not show abandoned dialog when isAbandoned is false", () => {
57
+ const instance = renderStepper({ isAbandoned: false });
58
+ const output = getOutput(instance.lastFrame());
59
+ expect(output).not.toContain("AI Disconnected");
60
+ expect(output).toContain("Pick a language");
61
+ });
62
+ it("does not show abandoned dialog when isAbandoned is undefined", () => {
63
+ const instance = renderStepper();
64
+ const output = getOutput(instance.lastFrame());
65
+ expect(output).not.toContain("AI Disconnected");
66
+ expect(output).toContain("Pick a language");
67
+ });
68
+ it("selecting 'Answer anyway' dismisses dialog and shows questions", async () => {
69
+ const instance = renderStepper({ isAbandoned: true });
70
+ await vi.waitFor(() => {
71
+ const output = getOutput(instance.lastFrame());
72
+ expect(output).toContain("AI Disconnected");
73
+ });
74
+ // Press Enter to select the first option ("Answer anyway")
75
+ instance.stdin.write("\r");
76
+ await vi.waitFor(() => {
77
+ const output = getOutput(instance.lastFrame());
78
+ expect(output).toContain("Pick a language");
79
+ });
80
+ const output = getOutput(instance.lastFrame());
81
+ expect(output).not.toContain("AI Disconnected");
82
+ });
83
+ it("selecting 'Cancel' calls onAbandonedCancel", async () => {
84
+ const onAbandonedCancel = vi.fn();
85
+ const instance = renderStepper({
86
+ isAbandoned: true,
87
+ onAbandonedCancel,
88
+ });
89
+ await vi.waitFor(() => {
90
+ const output = getOutput(instance.lastFrame());
91
+ expect(output).toContain("AI Disconnected");
92
+ });
93
+ // Navigate down to "Cancel" then press Enter
94
+ // Use OA/OB sequences which ink reliably parses as arrows
95
+ instance.stdin.write("\x1bOB"); // Down arrow (application mode)
96
+ await new Promise((r) => setTimeout(r, 100));
97
+ // Press Enter to select "Cancel"
98
+ instance.stdin.write("\r");
99
+ await vi.waitFor(() => {
100
+ expect(onAbandonedCancel).toHaveBeenCalledOnce();
101
+ });
102
+ });
103
+ it("pressing Escape calls onAbandonedCancel", async () => {
104
+ const onAbandonedCancel = vi.fn();
105
+ const instance = renderStepper({
106
+ isAbandoned: true,
107
+ onAbandonedCancel,
108
+ });
109
+ await vi.waitFor(() => {
110
+ const output = getOutput(instance.lastFrame());
111
+ expect(output).toContain("AI Disconnected");
112
+ });
113
+ // Press Escape
114
+ instance.stdin.write("\x1b");
115
+ await vi.waitFor(() => {
116
+ expect(onAbandonedCancel).toHaveBeenCalledOnce();
117
+ });
118
+ });
119
+ it("after confirming, dialog does not reappear for the same session", async () => {
120
+ const instance = renderStepper({ isAbandoned: true });
121
+ // Wait for dialog
122
+ await vi.waitFor(() => {
123
+ expect(getOutput(instance.lastFrame())).toContain("AI Disconnected");
124
+ });
125
+ // Confirm ("Answer anyway")
126
+ instance.stdin.write("\r");
127
+ // Wait for questions to appear
128
+ await vi.waitFor(() => {
129
+ expect(getOutput(instance.lastFrame())).toContain("Pick a language");
130
+ });
131
+ // The dialog should stay dismissed - verify questions are still shown
132
+ const output = getOutput(instance.lastFrame());
133
+ expect(output).toContain("Pick a language");
134
+ expect(output).not.toContain("AI Disconnected");
135
+ });
136
+ it("emits showAbandonedConfirm in onFlowStateChange", async () => {
137
+ const onFlowStateChange = vi.fn();
138
+ renderStepper({
139
+ isAbandoned: true,
140
+ onFlowStateChange,
141
+ });
142
+ await vi.waitFor(() => {
143
+ expect(onFlowStateChange).toHaveBeenCalledWith(expect.objectContaining({ showAbandonedConfirm: true }));
144
+ });
145
+ });
146
+ it("blocks keyboard navigation while dialog is shown", async () => {
147
+ const instance = renderStepper({ isAbandoned: true });
148
+ await vi.waitFor(() => {
149
+ expect(getOutput(instance.lastFrame())).toContain("AI Disconnected");
150
+ });
151
+ // Try Tab to navigate questions - should do nothing
152
+ instance.stdin.write("\t");
153
+ // Dialog should still be shown
154
+ await vi.waitFor(() => {
155
+ const output = getOutput(instance.lastFrame());
156
+ expect(output).toContain("AI Disconnected");
157
+ expect(output).not.toContain("Pick a language");
158
+ });
159
+ });
160
+ });
@@ -0,0 +1,135 @@
1
+ import React from "react";
2
+ import { cleanup, render } from "ink-testing-library";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ import { ConfigProvider } from "../../ConfigContext.js";
5
+ import { ThemeContext } from "../../ThemeContext.js";
6
+ import { darkTheme } from "../../themes/dark.js";
7
+ import { StepperView } from "../StepperView.js";
8
+ const mockThemeValue = {
9
+ theme: darkTheme,
10
+ themeName: "AUQ dark",
11
+ cycleTheme: () => { },
12
+ };
13
+ const sessionRequest = {
14
+ sessionId: "kbd-test-session",
15
+ status: "pending",
16
+ timestamp: new Date().toISOString(),
17
+ callId: "call-kbd-1",
18
+ questions: [
19
+ {
20
+ title: "Q1",
21
+ prompt: "First question?",
22
+ options: [{ label: "A" }, { label: "B" }],
23
+ multiSelect: false,
24
+ },
25
+ {
26
+ title: "Q2",
27
+ prompt: "Second question?",
28
+ options: [{ label: "C" }, { label: "D" }],
29
+ multiSelect: false,
30
+ },
31
+ {
32
+ title: "Q3",
33
+ prompt: "Third question?",
34
+ options: [{ label: "E" }, { label: "F" }],
35
+ multiSelect: false,
36
+ },
37
+ ],
38
+ };
39
+ function renderStepper(props = {}) {
40
+ return render(React.createElement(ThemeContext.Provider, { value: mockThemeValue },
41
+ React.createElement(ConfigProvider, null,
42
+ React.createElement(StepperView, { sessionId: sessionRequest.sessionId, sessionRequest: sessionRequest, ...props }))));
43
+ }
44
+ function getOutput(frame) {
45
+ return (frame ?? "").replace(/\x1b\[[0-9;]*m/g, "").replace(/\r/g, "");
46
+ }
47
+ afterEach(() => {
48
+ cleanup();
49
+ vi.restoreAllMocks();
50
+ });
51
+ describe("StepperView keyboard shortcuts", () => {
52
+ it("Escape shows rejection confirmation dialog", async () => {
53
+ const instance = renderStepper();
54
+ // Verify we start on question 1
55
+ let output = getOutput(instance.lastFrame());
56
+ expect(output).toContain("First question?");
57
+ // Press Escape
58
+ instance.stdin.write("\x1b");
59
+ await vi.waitFor(() => {
60
+ const out = getOutput(instance.lastFrame());
61
+ expect(out).toContain("Reject");
62
+ });
63
+ });
64
+ it("Tab navigates to next question", async () => {
65
+ const instance = renderStepper();
66
+ // Verify starting on question 1
67
+ let output = getOutput(instance.lastFrame());
68
+ expect(output).toContain("First question?");
69
+ // Press Tab to advance to Q2
70
+ instance.stdin.write("\t");
71
+ await vi.waitFor(() => {
72
+ const out = getOutput(instance.lastFrame());
73
+ expect(out).toContain("Second question?");
74
+ });
75
+ });
76
+ it("Shift+Tab navigates to previous question", async () => {
77
+ const onStateSnapshot = vi.fn();
78
+ const instance = renderStepper({ onStateSnapshot });
79
+ // First advance to Q2 with Tab
80
+ instance.stdin.write("\t");
81
+ await vi.waitFor(() => {
82
+ const out = getOutput(instance.lastFrame());
83
+ expect(out).toContain("Second question?");
84
+ });
85
+ // Press Shift+Tab (escape sequence for shift-tab in ink)
86
+ instance.stdin.write("\x1b[Z");
87
+ await vi.waitFor(() => {
88
+ const out = getOutput(instance.lastFrame());
89
+ expect(out).toContain("First question?");
90
+ });
91
+ });
92
+ it("Tab does not navigate past the last question", async () => {
93
+ const instance = renderStepper();
94
+ // Navigate to Q3 (last question)
95
+ instance.stdin.write("\t"); // Q1 -> Q2
96
+ await vi.waitFor(() => {
97
+ const out = getOutput(instance.lastFrame());
98
+ expect(out).toContain("Second question?");
99
+ });
100
+ instance.stdin.write("\t"); // Q2 -> Q3
101
+ await vi.waitFor(() => {
102
+ const out = getOutput(instance.lastFrame());
103
+ expect(out).toContain("Third question?");
104
+ });
105
+ // Tab again should stay on Q3 (clamped)
106
+ instance.stdin.write("\t");
107
+ await vi.waitFor(() => {
108
+ const out = getOutput(instance.lastFrame());
109
+ expect(out).toContain("Third question?");
110
+ });
111
+ });
112
+ it("Shift+Tab does not navigate before the first question", async () => {
113
+ const instance = renderStepper();
114
+ // Verify on Q1
115
+ let output = getOutput(instance.lastFrame());
116
+ expect(output).toContain("First question?");
117
+ // Press Shift+Tab at Q1 — should remain on Q1
118
+ instance.stdin.write("\x1b[Z");
119
+ // Allow state to settle
120
+ await vi.waitFor(() => {
121
+ const out = getOutput(instance.lastFrame());
122
+ expect(out).toContain("First question?");
123
+ });
124
+ });
125
+ it("emits onProgress when Tab advances question", async () => {
126
+ const onProgress = vi.fn();
127
+ const instance = renderStepper({ onProgress });
128
+ // Tab to advance from Q1 to Q2
129
+ instance.stdin.write("\t");
130
+ await vi.waitFor(() => {
131
+ // onProgress should be called with (answered=1, total=3)
132
+ expect(onProgress).toHaveBeenCalledWith(1, 3);
133
+ });
134
+ });
135
+ });
@@ -96,6 +96,7 @@ describe("StepperView SessionUIState boundary", () => {
96
96
  expect(onFlowStateChange).toHaveBeenCalledWith({
97
97
  showReview: false,
98
98
  showRejectionConfirm: false,
99
+ showAbandonedConfirm: false,
99
100
  });
100
101
  });
101
102
  });
@@ -0,0 +1,60 @@
1
+ import React from "react";
2
+ import { cleanup, render } from "ink-testing-library";
3
+ import { afterEach, describe, expect, it, vi } from "vitest";
4
+ const inputState = vi.hoisted(() => ({
5
+ handler: null,
6
+ }));
7
+ vi.mock("ink", async () => {
8
+ const actual = await vi.importActual("ink");
9
+ return {
10
+ ...actual,
11
+ useInput: (handler, options) => {
12
+ // WaitingScreen calls useInput without options, so always capture
13
+ if (!options || options.isActive !== false) {
14
+ inputState.handler = handler;
15
+ }
16
+ },
17
+ };
18
+ });
19
+ // Mock AnimatedGradient to avoid timer/animation side effects
20
+ vi.mock("../AnimatedGradient.js", () => ({
21
+ AnimatedGradient: ({ text }) => React.createElement("ink-text", null, text),
22
+ }));
23
+ import { ThemeContext } from "../../ThemeContext.js";
24
+ import { darkTheme } from "../../themes/dark.js";
25
+ import { WaitingScreen } from "../WaitingScreen.js";
26
+ const mockThemeValue = {
27
+ theme: darkTheme,
28
+ themeName: "AUQ dark",
29
+ cycleTheme: () => { },
30
+ };
31
+ function renderWithTheme(ui) {
32
+ return render(React.createElement(ThemeContext.Provider, { value: mockThemeValue }, ui));
33
+ }
34
+ afterEach(() => {
35
+ cleanup();
36
+ inputState.handler = null;
37
+ vi.restoreAllMocks();
38
+ });
39
+ describe("WaitingScreen keyboard handling", () => {
40
+ it("q key triggers quit", async () => {
41
+ const exitSpy = vi
42
+ .spyOn(process, "exit")
43
+ .mockImplementation((() => { }));
44
+ renderWithTheme(React.createElement(WaitingScreen, { queueCount: 0 }));
45
+ expect(inputState.handler).not.toBeNull();
46
+ inputState.handler("q", {});
47
+ await Promise.resolve();
48
+ expect(exitSpy).toHaveBeenCalledWith(0);
49
+ });
50
+ it("Q key (uppercase) triggers quit (case-insensitive)", async () => {
51
+ const exitSpy = vi
52
+ .spyOn(process, "exit")
53
+ .mockImplementation((() => { }));
54
+ renderWithTheme(React.createElement(WaitingScreen, { queueCount: 0 }));
55
+ expect(inputState.handler).not.toBeNull();
56
+ inputState.handler("Q", {});
57
+ await Promise.resolve();
58
+ expect(exitSpy).toHaveBeenCalledWith(0);
59
+ });
60
+ });
@@ -0,0 +1,40 @@
1
+ // Keybinding constants for the TUI application
2
+ // All keyboard shortcut definitions are centralized here.
3
+ export const KEYS = {
4
+ // Session switching (bare keys, no Ctrl)
5
+ SESSION_NEXT: "]",
6
+ SESSION_PREV: "[",
7
+ SESSION_JUMP_MIN: 1,
8
+ SESSION_JUMP_MAX: 9,
9
+ // Question navigation
10
+ RECOMMEND: "r",
11
+ QUICK_SUBMIT: "r", // used with key.ctrl
12
+ // Theme
13
+ THEME_CYCLE: "t", // used with key.ctrl
14
+ // Confirmation shortcuts
15
+ CONFIRM_YES: /^[yY]$/,
16
+ CONFIRM_NO: /^[nN]$/,
17
+ // Review
18
+ GO_BACK: /^[nN]$/,
19
+ // Waiting
20
+ QUIT: /^[qQ]$/,
21
+ };
22
+ // Display labels for Footer keybinding hints
23
+ export const KEY_LABELS = {
24
+ SESSION_SWITCH: "]/[",
25
+ SESSION_LIST: "Ctrl+S",
26
+ QUICK_SUBMIT: "Ctrl+R",
27
+ RECOMMEND: "R",
28
+ THEME: "Ctrl+T",
29
+ NAVIGATE_QUESTIONS: "←→",
30
+ NAVIGATE_QUESTIONS_TAB: "Tab/S+Tab",
31
+ NAVIGATE_OPTIONS: "↑↓",
32
+ SELECT: "Space",
33
+ SELECT_NEXT: "Enter",
34
+ NEXT: "Enter",
35
+ CURSOR: "←→",
36
+ NEWLINE: "Enter",
37
+ REJECT: "Esc",
38
+ BACK: "n",
39
+ SUBMIT: "Enter",
40
+ };
@@ -72,6 +72,56 @@ export class EnhancedTUISessionWatcher extends TUISessionWatcher {
72
72
  return [];
73
73
  }
74
74
  }
75
+ /**
76
+ * Get pending and abandoned sessions with full status metadata.
77
+ * Unlike getPendingSessions(), this includes abandoned sessions
78
+ * and returns status + createdAt for stale detection.
79
+ */
80
+ async getPendingSessionsWithStatus() {
81
+ const fs = await import("fs/promises");
82
+ const { join } = await import("path");
83
+ try {
84
+ const sessionDir = this.watchedPath;
85
+ const entries = await fs.readdir(sessionDir, { withFileTypes: true });
86
+ const results = [];
87
+ for (const entry of entries) {
88
+ if (!entry.isDirectory())
89
+ continue;
90
+ const sessionPath = join(sessionDir, entry.name);
91
+ const answersPath = join(sessionPath, SESSION_FILES.ANSWERS);
92
+ const statusPath = join(sessionPath, SESSION_FILES.STATUS);
93
+ try {
94
+ // Skip sessions that already have answers
95
+ await fs.access(answersPath);
96
+ }
97
+ catch {
98
+ // No answers file — read status.json
99
+ try {
100
+ const statusContent = await fs.readFile(statusPath, "utf-8");
101
+ const status = JSON.parse(statusContent);
102
+ // Include pending, in-progress, AND abandoned sessions
103
+ if (status.status === "pending" ||
104
+ status.status === "in-progress" ||
105
+ status.status === "abandoned") {
106
+ results.push({
107
+ createdAt: status.createdAt,
108
+ sessionId: entry.name,
109
+ status: status.status,
110
+ });
111
+ }
112
+ }
113
+ catch {
114
+ // No valid status file — skip
115
+ }
116
+ }
117
+ }
118
+ return results.sort((a, b) => a.sessionId.localeCompare(b.sessionId));
119
+ }
120
+ catch (error) {
121
+ console.warn("Failed to scan for pending sessions with status:", error);
122
+ return [];
123
+ }
124
+ }
75
125
  /**
76
126
  * Get session request data for a specific session
77
127
  */
@@ -91,6 +91,7 @@ export const catppuccinLatteTheme = {
91
91
  successPillBg: "#dce0e8",
92
92
  error: "#d20f39",
93
93
  info: "#1e66f5",
94
+ warning: "#df8e1d",
94
95
  border: "#ccd0da",
95
96
  },
96
97
  markdown: {
@@ -105,6 +106,8 @@ export const catppuccinLatteTheme = {
105
106
  untouched: "#acb0c0",
106
107
  number: "#4c4f69",
107
108
  activeNumber: "#1e66f5",
109
+ stale: "#df8e1d",
110
+ abandoned: "#d20f39",
108
111
  },
109
112
  sessionPicker: {
110
113
  border: "#1e66f5",
@@ -115,6 +118,10 @@ export const catppuccinLatteTheme = {
115
118
  highlightFg: "#40a02b",
116
119
  activeMark: "#1e66f5",
117
120
  progress: "#04a5e5",
121
+ staleIcon: "#df8e1d",
122
+ staleText: "#df8e1d",
123
+ staleAge: "#df8e1d",
124
+ staleSubtitle: "#acb0c0",
118
125
  },
119
126
  },
120
127
  };
@@ -91,6 +91,7 @@ export const catppuccinMochaTheme = {
91
91
  successPillBg: "#313244",
92
92
  error: "#f38ba8",
93
93
  info: "#89b4fa",
94
+ warning: "#f9e2af",
94
95
  border: "#313244",
95
96
  },
96
97
  markdown: {
@@ -105,6 +106,8 @@ export const catppuccinMochaTheme = {
105
106
  untouched: "#8688a0",
106
107
  number: "#cdd6f4",
107
108
  activeNumber: "#89b4fa",
109
+ stale: "#f9e2af",
110
+ abandoned: "#f38ba8",
108
111
  },
109
112
  sessionPicker: {
110
113
  border: "#89b4fa",
@@ -115,6 +118,10 @@ export const catppuccinMochaTheme = {
115
118
  highlightFg: "#a6e3a1",
116
119
  activeMark: "#89b4fa",
117
120
  progress: "#89dceb",
121
+ staleIcon: "#f9e2af",
122
+ staleText: "#f9e2af",
123
+ staleAge: "#f9e2af",
124
+ staleSubtitle: "#8688a0",
118
125
  },
119
126
  },
120
127
  };
@@ -92,6 +92,7 @@ export const darkTheme = {
92
92
  successPillBg: "#0F2417",
93
93
  error: "#FF5C57",
94
94
  info: "#46D9FF",
95
+ warning: "#FFD36A",
95
96
  border: "#2A3238",
96
97
  },
97
98
  markdown: {
@@ -106,6 +107,8 @@ export const darkTheme = {
106
107
  untouched: "#A0AAB4",
107
108
  number: "#E7EEF5",
108
109
  activeNumber: "#46D9FF",
110
+ stale: "#FFD36A",
111
+ abandoned: "#FF5C57",
109
112
  },
110
113
  sessionPicker: {
111
114
  border: "#46D9FF",
@@ -116,6 +119,10 @@ export const darkTheme = {
116
119
  highlightFg: "#5AF78E",
117
120
  activeMark: "#46D9FF",
118
121
  progress: "#46D9FF",
122
+ staleIcon: "#FFD36A",
123
+ staleText: "#FFD36A",
124
+ staleAge: "#FFD36A",
125
+ staleSubtitle: "#A0AAB4",
119
126
  },
120
127
  },
121
128
  };
@@ -91,6 +91,7 @@ export const draculaTheme = {
91
91
  successPillBg: "#44475a", // current line
92
92
  error: "#ff5555",
93
93
  info: "#bd93f9",
94
+ warning: "#f1fa8c",
94
95
  border: "#44475a",
95
96
  },
96
97
  markdown: {
@@ -105,6 +106,8 @@ export const draculaTheme = {
105
106
  untouched: "#7C8BBE",
106
107
  number: "#f8f8f2",
107
108
  activeNumber: "#bd93f9",
109
+ stale: "#f1fa8c",
110
+ abandoned: "#ff5555",
108
111
  },
109
112
  sessionPicker: {
110
113
  border: "#bd93f9",
@@ -115,6 +118,10 @@ export const draculaTheme = {
115
118
  highlightFg: "#50fa7b",
116
119
  activeMark: "#bd93f9",
117
120
  progress: "#8be9fd",
121
+ staleIcon: "#f1fa8c",
122
+ staleText: "#f1fa8c",
123
+ staleAge: "#f1fa8c",
124
+ staleSubtitle: "#7C8BBE",
118
125
  },
119
126
  },
120
127
  };
@@ -90,6 +90,7 @@ export const githubDarkTheme = {
90
90
  successPillBg: "#161b22",
91
91
  error: "#f85149",
92
92
  info: "#58a6ff",
93
+ warning: "#d29922",
93
94
  border: "#30363d",
94
95
  },
95
96
  markdown: {
@@ -104,6 +105,8 @@ export const githubDarkTheme = {
104
105
  untouched: "#a0a8b4",
105
106
  number: "#c9d1d9",
106
107
  activeNumber: "#58a6ff",
108
+ stale: "#d29922",
109
+ abandoned: "#f85149",
107
110
  },
108
111
  sessionPicker: {
109
112
  border: "#58a6ff",
@@ -114,6 +117,10 @@ export const githubDarkTheme = {
114
117
  highlightFg: "#3fb950",
115
118
  activeMark: "#58a6ff",
116
119
  progress: "#58a6ff",
120
+ staleIcon: "#d29922",
121
+ staleText: "#d29922",
122
+ staleAge: "#d29922",
123
+ staleSubtitle: "#a0a8b4",
117
124
  },
118
125
  },
119
126
  };
@@ -90,6 +90,7 @@ export const githubLightTheme = {
90
90
  successPillBg: "#f6f8fa",
91
91
  error: "#CF222E",
92
92
  info: "#0969DA",
93
+ warning: "#9A6700",
93
94
  border: "#D0D7DE",
94
95
  },
95
96
  markdown: {
@@ -104,6 +105,8 @@ export const githubLightTheme = {
104
105
  untouched: "#6E7781",
105
106
  number: "#24292F",
106
107
  activeNumber: "#0969DA",
108
+ stale: "#9A6700",
109
+ abandoned: "#CF222E",
107
110
  },
108
111
  sessionPicker: {
109
112
  border: "#0969DA",
@@ -114,6 +117,10 @@ export const githubLightTheme = {
114
117
  highlightFg: "#1A7F37",
115
118
  activeMark: "#0969DA",
116
119
  progress: "#0969DA",
120
+ staleIcon: "#9A6700",
121
+ staleText: "#9A6700",
122
+ staleAge: "#9A6700",
123
+ staleSubtitle: "#6E7781",
117
124
  },
118
125
  },
119
126
  };
@@ -91,6 +91,7 @@ export const gruvboxDarkTheme = {
91
91
  successPillBg: "#3c3836", // dark1
92
92
  error: "#cc241d",
93
93
  info: "#458588",
94
+ warning: "#d79921",
94
95
  border: "#3c3836",
95
96
  },
96
97
  markdown: {
@@ -105,6 +106,8 @@ export const gruvboxDarkTheme = {
105
106
  untouched: "#a89984",
106
107
  number: "#ebdbb2",
107
108
  activeNumber: "#458588",
109
+ stale: "#d79921",
110
+ abandoned: "#cc241d",
108
111
  },
109
112
  sessionPicker: {
110
113
  border: "#458588",
@@ -115,6 +118,10 @@ export const gruvboxDarkTheme = {
115
118
  highlightFg: "#98971a",
116
119
  activeMark: "#458588",
117
120
  progress: "#458588",
121
+ staleIcon: "#d79921",
122
+ staleText: "#d79921",
123
+ staleAge: "#d79921",
124
+ staleSubtitle: "#a89984",
118
125
  },
119
126
  },
120
127
  };