@theia/ai-claude-code 1.67.0-next.59 → 1.67.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/lib/browser/claude-code-chat-agent.d.ts +8 -3
- package/lib/browser/claude-code-chat-agent.d.ts.map +1 -1
- package/lib/browser/claude-code-chat-agent.js +23 -10
- package/lib/browser/claude-code-chat-agent.js.map +1 -1
- package/lib/browser/claude-code-edit-tool-service.d.ts +2 -0
- package/lib/browser/claude-code-edit-tool-service.d.ts.map +1 -1
- package/lib/browser/claude-code-edit-tool-service.js +10 -5
- package/lib/browser/claude-code-edit-tool-service.js.map +1 -1
- package/lib/browser/claude-code-file-edit-backup-service.d.ts +2 -0
- package/lib/browser/claude-code-file-edit-backup-service.d.ts.map +1 -1
- package/lib/browser/claude-code-file-edit-backup-service.js +7 -1
- package/lib/browser/claude-code-file-edit-backup-service.js.map +1 -1
- package/lib/browser/claude-code-slash-commands-contribution.d.ts +24 -7
- package/lib/browser/claude-code-slash-commands-contribution.d.ts.map +1 -1
- package/lib/browser/claude-code-slash-commands-contribution.js +121 -57
- package/lib/browser/claude-code-slash-commands-contribution.js.map +1 -1
- package/lib/browser/renderers/web-fetch-tool-renderer.js +1 -1
- package/lib/browser/renderers/web-fetch-tool-renderer.js.map +1 -1
- package/lib/common/claude-code-service.d.ts +1 -0
- package/lib/common/claude-code-service.d.ts.map +1 -1
- package/lib/common/claude-code-service.js.map +1 -1
- package/package.json +11 -11
- package/src/browser/claude-code-chat-agent.ts +26 -12
- package/src/browser/claude-code-edit-tool-service.ts +10 -7
- package/src/browser/claude-code-file-edit-backup-service.ts +6 -2
- package/src/browser/claude-code-slash-commands-contribution.ts +152 -73
- package/src/browser/renderers/web-fetch-tool-renderer.tsx +1 -1
- package/src/common/claude-code-service.ts +1 -0
|
@@ -14,17 +14,18 @@
|
|
|
14
14
|
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
import { nls, URI } from '@theia/core';
|
|
17
|
+
import { PromptService } from '@theia/ai-core/lib/common/prompt-service';
|
|
18
|
+
import { DisposableCollection, ILogger, nls, URI } from '@theia/core';
|
|
19
19
|
import { FrontendApplicationContribution } from '@theia/core/lib/browser';
|
|
20
|
-
import {
|
|
21
|
-
import { inject, injectable } from '@theia/core/shared/inversify';
|
|
20
|
+
import { inject, injectable, named } from '@theia/core/shared/inversify';
|
|
22
21
|
import { FileService } from '@theia/filesystem/lib/browser/file-service';
|
|
23
|
-
import
|
|
22
|
+
import { FileChangeType } from '@theia/filesystem/lib/common/files';
|
|
24
23
|
import { WorkspaceService } from '@theia/workspace/lib/browser';
|
|
25
24
|
import { CLAUDE_CHAT_AGENT_ID } from './claude-code-chat-agent';
|
|
26
25
|
|
|
27
26
|
const CLAUDE_COMMANDS = '.claude/commands';
|
|
27
|
+
const COMMAND_FRAGMENT_PREFIX = 'claude-code-slash-';
|
|
28
|
+
const DYNAMIC_COMMAND_PREFIX = 'claude-code-dynamic-';
|
|
28
29
|
|
|
29
30
|
interface StaticSlashCommand {
|
|
30
31
|
name: string;
|
|
@@ -65,8 +66,11 @@ export class ClaudeCodeSlashCommandsContribution implements FrontendApplicationC
|
|
|
65
66
|
}
|
|
66
67
|
];
|
|
67
68
|
|
|
68
|
-
@inject(
|
|
69
|
-
protected readonly
|
|
69
|
+
@inject(ILogger) @named('claude-code')
|
|
70
|
+
protected readonly logger: ILogger;
|
|
71
|
+
|
|
72
|
+
@inject(PromptService)
|
|
73
|
+
protected readonly promptService: PromptService;
|
|
70
74
|
|
|
71
75
|
@inject(WorkspaceService)
|
|
72
76
|
protected readonly workspaceService: WorkspaceService;
|
|
@@ -74,86 +78,161 @@ export class ClaudeCodeSlashCommandsContribution implements FrontendApplicationC
|
|
|
74
78
|
@inject(FileService)
|
|
75
79
|
protected readonly fileService: FileService;
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
protected readonly toDispose = new DisposableCollection();
|
|
82
|
+
protected currentWorkspaceRoot: URI | undefined;
|
|
83
|
+
protected fileWatcherDisposable: DisposableCollection | undefined;
|
|
84
|
+
|
|
85
|
+
async onStart(): Promise<void> {
|
|
86
|
+
this.registerStaticCommands();
|
|
87
|
+
await this.initializeDynamicCommands();
|
|
88
|
+
|
|
89
|
+
this.toDispose.push(
|
|
90
|
+
this.workspaceService.onWorkspaceChanged(() => this.handleWorkspaceChange())
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
onStop(): void {
|
|
95
|
+
this.toDispose.dispose();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
protected registerStaticCommands(): void {
|
|
99
|
+
for (const command of this.staticCommands) {
|
|
100
|
+
this.promptService.addBuiltInPromptFragment({
|
|
101
|
+
id: `${COMMAND_FRAGMENT_PREFIX}${command.name}`,
|
|
102
|
+
template: `/${command.name}`,
|
|
103
|
+
isCommand: true,
|
|
104
|
+
commandName: command.name,
|
|
105
|
+
commandDescription: command.description,
|
|
106
|
+
commandAgents: [CLAUDE_CHAT_AGENT_ID]
|
|
107
|
+
});
|
|
92
108
|
}
|
|
109
|
+
}
|
|
93
110
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
protected async initializeDynamicCommands(): Promise<void> {
|
|
112
|
+
const workspaceRoot = this.getWorkspaceRoot();
|
|
113
|
+
if (!workspaceRoot) {
|
|
114
|
+
return;
|
|
97
115
|
}
|
|
98
116
|
|
|
117
|
+
this.currentWorkspaceRoot = workspaceRoot;
|
|
118
|
+
await this.registerDynamicCommandsForWorkspace(workspaceRoot);
|
|
119
|
+
this.setupFileWatcher(workspaceRoot);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
protected async registerDynamicCommandsForWorkspace(workspaceRoot: URI): Promise<void> {
|
|
123
|
+
const commandsUri = this.getCommandsUri(workspaceRoot);
|
|
124
|
+
const files = await this.listMarkdownFiles(commandsUri);
|
|
125
|
+
|
|
126
|
+
for (const filename of files) {
|
|
127
|
+
await this.registerDynamicCommand(commandsUri, filename);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
protected async registerDynamicCommand(commandsDir: URI, filename: string): Promise<void> {
|
|
132
|
+
const commandName = this.getCommandNameFromFilename(filename);
|
|
133
|
+
const fileUri = commandsDir.resolve(filename);
|
|
134
|
+
|
|
99
135
|
try {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
label: command.name,
|
|
108
|
-
range: completionRange,
|
|
109
|
-
detail: command.description
|
|
110
|
-
});
|
|
136
|
+
const content = await this.fileService.read(fileUri);
|
|
137
|
+
this.promptService.addBuiltInPromptFragment({
|
|
138
|
+
id: this.getDynamicCommandId(commandName),
|
|
139
|
+
template: content.value,
|
|
140
|
+
isCommand: true,
|
|
141
|
+
commandName,
|
|
142
|
+
commandAgents: [CLAUDE_CHAT_AGENT_ID]
|
|
111
143
|
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.logger.error(`Failed to register Claude Code slash command '${commandName}' from ${fileUri}:`, error);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
112
148
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const uri = roots[0].resource;
|
|
117
|
-
const claudeCommandsUri = uri.resolve(CLAUDE_COMMANDS);
|
|
118
|
-
const files = await this.listFilesDirectly(claudeCommandsUri);
|
|
119
|
-
const commands = files
|
|
120
|
-
.filter(file => file.endsWith('.md'))
|
|
121
|
-
.map(file => file.replace(/\.md$/, ''));
|
|
122
|
-
|
|
123
|
-
commands.forEach(commandName => {
|
|
124
|
-
suggestions.push({
|
|
125
|
-
insertText: `${commandName} `,
|
|
126
|
-
kind: monaco.languages.CompletionItemKind.Function,
|
|
127
|
-
label: commandName,
|
|
128
|
-
range: completionRange,
|
|
129
|
-
detail: nls.localize('theia/ai/claude-code/commandDetail', 'Claude command: {0}', commandName)
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
}
|
|
149
|
+
protected setupFileWatcher(workspaceRoot: URI): void {
|
|
150
|
+
this.fileWatcherDisposable?.dispose();
|
|
151
|
+
this.fileWatcherDisposable = new DisposableCollection();
|
|
133
152
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
153
|
+
const commandsUri = this.getCommandsUri(workspaceRoot);
|
|
154
|
+
|
|
155
|
+
this.fileWatcherDisposable.push(
|
|
156
|
+
this.fileService.onDidFilesChange(async event => {
|
|
157
|
+
const relevantChanges = event.changes.filter(change =>
|
|
158
|
+
this.isCommandFile(change.resource, commandsUri)
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (relevantChanges.length === 0) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (const change of relevantChanges) {
|
|
166
|
+
await this.handleFileChange(change.resource, change.type, commandsUri);
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
this.toDispose.push(this.fileWatcherDisposable);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
protected async handleFileChange(resource: URI, changeType: FileChangeType, commandsUri: URI): Promise<void> {
|
|
175
|
+
const filename = resource.path.base;
|
|
176
|
+
const commandName = this.getCommandNameFromFilename(filename);
|
|
177
|
+
const fragmentId = this.getDynamicCommandId(commandName);
|
|
178
|
+
|
|
179
|
+
if (changeType === FileChangeType.DELETED) {
|
|
180
|
+
this.promptService.removePromptFragment(fragmentId);
|
|
181
|
+
} else if (changeType === FileChangeType.ADDED || changeType === FileChangeType.UPDATED) {
|
|
182
|
+
await this.registerDynamicCommand(commandsUri, filename);
|
|
138
183
|
}
|
|
139
184
|
}
|
|
140
185
|
|
|
141
|
-
protected
|
|
142
|
-
const
|
|
143
|
-
const lineContent = model.getLineContent(position.lineNumber);
|
|
186
|
+
protected async handleWorkspaceChange(): Promise<void> {
|
|
187
|
+
const newRoot = this.getWorkspaceRoot();
|
|
144
188
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (characterBeforeCurrentWord !== triggerCharacter) {
|
|
148
|
-
return undefined;
|
|
189
|
+
if (this.currentWorkspaceRoot?.toString() === newRoot?.toString()) {
|
|
190
|
+
return;
|
|
149
191
|
}
|
|
150
192
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
193
|
+
await this.clearDynamicCommands();
|
|
194
|
+
this.currentWorkspaceRoot = newRoot;
|
|
195
|
+
await this.initializeDynamicCommands();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
protected async clearDynamicCommands(): Promise<void> {
|
|
199
|
+
if (!this.currentWorkspaceRoot) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const commandsUri = this.getCommandsUri(this.currentWorkspaceRoot);
|
|
204
|
+
const files = await this.listMarkdownFiles(commandsUri);
|
|
205
|
+
|
|
206
|
+
for (const filename of files) {
|
|
207
|
+
const commandName = this.getCommandNameFromFilename(filename);
|
|
208
|
+
this.promptService.removePromptFragment(this.getDynamicCommandId(commandName));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
protected getWorkspaceRoot(): URI | undefined {
|
|
213
|
+
const roots = this.workspaceService.tryGetRoots();
|
|
214
|
+
return roots.length > 0 ? roots[0].resource : undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
protected getCommandsUri(workspaceRoot: URI): URI {
|
|
218
|
+
return workspaceRoot.resolve(CLAUDE_COMMANDS);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
protected isCommandFile(resource: URI, commandsUri: URI): boolean {
|
|
222
|
+
return resource.toString().startsWith(commandsUri.toString()) && resource.path.ext === '.md';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
protected getCommandNameFromFilename(filename: string): string {
|
|
226
|
+
return filename.replace(/\.md$/, '');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
protected getDynamicCommandId(commandName: string): string {
|
|
230
|
+
return `${DYNAMIC_COMMAND_PREFIX}${commandName}`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
protected async listMarkdownFiles(uri: URI): Promise<string[]> {
|
|
234
|
+
const allFiles = await this.listFilesDirectly(uri);
|
|
235
|
+
return allFiles.filter(file => file.endsWith('.md'));
|
|
157
236
|
}
|
|
158
237
|
|
|
159
238
|
protected async listFilesDirectly(uri: URI): Promise<string[]> {
|
|
@@ -86,7 +86,7 @@ const WebFetchToolComponent: React.FC<{
|
|
|
86
86
|
const expandedContent = (
|
|
87
87
|
<div className="claude-code-tool details">
|
|
88
88
|
<div className="claude-code-tool detail-row">
|
|
89
|
-
<span className="claude-code-tool detail-label">{nls.
|
|
89
|
+
<span className="claude-code-tool detail-label">{nls.localizeByDefault('URL')}</span>
|
|
90
90
|
<code className="claude-code-tool detail-value">{input.url}</code>
|
|
91
91
|
</div>
|
|
92
92
|
<div className="claude-code-tool detail-row">
|