obsidian-catui-acp 0.1.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/LICENSE +21 -0
- package/README.md +52 -0
- package/THIRD_PARTY_NOTICES.txt +3002 -0
- package/esbuild.config.mjs +29 -0
- package/main.js +3499 -0
- package/manifest.json +9 -0
- package/package.json +57 -0
- package/src/acp/catui-client.ts +108 -0
- package/src/acp/catui-process.ts +49 -0
- package/src/acp/ndjson-rpc.ts +142 -0
- package/src/acp/types.ts +101 -0
- package/src/chat/acp-chat-transport.ts +28 -0
- package/src/chat/session-messages-cache.ts +40 -0
- package/src/context/vault-context.ts +35 -0
- package/src/main.ts +529 -0
- package/src/policy/permission-policy.ts +52 -0
- package/src/react/ChatApp.tsx +677 -0
- package/src/settings.ts +124 -0
- package/src/styles.css +270 -0
- package/src/ui/chat-view.tsx +38 -0
- package/styles.css +2 -0
- package/test/catui-client.test.ts +61 -0
- package/test/ndjson-rpc.test.ts +74 -0
- package/test/permission-policy.test.ts +31 -0
- package/test/session-messages-cache.test.ts +59 -0
- package/test/vault-context.test.ts +44 -0
- package/tsconfig.json +17 -0
package/src/settings.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { App, PluginSettingTab, Setting } from "obsidian";
|
|
2
|
+
import type CatuiAcpPlugin from "./main";
|
|
3
|
+
|
|
4
|
+
export interface CatuiAcpSettings {
|
|
5
|
+
command: string;
|
|
6
|
+
args: string;
|
|
7
|
+
personality: string;
|
|
8
|
+
defaultMode: "ask" | "read-only" | "bypass";
|
|
9
|
+
autoStart: boolean;
|
|
10
|
+
includeActiveNote: boolean;
|
|
11
|
+
includeSelection: boolean;
|
|
12
|
+
protectRawWrites: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_SETTINGS: CatuiAcpSettings = {
|
|
16
|
+
command: "catui",
|
|
17
|
+
args: "--acp",
|
|
18
|
+
personality: "vex",
|
|
19
|
+
defaultMode: "ask",
|
|
20
|
+
autoStart: true,
|
|
21
|
+
includeActiveNote: true,
|
|
22
|
+
includeSelection: true,
|
|
23
|
+
protectRawWrites: true,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function splitArgs(value: string): string[] {
|
|
27
|
+
return value.trim().length === 0 ? [] : value.trim().split(/\s+/);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class CatuiAcpSettingTab extends PluginSettingTab {
|
|
31
|
+
constructor(app: App, private readonly plugin: CatuiAcpPlugin) {
|
|
32
|
+
super(app, plugin);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
display(): void {
|
|
36
|
+
const { containerEl } = this;
|
|
37
|
+
containerEl.empty();
|
|
38
|
+
|
|
39
|
+
new Setting(containerEl)
|
|
40
|
+
.setName("CatUI command")
|
|
41
|
+
.setDesc("Executable used to start CatUI.")
|
|
42
|
+
.addText((text) =>
|
|
43
|
+
text.setValue(this.plugin.settings.command).onChange(async (value) => {
|
|
44
|
+
this.plugin.settings.command = value.trim() || DEFAULT_SETTINGS.command;
|
|
45
|
+
await this.plugin.saveSettings();
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
new Setting(containerEl)
|
|
50
|
+
.setName("CatUI arguments")
|
|
51
|
+
.setDesc("Arguments passed to the command.")
|
|
52
|
+
.addText((text) =>
|
|
53
|
+
text.setValue(this.plugin.settings.args).onChange(async (value) => {
|
|
54
|
+
this.plugin.settings.args = value;
|
|
55
|
+
await this.plugin.saveSettings();
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
new Setting(containerEl)
|
|
60
|
+
.setName("CatUI personality")
|
|
61
|
+
.setDesc("Default personality sent with each prompt.")
|
|
62
|
+
.addText((text) =>
|
|
63
|
+
text.setValue(this.plugin.settings.personality).onChange(async (value) => {
|
|
64
|
+
this.plugin.settings.personality = value.trim() || DEFAULT_SETTINGS.personality;
|
|
65
|
+
await this.plugin.saveSettings();
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
new Setting(containerEl)
|
|
70
|
+
.setName("Default mode")
|
|
71
|
+
.setDesc("ACP mode for new sessions.")
|
|
72
|
+
.addDropdown((dropdown) =>
|
|
73
|
+
dropdown
|
|
74
|
+
.addOption("ask", "Ask before write")
|
|
75
|
+
.addOption("read-only", "Read-only")
|
|
76
|
+
.addOption("bypass", "Bypass permissions")
|
|
77
|
+
.setValue(this.plugin.settings.defaultMode)
|
|
78
|
+
.onChange(async (value) => {
|
|
79
|
+
this.plugin.settings.defaultMode = value as CatuiAcpSettings["defaultMode"];
|
|
80
|
+
await this.plugin.saveSettings();
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
new Setting(containerEl)
|
|
85
|
+
.setName("Auto-start")
|
|
86
|
+
.setDesc("Start CatUI when the sidebar opens.")
|
|
87
|
+
.addToggle((toggle) =>
|
|
88
|
+
toggle.setValue(this.plugin.settings.autoStart).onChange(async (value) => {
|
|
89
|
+
this.plugin.settings.autoStart = value;
|
|
90
|
+
await this.plugin.saveSettings();
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
new Setting(containerEl)
|
|
95
|
+
.setName("Include active note")
|
|
96
|
+
.setDesc("Send the active note path with each prompt.")
|
|
97
|
+
.addToggle((toggle) =>
|
|
98
|
+
toggle.setValue(this.plugin.settings.includeActiveNote).onChange(async (value) => {
|
|
99
|
+
this.plugin.settings.includeActiveNote = value;
|
|
100
|
+
await this.plugin.saveSettings();
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
new Setting(containerEl)
|
|
105
|
+
.setName("Include selected text")
|
|
106
|
+
.setDesc("Send the current editor selection with each prompt.")
|
|
107
|
+
.addToggle((toggle) =>
|
|
108
|
+
toggle.setValue(this.plugin.settings.includeSelection).onChange(async (value) => {
|
|
109
|
+
this.plugin.settings.includeSelection = value;
|
|
110
|
+
await this.plugin.saveSettings();
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
new Setting(containerEl)
|
|
115
|
+
.setName("Protect raw writes")
|
|
116
|
+
.setDesc("Reject write/edit permission requests that target raw sources.")
|
|
117
|
+
.addToggle((toggle) =>
|
|
118
|
+
toggle.setValue(this.plugin.settings.protectRawWrites).onChange(async (value) => {
|
|
119
|
+
this.plugin.settings.protectRawWrites = value;
|
|
120
|
+
await this.plugin.saveSettings();
|
|
121
|
+
}),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
@import "tailwindcss/theme.css" prefix(catui);
|
|
2
|
+
|
|
3
|
+
@theme prefix(catui) {
|
|
4
|
+
--font-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
5
|
+
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
6
|
+
--color-bg: var(--catui-background);
|
|
7
|
+
--color-panel: var(--catui-muted);
|
|
8
|
+
--color-border: var(--catui-border);
|
|
9
|
+
--color-hover: var(--catui-foreground-3);
|
|
10
|
+
--color-normal: var(--catui-foreground);
|
|
11
|
+
--color-muted: var(--catui-muted-foreground);
|
|
12
|
+
--color-faint: var(--catui-foreground-35);
|
|
13
|
+
--color-accent: var(--catui-accent);
|
|
14
|
+
--color-accent-hover: color-mix(in oklch, var(--catui-accent) 88%, var(--catui-foreground));
|
|
15
|
+
--color-on-accent: white;
|
|
16
|
+
--color-success: var(--catui-success);
|
|
17
|
+
--color-error: var(--catui-destructive);
|
|
18
|
+
--color-error-bg: color-mix(in oklch, var(--catui-destructive) 8%, transparent);
|
|
19
|
+
--color-info: var(--catui-info);
|
|
20
|
+
--color-info-bg: color-mix(in oklch, var(--catui-info) 5%, transparent);
|
|
21
|
+
--color-foreground-subtle: var(--catui-foreground-3);
|
|
22
|
+
--color-foreground-soft: var(--catui-foreground-5);
|
|
23
|
+
--color-foreground-15: var(--catui-foreground-15);
|
|
24
|
+
--color-foreground-20: var(--catui-foreground-20);
|
|
25
|
+
--color-user-bubble: var(--catui-user-message-bubble);
|
|
26
|
+
--radius-catui-xs: 4px;
|
|
27
|
+
--radius-catui-sm: 6px;
|
|
28
|
+
--radius-catui-md: 8px;
|
|
29
|
+
--shadow-catui-minimal: var(--catui-shadow-minimal);
|
|
30
|
+
--shadow-catui-middle: var(--catui-shadow-middle);
|
|
31
|
+
--shadow-catui-focused: var(--catui-shadow-focused);
|
|
32
|
+
--shadow-catui-tinted: var(--catui-shadow-tinted);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@source "./src";
|
|
36
|
+
@tailwind utilities;
|
|
37
|
+
|
|
38
|
+
.catui-acp-view {
|
|
39
|
+
--catui-background: oklch(0.98 0.003 265);
|
|
40
|
+
--catui-foreground: oklch(0.185 0.01 270);
|
|
41
|
+
--catui-foreground-rgb: 38, 36, 42;
|
|
42
|
+
--catui-foreground-3: color-mix(in oklch, var(--catui-foreground) 3%, transparent);
|
|
43
|
+
--catui-foreground-5: color-mix(in oklch, var(--catui-foreground) 5%, transparent);
|
|
44
|
+
--catui-foreground-15: color-mix(in oklch, var(--catui-foreground) 15%, transparent);
|
|
45
|
+
--catui-foreground-20: color-mix(in oklch, var(--catui-foreground) 20%, transparent);
|
|
46
|
+
--catui-foreground-35: color-mix(in oklch, var(--catui-foreground) 35%, transparent);
|
|
47
|
+
--catui-muted: var(--catui-foreground-5);
|
|
48
|
+
--catui-muted-foreground: color-mix(in oklch, var(--catui-foreground) 50%, transparent);
|
|
49
|
+
--catui-border: var(--catui-foreground-5);
|
|
50
|
+
--catui-user-message-bubble: color-mix(in oklch, var(--catui-foreground) 5%, transparent);
|
|
51
|
+
--catui-accent: oklch(0.62 0.13 293);
|
|
52
|
+
--catui-success: oklch(0.55 0.17 145);
|
|
53
|
+
--catui-info: oklch(0.75 0.16 70);
|
|
54
|
+
--catui-info-rgb: 180, 120, 40;
|
|
55
|
+
--catui-destructive: oklch(0.58 0.24 28);
|
|
56
|
+
--catui-shadow-blur-opacity: 0.06;
|
|
57
|
+
--catui-shadow-minimal:
|
|
58
|
+
rgba(0, 0, 0, 0) 0 0 0 0,
|
|
59
|
+
rgba(0, 0, 0, 0) 0 0 0 0,
|
|
60
|
+
rgba(var(--catui-foreground-rgb), 0.06) 0 0 0 1px,
|
|
61
|
+
rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 1px 1px -0.5px,
|
|
62
|
+
rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 3px 3px -1.5px;
|
|
63
|
+
--catui-shadow-middle:
|
|
64
|
+
rgba(0, 0, 0, 0) 0 0 0 0,
|
|
65
|
+
rgba(0, 0, 0, 0) 0 0 0 0,
|
|
66
|
+
rgba(var(--catui-foreground-rgb), 0.06) 0 0 0 1px,
|
|
67
|
+
rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 1px 1px -0.5px,
|
|
68
|
+
rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 3px 3px -1.5px,
|
|
69
|
+
rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 6px 6px -3px;
|
|
70
|
+
--catui-shadow-focused:
|
|
71
|
+
0 0 0 1px color-mix(in oklch, var(--catui-accent) 45%, transparent),
|
|
72
|
+
rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 1px 1px -0.5px,
|
|
73
|
+
rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 3px 3px -1.5px,
|
|
74
|
+
rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 6px 6px -3px;
|
|
75
|
+
--catui-shadow-tinted:
|
|
76
|
+
rgba(var(--catui-info-rgb), 0.12) 0 0 0 1px,
|
|
77
|
+
rgba(var(--catui-info-rgb), 0.08) 0 1px 1px -0.5px,
|
|
78
|
+
rgba(var(--catui-info-rgb), 0.06) 0 3px 3px -1.5px;
|
|
79
|
+
background: var(--catui-background);
|
|
80
|
+
color: var(--catui-foreground);
|
|
81
|
+
font-size: 15px;
|
|
82
|
+
height: 100%;
|
|
83
|
+
min-height: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.theme-dark .catui-acp-view,
|
|
87
|
+
body.theme-dark .catui-acp-view {
|
|
88
|
+
--catui-background: oklch(0.145 0.015 270);
|
|
89
|
+
--catui-foreground: oklch(0.95 0.01 270);
|
|
90
|
+
--catui-foreground-rgb: 237, 236, 240;
|
|
91
|
+
--catui-accent: oklch(0.65 0.22 293);
|
|
92
|
+
--catui-success: oklch(0.60 0.17 145);
|
|
93
|
+
--catui-info: oklch(0.78 0.14 70);
|
|
94
|
+
--catui-info-rgb: 210, 155, 55;
|
|
95
|
+
--catui-destructive: oklch(0.65 0.22 28);
|
|
96
|
+
--catui-shadow-blur-opacity: 0.12;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
:where(.catui-acp-view) :where(*, *::before, *::after) {
|
|
100
|
+
box-sizing: border-box;
|
|
101
|
+
letter-spacing: 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
:where(.catui-acp-view) :where(button, textarea, select) {
|
|
105
|
+
appearance: none;
|
|
106
|
+
font: inherit;
|
|
107
|
+
letter-spacing: 0;
|
|
108
|
+
margin: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
:where(.catui-acp-view) :where(button) {
|
|
112
|
+
min-width: 0;
|
|
113
|
+
padding: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
:where(.catui-acp-view) :where(textarea, select) {
|
|
117
|
+
box-shadow: none;
|
|
118
|
+
outline: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.catui-acp-view textarea,
|
|
122
|
+
.catui-acp-view textarea:hover,
|
|
123
|
+
.catui-acp-view textarea:active,
|
|
124
|
+
.catui-acp-view textarea:focus,
|
|
125
|
+
.catui-acp-view textarea:focus-visible {
|
|
126
|
+
background: transparent !important;
|
|
127
|
+
border: 0 !important;
|
|
128
|
+
box-shadow: none !important;
|
|
129
|
+
outline: 0 !important;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.catui-reset-button,
|
|
133
|
+
.catui-reset-button:hover {
|
|
134
|
+
box-shadow: none;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.catui-acp-view svg.lucide {
|
|
138
|
+
color: inherit !important;
|
|
139
|
+
display: block !important;
|
|
140
|
+
opacity: 1 !important;
|
|
141
|
+
overflow: visible;
|
|
142
|
+
stroke: currentColor !important;
|
|
143
|
+
stroke-linecap: round;
|
|
144
|
+
stroke-linejoin: round;
|
|
145
|
+
stroke-width: 2;
|
|
146
|
+
visibility: visible !important;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.catui-conversation {
|
|
150
|
+
mask-image: linear-gradient(to bottom, transparent 0%, black 32px, black calc(100% - 32px), transparent 100%);
|
|
151
|
+
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 32px, black calc(100% - 32px), transparent 100%);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.catui-input-zone {
|
|
155
|
+
padding-bottom: calc(12px + var(--status-bar-scroll-padding, 30px));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.catui-acp-view .catui-session-menu {
|
|
159
|
+
background: var(--catui-background) !important;
|
|
160
|
+
border: 1px solid var(--catui-foreground-15) !important;
|
|
161
|
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.28) !important;
|
|
162
|
+
font-family: var(--catui-font-sans) !important;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.catui-acp-view .catui-session-menu-header {
|
|
166
|
+
min-height: 26px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.catui-acp-view .catui-session-menu-heading,
|
|
170
|
+
.catui-acp-view .catui-session-menu-count {
|
|
171
|
+
font-family: var(--catui-font-sans) !important;
|
|
172
|
+
line-height: 14px !important;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.catui-acp-view .catui-session-menu-heading {
|
|
176
|
+
font-size: 11px !important;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.catui-acp-view .catui-session-menu-count {
|
|
180
|
+
font-size: 10px !important;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.catui-acp-view button.catui-session-item,
|
|
184
|
+
.catui-acp-view button.catui-session-item:hover,
|
|
185
|
+
.catui-acp-view button.catui-session-item:focus,
|
|
186
|
+
.catui-acp-view button.catui-session-item:active {
|
|
187
|
+
background: transparent !important;
|
|
188
|
+
border: 0 !important;
|
|
189
|
+
box-shadow: none !important;
|
|
190
|
+
font-family: var(--catui-font-sans) !important;
|
|
191
|
+
height: auto !important;
|
|
192
|
+
min-height: 38px;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.catui-acp-view button.catui-session-item:hover,
|
|
196
|
+
.catui-acp-view button.catui-session-item:focus-visible,
|
|
197
|
+
.catui-acp-view button.catui-session-item[aria-current="true"] {
|
|
198
|
+
background: var(--catui-foreground-5) !important;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.catui-acp-view .catui-session-item-title {
|
|
202
|
+
font-family: var(--catui-font-sans) !important;
|
|
203
|
+
font-size: 12px !important;
|
|
204
|
+
font-weight: 500 !important;
|
|
205
|
+
line-height: 16px !important;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.catui-acp-view .catui-session-item-time {
|
|
209
|
+
font-family: var(--catui-font-sans) !important;
|
|
210
|
+
font-size: 10px !important;
|
|
211
|
+
font-weight: 400 !important;
|
|
212
|
+
line-height: 13px !important;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.catui-session-menu > summary::-webkit-details-marker,
|
|
216
|
+
.catui-tool-part > summary::-webkit-details-marker {
|
|
217
|
+
display: none;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.catui-activity-list {
|
|
221
|
+
max-height: 0;
|
|
222
|
+
opacity: 0;
|
|
223
|
+
overflow: hidden;
|
|
224
|
+
transition: max-height 250ms cubic-bezier(0.4, 0, 0.2, 1), opacity 150ms ease;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.catui-activity-list-open {
|
|
228
|
+
max-height: 420px;
|
|
229
|
+
opacity: 1;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.catui-activity-list-open .catui-tool-part {
|
|
233
|
+
animation: catui-activity-enter 300ms ease-out both;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.catui-activity-list-open .catui-tool-part:nth-child(2) { animation-delay: 30ms; }
|
|
237
|
+
.catui-activity-list-open .catui-tool-part:nth-child(3) { animation-delay: 60ms; }
|
|
238
|
+
.catui-activity-list-open .catui-tool-part:nth-child(4) { animation-delay: 90ms; }
|
|
239
|
+
.catui-activity-list-open .catui-tool-part:nth-child(5) { animation-delay: 120ms; }
|
|
240
|
+
|
|
241
|
+
.catui-thinking-label {
|
|
242
|
+
animation: catui-crossfade 400ms ease-in-out;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@keyframes catui-activity-enter {
|
|
246
|
+
from { opacity: 0; transform: translateX(-8px); }
|
|
247
|
+
to { opacity: 1; transform: translateX(0); }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
@keyframes catui-crossfade {
|
|
251
|
+
from { opacity: 0; }
|
|
252
|
+
to { opacity: 1; }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.catui-acp-markdown > :first-child { margin-top: 0; }
|
|
256
|
+
.catui-acp-markdown > :last-child { margin-bottom: 0; }
|
|
257
|
+
.catui-acp-markdown :where(h1, h2, h3, h4, h5, h6, p, ul, ol, blockquote, pre, table) { margin-top: 0; margin-bottom: 0; }
|
|
258
|
+
.catui-acp-markdown pre { white-space: pre; }
|
|
259
|
+
.catui-acp-markdown code { white-space: pre-wrap; }
|
|
260
|
+
.catui-acp-markdown pre code { white-space: pre; }
|
|
261
|
+
|
|
262
|
+
@media (prefers-reduced-motion: reduce) {
|
|
263
|
+
.catui-acp-view *,
|
|
264
|
+
.catui-acp-view *::before,
|
|
265
|
+
.catui-acp-view *::after {
|
|
266
|
+
animation-duration: 0.01ms !important;
|
|
267
|
+
animation-iteration-count: 1 !important;
|
|
268
|
+
transition-duration: 0.01ms !important;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ItemView, WorkspaceLeaf } from "obsidian";
|
|
2
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
3
|
+
import type CatuiAcpPlugin from "../main";
|
|
4
|
+
import { ChatApp } from "../react/ChatApp";
|
|
5
|
+
|
|
6
|
+
export const CATUI_ACP_VIEW_TYPE = "catui-acp-chat";
|
|
7
|
+
|
|
8
|
+
export class CatuiAcpView extends ItemView {
|
|
9
|
+
private root: Root | null = null;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
leaf: WorkspaceLeaf,
|
|
13
|
+
private readonly plugin: CatuiAcpPlugin,
|
|
14
|
+
) {
|
|
15
|
+
super(leaf);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getViewType(): string {
|
|
19
|
+
return CATUI_ACP_VIEW_TYPE;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getDisplayText(): string {
|
|
23
|
+
return "CatUI ACP";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async onOpen(): Promise<void> {
|
|
27
|
+
this.containerEl.empty();
|
|
28
|
+
const mount = this.containerEl.createDiv({ cls: "catui-acp-view" });
|
|
29
|
+
this.root = createRoot(mount);
|
|
30
|
+
this.root.render(<ChatApp plugin={this.plugin} />);
|
|
31
|
+
if (this.plugin.settings.autoStart) await this.plugin.ensureClient();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async onClose(): Promise<void> {
|
|
35
|
+
this.root?.unmount();
|
|
36
|
+
this.root = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/styles.css
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial}}}:root,:host{--catui-font-sans:system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--catui-font-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;--catui-spacing:.25rem;--catui-font-weight-medium:500;--catui-font-weight-semibold:600;--catui-animate-spin:spin 1s linear infinite;--catui-default-transition-duration:.15s;--catui-default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--catui-color-bg:var(--catui-background);--catui-color-panel:var(--catui-muted);--catui-color-border:var(--catui-border);--catui-color-hover:var(--catui-foreground-3);--catui-color-normal:var(--catui-foreground);--catui-color-muted:var(--catui-muted-foreground);--catui-color-faint:var(--catui-foreground-35);--catui-color-accent:var(--catui-accent);--catui-color-accent-hover:var(--catui-accent)}@supports (color:color-mix(in lab, red, red)){:root,:host{--catui-color-accent-hover:color-mix(in oklch, var(--catui-accent) 88%, var(--catui-foreground))}}:root,:host{--catui-color-on-accent:white;--catui-color-success:var(--catui-success);--catui-color-error:var(--catui-destructive);--catui-color-error-bg:var(--catui-destructive)}@supports (color:color-mix(in lab, red, red)){:root,:host{--catui-color-error-bg:color-mix(in oklch, var(--catui-destructive) 8%, transparent)}}:root,:host{--catui-color-info:var(--catui-info);--catui-color-info-bg:var(--catui-info)}@supports (color:color-mix(in lab, red, red)){:root,:host{--catui-color-info-bg:color-mix(in oklch, var(--catui-info) 5%, transparent)}}:root,:host{--catui-color-foreground-subtle:var(--catui-foreground-3);--catui-color-foreground-soft:var(--catui-foreground-5);--catui-color-foreground-15:var(--catui-foreground-15);--catui-color-foreground-20:var(--catui-foreground-20);--catui-color-user-bubble:var(--catui-user-message-bubble);--catui-radius-catui-xs:4px;--catui-radius-catui-sm:6px;--catui-radius-catui-md:8px}.catui\:pointer-events-none{pointer-events:none}.catui\:absolute{position:absolute}.catui\:relative{position:relative}.catui\:inset-0{inset:0}.catui\:bottom-full{bottom:100%}.catui\:left-0{left:0}.catui\:z-50{z-index:50}.catui\:m-0{margin:0}.catui\:mx-2{margin-inline:calc(var(--catui-spacing) * 2)}.catui\:mx-auto{margin-inline:auto}.catui\:my-0\.5{margin-block:calc(var(--catui-spacing) * .5)}.catui\:my-1{margin-block:var(--catui-spacing)}.catui\:my-1\.5{margin-block:calc(var(--catui-spacing) * 1.5)}.catui\:my-2{margin-block:calc(var(--catui-spacing) * 2)}.catui\:mt-0\.5{margin-top:calc(var(--catui-spacing) * .5)}.catui\:mt-1{margin-top:var(--catui-spacing)}.catui\:mt-1\.5{margin-top:calc(var(--catui-spacing) * 1.5)}.catui\:mt-2{margin-top:calc(var(--catui-spacing) * 2)}.catui\:mr-1{margin-right:var(--catui-spacing)}.catui\:-mb-1{margin-bottom:calc(var(--catui-spacing) * -1)}.catui\:mb-1{margin-bottom:var(--catui-spacing)}.catui\:mb-2{margin-bottom:calc(var(--catui-spacing) * 2)}.catui\:-ml-0\.5{margin-left:calc(var(--catui-spacing) * -.5)}.catui\:ml-1\.5{margin-left:calc(var(--catui-spacing) * 1.5)}.catui\:ml-2{margin-left:calc(var(--catui-spacing) * 2)}.catui\:ml-5{margin-left:calc(var(--catui-spacing) * 5)}.catui\:ml-\[13px\]{margin-left:13px}.catui\:block{display:block}.catui\:flex{display:flex}.catui\:grid{display:grid}.catui\:inline-flex{display:inline-flex}.catui\:size-3{width:calc(var(--catui-spacing) * 3);height:calc(var(--catui-spacing) * 3)}.catui\:size-4{width:calc(var(--catui-spacing) * 4);height:calc(var(--catui-spacing) * 4)}.catui\:size-6{width:calc(var(--catui-spacing) * 6);height:calc(var(--catui-spacing) * 6)}.catui\:size-7{width:calc(var(--catui-spacing) * 7);height:calc(var(--catui-spacing) * 7)}.catui\:h-6{height:calc(var(--catui-spacing) * 6)}.catui\:h-8{height:calc(var(--catui-spacing) * 8)}.catui\:h-full{height:100%}.catui\:max-h-24{max-height:calc(var(--catui-spacing) * 24)}.catui\:max-h-\[180px\]{max-height:180px}.catui\:max-h-\[220px\]{max-height:220px}.catui\:max-h-\[260px\]{max-height:260px}.catui\:max-h-\[280px\]{max-height:280px}.catui\:max-h-\[360px\]{max-height:360px}.catui\:max-h-\[560px\]{max-height:560px}.catui\:min-h-0{min-height:0}.catui\:min-h-6{min-height:calc(var(--catui-spacing) * 6)}.catui\:min-h-\[88px\]{min-height:88px}.catui\:w-\[280px\]{width:280px}.catui\:w-full{width:100%}.catui\:max-w-\[80\%\]{max-width:80%}.catui\:max-w-\[148px\]{max-width:148px}.catui\:max-w-\[840px\]{max-width:840px}.catui\:max-w-\[calc\(100vw-32px\)\]{max-width:calc(100vw - 32px)}.catui\:min-w-0{min-width:0}.catui\:flex-1{flex:1}.catui\:shrink{flex-shrink:1}.catui\:shrink-0{flex-shrink:0}.catui\:border-collapse{border-collapse:collapse}.catui\:rotate-90{rotate:90deg}.catui\:rotate-180{rotate:180deg}.catui\:animate-spin{animation:var(--catui-animate-spin)}.catui\:cursor-default{cursor:default}.catui\:cursor-pointer{cursor:pointer}.catui\:resize-none{resize:none}.catui\:list-decimal{list-style-type:decimal}.catui\:list-disc{list-style-type:disc}.catui\:list-none{list-style-type:none}.catui\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.catui\:flex-col{flex-direction:column}.catui\:items-center{align-items:center}.catui\:items-end{align-items:flex-end}.catui\:justify-between{justify-content:space-between}.catui\:justify-center{justify-content:center}.catui\:gap-1{gap:var(--catui-spacing)}.catui\:gap-1\.5{gap:calc(var(--catui-spacing) * 1.5)}.catui\:gap-2{gap:calc(var(--catui-spacing) * 2)}.catui\:gap-3{gap:calc(var(--catui-spacing) * 3)}:where(.catui\:space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--catui-spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--catui-spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.catui\:space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--catui-spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--catui-spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.catui\:divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.catui\:divide-border>:not(:last-child)){border-color:var(--catui-color-border)}.catui\:truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.catui\:overflow-auto{overflow:auto}.catui\:overflow-hidden{overflow:hidden}.catui\:overflow-visible{overflow:visible}.catui\:overflow-x-auto{overflow-x:auto}.catui\:overflow-y-auto{overflow-y:auto}.catui\:rounded-\[16px\]{border-radius:16px}.catui\:rounded-catui-md{border-radius:var(--catui-radius-catui-md)}.catui\:rounded-catui-sm{border-radius:var(--catui-radius-catui-sm)}.catui\:rounded-catui-xs{border-radius:var(--catui-radius-catui-xs)}.catui\:rounded-full{border-radius:3.40282e38px}.catui\:border{border-style:var(--tw-border-style);border-width:1px}.catui\:border-0{border-style:var(--tw-border-style);border-width:0}.catui\:border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.catui\:border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.catui\:border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.catui\:border-accent{border-color:var(--catui-color-accent)}.catui\:border-border{border-color:var(--catui-color-border)}.catui\:border-error\/30{border-color:var(--catui-color-error)}@supports (color:color-mix(in lab, red, red)){.catui\:border-error\/30{border-color:color-mix(in oklab, var(--catui-color-error) 30%, transparent)}}.catui\:border-info\/30{border-color:var(--catui-color-info)}@supports (color:color-mix(in lab, red, red)){.catui\:border-info\/30{border-color:color-mix(in oklab, var(--catui-color-info) 30%, transparent)}}.catui\:border-muted{border-color:var(--catui-color-muted)}.catui\:bg-accent{background-color:var(--catui-color-accent)}.catui\:bg-bg{background-color:var(--catui-color-bg)}.catui\:bg-error-bg{background-color:var(--catui-color-error-bg)}.catui\:bg-foreground-soft{background-color:var(--catui-color-foreground-soft)}.catui\:bg-foreground-subtle{background-color:var(--catui-color-foreground-subtle)}.catui\:bg-hover{background-color:var(--catui-color-hover)}.catui\:bg-info-bg{background-color:var(--catui-color-info-bg)}.catui\:bg-normal{background-color:var(--catui-color-normal)}.catui\:bg-panel{background-color:var(--catui-color-panel)}.catui\:bg-transparent{background-color:#0000}.catui\:bg-user-bubble{background-color:var(--catui-color-user-bubble)}.catui\:p-1\.5{padding:calc(var(--catui-spacing) * 1.5)}.catui\:p-2{padding:calc(var(--catui-spacing) * 2)}.catui\:p-2\.5{padding:calc(var(--catui-spacing) * 2.5)}.catui\:p-3{padding:calc(var(--catui-spacing) * 3)}.catui\:p-4{padding:calc(var(--catui-spacing) * 4)}.catui\:px-1{padding-inline:var(--catui-spacing)}.catui\:px-1\.5{padding-inline:calc(var(--catui-spacing) * 1.5)}.catui\:px-2{padding-inline:calc(var(--catui-spacing) * 2)}.catui\:px-2\.5{padding-inline:calc(var(--catui-spacing) * 2.5)}.catui\:px-3{padding-inline:calc(var(--catui-spacing) * 3)}.catui\:px-4{padding-inline:calc(var(--catui-spacing) * 4)}.catui\:py-0{padding-block:0}.catui\:py-0\.5{padding-block:calc(var(--catui-spacing) * .5)}.catui\:py-1{padding-block:var(--catui-spacing)}.catui\:py-1\.5{padding-block:calc(var(--catui-spacing) * 1.5)}.catui\:py-2{padding-block:calc(var(--catui-spacing) * 2)}.catui\:py-2\.5{padding-block:calc(var(--catui-spacing) * 2.5)}.catui\:py-3{padding-block:calc(var(--catui-spacing) * 3)}.catui\:py-4{padding-block:calc(var(--catui-spacing) * 4)}.catui\:pt-1{padding-top:var(--catui-spacing)}.catui\:pt-2{padding-top:calc(var(--catui-spacing) * 2)}.catui\:pt-4{padding-top:calc(var(--catui-spacing) * 4)}.catui\:pr-1\.5{padding-right:calc(var(--catui-spacing) * 1.5)}.catui\:pr-2{padding-right:calc(var(--catui-spacing) * 2)}.catui\:pr-4{padding-right:calc(var(--catui-spacing) * 4)}.catui\:pb-1{padding-bottom:var(--catui-spacing)}.catui\:pb-1\.5{padding-bottom:calc(var(--catui-spacing) * 1.5)}.catui\:pb-3{padding-bottom:calc(var(--catui-spacing) * 3)}.catui\:pl-2{padding-left:calc(var(--catui-spacing) * 2)}.catui\:pl-2\.5{padding-left:calc(var(--catui-spacing) * 2.5)}.catui\:pl-4{padding-left:calc(var(--catui-spacing) * 4)}.catui\:pl-5{padding-left:calc(var(--catui-spacing) * 5)}.catui\:pl-\[22px\]{padding-left:22px}.catui\:text-center{text-align:center}.catui\:text-left{text-align:left}.catui\:font-mono{font-family:var(--catui-font-mono)}.catui\:font-sans{font-family:var(--catui-font-sans)}.catui\:text-\[10px\]{font-size:10px}.catui\:text-\[11px\]{font-size:11px}.catui\:text-\[12px\]{font-size:12px}.catui\:text-\[13px\]{font-size:13px}.catui\:text-\[14px\]{font-size:14px}.catui\:text-\[15px\]{font-size:15px}.catui\:leading-4{--tw-leading:calc(var(--catui-spacing) * 4);line-height:calc(var(--catui-spacing) * 4)}.catui\:leading-5{--tw-leading:calc(var(--catui-spacing) * 5);line-height:calc(var(--catui-spacing) * 5)}.catui\:leading-6{--tw-leading:calc(var(--catui-spacing) * 6);line-height:calc(var(--catui-spacing) * 6)}.catui\:leading-\[18px\]{--tw-leading:18px;line-height:18px}.catui\:leading-none{--tw-leading:1;line-height:1}.catui\:font-medium{--tw-font-weight:var(--catui-font-weight-medium);font-weight:var(--catui-font-weight-medium)}.catui\:font-semibold{--tw-font-weight:var(--catui-font-weight-semibold);font-weight:var(--catui-font-weight-semibold)}.catui\:break-words{overflow-wrap:break-word}.catui\:whitespace-pre{white-space:pre}.catui\:whitespace-pre-wrap{white-space:pre-wrap}.catui\:text-accent{color:var(--catui-color-accent)}.catui\:text-bg{color:var(--catui-color-bg)}.catui\:text-error{color:var(--catui-color-error)}.catui\:text-faint{color:var(--catui-color-faint)}.catui\:text-info{color:var(--catui-color-info)}.catui\:text-muted{color:var(--catui-color-muted)}.catui\:text-normal{color:var(--catui-color-normal)}.catui\:text-on-accent{color:var(--catui-color-on-accent)}.catui\:text-success{color:var(--catui-color-success)}.catui\:uppercase{text-transform:uppercase}.catui\:italic{font-style:italic}.catui\:tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.catui\:line-through{text-decoration-line:line-through}.catui\:underline{text-decoration-line:underline}.catui\:decoration-accent\/40{-webkit-text-decoration-color:var(--catui-color-accent);-webkit-text-decoration-color:var(--catui-color-accent);text-decoration-color:var(--catui-color-accent)}@supports (color:color-mix(in lab, red, red)){.catui\:decoration-accent\/40{-webkit-text-decoration-color:color-mix(in oklab, var(--catui-color-accent) 40%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--catui-color-accent) 40%, transparent);text-decoration-color:color-mix(in oklab, var(--catui-color-accent) 40%, transparent)}}.catui\:underline-offset-2{text-underline-offset:2px}.catui\:accent-accent{accent-color:var(--catui-color-accent)}.catui\:shadow-catui-middle{--tw-shadow:var(--catui-shadow-middle);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.catui\:shadow-catui-minimal{--tw-shadow:var(--catui-shadow-minimal);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.catui\:shadow-catui-tinted{--tw-shadow:var(--catui-shadow-tinted);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.catui\:transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--catui-default-transition-timing-function));transition-duration:var(--tw-duration,var(--catui-default-transition-duration))}.catui\:transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--catui-default-transition-timing-function));transition-duration:var(--tw-duration,var(--catui-default-transition-duration))}.catui\:transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--catui-default-transition-timing-function));transition-duration:var(--tw-duration,var(--catui-default-transition-duration))}.catui\:transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--catui-default-transition-timing-function));transition-duration:var(--tw-duration,var(--catui-default-transition-duration))}.catui\:duration-150{--tw-duration:.15s;transition-duration:.15s}.catui\:outline-none{--tw-outline-style:none;outline-style:none}.catui\:select-none{-webkit-user-select:none;user-select:none}.catui\:select-text{-webkit-user-select:text;user-select:text}.catui\:placeholder\:text-faint::placeholder{color:var(--catui-color-faint)}.catui\:last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.catui\:focus-within\:shadow-catui-focused:focus-within{--tw-shadow:var(--catui-shadow-focused);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}@media (hover:hover){.catui\:hover\:bg-accent-hover:hover{background-color:var(--catui-color-accent-hover)}.catui\:hover\:bg-error-bg:hover{background-color:var(--catui-color-error-bg)}.catui\:hover\:bg-foreground-15:hover{background-color:var(--catui-color-foreground-15)}.catui\:hover\:bg-hover:hover{background-color:var(--catui-color-hover)}.catui\:hover\:bg-info\/20:hover{background-color:var(--catui-color-info)}@supports (color:color-mix(in lab, red, red)){.catui\:hover\:bg-info\/20:hover{background-color:color-mix(in oklab, var(--catui-color-info) 20%, transparent)}}.catui\:hover\:text-error:hover{color:var(--catui-color-error)}.catui\:hover\:text-normal:hover{color:var(--catui-color-normal)}.catui\:hover\:text-on-accent:hover{color:var(--catui-color-on-accent)}.catui\:hover\:decoration-accent:hover{-webkit-text-decoration-color:var(--catui-color-accent);-webkit-text-decoration-color:var(--catui-color-accent);text-decoration-color:var(--catui-color-accent)}.catui\:hover\:opacity-80:hover{opacity:.8}}.catui\:focus-visible\:border-accent:focus-visible{border-color:var(--catui-color-accent)}.catui\:focus-visible\:bg-hover:focus-visible{background-color:var(--catui-color-hover)}.catui\:focus-visible\:decoration-accent:focus-visible{-webkit-text-decoration-color:var(--catui-color-accent);-webkit-text-decoration-color:var(--catui-color-accent);text-decoration-color:var(--catui-color-accent)}.catui\:focus-visible\:shadow-\[0_0_0_1px_var\(--interactive-accent\)\]:focus-visible{--tw-shadow:0 0 0 1px var(--tw-shadow-color,var(--interactive-accent));box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.catui\:focus-visible\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.catui\:focus-visible\:ring-accent:focus-visible{--tw-ring-color:var(--catui-color-accent)}.catui\:focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.catui\:active\:bg-foreground-20:active{background-color:var(--catui-color-foreground-20)}.catui\:disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.catui\:disabled\:opacity-30:disabled{opacity:.3}.catui\:disabled\:opacity-40:disabled{opacity:.4}.catui\:disabled\:opacity-50:disabled{opacity:.5}.catui-acp-view{--catui-background:oklch(98% .003 265);--catui-foreground:oklch(18.5% .01 270);--catui-foreground-rgb:38, 36, 42;--catui-foreground-3:var(--catui-foreground)}@supports (color:color-mix(in lab, red, red)){.catui-acp-view{--catui-foreground-3:color-mix(in oklch, var(--catui-foreground) 3%, transparent)}}.catui-acp-view{--catui-foreground-5:var(--catui-foreground)}@supports (color:color-mix(in lab, red, red)){.catui-acp-view{--catui-foreground-5:color-mix(in oklch, var(--catui-foreground) 5%, transparent)}}.catui-acp-view{--catui-foreground-15:var(--catui-foreground)}@supports (color:color-mix(in lab, red, red)){.catui-acp-view{--catui-foreground-15:color-mix(in oklch, var(--catui-foreground) 15%, transparent)}}.catui-acp-view{--catui-foreground-20:var(--catui-foreground)}@supports (color:color-mix(in lab, red, red)){.catui-acp-view{--catui-foreground-20:color-mix(in oklch, var(--catui-foreground) 20%, transparent)}}.catui-acp-view{--catui-foreground-35:var(--catui-foreground)}@supports (color:color-mix(in lab, red, red)){.catui-acp-view{--catui-foreground-35:color-mix(in oklch, var(--catui-foreground) 35%, transparent)}}.catui-acp-view{--catui-muted:var(--catui-foreground-5);--catui-muted-foreground:var(--catui-foreground)}@supports (color:color-mix(in lab, red, red)){.catui-acp-view{--catui-muted-foreground:color-mix(in oklch, var(--catui-foreground) 50%, transparent)}}.catui-acp-view{--catui-border:var(--catui-foreground-5);--catui-user-message-bubble:var(--catui-foreground)}@supports (color:color-mix(in lab, red, red)){.catui-acp-view{--catui-user-message-bubble:color-mix(in oklch, var(--catui-foreground) 5%, transparent)}}.catui-acp-view{--catui-accent:oklch(62% .13 293);--catui-success:oklch(55% .17 145);--catui-info:oklch(75% .16 70);--catui-info-rgb:180, 120, 40;--catui-destructive:oklch(58% .24 28);--catui-shadow-blur-opacity:.06;--catui-shadow-minimal:#0000 0 0 0 0, #0000 0 0 0 0, rgba(var(--catui-foreground-rgb), .06) 0 0 0 1px, rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 1px 1px -.5px, rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 3px 3px -1.5px;--catui-shadow-middle:#0000 0 0 0 0, #0000 0 0 0 0, rgba(var(--catui-foreground-rgb), .06) 0 0 0 1px, rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 1px 1px -.5px, rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 3px 3px -1.5px, rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 6px 6px -3px;--catui-shadow-focused:0 0 0 1px var(--catui-accent), rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 1px 1px -.5px, rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 3px 3px -1.5px, rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 6px 6px -3px}@supports (color:color-mix(in lab, red, red)){.catui-acp-view{--catui-shadow-focused:0 0 0 1px color-mix(in oklch, var(--catui-accent) 45%, transparent), rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 1px 1px -.5px, rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 3px 3px -1.5px, rgba(0, 0, 0, var(--catui-shadow-blur-opacity)) 0 6px 6px -3px}}.catui-acp-view{--catui-shadow-tinted:rgba(var(--catui-info-rgb), .12) 0 0 0 1px, rgba(var(--catui-info-rgb), .08) 0 1px 1px -.5px, rgba(var(--catui-info-rgb), .06) 0 3px 3px -1.5px;background:var(--catui-background);color:var(--catui-foreground);height:100%;min-height:0;font-size:15px}.theme-dark .catui-acp-view,body.theme-dark .catui-acp-view{--catui-background:oklch(14.5% .015 270);--catui-foreground:oklch(95% .01 270);--catui-foreground-rgb:237, 236, 240;--catui-accent:oklch(65% .22 293);--catui-success:oklch(60% .17 145);--catui-info:oklch(78% .14 70);--catui-info-rgb:210, 155, 55;--catui-destructive:oklch(65% .22 28);--catui-shadow-blur-opacity:.12}:where(.catui-acp-view) :where(*){box-sizing:border-box;letter-spacing:0}:where(.catui-acp-view) :where(button,textarea,select){appearance:none;font:inherit;letter-spacing:0;margin:0}:where(.catui-acp-view) :where(button){min-width:0;padding:0}:where(.catui-acp-view) :where(textarea,select){box-shadow:none;outline:0}.catui-acp-view textarea,.catui-acp-view textarea:hover,.catui-acp-view textarea:active,.catui-acp-view textarea:focus,.catui-acp-view textarea:focus-visible{box-shadow:none!important;background:0 0!important;border:0!important;outline:0!important}.catui-reset-button,.catui-reset-button:hover{box-shadow:none}.catui-acp-view svg.lucide{stroke-linecap:round;stroke-linejoin:round;stroke-width:2px;overflow:visible;color:inherit!important;opacity:1!important;stroke:currentColor!important;visibility:visible!important;display:block!important}.catui-conversation{-webkit-mask-image:linear-gradient(#0000 0%,#000 32px calc(100% - 32px),#0000 100%);mask-image:linear-gradient(#0000 0%,#000 32px calc(100% - 32px),#0000 100%)}.catui-input-zone{padding-bottom:calc(12px + var(--status-bar-scroll-padding,30px))}.catui-acp-view .catui-session-menu{background:var(--catui-background)!important;border:1px solid var(--catui-foreground-15)!important;font-family:var(--catui-font-sans)!important;box-shadow:0 12px 32px #00000047!important}.catui-acp-view .catui-session-menu-header{min-height:26px}.catui-acp-view .catui-session-menu-heading,.catui-acp-view .catui-session-menu-count{font-family:var(--catui-font-sans)!important;line-height:14px!important}.catui-acp-view .catui-session-menu-heading{font-size:11px!important}.catui-acp-view .catui-session-menu-count{font-size:10px!important}.catui-acp-view button.catui-session-item,.catui-acp-view button.catui-session-item:hover,.catui-acp-view button.catui-session-item:focus,.catui-acp-view button.catui-session-item:active{min-height:38px;box-shadow:none!important;font-family:var(--catui-font-sans)!important;background:0 0!important;border:0!important;height:auto!important}.catui-acp-view button.catui-session-item:hover,.catui-acp-view button.catui-session-item:focus-visible,.catui-acp-view button.catui-session-item[aria-current=true]{background:var(--catui-foreground-5)!important}.catui-acp-view .catui-session-item-title{font-family:var(--catui-font-sans)!important;font-size:12px!important;font-weight:500!important;line-height:16px!important}.catui-acp-view .catui-session-item-time{font-family:var(--catui-font-sans)!important;font-size:10px!important;font-weight:400!important;line-height:13px!important}.catui-session-menu>summary::-webkit-details-marker{display:none}.catui-tool-part>summary::-webkit-details-marker{display:none}.catui-activity-list{opacity:0;max-height:0;transition:max-height .25s cubic-bezier(.4,0,.2,1),opacity .15s;overflow:hidden}.catui-activity-list-open{opacity:1;max-height:420px}.catui-activity-list-open .catui-tool-part{animation:.3s ease-out both catui-activity-enter}.catui-activity-list-open .catui-tool-part:nth-child(2){animation-delay:30ms}.catui-activity-list-open .catui-tool-part:nth-child(3){animation-delay:60ms}.catui-activity-list-open .catui-tool-part:nth-child(4){animation-delay:90ms}.catui-activity-list-open .catui-tool-part:nth-child(5){animation-delay:.12s}.catui-thinking-label{animation:.4s ease-in-out catui-crossfade}@keyframes catui-activity-enter{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}@keyframes catui-crossfade{0%{opacity:0}to{opacity:1}}.catui-acp-markdown>:first-child{margin-top:0}.catui-acp-markdown>:last-child{margin-bottom:0}.catui-acp-markdown :where(h1,h2,h3,h4,h5,h6,p,ul,ol,blockquote,pre,table){margin-top:0;margin-bottom:0}.catui-acp-markdown pre{white-space:pre}.catui-acp-markdown code{white-space:pre-wrap}.catui-acp-markdown pre code{white-space:pre}@media (prefers-reduced-motion:reduce){.catui-acp-view *,.catui-acp-view :before,.catui-acp-view :after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { CatuiAcpClient } from "../src/acp/catui-client";
|
|
4
|
+
|
|
5
|
+
class FakePeer {
|
|
6
|
+
calls: Array<{ method: string; params: unknown }> = [];
|
|
7
|
+
async request<TResult = unknown>(method: string, params?: unknown): Promise<TResult> {
|
|
8
|
+
this.calls.push({ method, params });
|
|
9
|
+
if (method === "initialize") {
|
|
10
|
+
return { protocolVersion: 1, agentInfo: { name: "CatUI", version: "test" }, agentCapabilities: {} } as TResult;
|
|
11
|
+
}
|
|
12
|
+
if (method === "session/new") {
|
|
13
|
+
return {
|
|
14
|
+
sessionId: "s1",
|
|
15
|
+
modes: { currentModeId: "ask", availableModes: [] },
|
|
16
|
+
models: { currentModelId: "m1", availableModels: [] },
|
|
17
|
+
} as TResult;
|
|
18
|
+
}
|
|
19
|
+
if (method === "session/list") return { sessions: [] } as TResult;
|
|
20
|
+
if (method === "session/prompt") return { stopReason: "end_turn" } as TResult;
|
|
21
|
+
return {} as TResult;
|
|
22
|
+
}
|
|
23
|
+
notify(method: string, params?: unknown): void {
|
|
24
|
+
this.calls.push({ method, params });
|
|
25
|
+
}
|
|
26
|
+
onNotification(): void {}
|
|
27
|
+
onRequest(): void {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test("CatuiAcpClient initializes and creates a session for the vault root", async () => {
|
|
31
|
+
const peer = new FakePeer();
|
|
32
|
+
const client = new CatuiAcpClient(peer, "/vault");
|
|
33
|
+
|
|
34
|
+
await client.initialize();
|
|
35
|
+
await client.newSession();
|
|
36
|
+
|
|
37
|
+
assert.equal(peer.calls[0]!.method, "initialize");
|
|
38
|
+
assert.deepEqual(peer.calls[1], { method: "session/new", params: { cwd: "/vault", mcpServers: [] } });
|
|
39
|
+
assert.equal(client.sessionId, "s1");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("CatuiAcpClient sends prompt as ACP text content", async () => {
|
|
43
|
+
const peer = new FakePeer();
|
|
44
|
+
const client = new CatuiAcpClient(peer, "/vault");
|
|
45
|
+
await client.newSession();
|
|
46
|
+
await client.prompt("Hello");
|
|
47
|
+
|
|
48
|
+
assert.deepEqual(peer.calls.at(-1), {
|
|
49
|
+
method: "session/prompt",
|
|
50
|
+
params: { sessionId: "s1", prompt: [{ type: "text", text: "Hello" }] },
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("CatuiAcpClient cancels by notification", async () => {
|
|
55
|
+
const peer = new FakePeer();
|
|
56
|
+
const client = new CatuiAcpClient(peer, "/vault");
|
|
57
|
+
await client.newSession();
|
|
58
|
+
client.cancel();
|
|
59
|
+
|
|
60
|
+
assert.deepEqual(peer.calls.at(-1), { method: "session/cancel", params: { sessionId: "s1" } });
|
|
61
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { EventEmitter } from "node:events";
|
|
4
|
+
import { NdjsonRpcPeer } from "../src/acp/ndjson-rpc";
|
|
5
|
+
|
|
6
|
+
class FakeReadable extends EventEmitter {
|
|
7
|
+
setEncoding(_encoding: BufferEncoding): void {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class FakeWritable {
|
|
11
|
+
chunks: string[] = [];
|
|
12
|
+
write(chunk: string): boolean {
|
|
13
|
+
this.chunks.push(chunk);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("NdjsonRpcPeer sends requests with incrementing ids", async () => {
|
|
19
|
+
const input = new FakeReadable();
|
|
20
|
+
const output = new FakeWritable();
|
|
21
|
+
const peer = new NdjsonRpcPeer(input as never, output as never);
|
|
22
|
+
|
|
23
|
+
const promise = peer.request("initialize", { protocolVersion: 1 });
|
|
24
|
+
input.emit("data", '{"jsonrpc":"2.0","id":1,"result":{"ok":true}}\n');
|
|
25
|
+
|
|
26
|
+
assert.deepEqual(await promise, { ok: true });
|
|
27
|
+
assert.equal(JSON.parse(output.chunks[0]!).method, "initialize");
|
|
28
|
+
assert.equal(JSON.parse(output.chunks[0]!).id, 1);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("NdjsonRpcPeer dispatches agent requests and writes responses", async () => {
|
|
32
|
+
const input = new FakeReadable();
|
|
33
|
+
const output = new FakeWritable();
|
|
34
|
+
const peer = new NdjsonRpcPeer(input as never, output as never);
|
|
35
|
+
|
|
36
|
+
peer.onRequest("session/request_permission", async () => ({
|
|
37
|
+
outcome: { outcome: "selected", optionId: "allow_once" },
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
input.emit("data", '{"jsonrpc":"2.0","id":9,"method":"session/request_permission","params":{"sessionId":"s"}}\n');
|
|
41
|
+
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
43
|
+
const response = JSON.parse(output.chunks[0]!);
|
|
44
|
+
assert.equal(response.id, 9);
|
|
45
|
+
assert.equal(response.result.outcome.optionId, "allow_once");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("NdjsonRpcPeer dispatches notifications", () => {
|
|
49
|
+
const input = new FakeReadable();
|
|
50
|
+
const output = new FakeWritable();
|
|
51
|
+
const peer = new NdjsonRpcPeer(input as never, output as never);
|
|
52
|
+
const seen: unknown[] = [];
|
|
53
|
+
|
|
54
|
+
peer.onNotification("session/update", (params) => seen.push(params));
|
|
55
|
+
input.emit("data", '{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"s","update":{"sessionUpdate":"agent_message_chunk"}}}\n');
|
|
56
|
+
|
|
57
|
+
assert.deepEqual(seen, [{ sessionId: "s", update: { sessionUpdate: "agent_message_chunk" } }]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("NdjsonRpcPeer ignores non-json startup output without rejecting pending requests", async () => {
|
|
61
|
+
const input = new FakeReadable();
|
|
62
|
+
const output = new FakeWritable();
|
|
63
|
+
const peer = new NdjsonRpcPeer(input as never, output as never);
|
|
64
|
+
const errors: string[] = [];
|
|
65
|
+
peer.onError((error) => errors.push(error.message));
|
|
66
|
+
|
|
67
|
+
const promise = peer.request("initialize", {});
|
|
68
|
+
input.emit("data", "MCP: 3 server(s) ready\n");
|
|
69
|
+
input.emit("data", '{"jsonrpc":"2.0","id":1,"result":{"ok":true}}\n');
|
|
70
|
+
|
|
71
|
+
assert.deepEqual(await promise, { ok: true });
|
|
72
|
+
assert.equal(errors.length, 1);
|
|
73
|
+
assert.match(errors[0]!, /Ignored non-ACP output/);
|
|
74
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { classifyPermissionRisk, createPermissionOutcome } from "../src/policy/permission-policy";
|
|
4
|
+
|
|
5
|
+
test("classifyPermissionRisk blocks raw writes", () => {
|
|
6
|
+
const risk = classifyPermissionRisk({
|
|
7
|
+
protectRawWrites: true,
|
|
8
|
+
rawInput: { file_path: "/vault/raw/设计/source.md" },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
assert.equal(risk.level, "blocked");
|
|
12
|
+
assert.match(risk.reason, /raw/);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("classifyPermissionRisk allows wiki edits to be reviewed", () => {
|
|
16
|
+
const risk = classifyPermissionRisk({
|
|
17
|
+
protectRawWrites: true,
|
|
18
|
+
rawInput: { file_path: "/vault/wiki/design/AI应用.md" },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
assert.equal(risk.level, "review");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("createPermissionOutcome maps selected option ids", () => {
|
|
25
|
+
assert.deepEqual(createPermissionOutcome("allow_once"), {
|
|
26
|
+
outcome: { outcome: "selected", optionId: "allow_once" },
|
|
27
|
+
});
|
|
28
|
+
assert.deepEqual(createPermissionOutcome("cancelled"), {
|
|
29
|
+
outcome: { outcome: "cancelled" },
|
|
30
|
+
});
|
|
31
|
+
});
|