agentstudio 0.1.8 → 0.1.10

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 (182) hide show
  1. package/README.md +481 -37
  2. package/README.zh-CN.md +525 -0
  3. package/{dist → backend/dist}/bin/agentstudio.js +7 -5
  4. package/backend/dist/bin/agentstudio.js.map +1 -0
  5. package/backend/dist/index.d.ts +3 -0
  6. package/backend/dist/index.d.ts.map +1 -0
  7. package/{dist → backend/dist}/index.js +9 -28
  8. package/{dist → backend/dist}/index.js.map +1 -1
  9. package/{dist → backend/dist}/routes/settings.js +1 -1
  10. package/{dist → backend/dist}/routes/settings.js.map +1 -1
  11. package/package.json +35 -43
  12. package/.cc-sessions/ppt-editor/session_1756253549429_uau1hm6lh.json +0 -665
  13. package/.cc-sessions/ppt-editor/session_1756257240855_v0wa26mde.json +0 -394
  14. package/.env +0 -15
  15. package/dist/bin/agentstudio.js.map +0 -1
  16. package/dist/index.d.ts +0 -2
  17. package/dist/index.d.ts.map +0 -1
  18. package/docs/chat-clean-1.svg +0 -1
  19. package/docs/chat-clean.md +0 -60
  20. package/docs/chat-comprehensive-1.svg +0 -1
  21. package/docs/chat-comprehensive.md +0 -166
  22. package/docs/chat_api_sequence_diagram.md +0 -58
  23. package/docs/command-detection-logic.md +0 -306
  24. package/docs/command-detection-sequence.md +0 -186
  25. package/frontend/dist/assets/AgentsPage-Dqb_aqAA.js +0 -1
  26. package/frontend/dist/assets/ChatPage-BvQmXfcP.css +0 -1
  27. package/frontend/dist/assets/ChatPage-L8Paywyc.js +0 -91
  28. package/frontend/dist/assets/CommandForm-DLl7EIMS.js +0 -7
  29. package/frontend/dist/assets/CommandsPage-Bzavq0Ec.js +0 -1
  30. package/frontend/dist/assets/DashboardPage-B3o4AYFT.js +0 -15
  31. package/frontend/dist/assets/FileBrowser-DL3ayaqb.js +0 -1
  32. package/frontend/dist/assets/GeneralSettingsPage-CBN_de-V.js +0 -1
  33. package/frontend/dist/assets/LandingPage-Dl4ioKos.js +0 -1
  34. package/frontend/dist/assets/LoginPage-4QqRdiSi.js +0 -12
  35. package/frontend/dist/assets/McpPage-CY3tYiqj.js +0 -39
  36. package/frontend/dist/assets/MemorySettingsPage-DGxrok5K.js +0 -1
  37. package/frontend/dist/assets/ProjectSelector-hgmGYVFh.js +0 -1
  38. package/frontend/dist/assets/ProjectsPage-D399IM0c.js +0 -14
  39. package/frontend/dist/assets/SettingsLayout-CL_K-lzJ.js +0 -1
  40. package/frontend/dist/assets/SubagentForm-DXtTTIKg.js +0 -7
  41. package/frontend/dist/assets/SubagentsPage-Chbhj8p2.js +0 -1
  42. package/frontend/dist/assets/ToastTestPage-DT4wuN5C.js +0 -1
  43. package/frontend/dist/assets/UnifiedToolSelector-CsM9qBvs.js +0 -1
  44. package/frontend/dist/assets/VersionSettingsPage-74Q-LVgA.js +0 -5
  45. package/frontend/dist/assets/agents-ClAzIJTw.js +0 -1
  46. package/frontend/dist/assets/agents-DwCY2K8p.css +0 -1
  47. package/frontend/dist/assets/authFetch-BATQyPG5.js +0 -1
  48. package/frontend/dist/assets/data-structures-DLJedtzx.js +0 -27
  49. package/frontend/dist/assets/dateFormat-CXa8VnEC.js +0 -1
  50. package/frontend/dist/assets/index-B9YHa7XT.css +0 -1
  51. package/frontend/dist/assets/index-B_CTNvca.js +0 -268
  52. package/frontend/dist/assets/monaco-editor-C7Z4sOhS.js +0 -19
  53. package/frontend/dist/assets/syntax-highlighting-YWvMU4Hm.js +0 -24
  54. package/frontend/dist/assets/tabManager-DV8urRBM.js +0 -30
  55. package/frontend/dist/assets/table-D6q1rytw.js +0 -1
  56. package/frontend/dist/assets/tools-C4EPanYi.js +0 -1
  57. package/frontend/dist/assets/ui-components-Cw21Epuw.js +0 -481
  58. package/frontend/dist/assets/useAgents-DwnOE1_k.js +0 -2
  59. package/frontend/dist/assets/useClaudeVersions-CQdGnCqv.js +0 -1
  60. package/frontend/dist/assets/useCommands-CCVaurbt.js +0 -1
  61. package/frontend/dist/cc-studio.png +0 -0
  62. package/frontend/dist/index.html +0 -70
  63. package/frontend/dist/vite.svg +0 -1
  64. package/scripts/README.md +0 -76
  65. package/scripts/fix-project-names.js +0 -113
  66. package/scripts/migrate-projects.js +0 -159
  67. package/shared/index.d.ts +0 -6
  68. package/shared/index.d.ts.map +0 -1
  69. package/shared/index.js +0 -7
  70. package/shared/types/agents.d.ts +0 -80
  71. package/shared/types/agents.d.ts.map +0 -1
  72. package/shared/types/agents.js +0 -145
  73. package/shared/types/claude-history.d.ts +0 -61
  74. package/shared/types/claude-history.d.ts.map +0 -1
  75. package/shared/types/claude-history.js +0 -2
  76. package/shared/types/claude-versions.d.ts +0 -40
  77. package/shared/types/claude-versions.d.ts.map +0 -1
  78. package/shared/types/claude-versions.js +0 -1
  79. package/shared/types/commands.d.ts +0 -48
  80. package/shared/types/commands.d.ts.map +0 -1
  81. package/shared/types/commands.js +0 -19
  82. package/shared/types/projects.d.ts +0 -35
  83. package/shared/types/projects.d.ts.map +0 -1
  84. package/shared/types/projects.js +0 -2
  85. package/shared/types/subagents.d.ts +0 -26
  86. package/shared/types/subagents.d.ts.map +0 -1
  87. package/shared/types/subagents.js +0 -1
  88. package/shared/utils/agentStorage.d.ts +0 -27
  89. package/shared/utils/agentStorage.d.ts.map +0 -1
  90. package/shared/utils/agentStorage.js +0 -392
  91. package/shared/utils/claudeVersionStorage.d.ts +0 -16
  92. package/shared/utils/claudeVersionStorage.d.ts.map +0 -1
  93. package/shared/utils/claudeVersionStorage.js +0 -230
  94. package/shared/utils/projectMetadataStorage.d.ts +0 -94
  95. package/shared/utils/projectMetadataStorage.d.ts.map +0 -1
  96. package/shared/utils/projectMetadataStorage.js +0 -422
  97. package/shared/utils/toolMapping.d.ts +0 -56
  98. package/shared/utils/toolMapping.d.ts.map +0 -1
  99. package/shared/utils/toolMapping.js +0 -71
  100. package/src/bin/agentstudio.ts +0 -130
  101. package/src/index.ts +0 -183
  102. package/src/middleware/auth.ts +0 -26
  103. package/src/routes/agents.ts +0 -884
  104. package/src/routes/auth.ts +0 -73
  105. package/src/routes/commands.ts +0 -441
  106. package/src/routes/files.ts +0 -352
  107. package/src/routes/mcp.ts +0 -751
  108. package/src/routes/media.ts +0 -140
  109. package/src/routes/projects.ts +0 -601
  110. package/src/routes/sessions.ts +0 -809
  111. package/src/routes/settings.ts +0 -718
  112. package/src/routes/slides.ts +0 -170
  113. package/src/routes/subagents.ts +0 -364
  114. package/src/services/claudeSession.ts +0 -293
  115. package/src/services/messageQueue.ts +0 -71
  116. package/src/services/sessionManager.ts +0 -532
  117. package/src/utils/jwt.ts +0 -36
  118. package/tsconfig.json +0 -27
  119. /package/{dist → backend/dist}/bin/agentstudio.d.ts +0 -0
  120. /package/{dist → backend/dist}/bin/agentstudio.d.ts.map +0 -0
  121. /package/{dist → backend/dist}/middleware/auth.d.ts +0 -0
  122. /package/{dist → backend/dist}/middleware/auth.d.ts.map +0 -0
  123. /package/{dist → backend/dist}/middleware/auth.js +0 -0
  124. /package/{dist → backend/dist}/middleware/auth.js.map +0 -0
  125. /package/{dist → backend/dist}/routes/agents.d.ts +0 -0
  126. /package/{dist → backend/dist}/routes/agents.d.ts.map +0 -0
  127. /package/{dist → backend/dist}/routes/agents.js +0 -0
  128. /package/{dist → backend/dist}/routes/agents.js.map +0 -0
  129. /package/{dist → backend/dist}/routes/auth.d.ts +0 -0
  130. /package/{dist → backend/dist}/routes/auth.d.ts.map +0 -0
  131. /package/{dist → backend/dist}/routes/auth.js +0 -0
  132. /package/{dist → backend/dist}/routes/auth.js.map +0 -0
  133. /package/{dist → backend/dist}/routes/commands.d.ts +0 -0
  134. /package/{dist → backend/dist}/routes/commands.d.ts.map +0 -0
  135. /package/{dist → backend/dist}/routes/commands.js +0 -0
  136. /package/{dist → backend/dist}/routes/commands.js.map +0 -0
  137. /package/{dist → backend/dist}/routes/files.d.ts +0 -0
  138. /package/{dist → backend/dist}/routes/files.d.ts.map +0 -0
  139. /package/{dist → backend/dist}/routes/files.js +0 -0
  140. /package/{dist → backend/dist}/routes/files.js.map +0 -0
  141. /package/{dist → backend/dist}/routes/mcp.d.ts +0 -0
  142. /package/{dist → backend/dist}/routes/mcp.d.ts.map +0 -0
  143. /package/{dist → backend/dist}/routes/mcp.js +0 -0
  144. /package/{dist → backend/dist}/routes/mcp.js.map +0 -0
  145. /package/{dist → backend/dist}/routes/media.d.ts +0 -0
  146. /package/{dist → backend/dist}/routes/media.d.ts.map +0 -0
  147. /package/{dist → backend/dist}/routes/media.js +0 -0
  148. /package/{dist → backend/dist}/routes/media.js.map +0 -0
  149. /package/{dist → backend/dist}/routes/projects.d.ts +0 -0
  150. /package/{dist → backend/dist}/routes/projects.d.ts.map +0 -0
  151. /package/{dist → backend/dist}/routes/projects.js +0 -0
  152. /package/{dist → backend/dist}/routes/projects.js.map +0 -0
  153. /package/{dist → backend/dist}/routes/sessions.d.ts +0 -0
  154. /package/{dist → backend/dist}/routes/sessions.d.ts.map +0 -0
  155. /package/{dist → backend/dist}/routes/sessions.js +0 -0
  156. /package/{dist → backend/dist}/routes/sessions.js.map +0 -0
  157. /package/{dist → backend/dist}/routes/settings.d.ts +0 -0
  158. /package/{dist → backend/dist}/routes/settings.d.ts.map +0 -0
  159. /package/{dist → backend/dist}/routes/slides.d.ts +0 -0
  160. /package/{dist → backend/dist}/routes/slides.d.ts.map +0 -0
  161. /package/{dist → backend/dist}/routes/slides.js +0 -0
  162. /package/{dist → backend/dist}/routes/slides.js.map +0 -0
  163. /package/{dist → backend/dist}/routes/subagents.d.ts +0 -0
  164. /package/{dist → backend/dist}/routes/subagents.d.ts.map +0 -0
  165. /package/{dist → backend/dist}/routes/subagents.js +0 -0
  166. /package/{dist → backend/dist}/routes/subagents.js.map +0 -0
  167. /package/{dist → backend/dist}/services/claudeSession.d.ts +0 -0
  168. /package/{dist → backend/dist}/services/claudeSession.d.ts.map +0 -0
  169. /package/{dist → backend/dist}/services/claudeSession.js +0 -0
  170. /package/{dist → backend/dist}/services/claudeSession.js.map +0 -0
  171. /package/{dist → backend/dist}/services/messageQueue.d.ts +0 -0
  172. /package/{dist → backend/dist}/services/messageQueue.d.ts.map +0 -0
  173. /package/{dist → backend/dist}/services/messageQueue.js +0 -0
  174. /package/{dist → backend/dist}/services/messageQueue.js.map +0 -0
  175. /package/{dist → backend/dist}/services/sessionManager.d.ts +0 -0
  176. /package/{dist → backend/dist}/services/sessionManager.d.ts.map +0 -0
  177. /package/{dist → backend/dist}/services/sessionManager.js +0 -0
  178. /package/{dist → backend/dist}/services/sessionManager.js.map +0 -0
  179. /package/{dist → backend/dist}/utils/jwt.d.ts +0 -0
  180. /package/{dist → backend/dist}/utils/jwt.d.ts.map +0 -0
  181. /package/{dist → backend/dist}/utils/jwt.js +0 -0
  182. /package/{dist → backend/dist}/utils/jwt.js.map +0 -0
@@ -1,73 +0,0 @@
1
- import express, { Request, Response, Router } from 'express';
2
- import { generateToken, verifyToken } from '../utils/jwt.js';
3
-
4
- const router: Router = express.Router();
5
-
6
- const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123';
7
-
8
- /**
9
- * POST /api/auth/login
10
- * Authenticate with password and return JWT token
11
- */
12
- router.post('/login', (req: Request, res: Response) => {
13
- const { password } = req.body;
14
-
15
- if (!password) {
16
- res.status(400).json({ error: 'Password is required' });
17
- return;
18
- }
19
-
20
- if (password !== ADMIN_PASSWORD) {
21
- res.status(401).json({ error: 'Invalid password' });
22
- return;
23
- }
24
-
25
- // Generate JWT token
26
- const token = generateToken();
27
-
28
- res.json({
29
- success: true,
30
- token,
31
- message: 'Login successful',
32
- });
33
- });
34
-
35
- /**
36
- * POST /api/auth/verify
37
- * Verify if a token is valid
38
- */
39
- router.post('/verify', (req: Request, res: Response) => {
40
- const { token } = req.body;
41
-
42
- if (!token) {
43
- res.status(400).json({ error: 'Token is required' });
44
- return;
45
- }
46
-
47
- const payload = verifyToken(token);
48
-
49
- if (!payload) {
50
- res.status(401).json({ valid: false, error: 'Invalid or expired token' });
51
- return;
52
- }
53
-
54
- res.json({
55
- valid: true,
56
- payload,
57
- });
58
- });
59
-
60
- /**
61
- * POST /api/auth/logout
62
- * Logout endpoint (client-side token removal)
63
- */
64
- router.post('/logout', (req: Request, res: Response) => {
65
- // With JWT, logout is primarily handled client-side by removing the token
66
- // This endpoint is provided for consistency and future extensibility
67
- res.json({
68
- success: true,
69
- message: 'Logout successful',
70
- });
71
- });
72
-
73
- export default router;
@@ -1,441 +0,0 @@
1
- import express, { Router } from 'express';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { promisify } from 'util';
5
- import matter from 'gray-matter';
6
- import { SlashCommand, SlashCommandCreate, SlashCommandUpdate, SlashCommandFilter } from 'agentstudio-shared/types/commands';
7
-
8
- const router: Router = express.Router();
9
- const readdir = promisify(fs.readdir);
10
- const readFile = promisify(fs.readFile);
11
- const writeFile = promisify(fs.writeFile);
12
- const mkdir = promisify(fs.mkdir);
13
- const unlink = promisify(fs.unlink);
14
- const stat = promisify(fs.stat);
15
-
16
- // Get project commands directory (.claude/commands)
17
- const getProjectCommandsDir = (projectPath?: string) => {
18
- if (projectPath) {
19
- return path.join(projectPath, '.claude', 'commands');
20
- }
21
- return path.join(process.cwd(), '..', '.claude', 'commands');
22
- };
23
-
24
- // Get user commands directory (~/.claude/commands)
25
- const getUserCommandsDir = () => path.join(process.env.HOME || process.env.USERPROFILE || '', '.claude', 'commands');
26
-
27
- // Ensure directory exists
28
- async function ensureDir(dirPath: string) {
29
- try {
30
- await mkdir(dirPath, { recursive: true });
31
- } catch (error) {
32
- // Directory already exists
33
- }
34
- }
35
-
36
- // Parse command file content
37
- function parseCommandContent(content: string): { frontmatter: any; body: string } {
38
- try {
39
- const parsed = matter(content);
40
- return {
41
- frontmatter: parsed.data,
42
- body: parsed.content.trim()
43
- };
44
- } catch {
45
- return {
46
- frontmatter: {},
47
- body: content
48
- };
49
- }
50
- }
51
-
52
- // Format command content with frontmatter
53
- function formatCommandContent(command: SlashCommandCreate | SlashCommandUpdate, existingContent?: string): string {
54
- let frontmatter: any = {};
55
-
56
- if (existingContent) {
57
- const parsed = parseCommandContent(existingContent);
58
- frontmatter = parsed.frontmatter;
59
- }
60
-
61
- // Update frontmatter with new values
62
- if (command.description) frontmatter.description = command.description;
63
- if (command.argumentHint) frontmatter['argument-hint'] = command.argumentHint;
64
- if (command.allowedTools) frontmatter['allowed-tools'] = command.allowedTools.join(', ');
65
- if (command.model) frontmatter.model = command.model;
66
- if ('namespace' in command && command.namespace !== undefined) frontmatter.namespace = command.namespace;
67
-
68
- // Build content
69
- let content = '';
70
- if (Object.keys(frontmatter).length > 0) {
71
- content += '---\n';
72
- for (const [key, value] of Object.entries(frontmatter)) {
73
- // Quote values that contain special YAML characters
74
- const shouldQuote = typeof value === 'string' && (/[[\]{}:>|*&!%@`]/.test(value) || value.includes('#') || value.trim() !== value);
75
- const formattedValue = shouldQuote ? `"${value.replace(/"/g, '\\"')}"` : value;
76
- content += `${key}: ${formattedValue}\n`;
77
- }
78
- content += '---\n\n';
79
- }
80
-
81
- if ('content' in command && command.content) {
82
- content += command.content;
83
- } else if (existingContent) {
84
- const parsed = parseCommandContent(existingContent);
85
- content += parsed.body;
86
- }
87
-
88
- return content;
89
- }
90
-
91
- // Scan commands in directory
92
- async function scanCommands(dirPath: string, scope: 'project' | 'user'): Promise<SlashCommand[]> {
93
- try {
94
- await ensureDir(dirPath);
95
- const commands: SlashCommand[] = [];
96
-
97
- async function scanDirectory(currentDir: string, namespace?: string) {
98
- const items = await readdir(currentDir, { withFileTypes: true });
99
-
100
- for (const item of items) {
101
- const itemPath = path.join(currentDir, item.name);
102
-
103
- if (item.isDirectory()) {
104
- const subNamespace = namespace ? `${namespace}/${item.name}` : item.name;
105
- await scanDirectory(itemPath, subNamespace);
106
- } else if (item.name.endsWith('.md')) {
107
- const commandName = item.name.replace('.md', '');
108
- const content = await readFile(itemPath, 'utf-8');
109
- const parsed = parseCommandContent(content);
110
- const stats = await stat(itemPath);
111
-
112
- commands.push({
113
- id: `${scope}:${namespace ? namespace + '/' : ''}${commandName}`,
114
- name: commandName,
115
- description: parsed.frontmatter.description || parsed.body.split('\n')[0] || '',
116
- content: parsed.body,
117
- scope,
118
- namespace,
119
- argumentHint: parsed.frontmatter['argument-hint'],
120
- allowedTools: parsed.frontmatter['allowed-tools'] ?
121
- parsed.frontmatter['allowed-tools'].split(',').map((s: string) => s.trim()) : undefined,
122
- model: parsed.frontmatter.model,
123
- createdAt: stats.birthtime,
124
- updatedAt: stats.mtime
125
- });
126
- }
127
- }
128
- }
129
-
130
- await scanDirectory(dirPath);
131
- return commands;
132
- } catch (error) {
133
- console.error(`Error scanning commands in ${dirPath}:`, error);
134
- return [];
135
- }
136
- }
137
-
138
- // GET /api/commands - List all commands
139
- router.get('/', async (req, res) => {
140
- try {
141
- const filter: SlashCommandFilter = {
142
- scope: req.query.scope as any || 'all',
143
- namespace: req.query.namespace as string,
144
- search: req.query.search as string
145
- };
146
- const projectPath = req.query.projectPath as string;
147
-
148
- let commands: SlashCommand[] = [];
149
-
150
- // Scan project commands
151
- if (filter.scope === 'all' || filter.scope === 'project') {
152
- const projectCommands = await scanCommands(getProjectCommandsDir(projectPath), 'project');
153
- commands.push(...projectCommands);
154
- }
155
-
156
- // Scan user commands
157
- if (filter.scope === 'all' || filter.scope === 'user') {
158
- const userCommands = await scanCommands(getUserCommandsDir(), 'user');
159
- commands.push(...userCommands);
160
- }
161
-
162
- // Apply filters
163
- if (filter.namespace) {
164
- commands = commands.filter(cmd => cmd.namespace === filter.namespace);
165
- }
166
-
167
- if (filter.search) {
168
- const searchLower = filter.search.toLowerCase();
169
- // Remove leading '/' if present, as it's not part of the actual command name
170
- const cleanSearch = searchLower.startsWith('/') ? searchLower.slice(1) : searchLower;
171
-
172
- commands = commands.filter(cmd => {
173
- // Basic field matching
174
- const basicMatch = cmd.name.toLowerCase().includes(cleanSearch) ||
175
- cmd.description.toLowerCase().includes(cleanSearch) ||
176
- cmd.content.toLowerCase().includes(cleanSearch) ||
177
- (cmd.namespace && cmd.namespace.toLowerCase().includes(cleanSearch));
178
-
179
- // Special handling for namespace pattern matching (e.g., "code:" should match "code:testcmd")
180
- if (cleanSearch.endsWith(':') && cmd.namespace) {
181
- const namespacePrefix = cleanSearch.slice(0, -1); // Remove the trailing ':'
182
- if (cmd.namespace.toLowerCase() === namespacePrefix) {
183
- return true;
184
- }
185
- }
186
-
187
- // Full namespace:name pattern matching (e.g., "code:test" should match "code:testcmd")
188
- if (cleanSearch.includes(':') && cmd.namespace) {
189
- const fullDisplayName = `${cmd.namespace.toLowerCase()}:${cmd.name.toLowerCase()}`;
190
- if (fullDisplayName.includes(cleanSearch)) {
191
- return true;
192
- }
193
- }
194
-
195
- return basicMatch;
196
- });
197
- }
198
-
199
- // Sort by scope (project first) then by name
200
- commands.sort((a, b) => {
201
- if (a.scope !== b.scope) {
202
- return a.scope === 'project' ? -1 : 1;
203
- }
204
- return a.name.localeCompare(b.name);
205
- });
206
-
207
- res.json(commands);
208
- } catch (error) {
209
- console.error('Error listing commands:', error);
210
- res.status(500).json({ error: 'Failed to list commands' });
211
- }
212
- });
213
-
214
- // GET /api/commands/:id - Get specific command
215
- router.get('/:id', async (req, res) => {
216
- try {
217
- const { id } = req.params;
218
- const projectPath = req.query.projectPath as string;
219
- const [scope, ...nameParts] = id.split(':');
220
- const fullName = nameParts.join(':');
221
-
222
- if (!['project', 'user'].includes(scope)) {
223
- return res.status(400).json({ error: 'Invalid command scope' });
224
- }
225
-
226
- const baseDir = scope === 'project' ? getProjectCommandsDir(projectPath) : getUserCommandsDir();
227
- const filePath = path.join(baseDir, fullName + '.md');
228
-
229
- try {
230
- const content = await readFile(filePath, 'utf-8');
231
- const parsed = parseCommandContent(content);
232
- const stats = await stat(filePath);
233
-
234
- const pathParts = fullName.split('/');
235
- const commandName = pathParts.pop()!;
236
- const namespace = pathParts.length > 0 ? pathParts.join('/') : undefined;
237
-
238
- const command: SlashCommand = {
239
- id,
240
- name: commandName,
241
- description: parsed.frontmatter.description || parsed.body.split('\n')[0] || '',
242
- content: parsed.body,
243
- scope: scope as 'project' | 'user',
244
- namespace,
245
- argumentHint: parsed.frontmatter['argument-hint'],
246
- allowedTools: parsed.frontmatter['allowed-tools'] ?
247
- parsed.frontmatter['allowed-tools'].split(',').map((s: string) => s.trim()) : undefined,
248
- model: parsed.frontmatter.model,
249
- createdAt: stats.birthtime,
250
- updatedAt: stats.mtime
251
- };
252
-
253
- res.json(command);
254
- } catch (error) {
255
- res.status(404).json({ error: 'Command not found' });
256
- }
257
- } catch (error) {
258
- console.error('Error getting command:', error);
259
- res.status(500).json({ error: 'Failed to get command' });
260
- }
261
- });
262
-
263
- // POST /api/commands - Create new command
264
- router.post('/', async (req, res) => {
265
- try {
266
- const commandData: SlashCommandCreate = req.body;
267
- const projectPath = req.query.projectPath as string;
268
-
269
- if (!commandData.name || !commandData.content || !commandData.scope) {
270
- return res.status(400).json({ error: 'Missing required fields: name, content, scope' });
271
- }
272
-
273
- if (!['project', 'user'].includes(commandData.scope)) {
274
- return res.status(400).json({ error: 'Invalid scope. Must be "project" or "user"' });
275
- }
276
-
277
- const baseDir = commandData.scope === 'project' ? getProjectCommandsDir(projectPath) : getUserCommandsDir();
278
- const fileName = commandData.namespace
279
- ? path.join(commandData.namespace, commandData.name + '.md')
280
- : commandData.name + '.md';
281
- const filePath = path.join(baseDir, fileName);
282
-
283
- // Check if command already exists
284
- try {
285
- await stat(filePath);
286
- return res.status(409).json({ error: 'Command already exists' });
287
- } catch {
288
- // Command doesn't exist, continue
289
- }
290
-
291
- // Ensure directory exists
292
- await ensureDir(path.dirname(filePath));
293
-
294
- // Format and write content
295
- const content = formatCommandContent(commandData);
296
- await writeFile(filePath, content, 'utf-8');
297
-
298
- // Return created command
299
- const stats = await stat(filePath);
300
- const command: SlashCommand = {
301
- id: `${commandData.scope}:${commandData.namespace ? commandData.namespace + '/' : ''}${commandData.name}`,
302
- name: commandData.name,
303
- description: commandData.description || '',
304
- content: commandData.content,
305
- scope: commandData.scope,
306
- namespace: commandData.namespace,
307
- argumentHint: commandData.argumentHint,
308
- allowedTools: commandData.allowedTools,
309
- model: commandData.model,
310
- createdAt: stats.birthtime,
311
- updatedAt: stats.mtime
312
- };
313
-
314
- res.status(201).json(command);
315
- } catch (error) {
316
- console.error('Error creating command:', error);
317
- res.status(500).json({ error: 'Failed to create command' });
318
- }
319
- });
320
-
321
- // PUT /api/commands/:id - Update command
322
- router.put('/:id', async (req, res) => {
323
- try {
324
- const { id } = req.params;
325
- const updateData: SlashCommandUpdate = req.body;
326
- const projectPath = req.query.projectPath as string;
327
- const [scope, ...nameParts] = id.split(':');
328
- const fullName = nameParts.join(':');
329
-
330
- if (!['project', 'user'].includes(scope)) {
331
- return res.status(400).json({ error: 'Invalid command scope' });
332
- }
333
-
334
- const baseDir = scope === 'project' ? getProjectCommandsDir(projectPath) : getUserCommandsDir();
335
- const oldFilePath = path.join(baseDir, fullName + '.md');
336
-
337
- try {
338
- // Read existing content
339
- const existingContent = await readFile(oldFilePath, 'utf-8');
340
-
341
- // Parse existing command to get current namespace and name
342
- const pathParts = fullName.split('/');
343
- const commandName = pathParts.pop()!;
344
- const currentNamespace = pathParts.length > 0 ? pathParts.join('/') : undefined;
345
-
346
- // Determine new namespace (from update data or keep current)
347
- const newNamespace = updateData.namespace !== undefined ? updateData.namespace || undefined : currentNamespace;
348
-
349
- // Format updated content
350
- const content = formatCommandContent(updateData, existingContent);
351
-
352
- // Check if namespace changed - if so, we need to move the file
353
- let newFilePath = oldFilePath;
354
- let newId = id;
355
-
356
- if (newNamespace !== currentNamespace) {
357
- const newFileName = newNamespace
358
- ? path.join(newNamespace, commandName + '.md')
359
- : commandName + '.md';
360
- newFilePath = path.join(baseDir, newFileName);
361
- newId = `${scope}:${newNamespace ? newNamespace + '/' : ''}${commandName}`;
362
-
363
- // Ensure new directory exists
364
- await ensureDir(path.dirname(newFilePath));
365
-
366
- // Check if target file already exists
367
- try {
368
- await stat(newFilePath);
369
- if (newFilePath !== oldFilePath) {
370
- return res.status(409).json({ error: 'A command with this namespace and name already exists' });
371
- }
372
- } catch {
373
- // File doesn't exist, good to proceed
374
- }
375
- }
376
-
377
- // Write to new location
378
- await writeFile(newFilePath, content, 'utf-8');
379
-
380
- // If file location changed, remove old file
381
- if (newFilePath !== oldFilePath) {
382
- await unlink(oldFilePath);
383
- }
384
-
385
- // Return updated command
386
- const parsed = parseCommandContent(content);
387
- const stats = await stat(newFilePath);
388
-
389
- const command: SlashCommand = {
390
- id: newId,
391
- name: commandName,
392
- description: parsed.frontmatter.description || parsed.body.split('\n')[0] || '',
393
- content: parsed.body,
394
- scope: scope as 'project' | 'user',
395
- namespace: newNamespace,
396
- argumentHint: parsed.frontmatter['argument-hint'],
397
- allowedTools: parsed.frontmatter['allowed-tools'] ?
398
- parsed.frontmatter['allowed-tools'].split(',').map((s: string) => s.trim()) : undefined,
399
- model: parsed.frontmatter.model,
400
- createdAt: stats.birthtime,
401
- updatedAt: stats.mtime
402
- };
403
-
404
- res.json(command);
405
- } catch (error) {
406
- res.status(404).json({ error: 'Command not found' });
407
- }
408
- } catch (error) {
409
- console.error('Error updating command:', error);
410
- res.status(500).json({ error: 'Failed to update command' });
411
- }
412
- });
413
-
414
- // DELETE /api/commands/:id - Delete command
415
- router.delete('/:id', async (req, res) => {
416
- try {
417
- const { id } = req.params;
418
- const projectPath = req.query.projectPath as string;
419
- const [scope, ...nameParts] = id.split(':');
420
- const fullName = nameParts.join(':');
421
-
422
- if (!['project', 'user'].includes(scope)) {
423
- return res.status(400).json({ error: 'Invalid command scope' });
424
- }
425
-
426
- const baseDir = scope === 'project' ? getProjectCommandsDir(projectPath) : getUserCommandsDir();
427
- const filePath = path.join(baseDir, fullName + '.md');
428
-
429
- try {
430
- await unlink(filePath);
431
- res.status(204).send();
432
- } catch (error) {
433
- res.status(404).json({ error: 'Command not found' });
434
- }
435
- } catch (error) {
436
- console.error('Error deleting command:', error);
437
- res.status(500).json({ error: 'Failed to delete command' });
438
- }
439
- });
440
-
441
- export default router;