@mseep/obsidian-agent-client 0.10.6

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 (146) hide show
  1. package/.claude/hooks/gh-setup.sh +49 -0
  2. package/.claude/settings.json +15 -0
  3. package/.claude/skills/release-notes/SKILL.md +331 -0
  4. package/.editorconfig +10 -0
  5. package/.github/FUNDING.yml +2 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +90 -0
  7. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.yml +59 -0
  9. package/.github/copilot-instructions.md +45 -0
  10. package/.github/pull_request_template.md +32 -0
  11. package/.github/workflows/ci.yaml +25 -0
  12. package/.github/workflows/docs.yml +58 -0
  13. package/.github/workflows/relay_to_openclaw.yml +59 -0
  14. package/.github/workflows/release.yaml +45 -0
  15. package/.prettierignore +10 -0
  16. package/.prettierrc +13 -0
  17. package/.vscode/extensions.json +7 -0
  18. package/.vscode/settings.json +37 -0
  19. package/.zed/settings.json +42 -0
  20. package/AGENTS.md +330 -0
  21. package/ARCHITECTURE.md +390 -0
  22. package/CONTRIBUTING.md +216 -0
  23. package/LICENSE +202 -0
  24. package/NOTICE +2 -0
  25. package/README.ja.md +121 -0
  26. package/README.md +125 -0
  27. package/docs/.vitepress/config.mts +124 -0
  28. package/docs/.vitepress/theme/custom.css +111 -0
  29. package/docs/.vitepress/theme/index.ts +4 -0
  30. package/docs/agent-setup/claude-code.md +84 -0
  31. package/docs/agent-setup/codex.md +76 -0
  32. package/docs/agent-setup/custom-agents.md +67 -0
  33. package/docs/agent-setup/gemini-cli.md +99 -0
  34. package/docs/agent-setup/index.md +34 -0
  35. package/docs/announcements/gemini-cli-deprecation.md +73 -0
  36. package/docs/getting-started/index.md +78 -0
  37. package/docs/getting-started/quick-start.md +38 -0
  38. package/docs/help/faq.md +181 -0
  39. package/docs/help/troubleshooting.md +221 -0
  40. package/docs/index.md +63 -0
  41. package/docs/public/apple-touch-icon.png +0 -0
  42. package/docs/public/demo.mp4 +0 -0
  43. package/docs/public/favicon-16x16.png +0 -0
  44. package/docs/public/favicon-32x32.png +0 -0
  45. package/docs/public/favicon.ico +0 -0
  46. package/docs/public/images/editing.webp +0 -0
  47. package/docs/public/images/export.webp +0 -0
  48. package/docs/public/images/floating-chat-button.webp +0 -0
  49. package/docs/public/images/floating-chat-instance-menu.webp +0 -0
  50. package/docs/public/images/floating-chat-view.webp +0 -0
  51. package/docs/public/images/mode-selection.webp +0 -0
  52. package/docs/public/images/model-selection.webp +0 -0
  53. package/docs/public/images/multi-session.webp +0 -0
  54. package/docs/public/images/remove-image.webp +0 -0
  55. package/docs/public/images/ribbon-icon.webp +0 -0
  56. package/docs/public/images/selection-context.gif +0 -0
  57. package/docs/public/images/sending-images.webp +0 -0
  58. package/docs/public/images/sending-messages.webp +0 -0
  59. package/docs/public/images/session-history-button.webp +0 -0
  60. package/docs/public/images/slash-commands-1.webp +0 -0
  61. package/docs/public/images/slash-commands-2.webp +0 -0
  62. package/docs/public/images/switch-agent.webp +0 -0
  63. package/docs/public/images/switch-default-agent.webp +0 -0
  64. package/docs/public/images/temporary-disable.gif +0 -0
  65. package/docs/reference/acp-support.md +110 -0
  66. package/docs/usage/chat-export.md +80 -0
  67. package/docs/usage/commands.md +51 -0
  68. package/docs/usage/context-files.md +57 -0
  69. package/docs/usage/editing.md +69 -0
  70. package/docs/usage/floating-chat.md +84 -0
  71. package/docs/usage/index.md +97 -0
  72. package/docs/usage/mcp-tools.md +33 -0
  73. package/docs/usage/mentions.md +70 -0
  74. package/docs/usage/mode-selection.md +28 -0
  75. package/docs/usage/model-selection.md +32 -0
  76. package/docs/usage/multi-session.md +68 -0
  77. package/docs/usage/sending-images.md +64 -0
  78. package/docs/usage/session-history.md +91 -0
  79. package/docs/usage/slash-commands.md +44 -0
  80. package/esbuild.config.mjs +49 -0
  81. package/eslint.config.mjs +25 -0
  82. package/main.js +228 -0
  83. package/manifest.json +11 -0
  84. package/package.json +52 -0
  85. package/src/acp/acp-client.ts +921 -0
  86. package/src/acp/acp-handler.ts +252 -0
  87. package/src/acp/permission-handler.ts +282 -0
  88. package/src/acp/terminal-handler.ts +264 -0
  89. package/src/acp/type-converter.ts +272 -0
  90. package/src/hooks/useAgent.ts +250 -0
  91. package/src/hooks/useAgentMessages.ts +470 -0
  92. package/src/hooks/useAgentSession.ts +544 -0
  93. package/src/hooks/useChatActions.ts +400 -0
  94. package/src/hooks/useHistoryModal.ts +219 -0
  95. package/src/hooks/useSessionHistory.ts +863 -0
  96. package/src/hooks/useSettings.ts +19 -0
  97. package/src/hooks/useSuggestions.ts +342 -0
  98. package/src/main.ts +9 -0
  99. package/src/plugin.ts +1126 -0
  100. package/src/services/chat-exporter.ts +552 -0
  101. package/src/services/message-sender.ts +755 -0
  102. package/src/services/message-state.ts +375 -0
  103. package/src/services/session-helpers.ts +211 -0
  104. package/src/services/session-state.ts +130 -0
  105. package/src/services/session-storage.ts +267 -0
  106. package/src/services/settings-normalizer.ts +255 -0
  107. package/src/services/settings-service.ts +285 -0
  108. package/src/services/update-checker.ts +128 -0
  109. package/src/services/vault-service.ts +558 -0
  110. package/src/services/view-registry.ts +345 -0
  111. package/src/types/agent.ts +92 -0
  112. package/src/types/chat.ts +351 -0
  113. package/src/types/errors.ts +136 -0
  114. package/src/types/obsidian-internals.d.ts +14 -0
  115. package/src/types/session.ts +731 -0
  116. package/src/ui/ChangeDirectoryModal.ts +137 -0
  117. package/src/ui/ChatContext.ts +25 -0
  118. package/src/ui/ChatHeader.tsx +295 -0
  119. package/src/ui/ChatPanel.tsx +1162 -0
  120. package/src/ui/ChatView.tsx +348 -0
  121. package/src/ui/ErrorBanner.tsx +104 -0
  122. package/src/ui/FloatingButton.tsx +351 -0
  123. package/src/ui/FloatingChatView.tsx +531 -0
  124. package/src/ui/InputArea.tsx +1107 -0
  125. package/src/ui/InputToolbar.tsx +371 -0
  126. package/src/ui/MessageBubble.tsx +442 -0
  127. package/src/ui/MessageList.tsx +265 -0
  128. package/src/ui/PermissionBanner.tsx +61 -0
  129. package/src/ui/SessionHistoryModal.tsx +821 -0
  130. package/src/ui/SettingsTab.ts +1337 -0
  131. package/src/ui/SuggestionPopup.tsx +138 -0
  132. package/src/ui/TerminalBlock.tsx +107 -0
  133. package/src/ui/ToolCallBlock.tsx +456 -0
  134. package/src/ui/shared/AttachmentStrip.tsx +57 -0
  135. package/src/ui/shared/IconButton.tsx +55 -0
  136. package/src/ui/shared/MarkdownRenderer.tsx +103 -0
  137. package/src/ui/view-host.ts +56 -0
  138. package/src/utils/error-utils.ts +274 -0
  139. package/src/utils/logger.ts +44 -0
  140. package/src/utils/mention-parser.ts +129 -0
  141. package/src/utils/paths.ts +246 -0
  142. package/src/utils/platform.ts +425 -0
  143. package/styles.css +2322 -0
  144. package/tsconfig.json +18 -0
  145. package/version-bump.mjs +18 -0
  146. package/versions.json +3 -0
@@ -0,0 +1,19 @@
1
+ import { useSyncExternalStore } from "react";
2
+ import type AgentClientPlugin from "../plugin";
3
+
4
+ /**
5
+ * Hook for subscribing to plugin settings changes.
6
+ *
7
+ * Uses useSyncExternalStore to safely subscribe to the external settings store,
8
+ * ensuring React re-renders when settings change.
9
+ *
10
+ * @param plugin - Plugin instance containing the settings store
11
+ * @returns Current settings snapshot (AgentClientPluginSettings)
12
+ */
13
+ export function useSettings(plugin: AgentClientPlugin) {
14
+ return useSyncExternalStore(
15
+ plugin.settingsService.subscribe,
16
+ plugin.settingsService.getSnapshot,
17
+ plugin.settingsService.getSnapshot,
18
+ );
19
+ }
@@ -0,0 +1,342 @@
1
+ import { useState, useCallback, useMemo } from "react";
2
+ import type { NoteMetadata, IVaultAccess } from "../services/vault-service";
3
+ import {
4
+ detectMention,
5
+ replaceMention,
6
+ type MentionContext,
7
+ } from "../utils/mention-parser";
8
+ import type { SlashCommand } from "../types/session";
9
+ import type AgentClientPlugin from "../plugin";
10
+
11
+ // ============================================================================
12
+ // Types
13
+ // ============================================================================
14
+
15
+ export interface MentionsState {
16
+ /** Note suggestions matching the current mention query */
17
+ suggestions: NoteMetadata[];
18
+ /** Currently selected index in the dropdown */
19
+ selectedIndex: number;
20
+ /** Whether the dropdown is open */
21
+ isOpen: boolean;
22
+ /** Current mention context (query, position, etc.) */
23
+ context: MentionContext | null;
24
+
25
+ /** Update mention suggestions based on current input */
26
+ updateSuggestions: (input: string, cursorPosition: number) => Promise<void>;
27
+ /** Select a note from the dropdown. Returns updated input text */
28
+ selectSuggestion: (input: string, suggestion: NoteMetadata) => string;
29
+ /** Navigate the dropdown selection */
30
+ navigate: (direction: "up" | "down") => void;
31
+ /** Close the dropdown */
32
+ close: () => void;
33
+
34
+ /** Currently active note for auto-mention */
35
+ activeNote: NoteMetadata | null;
36
+ /** Whether auto-mention is temporarily disabled */
37
+ isAutoMentionDisabled: boolean;
38
+ /** Toggle auto-mention enabled/disabled state */
39
+ toggleAutoMention: (disabled?: boolean) => void;
40
+ /** Update the active note from the vault */
41
+ updateActiveNote: () => Promise<void>;
42
+ }
43
+
44
+ export interface CommandsState {
45
+ /** Filtered slash command suggestions */
46
+ suggestions: SlashCommand[];
47
+ /** Currently selected index in the dropdown */
48
+ selectedIndex: number;
49
+ /** Whether the dropdown is open */
50
+ isOpen: boolean;
51
+
52
+ /** Update slash command suggestions based on current input */
53
+ updateSuggestions: (input: string, cursorPosition: number) => void;
54
+ /** Select a slash command from the dropdown. Returns updated input text */
55
+ selectSuggestion: (input: string, command: SlashCommand) => string;
56
+ /** Navigate the dropdown selection */
57
+ navigate: (direction: "up" | "down") => void;
58
+ /** Close the dropdown */
59
+ close: () => void;
60
+ }
61
+
62
+ // Backward-compatible type aliases
63
+ export type UseMentionsReturn = MentionsState;
64
+ export type UseSlashCommandsReturn = CommandsState;
65
+
66
+ export interface UseSuggestionsReturn {
67
+ /** Mention dropdown state and operations */
68
+ mentions: MentionsState;
69
+ /** Slash command dropdown state and operations */
70
+ commands: CommandsState;
71
+ }
72
+
73
+ // ============================================================================
74
+ // Hook Implementation
75
+ // ============================================================================
76
+
77
+ /**
78
+ * Hook for managing input suggestions (mentions + slash commands).
79
+ *
80
+ * Handles:
81
+ * - @-mention detection, note searching, and dropdown interaction
82
+ * - /-command filtering and selection
83
+ * - Auto-mention toggle coordination (slash commands disable auto-mention)
84
+ *
85
+ * @param vaultAccess - Vault access for note searching
86
+ * @param plugin - Plugin instance for settings and configuration
87
+ * @param availableCommands - Available slash commands from the agent session
88
+ */
89
+ export function useSuggestions(
90
+ vaultAccess: IVaultAccess,
91
+ plugin: AgentClientPlugin,
92
+ availableCommands: SlashCommand[],
93
+ ): UseSuggestionsReturn {
94
+ // ============================================================
95
+ // Mention State
96
+ // ============================================================
97
+
98
+ const [mentionSuggestions, setMentionSuggestions] = useState<
99
+ NoteMetadata[]
100
+ >([]);
101
+ const [mentionSelectedIndex, setMentionSelectedIndex] = useState(0);
102
+ const [mentionContext, setMentionContext] = useState<MentionContext | null>(
103
+ null,
104
+ );
105
+ const [activeNote, setActiveNote] = useState<NoteMetadata | null>(null);
106
+ const [isAutoMentionDisabled, setIsAutoMentionDisabled] = useState(false);
107
+
108
+ const mentionIsOpen =
109
+ mentionSuggestions.length > 0 && mentionContext !== null;
110
+
111
+ // ============================================================
112
+ // Command State
113
+ // ============================================================
114
+
115
+ const [commandSuggestions, setCommandSuggestions] = useState<
116
+ SlashCommand[]
117
+ >([]);
118
+ const [commandSelectedIndex, setCommandSelectedIndex] = useState(0);
119
+
120
+ const commandIsOpen = commandSuggestions.length > 0;
121
+
122
+ // ============================================================
123
+ // Auto-mention toggle (shared between mentions and commands)
124
+ // ============================================================
125
+
126
+ const toggleAutoMention = useCallback((disabled?: boolean) => {
127
+ if (disabled === undefined) {
128
+ setIsAutoMentionDisabled((prev) => !prev);
129
+ } else {
130
+ setIsAutoMentionDisabled(disabled);
131
+ }
132
+ }, []);
133
+
134
+ // ============================================================
135
+ // Mention Callbacks
136
+ // ============================================================
137
+
138
+ const mentionUpdateSuggestions = useCallback(
139
+ async (input: string, cursorPosition: number) => {
140
+ const ctx = detectMention(input, cursorPosition);
141
+
142
+ if (!ctx) {
143
+ setMentionSuggestions([]);
144
+ setMentionSelectedIndex(0);
145
+ setMentionContext(null);
146
+ return;
147
+ }
148
+
149
+ const results = await vaultAccess.searchNotes(ctx.query);
150
+ setMentionSuggestions(results);
151
+ setMentionSelectedIndex(0);
152
+ setMentionContext(ctx);
153
+ },
154
+ [vaultAccess, plugin],
155
+ );
156
+
157
+ const mentionSelectSuggestion = useCallback(
158
+ (input: string, suggestion: NoteMetadata): string => {
159
+ if (!mentionContext) {
160
+ return input;
161
+ }
162
+
163
+ const { newText } = replaceMention(
164
+ input,
165
+ mentionContext,
166
+ suggestion.name,
167
+ );
168
+
169
+ setMentionSuggestions([]);
170
+ setMentionSelectedIndex(0);
171
+ setMentionContext(null);
172
+
173
+ return newText;
174
+ },
175
+ [mentionContext],
176
+ );
177
+
178
+ const mentionNavigate = useCallback(
179
+ (direction: "up" | "down") => {
180
+ if (!mentionIsOpen) return;
181
+
182
+ const maxIndex = mentionSuggestions.length - 1;
183
+ setMentionSelectedIndex((prev) => {
184
+ if (direction === "down") {
185
+ return Math.min(prev + 1, maxIndex);
186
+ } else {
187
+ return Math.max(prev - 1, 0);
188
+ }
189
+ });
190
+ },
191
+ [mentionIsOpen, mentionSuggestions.length],
192
+ );
193
+
194
+ const mentionClose = useCallback(() => {
195
+ setMentionSuggestions([]);
196
+ setMentionSelectedIndex(0);
197
+ setMentionContext(null);
198
+ }, []);
199
+
200
+ const updateActiveNote = useCallback(async () => {
201
+ const note = await vaultAccess.getActiveNote();
202
+ setActiveNote(note);
203
+ }, [vaultAccess]);
204
+
205
+ // ============================================================
206
+ // Command Callbacks
207
+ // ============================================================
208
+
209
+ const commandUpdateSuggestions = useCallback(
210
+ (input: string, cursorPosition: number) => {
211
+ const wasOpen = commandSuggestions.length > 0;
212
+
213
+ // Slash commands only trigger at the very beginning of input
214
+ if (!input.startsWith("/")) {
215
+ // Re-enable auto-mention only if dropdown was showing
216
+ if (wasOpen) {
217
+ toggleAutoMention(false);
218
+ }
219
+ setCommandSuggestions([]);
220
+ setCommandSelectedIndex(0);
221
+ return;
222
+ }
223
+
224
+ // Extract query after '/'
225
+ const textUpToCursor = input.slice(0, cursorPosition);
226
+ const afterSlash = textUpToCursor.slice(1);
227
+
228
+ // If there's a space, the command is complete and user is typing arguments
229
+ if (afterSlash.includes(" ")) {
230
+ setCommandSuggestions([]);
231
+ setCommandSelectedIndex(0);
232
+ // Keep auto-mention disabled (slash command is still active)
233
+ toggleAutoMention(true);
234
+ return;
235
+ }
236
+
237
+ const query = afterSlash.toLowerCase();
238
+
239
+ // Filter available commands
240
+ const filtered = availableCommands.filter((cmd) =>
241
+ cmd.name.toLowerCase().includes(query),
242
+ );
243
+
244
+ setCommandSuggestions(filtered);
245
+ setCommandSelectedIndex(0);
246
+ // Disable auto-mention when slash command is detected
247
+ toggleAutoMention(true);
248
+ },
249
+ [availableCommands, toggleAutoMention, commandSuggestions.length],
250
+ );
251
+
252
+ const commandSelectSuggestion = useCallback(
253
+ (_input: string, command: SlashCommand): string => {
254
+ const commandText = `/${command.name} `;
255
+
256
+ setCommandSuggestions([]);
257
+ setCommandSelectedIndex(0);
258
+
259
+ return commandText;
260
+ },
261
+ [],
262
+ );
263
+
264
+ const commandNavigate = useCallback(
265
+ (direction: "up" | "down") => {
266
+ if (commandSuggestions.length === 0) return;
267
+
268
+ const maxIndex = commandSuggestions.length - 1;
269
+ setCommandSelectedIndex((current) => {
270
+ if (direction === "down") {
271
+ return Math.min(current + 1, maxIndex);
272
+ } else {
273
+ return Math.max(current - 1, 0);
274
+ }
275
+ });
276
+ },
277
+ [commandSuggestions.length],
278
+ );
279
+
280
+ const commandClose = useCallback(() => {
281
+ setCommandSuggestions([]);
282
+ setCommandSelectedIndex(0);
283
+ }, []);
284
+
285
+ // ============================================================
286
+ // Return
287
+ // ============================================================
288
+
289
+ const mentions = useMemo(
290
+ () => ({
291
+ suggestions: mentionSuggestions,
292
+ selectedIndex: mentionSelectedIndex,
293
+ isOpen: mentionIsOpen,
294
+ context: mentionContext,
295
+ updateSuggestions: mentionUpdateSuggestions,
296
+ selectSuggestion: mentionSelectSuggestion,
297
+ navigate: mentionNavigate,
298
+ close: mentionClose,
299
+ activeNote,
300
+ isAutoMentionDisabled,
301
+ toggleAutoMention,
302
+ updateActiveNote,
303
+ }),
304
+ [
305
+ mentionSuggestions,
306
+ mentionSelectedIndex,
307
+ mentionIsOpen,
308
+ mentionContext,
309
+ mentionUpdateSuggestions,
310
+ mentionSelectSuggestion,
311
+ mentionNavigate,
312
+ mentionClose,
313
+ activeNote,
314
+ isAutoMentionDisabled,
315
+ toggleAutoMention,
316
+ updateActiveNote,
317
+ ],
318
+ );
319
+
320
+ const commands = useMemo(
321
+ () => ({
322
+ suggestions: commandSuggestions,
323
+ selectedIndex: commandSelectedIndex,
324
+ isOpen: commandIsOpen,
325
+ updateSuggestions: commandUpdateSuggestions,
326
+ selectSuggestion: commandSelectSuggestion,
327
+ navigate: commandNavigate,
328
+ close: commandClose,
329
+ }),
330
+ [
331
+ commandSuggestions,
332
+ commandSelectedIndex,
333
+ commandIsOpen,
334
+ commandUpdateSuggestions,
335
+ commandSelectSuggestion,
336
+ commandNavigate,
337
+ commandClose,
338
+ ],
339
+ );
340
+
341
+ return useMemo(() => ({ mentions, commands }), [mentions, commands]);
342
+ }
package/src/main.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Main entry point for the Agent Client Plugin
3
+ *
4
+ * This file serves as the plugin entry point for Obsidian.
5
+ * The actual implementation is in plugin.ts
6
+ */
7
+
8
+ export { default } from "./plugin";
9
+ export type { AgentClientPluginSettings } from "./plugin";