@cloudcli-ai/cloudcli 1.31.5 → 1.32.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/dist/assets/index-AaSE7-3t.js +1336 -0
- package/dist/assets/index-CKaku72N.css +32 -0
- package/dist/index.html +2 -2
- package/dist-server/server/claude-sdk.js +10 -5
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cursor-cli.js +0 -6
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +160 -34
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +11 -0
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/projects.db.js +12 -0
- package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -1
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js +61 -0
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +49 -8
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +1 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/projects/projects.routes.js +12 -3
- package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js +14 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +43 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +1 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +2 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +55 -2
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +153 -14
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +198 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +54 -2
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +21 -6
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +82 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +8 -30
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +189 -13
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +28 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +2 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +125 -2
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js +24 -8
- package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +7 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +33 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +2 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +23 -2
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +129 -13
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +7 -5
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +81 -7
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/skills.service.js +11 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -0
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +45 -0
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/tests/skills.test.js +226 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -0
- package/dist-server/server/openai-codex.js +56 -26
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/routes/commands.js +5 -5
- package/dist-server/server/routes/commands.js.map +1 -1
- package/dist-server/server/shared/claude-cli-path.js +103 -0
- package/dist-server/server/shared/claude-cli-path.js.map +1 -0
- package/dist-server/server/shared/claude-cli-path.test.js +45 -0
- package/dist-server/server/shared/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/{utils → shared}/frontmatter.js +3 -3
- package/dist-server/server/shared/frontmatter.js.map +1 -0
- package/dist-server/server/shared/utils.js +82 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/utils/commandParser.js +3 -3
- package/dist-server/server/utils/commandParser.js.map +1 -1
- package/dist-server/shared/modelConstants.js +1 -0
- package/dist-server/shared/modelConstants.js.map +1 -1
- package/package.json +1 -1
- package/server/claude-sdk.js +10 -5
- package/server/cursor-cli.js +0 -8
- package/server/gemini-cli.js +190 -42
- package/server/modules/database/migrations.ts +12 -0
- package/server/modules/database/repositories/projects.db.ts +13 -0
- package/server/modules/database/repositories/sessions.db.integration.test.ts +72 -0
- package/server/modules/database/repositories/sessions.db.ts +60 -9
- package/server/modules/database/schema.ts +1 -0
- package/server/modules/projects/projects.routes.ts +20 -3
- package/server/modules/projects/services/project-delete.service.ts +16 -1
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +64 -0
- package/server/modules/providers/README.md +346 -0
- package/server/modules/providers/index.ts +2 -1
- package/server/modules/providers/list/claude/claude-auth.provider.ts +8 -7
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +68 -2
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +175 -14
- package/server/modules/providers/list/claude/claude-skills.provider.ts +257 -0
- package/server/modules/providers/list/claude/claude.provider.ts +8 -1
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +62 -2
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +25 -6
- package/server/modules/providers/list/codex/codex-skills.provider.ts +100 -0
- package/server/modules/providers/list/codex/codex.provider.ts +8 -1
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +17 -40
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +216 -13
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +31 -0
- package/server/modules/providers/list/cursor/cursor.provider.ts +8 -1
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +158 -2
- package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +24 -20
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +7 -1
- package/server/modules/providers/list/gemini/gemini-skills.provider.ts +36 -0
- package/server/modules/providers/list/gemini/gemini.provider.ts +8 -1
- package/server/modules/providers/provider.routes.ts +35 -2
- package/server/modules/providers/services/session-conversations-search.service.ts +162 -13
- package/server/modules/providers/services/sessions-watcher.service.ts +7 -5
- package/server/modules/providers/services/sessions.service.ts +112 -9
- package/server/modules/providers/services/skills.service.ts +15 -0
- package/server/modules/providers/shared/base/abstract.provider.ts +2 -0
- package/server/modules/providers/shared/skills/skills.provider.ts +64 -0
- package/server/modules/providers/tests/skills.test.ts +446 -0
- package/server/openai-codex.js +59 -27
- package/server/routes/commands.js +7 -5
- package/server/shared/claude-cli-path.test.ts +61 -0
- package/server/shared/claude-cli-path.ts +139 -0
- package/server/{utils/frontmatter.js → shared/frontmatter.ts} +3 -3
- package/server/shared/interfaces.ts +19 -0
- package/server/shared/types.ts +78 -0
- package/server/shared/utils.ts +94 -0
- package/server/utils/commandParser.js +5 -3
- package/shared/modelConstants.js +1 -0
- package/dist/assets/index-6rYG_mRo.css +0 -32
- package/dist/assets/index-DqMVUeZS.js +0 -1331
- package/dist-server/server/utils/frontmatter.js.map +0 -1
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { providerSkillsService } from '@/modules/providers/services/skills.service.js';
|
|
8
|
+
|
|
9
|
+
const patchHomeDir = (nextHomeDir: string) => {
|
|
10
|
+
const original = os.homedir;
|
|
11
|
+
(os as any).homedir = () => nextHomeDir;
|
|
12
|
+
return () => {
|
|
13
|
+
(os as any).homedir = original;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const writeSkill = async (
|
|
18
|
+
skillsRoot: string,
|
|
19
|
+
directoryName: string,
|
|
20
|
+
name: string,
|
|
21
|
+
description: string,
|
|
22
|
+
): Promise<string> => {
|
|
23
|
+
const skillDir = path.join(skillsRoot, directoryName);
|
|
24
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
25
|
+
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
26
|
+
await fs.writeFile(
|
|
27
|
+
skillPath,
|
|
28
|
+
`---\nname: ${name}\ndescription: ${description}\n---\n\n`,
|
|
29
|
+
'utf8',
|
|
30
|
+
);
|
|
31
|
+
return skillPath;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const writeClaudePluginManifest = async (
|
|
35
|
+
installPath: string,
|
|
36
|
+
name: string,
|
|
37
|
+
): Promise<void> => {
|
|
38
|
+
const pluginConfigDir = path.join(installPath, '.claude-plugin');
|
|
39
|
+
await fs.mkdir(pluginConfigDir, { recursive: true });
|
|
40
|
+
await fs.writeFile(
|
|
41
|
+
path.join(pluginConfigDir, 'plugin.json'),
|
|
42
|
+
JSON.stringify(
|
|
43
|
+
{
|
|
44
|
+
name,
|
|
45
|
+
version: '0.1.0',
|
|
46
|
+
description: `${name} test plugin`,
|
|
47
|
+
},
|
|
48
|
+
null,
|
|
49
|
+
2,
|
|
50
|
+
),
|
|
51
|
+
'utf8',
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const writeClaudePluginCommand = async (
|
|
56
|
+
commandsRoot: string,
|
|
57
|
+
commandName: string,
|
|
58
|
+
description: string,
|
|
59
|
+
): Promise<string> => {
|
|
60
|
+
await fs.mkdir(commandsRoot, { recursive: true });
|
|
61
|
+
const commandPath = path.join(commandsRoot, `${commandName}.md`);
|
|
62
|
+
await fs.writeFile(
|
|
63
|
+
commandPath,
|
|
64
|
+
`---\ndescription: ${description}\nargument-hint: 'test args'\n---\n\nCommand body.\n`,
|
|
65
|
+
'utf8',
|
|
66
|
+
);
|
|
67
|
+
return commandPath;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* This test covers Claude user/project skill folders plus plugin discovery from
|
|
72
|
+
* installed plugin command files and fallback plugin skill files.
|
|
73
|
+
*/
|
|
74
|
+
test('providerSkillsService lists claude user, project, and enabled plugin skills', { concurrency: false }, async () => {
|
|
75
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-claude-'));
|
|
76
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
77
|
+
const commandPluginInstallPath = path.join(
|
|
78
|
+
tempRoot,
|
|
79
|
+
'.claude',
|
|
80
|
+
'plugins',
|
|
81
|
+
'cache',
|
|
82
|
+
'notion-plugin',
|
|
83
|
+
'notion',
|
|
84
|
+
'abc123',
|
|
85
|
+
);
|
|
86
|
+
const skillPluginInstallPath = path.join(
|
|
87
|
+
tempRoot,
|
|
88
|
+
'.claude',
|
|
89
|
+
'plugins',
|
|
90
|
+
'cache',
|
|
91
|
+
'anthropic-agent-skills',
|
|
92
|
+
'example-skills',
|
|
93
|
+
'def456',
|
|
94
|
+
);
|
|
95
|
+
const disabledPluginInstallPath = path.join(
|
|
96
|
+
tempRoot,
|
|
97
|
+
'.claude',
|
|
98
|
+
'plugins',
|
|
99
|
+
'cache',
|
|
100
|
+
'disabled-marketplace',
|
|
101
|
+
'disabled-skills',
|
|
102
|
+
'ghi789',
|
|
103
|
+
);
|
|
104
|
+
const emptyIdPluginInstallPath = path.join(
|
|
105
|
+
tempRoot,
|
|
106
|
+
'.claude',
|
|
107
|
+
'plugins',
|
|
108
|
+
'cache',
|
|
109
|
+
'invalid-empty-plugin',
|
|
110
|
+
'empty',
|
|
111
|
+
'000',
|
|
112
|
+
);
|
|
113
|
+
const atIdPluginInstallPath = path.join(
|
|
114
|
+
tempRoot,
|
|
115
|
+
'.claude',
|
|
116
|
+
'plugins',
|
|
117
|
+
'cache',
|
|
118
|
+
'invalid-at-plugin',
|
|
119
|
+
'at',
|
|
120
|
+
'000',
|
|
121
|
+
);
|
|
122
|
+
const siblingSkillPluginPath = path.join(path.dirname(skillPluginInstallPath), 'legacy777');
|
|
123
|
+
await fs.mkdir(workspacePath, { recursive: true });
|
|
124
|
+
|
|
125
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
126
|
+
try {
|
|
127
|
+
await writeSkill(
|
|
128
|
+
path.join(tempRoot, '.claude', 'skills'),
|
|
129
|
+
'claude-user-dir',
|
|
130
|
+
'claude-user',
|
|
131
|
+
'Claude user skill',
|
|
132
|
+
);
|
|
133
|
+
await writeSkill(
|
|
134
|
+
path.join(workspacePath, '.claude', 'skills'),
|
|
135
|
+
'claude-project-dir',
|
|
136
|
+
'claude-project',
|
|
137
|
+
'Claude project skill',
|
|
138
|
+
);
|
|
139
|
+
await writeClaudePluginManifest(commandPluginInstallPath, 'Notion');
|
|
140
|
+
await writeClaudePluginCommand(
|
|
141
|
+
path.join(commandPluginInstallPath, 'commands'),
|
|
142
|
+
'insert-row',
|
|
143
|
+
'Insert a Notion database row',
|
|
144
|
+
);
|
|
145
|
+
await writeSkill(
|
|
146
|
+
path.join(commandPluginInstallPath, 'skills'),
|
|
147
|
+
'ignored-command-plugin-skill-dir',
|
|
148
|
+
'ignored-command-plugin-skill',
|
|
149
|
+
'Command plugin fallback skill should be ignored',
|
|
150
|
+
);
|
|
151
|
+
await writeClaudePluginManifest(skillPluginInstallPath, 'ExampleSkills');
|
|
152
|
+
await writeSkill(
|
|
153
|
+
path.join(skillPluginInstallPath, 'skills'),
|
|
154
|
+
'claude-plugin-dir',
|
|
155
|
+
'claude-plugin',
|
|
156
|
+
'Claude plugin skill',
|
|
157
|
+
);
|
|
158
|
+
await writeSkill(
|
|
159
|
+
path.join(skillPluginInstallPath, 'skills'),
|
|
160
|
+
'claude-plugin-second-dir',
|
|
161
|
+
'claude-plugin-second',
|
|
162
|
+
'Second Claude plugin skill',
|
|
163
|
+
);
|
|
164
|
+
await writeSkill(
|
|
165
|
+
path.join(skillPluginInstallPath, 'skills', 'nested', 'collection'),
|
|
166
|
+
'claude-plugin-nested-dir',
|
|
167
|
+
'claude-plugin-nested',
|
|
168
|
+
'Nested Claude plugin skill',
|
|
169
|
+
);
|
|
170
|
+
await writeSkill(
|
|
171
|
+
path.join(siblingSkillPluginPath, 'skills'),
|
|
172
|
+
'claude-plugin-sibling-dir',
|
|
173
|
+
'claude-plugin-sibling',
|
|
174
|
+
'Sibling Claude plugin skill',
|
|
175
|
+
);
|
|
176
|
+
await writeClaudePluginManifest(disabledPluginInstallPath, 'DisabledSkills');
|
|
177
|
+
await writeClaudePluginCommand(
|
|
178
|
+
path.join(disabledPluginInstallPath, 'commands'),
|
|
179
|
+
'disabled-command',
|
|
180
|
+
'Disabled plugin command',
|
|
181
|
+
);
|
|
182
|
+
await writeClaudePluginCommand(
|
|
183
|
+
path.join(emptyIdPluginInstallPath, 'commands'),
|
|
184
|
+
'invalid-empty-command',
|
|
185
|
+
'Invalid empty id command',
|
|
186
|
+
);
|
|
187
|
+
await writeClaudePluginCommand(
|
|
188
|
+
path.join(atIdPluginInstallPath, 'commands'),
|
|
189
|
+
'invalid-at-command',
|
|
190
|
+
'Invalid at id command',
|
|
191
|
+
);
|
|
192
|
+
await writeSkill(
|
|
193
|
+
path.join(
|
|
194
|
+
disabledPluginInstallPath,
|
|
195
|
+
'skills',
|
|
196
|
+
),
|
|
197
|
+
'disabled-plugin-dir',
|
|
198
|
+
'disabled-plugin',
|
|
199
|
+
'Disabled plugin skill',
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
await fs.writeFile(
|
|
203
|
+
path.join(tempRoot, '.claude', 'settings.json'),
|
|
204
|
+
JSON.stringify(
|
|
205
|
+
{
|
|
206
|
+
enabledPlugins: {
|
|
207
|
+
'': true,
|
|
208
|
+
'@': true,
|
|
209
|
+
'notion@notion-marketplace': true,
|
|
210
|
+
'example-skills@anthropic-agent-skills': true,
|
|
211
|
+
'disabled-skills@disabled-marketplace': false,
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
null,
|
|
215
|
+
2,
|
|
216
|
+
),
|
|
217
|
+
'utf8',
|
|
218
|
+
);
|
|
219
|
+
await fs.writeFile(
|
|
220
|
+
path.join(tempRoot, '.claude', 'plugins', 'installed_plugins.json'),
|
|
221
|
+
JSON.stringify(
|
|
222
|
+
{
|
|
223
|
+
version: 2,
|
|
224
|
+
plugins: {
|
|
225
|
+
'': [
|
|
226
|
+
{
|
|
227
|
+
scope: 'user',
|
|
228
|
+
installPath: emptyIdPluginInstallPath,
|
|
229
|
+
version: '000',
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
'@': [
|
|
233
|
+
{
|
|
234
|
+
scope: 'user',
|
|
235
|
+
installPath: atIdPluginInstallPath,
|
|
236
|
+
version: '000',
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
'notion@notion-marketplace': [
|
|
240
|
+
{
|
|
241
|
+
scope: 'user',
|
|
242
|
+
installPath: commandPluginInstallPath,
|
|
243
|
+
version: 'abc123',
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
'example-skills@anthropic-agent-skills': [
|
|
247
|
+
{
|
|
248
|
+
scope: 'user',
|
|
249
|
+
installPath: skillPluginInstallPath,
|
|
250
|
+
version: 'def456',
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
'disabled-skills@disabled-marketplace': [
|
|
254
|
+
{
|
|
255
|
+
scope: 'user',
|
|
256
|
+
installPath: disabledPluginInstallPath,
|
|
257
|
+
version: 'ghi789',
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
null,
|
|
263
|
+
2,
|
|
264
|
+
),
|
|
265
|
+
'utf8',
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const skills = await providerSkillsService.listProviderSkills('claude', { workspacePath });
|
|
269
|
+
const byName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
270
|
+
|
|
271
|
+
assert.equal(byName.get('claude-user')?.scope, 'user');
|
|
272
|
+
assert.equal(byName.get('claude-user')?.command, '/claude-user');
|
|
273
|
+
assert.equal(byName.get('claude-project')?.scope, 'project');
|
|
274
|
+
assert.equal(byName.get('claude-project')?.command, '/claude-project');
|
|
275
|
+
|
|
276
|
+
const pluginCommand = byName.get('insert-row');
|
|
277
|
+
assert.equal(pluginCommand?.scope, 'plugin');
|
|
278
|
+
assert.equal(pluginCommand?.pluginName, 'Notion');
|
|
279
|
+
assert.equal(pluginCommand?.pluginId, 'notion@notion-marketplace');
|
|
280
|
+
assert.equal(pluginCommand?.command, '/Notion:insert-row');
|
|
281
|
+
assert.equal(pluginCommand?.description, 'Insert a Notion database row');
|
|
282
|
+
assert.match(pluginCommand?.sourcePath ?? '', /commands[\\/]insert-row\.md$/);
|
|
283
|
+
assert.equal(byName.has('ignored-command-plugin-skill'), false);
|
|
284
|
+
|
|
285
|
+
const pluginSkill = byName.get('claude-plugin');
|
|
286
|
+
assert.equal(pluginSkill?.scope, 'plugin');
|
|
287
|
+
assert.equal(pluginSkill?.pluginName, 'ExampleSkills');
|
|
288
|
+
assert.equal(pluginSkill?.pluginId, 'example-skills@anthropic-agent-skills');
|
|
289
|
+
assert.equal(pluginSkill?.command, '/ExampleSkills:claude-plugin');
|
|
290
|
+
assert.equal(pluginSkill?.description, 'Claude plugin skill');
|
|
291
|
+
assert.match(
|
|
292
|
+
pluginSkill?.sourcePath ?? '',
|
|
293
|
+
/cache[\\/]anthropic-agent-skills[\\/]example-skills[\\/]def456[\\/]skills[\\/]/,
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const secondPluginSkill = byName.get('claude-plugin-second');
|
|
297
|
+
assert.equal(secondPluginSkill?.scope, 'plugin');
|
|
298
|
+
assert.equal(secondPluginSkill?.command, '/ExampleSkills:claude-plugin-second');
|
|
299
|
+
|
|
300
|
+
const nestedPluginSkill = byName.get('claude-plugin-nested');
|
|
301
|
+
assert.equal(nestedPluginSkill?.scope, 'plugin');
|
|
302
|
+
assert.equal(nestedPluginSkill?.command, '/ExampleSkills:claude-plugin-nested');
|
|
303
|
+
assert.equal(nestedPluginSkill?.description, 'Nested Claude plugin skill');
|
|
304
|
+
|
|
305
|
+
const siblingPluginSkill = byName.get('claude-plugin-sibling');
|
|
306
|
+
assert.equal(siblingPluginSkill?.scope, 'plugin');
|
|
307
|
+
assert.equal(siblingPluginSkill?.pluginName, 'example-skills');
|
|
308
|
+
assert.equal(siblingPluginSkill?.command, '/example-skills:claude-plugin-sibling');
|
|
309
|
+
assert.equal(siblingPluginSkill?.description, 'Sibling Claude plugin skill');
|
|
310
|
+
assert.equal(byName.has('disabled-command'), false);
|
|
311
|
+
assert.equal(byName.has('disabled-plugin'), false);
|
|
312
|
+
assert.equal(byName.has('invalid-empty-command'), false);
|
|
313
|
+
assert.equal(byName.has('invalid-at-command'), false);
|
|
314
|
+
assert.equal(skills.some((skill) => skill.command.startsWith('/:')), false);
|
|
315
|
+
} finally {
|
|
316
|
+
restoreHomeDir();
|
|
317
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* This test covers Codex repository/user/system skill folders and verifies that
|
|
323
|
+
* repository lookup includes cwd, parent, and git root skill locations.
|
|
324
|
+
*/
|
|
325
|
+
test('providerSkillsService lists codex repository, user, and system skills', { concurrency: false }, async () => {
|
|
326
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-codex-'));
|
|
327
|
+
const repoRoot = path.join(tempRoot, 'repo');
|
|
328
|
+
const workspacePath = path.join(repoRoot, 'packages', 'app');
|
|
329
|
+
await fs.mkdir(path.join(repoRoot, '.git'), { recursive: true });
|
|
330
|
+
await fs.mkdir(workspacePath, { recursive: true });
|
|
331
|
+
|
|
332
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
333
|
+
try {
|
|
334
|
+
await writeSkill(
|
|
335
|
+
path.join(workspacePath, '.agents', 'skills'),
|
|
336
|
+
'codex-cwd-dir',
|
|
337
|
+
'codex-cwd',
|
|
338
|
+
'Codex cwd skill',
|
|
339
|
+
);
|
|
340
|
+
await writeSkill(
|
|
341
|
+
path.join(repoRoot, 'packages', '.agents', 'skills'),
|
|
342
|
+
'codex-parent-dir',
|
|
343
|
+
'codex-parent',
|
|
344
|
+
'Codex parent skill',
|
|
345
|
+
);
|
|
346
|
+
await writeSkill(
|
|
347
|
+
path.join(repoRoot, '.agents', 'skills'),
|
|
348
|
+
'codex-root-dir',
|
|
349
|
+
'codex-root',
|
|
350
|
+
'Codex root skill',
|
|
351
|
+
);
|
|
352
|
+
await writeSkill(
|
|
353
|
+
path.join(tempRoot, '.agents', 'skills'),
|
|
354
|
+
'codex-user-dir',
|
|
355
|
+
'codex-user',
|
|
356
|
+
'Codex user skill',
|
|
357
|
+
);
|
|
358
|
+
await writeSkill(
|
|
359
|
+
path.join(tempRoot, '.codex', 'skills', '.system'),
|
|
360
|
+
'codex-system-dir',
|
|
361
|
+
'codex-system',
|
|
362
|
+
'Codex system skill',
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const skills = await providerSkillsService.listProviderSkills('codex', { workspacePath });
|
|
366
|
+
const byName = new Map(skills.map((skill) => [skill.name, skill]));
|
|
367
|
+
|
|
368
|
+
assert.equal(byName.get('codex-cwd')?.scope, 'repo');
|
|
369
|
+
assert.equal(byName.get('codex-parent')?.scope, 'repo');
|
|
370
|
+
assert.equal(byName.get('codex-root')?.scope, 'repo');
|
|
371
|
+
assert.equal(byName.get('codex-user')?.scope, 'user');
|
|
372
|
+
assert.equal(byName.get('codex-system')?.scope, 'system');
|
|
373
|
+
assert.equal(byName.get('codex-root')?.command, '$codex-root');
|
|
374
|
+
} finally {
|
|
375
|
+
restoreHomeDir();
|
|
376
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* This test covers Gemini and Cursor skill directory rules, including shared
|
|
382
|
+
* `.agents/skills` project support.
|
|
383
|
+
*/
|
|
384
|
+
test('providerSkillsService lists gemini and cursor skills from their configured directories', { concurrency: false }, async () => {
|
|
385
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-skills-gc-'));
|
|
386
|
+
const workspacePath = path.join(tempRoot, 'workspace');
|
|
387
|
+
await fs.mkdir(workspacePath, { recursive: true });
|
|
388
|
+
|
|
389
|
+
const restoreHomeDir = patchHomeDir(tempRoot);
|
|
390
|
+
try {
|
|
391
|
+
await writeSkill(
|
|
392
|
+
path.join(tempRoot, '.gemini', 'skills'),
|
|
393
|
+
'gemini-user-dir',
|
|
394
|
+
'gemini-user',
|
|
395
|
+
'Gemini user skill',
|
|
396
|
+
);
|
|
397
|
+
await writeSkill(
|
|
398
|
+
path.join(tempRoot, '.agents', 'skills'),
|
|
399
|
+
'agents-user-dir',
|
|
400
|
+
'agents-user',
|
|
401
|
+
'Agents user skill',
|
|
402
|
+
);
|
|
403
|
+
await writeSkill(
|
|
404
|
+
path.join(workspacePath, '.gemini', 'skills'),
|
|
405
|
+
'gemini-project-dir',
|
|
406
|
+
'gemini-project',
|
|
407
|
+
'Gemini project skill',
|
|
408
|
+
);
|
|
409
|
+
await writeSkill(
|
|
410
|
+
path.join(workspacePath, '.agents', 'skills'),
|
|
411
|
+
'agents-project-dir',
|
|
412
|
+
'agents-project',
|
|
413
|
+
'Agents project skill',
|
|
414
|
+
);
|
|
415
|
+
await writeSkill(
|
|
416
|
+
path.join(workspacePath, '.cursor', 'skills'),
|
|
417
|
+
'cursor-project-dir',
|
|
418
|
+
'cursor-project',
|
|
419
|
+
'Cursor project skill',
|
|
420
|
+
);
|
|
421
|
+
await writeSkill(
|
|
422
|
+
path.join(tempRoot, '.cursor', 'skills'),
|
|
423
|
+
'cursor-user-dir',
|
|
424
|
+
'cursor-user',
|
|
425
|
+
'Cursor user skill',
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
const geminiSkills = await providerSkillsService.listProviderSkills('gemini', { workspacePath });
|
|
429
|
+
const geminiByName = new Map(geminiSkills.map((skill) => [skill.name, skill]));
|
|
430
|
+
assert.equal(geminiByName.get('gemini-user')?.scope, 'user');
|
|
431
|
+
assert.equal(geminiByName.get('agents-user')?.scope, 'user');
|
|
432
|
+
assert.equal(geminiByName.get('gemini-project')?.scope, 'project');
|
|
433
|
+
assert.equal(geminiByName.get('agents-project')?.scope, 'project');
|
|
434
|
+
assert.equal(geminiByName.get('gemini-project')?.command, '/gemini-project');
|
|
435
|
+
|
|
436
|
+
const cursorSkills = await providerSkillsService.listProviderSkills('cursor', { workspacePath });
|
|
437
|
+
const cursorByName = new Map(cursorSkills.map((skill) => [skill.name, skill]));
|
|
438
|
+
assert.equal(cursorByName.get('agents-project')?.scope, 'project');
|
|
439
|
+
assert.equal(cursorByName.get('cursor-project')?.scope, 'project');
|
|
440
|
+
assert.equal(cursorByName.get('cursor-user')?.scope, 'user');
|
|
441
|
+
assert.equal(cursorByName.get('cursor-user')?.command, '/cursor-user');
|
|
442
|
+
} finally {
|
|
443
|
+
restoreHomeDir();
|
|
444
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
445
|
+
}
|
|
446
|
+
});
|
package/server/openai-codex.js
CHANGED
|
@@ -143,7 +143,7 @@ function transformCodexEvent(event) {
|
|
|
143
143
|
case 'thread.started':
|
|
144
144
|
return {
|
|
145
145
|
type: 'thread_started',
|
|
146
|
-
threadId: event.id
|
|
146
|
+
threadId: event.thread_id || event.id
|
|
147
147
|
};
|
|
148
148
|
|
|
149
149
|
case 'error':
|
|
@@ -207,7 +207,8 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
207
207
|
|
|
208
208
|
let codex;
|
|
209
209
|
let thread;
|
|
210
|
-
let
|
|
210
|
+
let capturedSessionId = sessionId;
|
|
211
|
+
let sessionCreatedSent = false;
|
|
211
212
|
let terminalFailure = null;
|
|
212
213
|
const abortController = new AbortController();
|
|
213
214
|
|
|
@@ -231,20 +232,23 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
231
232
|
thread = codex.startThread(threadOptions);
|
|
232
233
|
}
|
|
233
234
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
235
|
+
const registerSession = (id) => {
|
|
236
|
+
if (!id) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
activeCodexSessions.set(id, {
|
|
240
|
+
thread,
|
|
241
|
+
codex,
|
|
242
|
+
status: 'running',
|
|
243
|
+
abortController,
|
|
244
|
+
startedAt: new Date().toISOString()
|
|
245
|
+
});
|
|
246
|
+
};
|
|
245
247
|
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
+
// Existing sessions can be tracked immediately; new sessions are tracked after thread.started.
|
|
249
|
+
if (capturedSessionId) {
|
|
250
|
+
registerSession(capturedSessionId);
|
|
251
|
+
}
|
|
248
252
|
|
|
249
253
|
// Execute with streaming
|
|
250
254
|
const streamedTurn = await thread.runStreamed(command, {
|
|
@@ -252,11 +256,34 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
252
256
|
});
|
|
253
257
|
|
|
254
258
|
for await (const event of streamedTurn.events) {
|
|
259
|
+
// Capture thread/session id lazily from the stream (Codex emits this asynchronously).
|
|
260
|
+
if (event.type === 'thread.started') {
|
|
261
|
+
const discoveredSessionId = event.thread_id || event.id || null;
|
|
262
|
+
if (discoveredSessionId && !capturedSessionId) {
|
|
263
|
+
capturedSessionId = discoveredSessionId;
|
|
264
|
+
registerSession(capturedSessionId);
|
|
265
|
+
|
|
266
|
+
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
|
|
267
|
+
ws.setSessionId(capturedSessionId);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!sessionId && !sessionCreatedSent) {
|
|
271
|
+
sessionCreatedSent = true;
|
|
272
|
+
sendMessage(ws, createNormalizedMessage({ kind: 'session_created', newSessionId: capturedSessionId, sessionId: capturedSessionId, provider: 'codex' }));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
255
277
|
// Check if session was aborted
|
|
256
|
-
|
|
257
|
-
if (!session || session.status === 'aborted') {
|
|
278
|
+
if (abortController.signal.aborted) {
|
|
258
279
|
break;
|
|
259
280
|
}
|
|
281
|
+
if (capturedSessionId) {
|
|
282
|
+
const session = activeCodexSessions.get(capturedSessionId);
|
|
283
|
+
if (session?.status === 'aborted') {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
260
287
|
|
|
261
288
|
if (event.type === 'item.started' || event.type === 'item.updated') {
|
|
262
289
|
continue;
|
|
@@ -265,7 +292,7 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
265
292
|
const transformed = transformCodexEvent(event);
|
|
266
293
|
|
|
267
294
|
// Normalize the transformed event into NormalizedMessage(s) via adapter
|
|
268
|
-
const normalizedMsgs = sessionsService.normalizeMessage('codex', transformed,
|
|
295
|
+
const normalizedMsgs = sessionsService.normalizeMessage('codex', transformed, capturedSessionId || sessionId || null);
|
|
269
296
|
for (const msg of normalizedMsgs) {
|
|
270
297
|
sendMessage(ws, msg);
|
|
271
298
|
}
|
|
@@ -275,7 +302,7 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
275
302
|
notifyRunFailed({
|
|
276
303
|
userId: ws?.userId || null,
|
|
277
304
|
provider: 'codex',
|
|
278
|
-
sessionId:
|
|
305
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
279
306
|
sessionName: sessionSummary,
|
|
280
307
|
error: terminalFailure
|
|
281
308
|
});
|
|
@@ -284,24 +311,29 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
284
311
|
// Extract and send token usage if available (normalized to match Claude format)
|
|
285
312
|
if (event.type === 'turn.completed' && event.usage) {
|
|
286
313
|
const totalTokens = (event.usage.input_tokens || 0) + (event.usage.output_tokens || 0);
|
|
287
|
-
sendMessage(ws, createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: { used: totalTokens, total: 200000 }, sessionId:
|
|
314
|
+
sendMessage(ws, createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: { used: totalTokens, total: 200000 }, sessionId: capturedSessionId || sessionId || null, provider: 'codex' }));
|
|
288
315
|
}
|
|
289
316
|
}
|
|
290
317
|
|
|
291
318
|
// Send completion event
|
|
292
319
|
if (!terminalFailure) {
|
|
293
|
-
sendMessage(ws, createNormalizedMessage({
|
|
320
|
+
sendMessage(ws, createNormalizedMessage({
|
|
321
|
+
kind: 'complete',
|
|
322
|
+
actualSessionId: capturedSessionId || thread.id || sessionId || null,
|
|
323
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
324
|
+
provider: 'codex'
|
|
325
|
+
}));
|
|
294
326
|
notifyRunStopped({
|
|
295
327
|
userId: ws?.userId || null,
|
|
296
328
|
provider: 'codex',
|
|
297
|
-
sessionId:
|
|
329
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
298
330
|
sessionName: sessionSummary,
|
|
299
331
|
stopReason: 'completed'
|
|
300
332
|
});
|
|
301
333
|
}
|
|
302
334
|
|
|
303
335
|
} catch (error) {
|
|
304
|
-
const session =
|
|
336
|
+
const session = capturedSessionId ? activeCodexSessions.get(capturedSessionId) : null;
|
|
305
337
|
const wasAborted =
|
|
306
338
|
session?.status === 'aborted' ||
|
|
307
339
|
error?.name === 'AbortError' ||
|
|
@@ -316,12 +348,12 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
316
348
|
? 'Codex CLI is not configured. Please set up authentication first.'
|
|
317
349
|
: error.message;
|
|
318
350
|
|
|
319
|
-
sendMessage(ws, createNormalizedMessage({ kind: 'error', content: errorContent, sessionId:
|
|
351
|
+
sendMessage(ws, createNormalizedMessage({ kind: 'error', content: errorContent, sessionId: capturedSessionId || sessionId || null, provider: 'codex' }));
|
|
320
352
|
if (!terminalFailure) {
|
|
321
353
|
notifyRunFailed({
|
|
322
354
|
userId: ws?.userId || null,
|
|
323
355
|
provider: 'codex',
|
|
324
|
-
sessionId:
|
|
356
|
+
sessionId: capturedSessionId || sessionId || null,
|
|
325
357
|
sessionName: sessionSummary,
|
|
326
358
|
error
|
|
327
359
|
});
|
|
@@ -330,8 +362,8 @@ export async function queryCodex(command, options = {}, ws) {
|
|
|
330
362
|
|
|
331
363
|
} finally {
|
|
332
364
|
// Update session status
|
|
333
|
-
if (
|
|
334
|
-
const session = activeCodexSessions.get(
|
|
365
|
+
if (capturedSessionId) {
|
|
366
|
+
const session = activeCodexSessions.get(capturedSessionId);
|
|
335
367
|
if (session) {
|
|
336
368
|
session.status = session.status === 'aborted' ? 'aborted' : 'completed';
|
|
337
369
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
1
|
import { promises as fs } from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
2
|
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
import express from 'express';
|
|
6
|
+
|
|
5
7
|
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
|
|
6
|
-
import {
|
|
8
|
+
import { parseFrontMatter } from '../shared/frontmatter.js';
|
|
7
9
|
import { findAppRoot, getModuleDir } from '../utils/runtime-paths.js';
|
|
8
10
|
|
|
9
11
|
const __dirname = getModuleDir(import.meta.url);
|
|
@@ -40,7 +42,7 @@ async function scanCommandsDirectory(dir, baseDir, namespace) {
|
|
|
40
42
|
// Parse markdown file for metadata
|
|
41
43
|
try {
|
|
42
44
|
const content = await fs.readFile(fullPath, 'utf8');
|
|
43
|
-
const { data: frontmatter, content: commandContent } =
|
|
45
|
+
const { data: frontmatter, content: commandContent } = parseFrontMatter(content);
|
|
44
46
|
|
|
45
47
|
// Calculate relative path from baseDir for command name
|
|
46
48
|
const relativePath = path.relative(baseDir, fullPath);
|
|
@@ -513,7 +515,7 @@ router.post('/execute', async (req, res) => {
|
|
|
513
515
|
}
|
|
514
516
|
}
|
|
515
517
|
const content = await fs.readFile(commandPath, 'utf8');
|
|
516
|
-
const { data: metadata, content: commandContent } =
|
|
518
|
+
const { data: metadata, content: commandContent } = parseFrontMatter(content);
|
|
517
519
|
// Basic argument replacement (will be enhanced in command parser utility)
|
|
518
520
|
let processedContent = commandContent;
|
|
519
521
|
|