decibel-tools-mcp 1.0.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.
Files changed (255) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +335 -0
  3. package/dist/agentic/compiler.d.ts +21 -0
  4. package/dist/agentic/compiler.d.ts.map +1 -0
  5. package/dist/agentic/compiler.js +267 -0
  6. package/dist/agentic/compiler.js.map +1 -0
  7. package/dist/agentic/golden.d.ts +25 -0
  8. package/dist/agentic/golden.d.ts.map +1 -0
  9. package/dist/agentic/golden.js +255 -0
  10. package/dist/agentic/golden.js.map +1 -0
  11. package/dist/agentic/index.d.ts +17 -0
  12. package/dist/agentic/index.d.ts.map +1 -0
  13. package/dist/agentic/index.js +153 -0
  14. package/dist/agentic/index.js.map +1 -0
  15. package/dist/agentic/linter.d.ts +20 -0
  16. package/dist/agentic/linter.d.ts.map +1 -0
  17. package/dist/agentic/linter.js +340 -0
  18. package/dist/agentic/linter.js.map +1 -0
  19. package/dist/agentic/renderer.d.ts +17 -0
  20. package/dist/agentic/renderer.d.ts.map +1 -0
  21. package/dist/agentic/renderer.js +277 -0
  22. package/dist/agentic/renderer.js.map +1 -0
  23. package/dist/agentic/types.d.ts +199 -0
  24. package/dist/agentic/types.d.ts.map +1 -0
  25. package/dist/agentic/types.js +8 -0
  26. package/dist/agentic/types.js.map +1 -0
  27. package/dist/architectAdrs.d.ts +19 -0
  28. package/dist/architectAdrs.d.ts.map +1 -0
  29. package/dist/architectAdrs.js +123 -0
  30. package/dist/architectAdrs.js.map +1 -0
  31. package/dist/config.d.ts +8 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +19 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/dataRoot.d.ts +45 -0
  36. package/dist/dataRoot.d.ts.map +1 -0
  37. package/dist/dataRoot.js +201 -0
  38. package/dist/dataRoot.js.map +1 -0
  39. package/dist/decibelPaths.d.ts +42 -0
  40. package/dist/decibelPaths.d.ts.map +1 -0
  41. package/dist/decibelPaths.js +150 -0
  42. package/dist/decibelPaths.js.map +1 -0
  43. package/dist/httpServer.d.ts +49 -0
  44. package/dist/httpServer.d.ts.map +1 -0
  45. package/dist/httpServer.js +1472 -0
  46. package/dist/httpServer.js.map +1 -0
  47. package/dist/lib/benchmark.d.ts +110 -0
  48. package/dist/lib/benchmark.d.ts.map +1 -0
  49. package/dist/lib/benchmark.js +338 -0
  50. package/dist/lib/benchmark.js.map +1 -0
  51. package/dist/lib/supabase.d.ts +123 -0
  52. package/dist/lib/supabase.d.ts.map +1 -0
  53. package/dist/lib/supabase.js +91 -0
  54. package/dist/lib/supabase.js.map +1 -0
  55. package/dist/projectPaths.d.ts +27 -0
  56. package/dist/projectPaths.d.ts.map +1 -0
  57. package/dist/projectPaths.js +86 -0
  58. package/dist/projectPaths.js.map +1 -0
  59. package/dist/projectRegistry.d.ts +97 -0
  60. package/dist/projectRegistry.d.ts.map +1 -0
  61. package/dist/projectRegistry.js +362 -0
  62. package/dist/projectRegistry.js.map +1 -0
  63. package/dist/sentinelIssues.d.ts +44 -0
  64. package/dist/sentinelIssues.d.ts.map +1 -0
  65. package/dist/sentinelIssues.js +213 -0
  66. package/dist/sentinelIssues.js.map +1 -0
  67. package/dist/server.d.ts +3 -0
  68. package/dist/server.d.ts.map +1 -0
  69. package/dist/server.js +93 -0
  70. package/dist/server.js.map +1 -0
  71. package/dist/test.d.ts +7 -0
  72. package/dist/test.d.ts.map +1 -0
  73. package/dist/test.js +77 -0
  74. package/dist/test.js.map +1 -0
  75. package/dist/tools/agentic/index.d.ts +7 -0
  76. package/dist/tools/agentic/index.d.ts.map +1 -0
  77. package/dist/tools/agentic/index.js +180 -0
  78. package/dist/tools/agentic/index.js.map +1 -0
  79. package/dist/tools/architect/index.d.ts +9 -0
  80. package/dist/tools/architect/index.d.ts.map +1 -0
  81. package/dist/tools/architect/index.js +383 -0
  82. package/dist/tools/architect/index.js.map +1 -0
  83. package/dist/tools/architect.d.ts +19 -0
  84. package/dist/tools/architect.d.ts.map +1 -0
  85. package/dist/tools/architect.js +88 -0
  86. package/dist/tools/architect.js.map +1 -0
  87. package/dist/tools/bench.d.ts +89 -0
  88. package/dist/tools/bench.d.ts.map +1 -0
  89. package/dist/tools/bench.js +826 -0
  90. package/dist/tools/bench.js.map +1 -0
  91. package/dist/tools/context/index.d.ts +11 -0
  92. package/dist/tools/context/index.d.ts.map +1 -0
  93. package/dist/tools/context/index.js +439 -0
  94. package/dist/tools/context/index.js.map +1 -0
  95. package/dist/tools/context.d.ts +146 -0
  96. package/dist/tools/context.d.ts.map +1 -0
  97. package/dist/tools/context.js +481 -0
  98. package/dist/tools/context.js.map +1 -0
  99. package/dist/tools/crit.d.ts +63 -0
  100. package/dist/tools/crit.d.ts.map +1 -0
  101. package/dist/tools/crit.js +159 -0
  102. package/dist/tools/crit.js.map +1 -0
  103. package/dist/tools/data-inspector.d.ts +188 -0
  104. package/dist/tools/data-inspector.d.ts.map +1 -0
  105. package/dist/tools/data-inspector.js +650 -0
  106. package/dist/tools/data-inspector.js.map +1 -0
  107. package/dist/tools/deck.d.ts +11 -0
  108. package/dist/tools/deck.d.ts.map +1 -0
  109. package/dist/tools/deck.js +170 -0
  110. package/dist/tools/deck.js.map +1 -0
  111. package/dist/tools/designer/index.d.ts +6 -0
  112. package/dist/tools/designer/index.d.ts.map +1 -0
  113. package/dist/tools/designer/index.js +177 -0
  114. package/dist/tools/designer/index.js.map +1 -0
  115. package/dist/tools/designer.d.ts +20 -0
  116. package/dist/tools/designer.d.ts.map +1 -0
  117. package/dist/tools/designer.js +75 -0
  118. package/dist/tools/designer.js.map +1 -0
  119. package/dist/tools/dojo/index.d.ts +13 -0
  120. package/dist/tools/dojo/index.d.ts.map +1 -0
  121. package/dist/tools/dojo/index.js +547 -0
  122. package/dist/tools/dojo/index.js.map +1 -0
  123. package/dist/tools/dojo.d.ts +254 -0
  124. package/dist/tools/dojo.d.ts.map +1 -0
  125. package/dist/tools/dojo.js +933 -0
  126. package/dist/tools/dojo.js.map +1 -0
  127. package/dist/tools/dojoBench.d.ts +49 -0
  128. package/dist/tools/dojoBench.d.ts.map +1 -0
  129. package/dist/tools/dojoBench.js +205 -0
  130. package/dist/tools/dojoBench.js.map +1 -0
  131. package/dist/tools/dojoGraduated.d.ts +50 -0
  132. package/dist/tools/dojoGraduated.d.ts.map +1 -0
  133. package/dist/tools/dojoGraduated.js +174 -0
  134. package/dist/tools/dojoGraduated.js.map +1 -0
  135. package/dist/tools/dojoPolicy.d.ts +65 -0
  136. package/dist/tools/dojoPolicy.d.ts.map +1 -0
  137. package/dist/tools/dojoPolicy.js +263 -0
  138. package/dist/tools/dojoPolicy.js.map +1 -0
  139. package/dist/tools/friction/index.d.ts +7 -0
  140. package/dist/tools/friction/index.d.ts.map +1 -0
  141. package/dist/tools/friction/index.js +239 -0
  142. package/dist/tools/friction/index.js.map +1 -0
  143. package/dist/tools/friction.d.ts +82 -0
  144. package/dist/tools/friction.d.ts.map +1 -0
  145. package/dist/tools/friction.js +331 -0
  146. package/dist/tools/friction.js.map +1 -0
  147. package/dist/tools/index.d.ts +5 -0
  148. package/dist/tools/index.d.ts.map +1 -0
  149. package/dist/tools/index.js +52 -0
  150. package/dist/tools/index.js.map +1 -0
  151. package/dist/tools/learnings/index.d.ts +5 -0
  152. package/dist/tools/learnings/index.d.ts.map +1 -0
  153. package/dist/tools/learnings/index.js +123 -0
  154. package/dist/tools/learnings/index.js.map +1 -0
  155. package/dist/tools/learnings.d.ts +41 -0
  156. package/dist/tools/learnings.d.ts.map +1 -0
  157. package/dist/tools/learnings.js +149 -0
  158. package/dist/tools/learnings.js.map +1 -0
  159. package/dist/tools/oracle/index.d.ts +5 -0
  160. package/dist/tools/oracle/index.d.ts.map +1 -0
  161. package/dist/tools/oracle/index.js +97 -0
  162. package/dist/tools/oracle/index.js.map +1 -0
  163. package/dist/tools/oracle.d.ts +90 -0
  164. package/dist/tools/oracle.d.ts.map +1 -0
  165. package/dist/tools/oracle.js +529 -0
  166. package/dist/tools/oracle.js.map +1 -0
  167. package/dist/tools/policy.d.ts +119 -0
  168. package/dist/tools/policy.d.ts.map +1 -0
  169. package/dist/tools/policy.js +406 -0
  170. package/dist/tools/policy.js.map +1 -0
  171. package/dist/tools/provenance/index.d.ts +4 -0
  172. package/dist/tools/provenance/index.d.ts.map +1 -0
  173. package/dist/tools/provenance/index.js +58 -0
  174. package/dist/tools/provenance/index.js.map +1 -0
  175. package/dist/tools/provenance.d.ts +75 -0
  176. package/dist/tools/provenance.d.ts.map +1 -0
  177. package/dist/tools/provenance.js +197 -0
  178. package/dist/tools/provenance.js.map +1 -0
  179. package/dist/tools/rateLimiter.d.ts +45 -0
  180. package/dist/tools/rateLimiter.d.ts.map +1 -0
  181. package/dist/tools/rateLimiter.js +91 -0
  182. package/dist/tools/rateLimiter.js.map +1 -0
  183. package/dist/tools/registry/index.d.ts +10 -0
  184. package/dist/tools/registry/index.d.ts.map +1 -0
  185. package/dist/tools/registry/index.js +467 -0
  186. package/dist/tools/registry/index.js.map +1 -0
  187. package/dist/tools/registry.d.ts +3 -0
  188. package/dist/tools/registry.d.ts.map +1 -0
  189. package/dist/tools/registry.js +189 -0
  190. package/dist/tools/registry.js.map +1 -0
  191. package/dist/tools/roadmap/index.d.ts +9 -0
  192. package/dist/tools/roadmap/index.d.ts.map +1 -0
  193. package/dist/tools/roadmap/index.js +250 -0
  194. package/dist/tools/roadmap/index.js.map +1 -0
  195. package/dist/tools/roadmap.d.ts +88 -0
  196. package/dist/tools/roadmap.d.ts.map +1 -0
  197. package/dist/tools/roadmap.js +365 -0
  198. package/dist/tools/roadmap.js.map +1 -0
  199. package/dist/tools/sentinel/index.d.ts +19 -0
  200. package/dist/tools/sentinel/index.d.ts.map +1 -0
  201. package/dist/tools/sentinel/index.js +832 -0
  202. package/dist/tools/sentinel/index.js.map +1 -0
  203. package/dist/tools/sentinel-scan-data.d.ts +90 -0
  204. package/dist/tools/sentinel-scan-data.d.ts.map +1 -0
  205. package/dist/tools/sentinel-scan-data.js +122 -0
  206. package/dist/tools/sentinel-scan-data.js.map +1 -0
  207. package/dist/tools/sentinel.d.ts +156 -0
  208. package/dist/tools/sentinel.d.ts.map +1 -0
  209. package/dist/tools/sentinel.js +603 -0
  210. package/dist/tools/sentinel.js.map +1 -0
  211. package/dist/tools/shared/index.d.ts +4 -0
  212. package/dist/tools/shared/index.d.ts.map +1 -0
  213. package/dist/tools/shared/index.js +7 -0
  214. package/dist/tools/shared/index.js.map +1 -0
  215. package/dist/tools/shared/project.d.ts +17 -0
  216. package/dist/tools/shared/project.d.ts.map +1 -0
  217. package/dist/tools/shared/project.js +36 -0
  218. package/dist/tools/shared/project.js.map +1 -0
  219. package/dist/tools/shared/response.d.ts +10 -0
  220. package/dist/tools/shared/response.d.ts.map +1 -0
  221. package/dist/tools/shared/response.js +36 -0
  222. package/dist/tools/shared/response.js.map +1 -0
  223. package/dist/tools/shared/validation.d.ts +10 -0
  224. package/dist/tools/shared/validation.d.ts.map +1 -0
  225. package/dist/tools/shared/validation.js +26 -0
  226. package/dist/tools/shared/validation.js.map +1 -0
  227. package/dist/tools/studio/cloud-spine.d.ts +27 -0
  228. package/dist/tools/studio/cloud-spine.d.ts.map +1 -0
  229. package/dist/tools/studio/cloud-spine.js +773 -0
  230. package/dist/tools/studio/cloud-spine.js.map +1 -0
  231. package/dist/tools/studio/index.d.ts +154 -0
  232. package/dist/tools/studio/index.d.ts.map +1 -0
  233. package/dist/tools/studio/index.js +525 -0
  234. package/dist/tools/studio/index.js.map +1 -0
  235. package/dist/tools/testSpec.d.ts +122 -0
  236. package/dist/tools/testSpec.d.ts.map +1 -0
  237. package/dist/tools/testSpec.js +525 -0
  238. package/dist/tools/testSpec.js.map +1 -0
  239. package/dist/tools/toolsIndex.d.ts +5 -0
  240. package/dist/tools/toolsIndex.d.ts.map +1 -0
  241. package/dist/tools/toolsIndex.js +37 -0
  242. package/dist/tools/toolsIndex.js.map +1 -0
  243. package/dist/tools/types.d.ts +30 -0
  244. package/dist/tools/types.d.ts.map +1 -0
  245. package/dist/tools/types.js +7 -0
  246. package/dist/tools/types.js.map +1 -0
  247. package/dist/tools/voice/index.d.ts +8 -0
  248. package/dist/tools/voice/index.d.ts.map +1 -0
  249. package/dist/tools/voice/index.js +176 -0
  250. package/dist/tools/voice/index.js.map +1 -0
  251. package/dist/tools/voice.d.ts +291 -0
  252. package/dist/tools/voice.d.ts.map +1 -0
  253. package/dist/tools/voice.js +734 -0
  254. package/dist/tools/voice.js.map +1 -0
  255. package/package.json +54 -0
@@ -0,0 +1,773 @@
1
+ /**
2
+ * Studio Cloud Spine Tools
3
+ *
4
+ * Supabase-backed tools for Decibel Studio cloud sync.
5
+ * Integrates with senken.pro Supabase for:
6
+ * - Project management
7
+ * - Artifact sync
8
+ * - Event streaming
9
+ * - Device registration
10
+ *
11
+ * EPIC-0002: Cloud Spine (Supabase Backend)
12
+ */
13
+ import { toolSuccess, toolError, requireFields } from '../shared/index.js';
14
+ import { getSupabaseClient, isSupabaseConfigured, formatSupabaseError, } from '../../lib/supabase.js';
15
+ import { log } from '../../config.js';
16
+ // ============================================================================
17
+ // Project Tools
18
+ // ============================================================================
19
+ export const studioListProjectsTool = {
20
+ definition: {
21
+ name: 'studio_list_projects',
22
+ description: 'List all Decibel Studio projects for the authenticated user.',
23
+ inputSchema: {
24
+ type: 'object',
25
+ properties: {
26
+ include_deleted: {
27
+ type: 'boolean',
28
+ description: 'Include soft-deleted projects (default: false)',
29
+ },
30
+ limit: {
31
+ type: 'number',
32
+ description: 'Maximum number of projects to return (default: 50)',
33
+ },
34
+ },
35
+ },
36
+ },
37
+ handler: async (args) => {
38
+ try {
39
+ if (!isSupabaseConfigured()) {
40
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
41
+ }
42
+ const client = getSupabaseClient();
43
+ const includeDeleted = args.include_deleted ?? false;
44
+ const limit = args.limit ?? 50;
45
+ log(`[Studio] Listing projects (includeDeleted=${includeDeleted}, limit=${limit})`);
46
+ let query = client
47
+ .from('projects')
48
+ .select('*')
49
+ .order('updated_at', { ascending: false })
50
+ .limit(limit);
51
+ if (!includeDeleted) {
52
+ query = query.is('deleted_at', null);
53
+ }
54
+ const { data, error } = await query;
55
+ if (error) {
56
+ return toolError(formatSupabaseError(error).error);
57
+ }
58
+ return toolSuccess({
59
+ projects: data,
60
+ count: data?.length ?? 0,
61
+ });
62
+ }
63
+ catch (err) {
64
+ return toolError(err instanceof Error ? err.message : String(err));
65
+ }
66
+ },
67
+ };
68
+ export const studioCreateProjectTool = {
69
+ definition: {
70
+ name: 'studio_create_project',
71
+ description: 'Create a new Decibel Studio project.',
72
+ inputSchema: {
73
+ type: 'object',
74
+ properties: {
75
+ name: {
76
+ type: 'string',
77
+ description: 'Project name',
78
+ },
79
+ description: {
80
+ type: 'string',
81
+ description: 'Optional project description',
82
+ },
83
+ },
84
+ required: ['name'],
85
+ },
86
+ },
87
+ handler: async (args) => {
88
+ try {
89
+ requireFields(args, 'name');
90
+ if (!isSupabaseConfigured()) {
91
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
92
+ }
93
+ const client = getSupabaseClient();
94
+ // Get current user
95
+ const { data: { user }, error: authError } = await client.auth.getUser();
96
+ if (authError || !user) {
97
+ return toolError('Not authenticated. Please sign in first.');
98
+ }
99
+ log(`[Studio] Creating project: ${args.name}`);
100
+ const insert = {
101
+ owner_id: user.id,
102
+ name: args.name,
103
+ description: args.description ?? null,
104
+ };
105
+ const { data, error } = await client
106
+ .from('projects')
107
+ .insert(insert)
108
+ .select()
109
+ .single();
110
+ if (error) {
111
+ return toolError(formatSupabaseError(error).error);
112
+ }
113
+ return toolSuccess({
114
+ project: data,
115
+ message: `Project "${args.name}" created successfully`,
116
+ });
117
+ }
118
+ catch (err) {
119
+ return toolError(err instanceof Error ? err.message : String(err));
120
+ }
121
+ },
122
+ };
123
+ export const studioGetProjectTool = {
124
+ definition: {
125
+ name: 'studio_get_project',
126
+ description: 'Get details of a specific Decibel Studio project.',
127
+ inputSchema: {
128
+ type: 'object',
129
+ properties: {
130
+ project_id: {
131
+ type: 'string',
132
+ description: 'Project UUID',
133
+ },
134
+ },
135
+ required: ['project_id'],
136
+ },
137
+ },
138
+ handler: async (args) => {
139
+ try {
140
+ requireFields(args, 'project_id');
141
+ if (!isSupabaseConfigured()) {
142
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
143
+ }
144
+ const client = getSupabaseClient();
145
+ const { data, error } = await client
146
+ .from('projects')
147
+ .select('*')
148
+ .eq('id', args.project_id)
149
+ .single();
150
+ if (error) {
151
+ return toolError(formatSupabaseError(error).error);
152
+ }
153
+ return toolSuccess({ project: data });
154
+ }
155
+ catch (err) {
156
+ return toolError(err instanceof Error ? err.message : String(err));
157
+ }
158
+ },
159
+ };
160
+ // ============================================================================
161
+ // Artifact Tools
162
+ // ============================================================================
163
+ export const studioListArtifactsTool = {
164
+ definition: {
165
+ name: 'studio_list_artifacts',
166
+ description: 'List artifacts in a Decibel Studio project.',
167
+ inputSchema: {
168
+ type: 'object',
169
+ properties: {
170
+ project_id: {
171
+ type: 'string',
172
+ description: 'Project UUID',
173
+ },
174
+ type: {
175
+ type: 'string',
176
+ enum: ['image', 'video', '3d', 'audio', 'text'],
177
+ description: 'Filter by artifact type',
178
+ },
179
+ status: {
180
+ type: 'string',
181
+ enum: ['generating', 'partial', 'complete', 'failed'],
182
+ description: 'Filter by status',
183
+ },
184
+ is_pinned: {
185
+ type: 'boolean',
186
+ description: 'Filter by pinned status',
187
+ },
188
+ limit: {
189
+ type: 'number',
190
+ description: 'Maximum number of artifacts to return (default: 50)',
191
+ },
192
+ },
193
+ required: ['project_id'],
194
+ },
195
+ },
196
+ handler: async (args) => {
197
+ try {
198
+ requireFields(args, 'project_id');
199
+ if (!isSupabaseConfigured()) {
200
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
201
+ }
202
+ const client = getSupabaseClient();
203
+ const limit = args.limit ?? 50;
204
+ log(`[Studio] Listing artifacts for project ${args.project_id}`);
205
+ let query = client
206
+ .from('artifacts')
207
+ .select('*')
208
+ .eq('project_id', args.project_id)
209
+ .is('deleted_at', null)
210
+ .order('created_at', { ascending: false })
211
+ .limit(limit);
212
+ if (args.type) {
213
+ query = query.eq('type', args.type);
214
+ }
215
+ if (args.status) {
216
+ query = query.eq('status', args.status);
217
+ }
218
+ if (args.is_pinned !== undefined) {
219
+ query = query.eq('is_pinned', args.is_pinned);
220
+ }
221
+ const { data, error } = await query;
222
+ if (error) {
223
+ return toolError(formatSupabaseError(error).error);
224
+ }
225
+ return toolSuccess({
226
+ artifacts: data,
227
+ count: data?.length ?? 0,
228
+ });
229
+ }
230
+ catch (err) {
231
+ return toolError(err instanceof Error ? err.message : String(err));
232
+ }
233
+ },
234
+ };
235
+ export const studioCreateArtifactTool = {
236
+ definition: {
237
+ name: 'studio_create_artifact',
238
+ description: 'Create a new artifact record in a Decibel Studio project. Use this to register an artifact before uploading files.',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ project_id: {
243
+ type: 'string',
244
+ description: 'Project UUID',
245
+ },
246
+ type: {
247
+ type: 'string',
248
+ enum: ['image', 'video', '3d', 'audio', 'text'],
249
+ description: 'Artifact type',
250
+ },
251
+ prompt: {
252
+ type: 'string',
253
+ description: 'Generation prompt',
254
+ },
255
+ model: {
256
+ type: 'string',
257
+ description: 'Model used for generation',
258
+ },
259
+ provider: {
260
+ type: 'string',
261
+ description: 'Provider (e.g., openai, anthropic)',
262
+ },
263
+ parent_artifact_id: {
264
+ type: 'string',
265
+ description: 'Parent artifact UUID for lineage tracking',
266
+ },
267
+ workflow_id: {
268
+ type: 'string',
269
+ description: 'Workflow ID if part of a workflow run',
270
+ },
271
+ tags: {
272
+ type: 'array',
273
+ items: { type: 'string' },
274
+ description: 'Tags for organization',
275
+ },
276
+ },
277
+ required: ['project_id', 'type'],
278
+ },
279
+ },
280
+ handler: async (args) => {
281
+ try {
282
+ requireFields(args, 'project_id', 'type');
283
+ if (!isSupabaseConfigured()) {
284
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
285
+ }
286
+ const client = getSupabaseClient();
287
+ log(`[Studio] Creating artifact in project ${args.project_id}`);
288
+ const insert = {
289
+ project_id: args.project_id,
290
+ type: args.type,
291
+ status: 'generating',
292
+ platform: 'claude-code',
293
+ app_version: '1.0.0',
294
+ prompt: args.prompt ?? null,
295
+ model: args.model ?? null,
296
+ provider: args.provider ?? null,
297
+ parent_artifact_id: args.parent_artifact_id ?? null,
298
+ workflow_id: args.workflow_id ?? null,
299
+ tags: args.tags ?? [],
300
+ };
301
+ const { data, error } = await client
302
+ .from('artifacts')
303
+ .insert(insert)
304
+ .select()
305
+ .single();
306
+ if (error) {
307
+ return toolError(formatSupabaseError(error).error);
308
+ }
309
+ return toolSuccess({
310
+ artifact: data,
311
+ message: 'Artifact created successfully',
312
+ });
313
+ }
314
+ catch (err) {
315
+ return toolError(err instanceof Error ? err.message : String(err));
316
+ }
317
+ },
318
+ };
319
+ export const studioUpdateArtifactTool = {
320
+ definition: {
321
+ name: 'studio_update_artifact',
322
+ description: 'Update an artifact record (status, rating, pinned, etc).',
323
+ inputSchema: {
324
+ type: 'object',
325
+ properties: {
326
+ artifact_id: {
327
+ type: 'string',
328
+ description: 'Artifact UUID',
329
+ },
330
+ status: {
331
+ type: 'string',
332
+ enum: ['generating', 'partial', 'complete', 'failed'],
333
+ description: 'New status',
334
+ },
335
+ progress: {
336
+ type: 'number',
337
+ description: 'Progress 0.0-1.0',
338
+ },
339
+ package_url: {
340
+ type: 'string',
341
+ description: 'URL to .studio.zip in storage',
342
+ },
343
+ package_sha256: {
344
+ type: 'string',
345
+ description: 'SHA256 hash of the package',
346
+ },
347
+ is_pinned: {
348
+ type: 'boolean',
349
+ description: 'Pin/unpin the artifact',
350
+ },
351
+ rating: {
352
+ type: 'number',
353
+ description: 'Rating 1-5',
354
+ },
355
+ tags: {
356
+ type: 'array',
357
+ items: { type: 'string' },
358
+ description: 'Replace tags',
359
+ },
360
+ },
361
+ required: ['artifact_id'],
362
+ },
363
+ },
364
+ handler: async (args) => {
365
+ try {
366
+ requireFields(args, 'artifact_id');
367
+ if (!isSupabaseConfigured()) {
368
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
369
+ }
370
+ const client = getSupabaseClient();
371
+ log(`[Studio] Updating artifact ${args.artifact_id}`);
372
+ const update = {};
373
+ if (args.status !== undefined)
374
+ update.status = args.status;
375
+ if (args.progress !== undefined)
376
+ update.progress = args.progress;
377
+ if (args.package_url !== undefined)
378
+ update.package_url = args.package_url;
379
+ if (args.package_sha256 !== undefined)
380
+ update.package_sha256 = args.package_sha256;
381
+ if (args.is_pinned !== undefined)
382
+ update.is_pinned = args.is_pinned;
383
+ if (args.rating !== undefined)
384
+ update.rating = args.rating;
385
+ if (args.tags !== undefined)
386
+ update.tags = args.tags;
387
+ const { data, error } = await client
388
+ .from('artifacts')
389
+ .update(update)
390
+ .eq('id', args.artifact_id)
391
+ .select()
392
+ .single();
393
+ if (error) {
394
+ return toolError(formatSupabaseError(error).error);
395
+ }
396
+ return toolSuccess({
397
+ artifact: data,
398
+ message: 'Artifact updated successfully',
399
+ });
400
+ }
401
+ catch (err) {
402
+ return toolError(err instanceof Error ? err.message : String(err));
403
+ }
404
+ },
405
+ };
406
+ // ============================================================================
407
+ // Event Sync Tools
408
+ // ============================================================================
409
+ export const studioSyncEventsTool = {
410
+ definition: {
411
+ name: 'studio_sync_events',
412
+ description: 'Get events for a project since a given sequence number. Use for incremental sync.',
413
+ inputSchema: {
414
+ type: 'object',
415
+ properties: {
416
+ project_id: {
417
+ type: 'string',
418
+ description: 'Project UUID',
419
+ },
420
+ since_seq: {
421
+ type: 'number',
422
+ description: 'Return events after this sequence number (exclusive). Use 0 for initial sync.',
423
+ },
424
+ limit: {
425
+ type: 'number',
426
+ description: 'Maximum events to return (default: 100)',
427
+ },
428
+ },
429
+ required: ['project_id'],
430
+ },
431
+ },
432
+ handler: async (args) => {
433
+ try {
434
+ requireFields(args, 'project_id');
435
+ if (!isSupabaseConfigured()) {
436
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
437
+ }
438
+ const client = getSupabaseClient();
439
+ const sinceSeq = args.since_seq ?? 0;
440
+ const limit = args.limit ?? 100;
441
+ log(`[Studio] Syncing events for project ${args.project_id} since seq ${sinceSeq}`);
442
+ const { data, error } = await client
443
+ .from('events')
444
+ .select('*')
445
+ .eq('project_id', args.project_id)
446
+ .gt('seq', sinceSeq)
447
+ .order('seq', { ascending: true })
448
+ .limit(limit);
449
+ if (error) {
450
+ return toolError(formatSupabaseError(error).error);
451
+ }
452
+ const events = data ?? [];
453
+ const lastSeq = events.length > 0 ? events[events.length - 1].seq : sinceSeq;
454
+ return toolSuccess({
455
+ events,
456
+ count: events.length,
457
+ last_seq: lastSeq,
458
+ has_more: events.length === limit,
459
+ });
460
+ }
461
+ catch (err) {
462
+ return toolError(err instanceof Error ? err.message : String(err));
463
+ }
464
+ },
465
+ };
466
+ // ============================================================================
467
+ // Device Registration Tools
468
+ // ============================================================================
469
+ export const studioRegisterDeviceTool = {
470
+ definition: {
471
+ name: 'studio_register_device',
472
+ description: 'Register this device for Decibel Studio sync and job routing.',
473
+ inputSchema: {
474
+ type: 'object',
475
+ properties: {
476
+ name: {
477
+ type: 'string',
478
+ description: 'Device name (e.g., "Ben\'s MacBook Pro")',
479
+ },
480
+ capabilities: {
481
+ type: 'object',
482
+ description: 'Device capabilities (e.g., { "gpu": true, "local_generation": true })',
483
+ },
484
+ },
485
+ required: ['name'],
486
+ },
487
+ },
488
+ handler: async (args) => {
489
+ try {
490
+ requireFields(args, 'name');
491
+ if (!isSupabaseConfigured()) {
492
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
493
+ }
494
+ const client = getSupabaseClient();
495
+ // Get current user
496
+ const { data: { user }, error: authError } = await client.auth.getUser();
497
+ if (authError || !user) {
498
+ return toolError('Not authenticated. Please sign in first.');
499
+ }
500
+ log(`[Studio] Registering device: ${args.name}`);
501
+ const deviceData = {
502
+ user_id: user.id,
503
+ name: args.name,
504
+ platform: 'claude-code',
505
+ capabilities: args.capabilities ?? { claude_code: true },
506
+ last_seen_at: new Date().toISOString(),
507
+ };
508
+ // Upsert: insert or update existing device with same user_id + name
509
+ const { data, error } = await client
510
+ .from('devices')
511
+ .upsert(deviceData, {
512
+ onConflict: 'user_id,name',
513
+ ignoreDuplicates: false,
514
+ })
515
+ .select()
516
+ .single();
517
+ if (error) {
518
+ return toolError(formatSupabaseError(error).error);
519
+ }
520
+ return toolSuccess({
521
+ device: data,
522
+ message: 'Device registered successfully',
523
+ });
524
+ }
525
+ catch (err) {
526
+ return toolError(err instanceof Error ? err.message : String(err));
527
+ }
528
+ },
529
+ };
530
+ export const studioHeartbeatTool = {
531
+ definition: {
532
+ name: 'studio_heartbeat',
533
+ description: 'Update device last_seen_at timestamp. Call periodically to keep device active.',
534
+ inputSchema: {
535
+ type: 'object',
536
+ properties: {
537
+ device_id: {
538
+ type: 'string',
539
+ description: 'Device UUID',
540
+ },
541
+ },
542
+ required: ['device_id'],
543
+ },
544
+ },
545
+ handler: async (args) => {
546
+ try {
547
+ requireFields(args, 'device_id');
548
+ if (!isSupabaseConfigured()) {
549
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
550
+ }
551
+ const client = getSupabaseClient();
552
+ const { error } = await client
553
+ .from('devices')
554
+ .update({ last_seen_at: new Date().toISOString() })
555
+ .eq('id', args.device_id);
556
+ if (error) {
557
+ return toolError(formatSupabaseError(error).error);
558
+ }
559
+ return toolSuccess({ message: 'Heartbeat recorded' });
560
+ }
561
+ catch (err) {
562
+ return toolError(err instanceof Error ? err.message : String(err));
563
+ }
564
+ },
565
+ };
566
+ // ============================================================================
567
+ // Job Tools
568
+ // ============================================================================
569
+ export const studioListJobsTool = {
570
+ definition: {
571
+ name: 'studio_list_jobs',
572
+ description: 'List pending jobs that can be claimed by this device.',
573
+ inputSchema: {
574
+ type: 'object',
575
+ properties: {
576
+ device_id: {
577
+ type: 'string',
578
+ description: 'This device\'s UUID (to find targeted jobs)',
579
+ },
580
+ status: {
581
+ type: 'string',
582
+ enum: ['pending', 'claimed', 'running', 'complete', 'failed', 'cancelled'],
583
+ description: 'Filter by status (default: pending)',
584
+ },
585
+ limit: {
586
+ type: 'number',
587
+ description: 'Maximum jobs to return (default: 20)',
588
+ },
589
+ },
590
+ },
591
+ },
592
+ handler: async (args) => {
593
+ try {
594
+ if (!isSupabaseConfigured()) {
595
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
596
+ }
597
+ const client = getSupabaseClient();
598
+ const status = args.status ?? 'pending';
599
+ const limit = args.limit ?? 20;
600
+ log(`[Studio] Listing jobs (status=${status})`);
601
+ let query = client
602
+ .from('jobs')
603
+ .select('*')
604
+ .eq('status', status)
605
+ .order('created_at', { ascending: true })
606
+ .limit(limit);
607
+ // If device_id provided, also include jobs targeted at this device
608
+ if (args.device_id) {
609
+ query = query.or(`target_device_id.is.null,target_device_id.eq.${args.device_id}`);
610
+ }
611
+ const { data, error } = await query;
612
+ if (error) {
613
+ return toolError(formatSupabaseError(error).error);
614
+ }
615
+ return toolSuccess({
616
+ jobs: data,
617
+ count: data?.length ?? 0,
618
+ });
619
+ }
620
+ catch (err) {
621
+ return toolError(err instanceof Error ? err.message : String(err));
622
+ }
623
+ },
624
+ };
625
+ export const studioClaimJobTool = {
626
+ definition: {
627
+ name: 'studio_claim_job',
628
+ description: 'Claim a pending job for this device to execute.',
629
+ inputSchema: {
630
+ type: 'object',
631
+ properties: {
632
+ job_id: {
633
+ type: 'string',
634
+ description: 'Job UUID to claim',
635
+ },
636
+ device_id: {
637
+ type: 'string',
638
+ description: 'This device\'s UUID',
639
+ },
640
+ },
641
+ required: ['job_id', 'device_id'],
642
+ },
643
+ },
644
+ handler: async (args) => {
645
+ try {
646
+ requireFields(args, 'job_id', 'device_id');
647
+ if (!isSupabaseConfigured()) {
648
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
649
+ }
650
+ const client = getSupabaseClient();
651
+ log(`[Studio] Claiming job ${args.job_id} for device ${args.device_id}`);
652
+ // Atomic claim: only update if still pending
653
+ const { data, error } = await client
654
+ .from('jobs')
655
+ .update({
656
+ status: 'claimed',
657
+ claimed_by_device_id: args.device_id,
658
+ claimed_at: new Date().toISOString(),
659
+ })
660
+ .eq('id', args.job_id)
661
+ .eq('status', 'pending')
662
+ .select()
663
+ .single();
664
+ if (error) {
665
+ return toolError(formatSupabaseError(error).error);
666
+ }
667
+ if (!data) {
668
+ return toolError('Job not found or already claimed');
669
+ }
670
+ return toolSuccess({
671
+ job: data,
672
+ message: 'Job claimed successfully',
673
+ });
674
+ }
675
+ catch (err) {
676
+ return toolError(err instanceof Error ? err.message : String(err));
677
+ }
678
+ },
679
+ };
680
+ export const studioUpdateJobTool = {
681
+ definition: {
682
+ name: 'studio_update_job',
683
+ description: 'Update job progress or complete/fail the job.',
684
+ inputSchema: {
685
+ type: 'object',
686
+ properties: {
687
+ job_id: {
688
+ type: 'string',
689
+ description: 'Job UUID',
690
+ },
691
+ status: {
692
+ type: 'string',
693
+ enum: ['running', 'complete', 'failed', 'cancelled'],
694
+ description: 'New status',
695
+ },
696
+ progress: {
697
+ type: 'number',
698
+ description: 'Progress 0.0-1.0',
699
+ },
700
+ error_message: {
701
+ type: 'string',
702
+ description: 'Error message if failed',
703
+ },
704
+ result_artifact_id: {
705
+ type: 'string',
706
+ description: 'Result artifact UUID if complete',
707
+ },
708
+ },
709
+ required: ['job_id'],
710
+ },
711
+ },
712
+ handler: async (args) => {
713
+ try {
714
+ requireFields(args, 'job_id');
715
+ if (!isSupabaseConfigured()) {
716
+ return toolError('Supabase not configured. Set SUPABASE_URL and SUPABASE_ANON_KEY.');
717
+ }
718
+ const client = getSupabaseClient();
719
+ log(`[Studio] Updating job ${args.job_id}`);
720
+ const update = {};
721
+ if (args.status !== undefined)
722
+ update.status = args.status;
723
+ if (args.progress !== undefined)
724
+ update.progress = args.progress;
725
+ if (args.error_message !== undefined)
726
+ update.error_message = args.error_message;
727
+ if (args.result_artifact_id !== undefined)
728
+ update.result_artifact_id = args.result_artifact_id;
729
+ if (args.status === 'complete' || args.status === 'failed') {
730
+ update.completed_at = new Date().toISOString();
731
+ }
732
+ const { data, error } = await client
733
+ .from('jobs')
734
+ .update(update)
735
+ .eq('id', args.job_id)
736
+ .select()
737
+ .single();
738
+ if (error) {
739
+ return toolError(formatSupabaseError(error).error);
740
+ }
741
+ return toolSuccess({
742
+ job: data,
743
+ message: 'Job updated successfully',
744
+ });
745
+ }
746
+ catch (err) {
747
+ return toolError(err instanceof Error ? err.message : String(err));
748
+ }
749
+ },
750
+ };
751
+ // ============================================================================
752
+ // Domain Export
753
+ // ============================================================================
754
+ export const studioCloudSpineTools = [
755
+ // Projects
756
+ studioListProjectsTool,
757
+ studioCreateProjectTool,
758
+ studioGetProjectTool,
759
+ // Artifacts
760
+ studioListArtifactsTool,
761
+ studioCreateArtifactTool,
762
+ studioUpdateArtifactTool,
763
+ // Events
764
+ studioSyncEventsTool,
765
+ // Devices
766
+ studioRegisterDeviceTool,
767
+ studioHeartbeatTool,
768
+ // Jobs
769
+ studioListJobsTool,
770
+ studioClaimJobTool,
771
+ studioUpdateJobTool,
772
+ ];
773
+ //# sourceMappingURL=cloud-spine.js.map