@lovelybunch/api 1.0.75-alpha.0 → 1.0.75-alpha.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.
- package/dist/lib/auth/auth-manager.d.ts +5 -0
- package/dist/lib/auth/auth-manager.js +9 -0
- package/dist/lib/storage/file-storage.d.ts +16 -16
- package/dist/lib/storage/file-storage.js +84 -79
- package/dist/lib/terminal/terminal-manager.d.ts +3 -5
- package/dist/lib/terminal/terminal-manager.js +19 -63
- package/dist/middleware/auth.js +36 -0
- package/dist/routes/api/v1/ai/index.js +0 -2
- package/dist/routes/api/v1/ai/route.js +445 -259
- package/dist/routes/api/v1/chats/[id]/index.js +2 -1
- package/dist/routes/api/v1/chats/[id]/route.d.ts +7 -0
- package/dist/routes/api/v1/chats/[id]/route.js +30 -1
- package/dist/routes/api/v1/context/index.js +0 -2
- package/dist/routes/api/v1/events/route.js +1 -1
- package/dist/routes/api/v1/knowledge/[filename]/index.d.ts +1 -0
- package/dist/routes/api/v1/knowledge/[filename]/index.js +1 -0
- package/dist/routes/api/v1/knowledge/[filename]/route.d.ts +3 -0
- package/dist/routes/api/v1/knowledge/[filename]/route.js +254 -0
- package/dist/routes/api/v1/knowledge/index.d.ts +1 -0
- package/dist/routes/api/v1/knowledge/index.js +1 -0
- package/dist/routes/api/v1/knowledge/route.d.ts +3 -0
- package/dist/routes/api/v1/knowledge/route.js +176 -0
- package/dist/routes/api/v1/mcp/index.js +109 -34
- package/dist/routes/api/v1/skills/[id]/index.d.ts +1 -0
- package/dist/routes/api/v1/skills/[id]/index.js +1 -0
- package/dist/routes/api/v1/skills/[id]/route.d.ts +3 -0
- package/dist/routes/api/v1/skills/[id]/route.js +199 -0
- package/dist/routes/api/v1/skills/index.d.ts +1 -0
- package/dist/routes/api/v1/skills/index.js +1 -0
- package/dist/routes/api/v1/skills/route.d.ts +3 -0
- package/dist/routes/api/v1/skills/route.js +329 -0
- package/dist/routes/api/v1/tasks/[id]/route.d.ts +351 -0
- package/dist/routes/api/v1/tasks/[id]/route.js +156 -0
- package/dist/routes/api/v1/tasks/index.d.ts +3 -0
- package/dist/routes/api/v1/tasks/index.js +10 -0
- package/dist/routes/api/v1/tasks/route.d.ts +329 -0
- package/dist/routes/api/v1/tasks/route.js +126 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +2 -2
- package/dist/routes/api/v1/terminal/[taskId]/create/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[taskId]/create/index.js +5 -0
- package/dist/routes/api/v1/terminal/[taskId]/create/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[taskId]/create/route.js +27 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/index.js +5 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/route.js +21 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/index.js +5 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/route.js +21 -0
- package/dist/routes/api/v1/terminal/sessions/route.js +4 -4
- package/dist/server-with-static.js +18 -14
- package/dist/server.js +15 -13
- package/package.json +8 -4
- package/static/assets/{ActivityPage-Dl3u61DF.js → ActivityPage-CbmEnYhg.js} +1 -1
- package/static/assets/ApiKeysSettingsPage-DLxQIqTT.js +2 -0
- package/static/assets/{ArchitectureEditPage-CVxBD88N.js → ArchitectureEditPage-CbtzgIQv.js} +4 -4
- package/static/assets/{ArchitecturePage-GchbFR8L.js → ArchitecturePage-CcAMfFZ8.js} +1 -1
- package/static/assets/{AuthSettingsPage-B7a-i1_T.js → AuthSettingsPage-4Prb7nAt.js} +2 -2
- package/static/assets/{CallbackPage-xYgfYT1Z.js → CallbackPage-CmWv_5Nh.js} +1 -1
- package/static/assets/CodePage-COC7rcwM.js +2 -0
- package/static/assets/{CollapsibleSection-ka7CEJhS.js → CollapsibleSection-C_VVeVSc.js} +1 -1
- package/static/assets/DashboardPage-BACFVIaW.js +41 -0
- package/static/assets/{GitPage-B-aSKe9Z.js → GitPage-rFngEr_A.js} +2 -2
- package/static/assets/GitSettingsPage-CDQovOqx.js +6 -0
- package/static/assets/IdentityPage-CS4REq2E.js +11 -0
- package/static/assets/{ImplementationStepsEditor-BC8bzHNj.js → ImplementationStepsEditor-CBIgppkZ.js} +2 -2
- package/static/assets/IntegrationsSettingsPage-B7HoraBH.js +1 -0
- package/static/assets/JobDetailPage-QUhWsaWV.js +1 -0
- package/static/assets/KnowledgeDetailPage-DCJstmIr.js +1 -0
- package/static/assets/KnowledgeEditPage-EYpFPfcJ.js +1 -0
- package/static/assets/KnowledgePage-D-7skvn5.js +8 -0
- package/static/assets/{LoginPage-Dp6lQSZW.js → LoginPage-L2aoqSDT.js} +1 -1
- package/static/assets/McpSettingsPage-CLwqDjw_.js +1 -0
- package/static/assets/NewKnowledgePage-Fq_QD8um.js +9 -0
- package/static/assets/NewSkillPage-DRcgovk0.js +1 -0
- package/static/assets/NewTaskPage-CleA8rH5.js +90 -0
- package/static/assets/ProjectEditPage-GIMOKgmh.js +11 -0
- package/static/assets/ProjectPage-DlWZOqnb.js +1 -0
- package/static/assets/PromptsSettingsPage-DFagGfo-.js +1 -0
- package/static/assets/ResourceDetailPage-CnFRpDec.js +1 -0
- package/static/assets/ResourcesPage-Cw3fu0JE.js +41 -0
- package/static/assets/{RoleEditPage-Ne8Dh8Tt.js → RoleEditPage-ARXq_eCs.js} +1 -1
- package/static/assets/{RolePage-3sb7lhw8.js → RolePage--XBBIrq8.js} +1 -1
- package/static/assets/{RulesSettingsPage-CEWEvNFC.js → RulesSettingsPage-DEqaRH96.js} +3 -3
- package/static/assets/SchedulePage-C_B2k9P6.js +4 -0
- package/static/assets/SkillDetailPage-Dr6xyKAX.js +1 -0
- package/static/assets/SkillEditPage-BsHXmfEZ.js +1 -0
- package/static/assets/SkillsPage-BrDdAAPx.js +8 -0
- package/static/assets/SkillsSettingsPage-Cvi7xaDE.js +1 -0
- package/static/assets/SourceInput-DxF_GGj8.js +1 -0
- package/static/assets/{TagInput-DRSgjTau.js → TagInput-DYvHbVZq.js} +1 -1
- package/static/assets/TaskDetailPage-DzcD6t03.js +1 -0
- package/static/assets/TaskEditPage-C7a6FOeJ.js +1 -0
- package/static/assets/TasksPage-ChWRSO_S.js +17 -0
- package/static/assets/TerminalPage-CoPwn8cU.js +1 -0
- package/static/assets/TerminalSessionPage-BQBZrOJa.js +13 -0
- package/static/assets/UserPreferencesPage-DjtU7veO.js +1 -0
- package/static/assets/UserSettingsPage-CfU8boJQ.js +1 -0
- package/static/assets/UtilitiesPage-sP1Crg-X.js +1 -0
- package/static/assets/{alert-CBWqAyb9.js → alert-BqZa-crG.js} +1 -1
- package/static/assets/{arrow-down-PY3QZW33.js → arrow-down-3faV_GyO.js} +1 -1
- package/static/assets/{arrow-left-JpEq5x30.js → arrow-left-FD3wQmzH.js} +1 -1
- package/static/assets/{arrow-up-qJ-_eNlY.js → arrow-up-BzP0YNVk.js} +1 -1
- package/static/assets/{badge-D6QJCkgl.js → badge-DRyeFib9.js} +1 -1
- package/static/assets/{browser-modal-DT5zGtM2.js → browser-modal-vnePkRfO.js} +2 -2
- package/static/assets/{card-CjY6k9qN.js → card-CuQs3dpy.js} +1 -1
- package/static/assets/{chevron-left-DiMUBSvf.js → chevron-left-QZIoYcVa.js} +1 -1
- package/static/assets/{plus-BCs8PAn6.js → chevron-up-DreyvhRd.js} +2 -2
- package/static/assets/{chevrons-up-CKVPmJ25.js → chevrons-up-CsAkc9vE.js} +1 -1
- package/static/assets/{circle-alert-Bc4Eo-aL.js → circle-alert-ewz28SE3.js} +1 -1
- package/static/assets/{circle-check-C8DZc7FJ.js → circle-check-2TuD-EHs.js} +1 -1
- package/static/assets/{circle-check-big-D_PvuS7-.js → circle-check-big-1xNuBPkR.js} +1 -1
- package/static/assets/{circle-play-Ba60SPvN.js → circle-play-C_w-qCn4.js} +1 -1
- package/static/assets/{circle-x-BYe1-ctZ.js → circle-x-B_d4wjG-.js} +1 -1
- package/static/assets/{clipboard-DZHDxSAN.js → clipboard-B6vBm1BP.js} +1 -1
- package/static/assets/{clock-E0FNqZih.js → clock-BIzEsx1g.js} +1 -1
- package/static/assets/{download-DbHXGD3a.js → download-Dv-RIvKK.js} +1 -1
- package/static/assets/droid-GYYyVzN-.js +18 -0
- package/static/assets/external-link-Cr8wjV6X.js +6 -0
- package/static/assets/{eye-CvcvqrS0.js → eye-DN958vyL.js} +1 -1
- package/static/assets/{folder-git-2-DK9b1q2x.js → folder-git-2-BproRzAR.js} +1 -1
- package/static/assets/index-Co_SJV3n.js +472 -0
- package/static/assets/index-GFQ5RqVh.css +2 -0
- package/static/assets/info-CzKk8mbR.js +6 -0
- package/static/assets/{label-DGhQh35t.js → label-h5GIKGcJ.js} +1 -1
- package/static/assets/{markdown-editor-52vxb0rp.js → markdown-editor-C6il4XWv.js} +1 -1
- package/static/assets/{pause-B7dyLcuH.js → pause-BDsjEmXM.js} +1 -1
- package/static/assets/{play-C3zkOZiK.js → play-CGsVQUJG.js} +1 -1
- package/static/assets/{radio-group-BseQ4OVV.js → radio-group-Bsd75ahK.js} +1 -1
- package/static/assets/{refresh-cw-ucF4I6xx.js → refresh-cw-qE1iNkL_.js} +1 -1
- package/static/assets/{search-TqAjHy3_.js → search-dvi0J4Dr.js} +1 -1
- package/static/assets/select-DylRS99W.js +1 -0
- package/static/assets/status-utils-CDkPeVfP.js +1 -0
- package/static/assets/{switch-B6px80Oh.js → switch-CsB3wpq9.js} +1 -1
- package/static/assets/{tabs-BHSWuVOa.js → tabs-Bw_4k2Rs.js} +1 -1
- package/static/assets/{tag-DjI_1POr.js → tag-Dh5PraRd.js} +1 -1
- package/static/assets/{terminal-preview-C0DTE3G7.js → terminal-preview-CfOb7xMx.js} +1 -1
- package/static/assets/use-terminal-T_tdJTCU.js +1 -0
- package/static/assets/video-bO6uuAjA.js +36 -0
- package/static/assets/{zap-DruahTJJ.js → zap-DHZ91NcK.js} +1 -1
- package/static/index.html +2 -2
- package/static/assets/AgentDetailPage-BJD22FSv.js +0 -1
- package/static/assets/AgentEditPage-ZYd0Rode.js +0 -1
- package/static/assets/AgentsPage-CUuxxQ2o.js +0 -3
- package/static/assets/AgentsSettingsPage-Dd8MrsKx.js +0 -6
- package/static/assets/ApiKeysSettingsPage-DTZStkII.js +0 -7
- package/static/assets/CodePage-xpXX0hlR.js +0 -2
- package/static/assets/DashboardPage-DQFlhP54.js +0 -41
- package/static/assets/GitSettingsPage-vLNJs4r3.js +0 -6
- package/static/assets/IdentityPage-ZAp_iARt.js +0 -11
- package/static/assets/IntegrationsSettingsPage-CqWZ8GUI.js +0 -1
- package/static/assets/JobDetailPage-BWq4XLO2.js +0 -1
- package/static/assets/KnowledgeDetailPage-C8evStGp.js +0 -1
- package/static/assets/KnowledgeEditPage-DquJprU_.js +0 -1
- package/static/assets/KnowledgePage-BBUKBhzS.js +0 -8
- package/static/assets/McpSettingsPage-BGIEripM.js +0 -1
- package/static/assets/NewAgentPage-D9hL7NY6.js +0 -1
- package/static/assets/NewKnowledgePage-CEdd8Yns.js +0 -9
- package/static/assets/NewProposalPage-B152OXnT.js +0 -90
- package/static/assets/ProjectEditPage-D0uSaGy0.js +0 -11
- package/static/assets/ProjectPage-DzWJky4Y.js +0 -1
- package/static/assets/PromptsSettingsPage-DH-4vcbF.js +0 -1
- package/static/assets/ProposalDetailPage-BI5HDp5I.js +0 -1
- package/static/assets/ProposalEditPage-D4412azZ.js +0 -1
- package/static/assets/ProposalsPage-D2gMbCBV.js +0 -17
- package/static/assets/ResourcesPage-7tbvXxDb.js +0 -71
- package/static/assets/SchedulePage-Crd-VMiL.js +0 -4
- package/static/assets/SourceInput-fvbAHpzT.js +0 -1
- package/static/assets/TerminalPage-BGZJDqvR.js +0 -1
- package/static/assets/TerminalSessionPage-DILvcnLG.js +0 -13
- package/static/assets/UserPreferencesPage-ChHrBs8H.js +0 -1
- package/static/assets/UserSettingsPage-Cv60YInV.js +0 -1
- package/static/assets/UtilitiesPage-ChLTn_u9.js +0 -1
- package/static/assets/calendar-sFw_mHQb.js +0 -6
- package/static/assets/droid-Caom7ttu.js +0 -4
- package/static/assets/index-C4LGSoHO.js +0 -468
- package/static/assets/index-CzjbtPHw.css +0 -2
- package/static/assets/use-terminal-KpWB08HL.js +0 -1
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
import AdmZip from 'adm-zip';
|
|
6
|
+
const app = new Hono();
|
|
7
|
+
const ALLOWED_ARCHIVE_HOSTS = [
|
|
8
|
+
'registry.coconut.dev',
|
|
9
|
+
];
|
|
10
|
+
const MAX_ARCHIVE_SIZE = 50 * 1024 * 1024; // 50 MB
|
|
11
|
+
/** Paths to skip entirely when extracting zip archives (macOS resource forks, etc.) */
|
|
12
|
+
const IGNORED_ZIP_PREFIXES = ['__MACOSX/', '.DS_Store'];
|
|
13
|
+
function isIgnoredZipEntry(entryName) {
|
|
14
|
+
return IGNORED_ZIP_PREFIXES.some(prefix => entryName.startsWith(prefix) || entryName === prefix);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Detect if all zip entries share a single wrapping directory (e.g., `pptx/...`).
|
|
18
|
+
* Returns the prefix (with trailing slash) to strip, or null if no wrapper.
|
|
19
|
+
* Ignores macOS resource fork entries (__MACOSX/) when detecting the prefix.
|
|
20
|
+
*/
|
|
21
|
+
function detectWrapperPrefix(entries) {
|
|
22
|
+
const paths = entries
|
|
23
|
+
.map(e => e.entryName)
|
|
24
|
+
.filter(name => name !== '/' && name !== '' && !isIgnoredZipEntry(name));
|
|
25
|
+
if (paths.length === 0)
|
|
26
|
+
return null;
|
|
27
|
+
const prefixes = new Set(paths.map(p => {
|
|
28
|
+
const firstSlash = p.indexOf('/');
|
|
29
|
+
return firstSlash >= 0 ? p.substring(0, firstSlash + 1) : null;
|
|
30
|
+
}));
|
|
31
|
+
// If all entries share exactly one prefix directory, strip it
|
|
32
|
+
if (prefixes.size === 1) {
|
|
33
|
+
const prefix = [...prefixes][0];
|
|
34
|
+
if (prefix !== null)
|
|
35
|
+
return prefix;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Download a zip archive from a trusted URL and extract it into destDir.
|
|
41
|
+
* Returns true if extraction succeeded and SKILL.md exists in the output.
|
|
42
|
+
*/
|
|
43
|
+
async function downloadAndExtractArchive(archiveUrl, destDir) {
|
|
44
|
+
// Validate URL against allow-list
|
|
45
|
+
let parsed;
|
|
46
|
+
try {
|
|
47
|
+
parsed = new URL(archiveUrl);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new Error('Invalid archive URL');
|
|
51
|
+
}
|
|
52
|
+
if (!ALLOWED_ARCHIVE_HOSTS.includes(parsed.hostname)) {
|
|
53
|
+
throw new Error(`Archive host '${parsed.hostname}' is not trusted`);
|
|
54
|
+
}
|
|
55
|
+
// Download the zip
|
|
56
|
+
const response = await fetch(archiveUrl, {
|
|
57
|
+
headers: { 'User-Agent': 'coconut-api' },
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
throw new Error(`Failed to download archive: ${response.status} ${response.statusText}`);
|
|
61
|
+
}
|
|
62
|
+
const contentLength = Number(response.headers.get('content-length') || '0');
|
|
63
|
+
if (contentLength > MAX_ARCHIVE_SIZE) {
|
|
64
|
+
throw new Error(`Archive too large (${contentLength} bytes, max ${MAX_ARCHIVE_SIZE})`);
|
|
65
|
+
}
|
|
66
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
67
|
+
if (buffer.length > MAX_ARCHIVE_SIZE) {
|
|
68
|
+
throw new Error(`Archive too large (${buffer.length} bytes, max ${MAX_ARCHIVE_SIZE})`);
|
|
69
|
+
}
|
|
70
|
+
// Extract with path-traversal protection
|
|
71
|
+
const zip = new AdmZip(buffer);
|
|
72
|
+
const entries = zip.getEntries();
|
|
73
|
+
const wrapperPrefix = detectWrapperPrefix(entries);
|
|
74
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
let entryName = entry.entryName;
|
|
77
|
+
// Skip macOS resource fork junk and other ignored entries
|
|
78
|
+
if (isIgnoredZipEntry(entryName))
|
|
79
|
+
continue;
|
|
80
|
+
// Strip single wrapping directory if detected (e.g., pptx/SKILL.md -> SKILL.md)
|
|
81
|
+
if (wrapperPrefix && entryName.startsWith(wrapperPrefix)) {
|
|
82
|
+
entryName = entryName.substring(wrapperPrefix.length);
|
|
83
|
+
}
|
|
84
|
+
if (!entryName || entryName === '/')
|
|
85
|
+
continue;
|
|
86
|
+
// Reject paths that attempt directory traversal
|
|
87
|
+
const resolved = path.resolve(destDir, entryName);
|
|
88
|
+
if (!resolved.startsWith(path.resolve(destDir) + path.sep) && resolved !== path.resolve(destDir)) {
|
|
89
|
+
console.warn(`Skipping zip entry with path traversal: ${entryName}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (entry.isDirectory) {
|
|
93
|
+
await fs.mkdir(resolved, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
await fs.mkdir(path.dirname(resolved), { recursive: true });
|
|
97
|
+
await fs.writeFile(resolved, entry.getData());
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Check if SKILL.md was extracted
|
|
101
|
+
try {
|
|
102
|
+
await fs.access(path.join(destDir, 'SKILL.md'));
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate skill name per Agent Skills spec:
|
|
111
|
+
* - 1-64 characters
|
|
112
|
+
* - Lowercase letters, numbers, hyphens only
|
|
113
|
+
* - No leading/trailing hyphens
|
|
114
|
+
* - No consecutive hyphens
|
|
115
|
+
*/
|
|
116
|
+
function validateSkillName(name) {
|
|
117
|
+
if (!name || name.length === 0)
|
|
118
|
+
return 'Name is required';
|
|
119
|
+
if (name.length > 64)
|
|
120
|
+
return 'Name must be 64 characters or fewer';
|
|
121
|
+
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(name)) {
|
|
122
|
+
return 'Name must contain only lowercase letters, numbers, and hyphens, and must not start or end with a hyphen';
|
|
123
|
+
}
|
|
124
|
+
if (/--/.test(name))
|
|
125
|
+
return 'Name must not contain consecutive hyphens';
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
function generateSkillId(name) {
|
|
129
|
+
return name
|
|
130
|
+
.toLowerCase()
|
|
131
|
+
.replace(/[^a-z0-9\s-]/g, '')
|
|
132
|
+
.replace(/\s+/g, '-')
|
|
133
|
+
.replace(/--+/g, '-')
|
|
134
|
+
.replace(/^-|-$/g, '');
|
|
135
|
+
}
|
|
136
|
+
function getSkillsPath() {
|
|
137
|
+
let basePath;
|
|
138
|
+
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
139
|
+
basePath = process.env.GAIT_DEV_ROOT;
|
|
140
|
+
}
|
|
141
|
+
else if (process.env.GAIT_DATA_PATH) {
|
|
142
|
+
basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
basePath = path.resolve(process.cwd(), '.nut');
|
|
146
|
+
}
|
|
147
|
+
return path.join(basePath, 'skills');
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* GET /api/v1/skills
|
|
151
|
+
* Load all skill documents
|
|
152
|
+
*/
|
|
153
|
+
app.get('/', async (c) => {
|
|
154
|
+
try {
|
|
155
|
+
const skillsPath = getSkillsPath();
|
|
156
|
+
await fs.mkdir(skillsPath, { recursive: true });
|
|
157
|
+
const entries = await fs.readdir(skillsPath, { withFileTypes: true });
|
|
158
|
+
const skills = await Promise.all(entries
|
|
159
|
+
.filter(entry => entry.isDirectory())
|
|
160
|
+
.map(async (entry) => {
|
|
161
|
+
const skillFile = path.join(skillsPath, entry.name, 'SKILL.md');
|
|
162
|
+
try {
|
|
163
|
+
const fileContent = await fs.readFile(skillFile, 'utf-8');
|
|
164
|
+
const { data, content } = matter(fileContent);
|
|
165
|
+
const nestedMeta = typeof data.metadata === 'object' ? data.metadata : undefined;
|
|
166
|
+
const doc = {
|
|
167
|
+
id: entry.name,
|
|
168
|
+
name: data.name || entry.name,
|
|
169
|
+
description: data.description || '',
|
|
170
|
+
...(data.license ? { license: data.license } : {}),
|
|
171
|
+
...(data.compatibility ? { compatibility: data.compatibility } : {}),
|
|
172
|
+
...(data['allowed-tools'] || data.allowedTools ? { allowedTools: data['allowed-tools'] || data.allowedTools } : {}),
|
|
173
|
+
metadata: {
|
|
174
|
+
name: data.name || entry.name,
|
|
175
|
+
description: data.description || '',
|
|
176
|
+
...(nestedMeta || {}),
|
|
177
|
+
},
|
|
178
|
+
content,
|
|
179
|
+
};
|
|
180
|
+
return doc;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Skip directories without valid SKILL.md
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}));
|
|
187
|
+
const validSkills = skills.filter((s) => s !== null);
|
|
188
|
+
return c.json({
|
|
189
|
+
success: true,
|
|
190
|
+
documents: validSkills.sort((a, b) => a.id.localeCompare(b.id))
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
console.error('Error loading skill documents:', error);
|
|
195
|
+
return c.json({
|
|
196
|
+
success: false,
|
|
197
|
+
error: 'Failed to load skill documents',
|
|
198
|
+
documents: []
|
|
199
|
+
}, 500);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
/**
|
|
203
|
+
* POST /api/v1/skills
|
|
204
|
+
* Create a new skill document
|
|
205
|
+
*/
|
|
206
|
+
app.post('/', async (c) => {
|
|
207
|
+
try {
|
|
208
|
+
const body = await c.req.json();
|
|
209
|
+
if (!body.name || !body.description) {
|
|
210
|
+
return c.json({ success: false, error: 'Name and description are required' }, 400);
|
|
211
|
+
}
|
|
212
|
+
const skillId = generateSkillId(body.name);
|
|
213
|
+
const nameError = validateSkillName(skillId);
|
|
214
|
+
if (nameError) {
|
|
215
|
+
return c.json({ success: false, error: nameError }, 400);
|
|
216
|
+
}
|
|
217
|
+
const skillsPath = getSkillsPath();
|
|
218
|
+
const skillDir = path.join(skillsPath, skillId);
|
|
219
|
+
// Check if skill directory already exists
|
|
220
|
+
try {
|
|
221
|
+
await fs.access(path.join(skillDir, 'SKILL.md'));
|
|
222
|
+
return c.json({ success: false, error: 'A skill with this name already exists' }, 409);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// Doesn't exist, proceed
|
|
226
|
+
}
|
|
227
|
+
// Create skill directory
|
|
228
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
229
|
+
let finalContent = body.content || '';
|
|
230
|
+
if (body.archiveUrl) {
|
|
231
|
+
// Download and extract archive — SKILL.md comes from the zip
|
|
232
|
+
try {
|
|
233
|
+
const hasSkillMd = await downloadAndExtractArchive(body.archiveUrl, skillDir);
|
|
234
|
+
if (!hasSkillMd) {
|
|
235
|
+
// Fallback: generate SKILL.md from JSON fields if the archive didn't include one
|
|
236
|
+
const frontmatter = {
|
|
237
|
+
name: skillId,
|
|
238
|
+
description: body.description,
|
|
239
|
+
};
|
|
240
|
+
if (body.license)
|
|
241
|
+
frontmatter.license = body.license;
|
|
242
|
+
if (body.compatibility)
|
|
243
|
+
frontmatter.compatibility = body.compatibility;
|
|
244
|
+
if (body.allowedTools)
|
|
245
|
+
frontmatter['allowed-tools'] = body.allowedTools;
|
|
246
|
+
if (body.metadata && Object.keys(body.metadata).length > 0) {
|
|
247
|
+
const meta = {};
|
|
248
|
+
for (const [key, value] of Object.entries(body.metadata)) {
|
|
249
|
+
if (value !== undefined && value !== null) {
|
|
250
|
+
meta[key] = value;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (Object.keys(meta).length > 0) {
|
|
254
|
+
frontmatter.metadata = meta;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const fileContent = matter.stringify(finalContent, frontmatter);
|
|
258
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), fileContent, 'utf-8');
|
|
259
|
+
}
|
|
260
|
+
// Read back SKILL.md content for the response
|
|
261
|
+
try {
|
|
262
|
+
const written = await fs.readFile(path.join(skillDir, 'SKILL.md'), 'utf-8');
|
|
263
|
+
const { data: extractedFrontmatter, content: mdContent } = matter(written);
|
|
264
|
+
finalContent = mdContent;
|
|
265
|
+
// If the extracted SKILL.md body is empty but we have registry content,
|
|
266
|
+
// append it as a fallback and rewrite the file
|
|
267
|
+
if (!mdContent.trim() && body.content?.trim()) {
|
|
268
|
+
const rewritten = matter.stringify(body.content, extractedFrontmatter);
|
|
269
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), rewritten, 'utf-8');
|
|
270
|
+
finalContent = body.content;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// best effort
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (archiveError) {
|
|
278
|
+
// Clean up the directory on failure
|
|
279
|
+
await fs.rm(skillDir, { recursive: true, force: true }).catch(() => { });
|
|
280
|
+
return c.json({ success: false, error: `Archive extraction failed: ${archiveError.message}` }, 400);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
// No archive — build SKILL.md from JSON fields (existing behavior)
|
|
285
|
+
const frontmatter = {
|
|
286
|
+
name: skillId,
|
|
287
|
+
description: body.description,
|
|
288
|
+
};
|
|
289
|
+
if (body.license)
|
|
290
|
+
frontmatter.license = body.license;
|
|
291
|
+
if (body.compatibility)
|
|
292
|
+
frontmatter.compatibility = body.compatibility;
|
|
293
|
+
if (body.allowedTools)
|
|
294
|
+
frontmatter['allowed-tools'] = body.allowedTools;
|
|
295
|
+
if (body.metadata && Object.keys(body.metadata).length > 0) {
|
|
296
|
+
const meta = {};
|
|
297
|
+
for (const [key, value] of Object.entries(body.metadata)) {
|
|
298
|
+
if (value !== undefined && value !== null) {
|
|
299
|
+
meta[key] = value;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (Object.keys(meta).length > 0) {
|
|
303
|
+
frontmatter.metadata = meta;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const fileContent = matter.stringify(finalContent, frontmatter);
|
|
307
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), fileContent, 'utf-8');
|
|
308
|
+
}
|
|
309
|
+
return c.json({
|
|
310
|
+
success: true,
|
|
311
|
+
document: {
|
|
312
|
+
id: skillId,
|
|
313
|
+
name: skillId,
|
|
314
|
+
description: body.description,
|
|
315
|
+
metadata: {
|
|
316
|
+
name: skillId,
|
|
317
|
+
description: body.description,
|
|
318
|
+
...(body.metadata || {}),
|
|
319
|
+
},
|
|
320
|
+
content: finalContent
|
|
321
|
+
}
|
|
322
|
+
}, 201);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
console.error('Error creating skill document:', error);
|
|
326
|
+
return c.json({ success: false, error: 'Failed to create skill document' }, 500);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
export default app;
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
3
|
+
success: false;
|
|
4
|
+
error: {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
9
|
+
success: true;
|
|
10
|
+
data: {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
intent?: string;
|
|
14
|
+
content?: string;
|
|
15
|
+
author: {
|
|
16
|
+
type: import("@lovelybunch/core").AuthorType;
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
};
|
|
21
|
+
productSpecRef?: string;
|
|
22
|
+
planSteps: {
|
|
23
|
+
id: string;
|
|
24
|
+
description: string;
|
|
25
|
+
command?: string;
|
|
26
|
+
expectedOutcome?: string;
|
|
27
|
+
status: "pending" | "in-progress" | "completed" | "failed";
|
|
28
|
+
output?: string;
|
|
29
|
+
error?: string;
|
|
30
|
+
executedAt?: string;
|
|
31
|
+
}[];
|
|
32
|
+
evidence: {
|
|
33
|
+
type: "test" | "benchmark" | "screenshot" | "log" | "other";
|
|
34
|
+
description: string;
|
|
35
|
+
data: any;
|
|
36
|
+
timestamp: string;
|
|
37
|
+
}[];
|
|
38
|
+
policies: {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
description: string;
|
|
42
|
+
status: "pending" | "passed" | "failed" | "skipped";
|
|
43
|
+
message?: string;
|
|
44
|
+
checkedAt?: string;
|
|
45
|
+
}[];
|
|
46
|
+
featureFlags: {
|
|
47
|
+
id: string;
|
|
48
|
+
version: string;
|
|
49
|
+
name: string;
|
|
50
|
+
description: string;
|
|
51
|
+
type: import("@lovelybunch/core").FeatureFlagType;
|
|
52
|
+
defaultValue: any;
|
|
53
|
+
scopes: string[];
|
|
54
|
+
targets: {
|
|
55
|
+
audience: string;
|
|
56
|
+
value: any;
|
|
57
|
+
percentage?: number;
|
|
58
|
+
}[];
|
|
59
|
+
killSwitch: boolean;
|
|
60
|
+
dependencies: string[];
|
|
61
|
+
createdAt: string;
|
|
62
|
+
updatedAt: string;
|
|
63
|
+
}[];
|
|
64
|
+
experiments: {
|
|
65
|
+
id: string;
|
|
66
|
+
name: string;
|
|
67
|
+
hypothesis: string;
|
|
68
|
+
variants: {
|
|
69
|
+
id: string;
|
|
70
|
+
name: string;
|
|
71
|
+
description: string;
|
|
72
|
+
allocation: number;
|
|
73
|
+
config: {
|
|
74
|
+
[x: string]: any;
|
|
75
|
+
};
|
|
76
|
+
}[];
|
|
77
|
+
allocation: {
|
|
78
|
+
method: "random" | "deterministic" | "weighted";
|
|
79
|
+
seed?: string;
|
|
80
|
+
totalAllocation: number;
|
|
81
|
+
};
|
|
82
|
+
successMetrics: string[];
|
|
83
|
+
guardrailMetrics: {
|
|
84
|
+
name: string;
|
|
85
|
+
threshold: number;
|
|
86
|
+
comparison: "greater" | "less" | "equal";
|
|
87
|
+
action: "alert" | "pause" | "stop";
|
|
88
|
+
}[];
|
|
89
|
+
stopRules: {
|
|
90
|
+
metric: string;
|
|
91
|
+
condition: string;
|
|
92
|
+
action: "stop" | "pause";
|
|
93
|
+
}[];
|
|
94
|
+
analysisConfig: {
|
|
95
|
+
confidenceLevel: number;
|
|
96
|
+
minimumSampleSize: number;
|
|
97
|
+
testType: "two-tailed" | "one-tailed";
|
|
98
|
+
};
|
|
99
|
+
status: import("@lovelybunch/core").ExperimentStatus;
|
|
100
|
+
startedAt?: string;
|
|
101
|
+
endedAt?: string;
|
|
102
|
+
}[];
|
|
103
|
+
telemetryContracts: {
|
|
104
|
+
eventName: string;
|
|
105
|
+
version: string;
|
|
106
|
+
schema: {
|
|
107
|
+
[x: string]: any;
|
|
108
|
+
};
|
|
109
|
+
piiFields: {
|
|
110
|
+
field: string;
|
|
111
|
+
classification: "none" | "quasi" | "sensitive" | "highly-sensitive";
|
|
112
|
+
handling: "clear" | "hash" | "encrypt" | "remove";
|
|
113
|
+
}[];
|
|
114
|
+
samplingRate: number;
|
|
115
|
+
retentionPolicy: {
|
|
116
|
+
duration: number;
|
|
117
|
+
archiveAfter?: number;
|
|
118
|
+
deleteAfter: number;
|
|
119
|
+
};
|
|
120
|
+
approvals: {
|
|
121
|
+
approver: string;
|
|
122
|
+
approvedAt: string;
|
|
123
|
+
comment?: string;
|
|
124
|
+
}[];
|
|
125
|
+
}[];
|
|
126
|
+
releasePlan: {
|
|
127
|
+
strategy: "immediate" | "gradual" | "scheduled" | "gated";
|
|
128
|
+
stages?: {
|
|
129
|
+
name: string;
|
|
130
|
+
percentage: number;
|
|
131
|
+
duration?: number;
|
|
132
|
+
criteria?: string[];
|
|
133
|
+
}[];
|
|
134
|
+
schedule?: string;
|
|
135
|
+
rollbackPlan?: string;
|
|
136
|
+
};
|
|
137
|
+
status: import("@lovelybunch/core").TaskStatus;
|
|
138
|
+
comments?: {
|
|
139
|
+
id: string;
|
|
140
|
+
author: string;
|
|
141
|
+
content: string;
|
|
142
|
+
createdAt: string;
|
|
143
|
+
}[];
|
|
144
|
+
metadata: {
|
|
145
|
+
createdAt: string;
|
|
146
|
+
updatedAt: string;
|
|
147
|
+
reviewers: string[];
|
|
148
|
+
aiInteractions: {
|
|
149
|
+
id: string;
|
|
150
|
+
timestamp: string;
|
|
151
|
+
model: string;
|
|
152
|
+
prompt: string;
|
|
153
|
+
response: string;
|
|
154
|
+
tokens: {
|
|
155
|
+
prompt: number;
|
|
156
|
+
completion: number;
|
|
157
|
+
total: number;
|
|
158
|
+
};
|
|
159
|
+
cost?: number;
|
|
160
|
+
}[];
|
|
161
|
+
tags?: string[];
|
|
162
|
+
priority?: "low" | "medium" | "high" | "critical";
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
166
|
+
success: false;
|
|
167
|
+
error: {
|
|
168
|
+
code: string;
|
|
169
|
+
message: any;
|
|
170
|
+
};
|
|
171
|
+
}, 500, "json">)>;
|
|
172
|
+
export declare function PATCH(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
173
|
+
success: true;
|
|
174
|
+
data: {
|
|
175
|
+
id: string;
|
|
176
|
+
title: string;
|
|
177
|
+
intent?: string;
|
|
178
|
+
content?: string;
|
|
179
|
+
author: {
|
|
180
|
+
type: import("@lovelybunch/core").AuthorType;
|
|
181
|
+
id: string;
|
|
182
|
+
name: string;
|
|
183
|
+
email?: string;
|
|
184
|
+
};
|
|
185
|
+
productSpecRef?: string;
|
|
186
|
+
planSteps: {
|
|
187
|
+
id: string;
|
|
188
|
+
description: string;
|
|
189
|
+
command?: string;
|
|
190
|
+
expectedOutcome?: string;
|
|
191
|
+
status: "pending" | "in-progress" | "completed" | "failed";
|
|
192
|
+
output?: string;
|
|
193
|
+
error?: string;
|
|
194
|
+
executedAt?: string;
|
|
195
|
+
}[];
|
|
196
|
+
evidence: {
|
|
197
|
+
type: "test" | "benchmark" | "screenshot" | "log" | "other";
|
|
198
|
+
description: string;
|
|
199
|
+
data: any;
|
|
200
|
+
timestamp: string;
|
|
201
|
+
}[];
|
|
202
|
+
policies: {
|
|
203
|
+
id: string;
|
|
204
|
+
name: string;
|
|
205
|
+
description: string;
|
|
206
|
+
status: "pending" | "passed" | "failed" | "skipped";
|
|
207
|
+
message?: string;
|
|
208
|
+
checkedAt?: string;
|
|
209
|
+
}[];
|
|
210
|
+
featureFlags: {
|
|
211
|
+
id: string;
|
|
212
|
+
version: string;
|
|
213
|
+
name: string;
|
|
214
|
+
description: string;
|
|
215
|
+
type: import("@lovelybunch/core").FeatureFlagType;
|
|
216
|
+
defaultValue: any;
|
|
217
|
+
scopes: string[];
|
|
218
|
+
targets: {
|
|
219
|
+
audience: string;
|
|
220
|
+
value: any;
|
|
221
|
+
percentage?: number;
|
|
222
|
+
}[];
|
|
223
|
+
killSwitch: boolean;
|
|
224
|
+
dependencies: string[];
|
|
225
|
+
createdAt: string;
|
|
226
|
+
updatedAt: string;
|
|
227
|
+
}[];
|
|
228
|
+
experiments: {
|
|
229
|
+
id: string;
|
|
230
|
+
name: string;
|
|
231
|
+
hypothesis: string;
|
|
232
|
+
variants: {
|
|
233
|
+
id: string;
|
|
234
|
+
name: string;
|
|
235
|
+
description: string;
|
|
236
|
+
allocation: number;
|
|
237
|
+
config: {
|
|
238
|
+
[x: string]: any;
|
|
239
|
+
};
|
|
240
|
+
}[];
|
|
241
|
+
allocation: {
|
|
242
|
+
method: "random" | "deterministic" | "weighted";
|
|
243
|
+
seed?: string;
|
|
244
|
+
totalAllocation: number;
|
|
245
|
+
};
|
|
246
|
+
successMetrics: string[];
|
|
247
|
+
guardrailMetrics: {
|
|
248
|
+
name: string;
|
|
249
|
+
threshold: number;
|
|
250
|
+
comparison: "greater" | "less" | "equal";
|
|
251
|
+
action: "alert" | "pause" | "stop";
|
|
252
|
+
}[];
|
|
253
|
+
stopRules: {
|
|
254
|
+
metric: string;
|
|
255
|
+
condition: string;
|
|
256
|
+
action: "stop" | "pause";
|
|
257
|
+
}[];
|
|
258
|
+
analysisConfig: {
|
|
259
|
+
confidenceLevel: number;
|
|
260
|
+
minimumSampleSize: number;
|
|
261
|
+
testType: "two-tailed" | "one-tailed";
|
|
262
|
+
};
|
|
263
|
+
status: import("@lovelybunch/core").ExperimentStatus;
|
|
264
|
+
startedAt?: string;
|
|
265
|
+
endedAt?: string;
|
|
266
|
+
}[];
|
|
267
|
+
telemetryContracts: {
|
|
268
|
+
eventName: string;
|
|
269
|
+
version: string;
|
|
270
|
+
schema: {
|
|
271
|
+
[x: string]: any;
|
|
272
|
+
};
|
|
273
|
+
piiFields: {
|
|
274
|
+
field: string;
|
|
275
|
+
classification: "none" | "quasi" | "sensitive" | "highly-sensitive";
|
|
276
|
+
handling: "clear" | "hash" | "encrypt" | "remove";
|
|
277
|
+
}[];
|
|
278
|
+
samplingRate: number;
|
|
279
|
+
retentionPolicy: {
|
|
280
|
+
duration: number;
|
|
281
|
+
archiveAfter?: number;
|
|
282
|
+
deleteAfter: number;
|
|
283
|
+
};
|
|
284
|
+
approvals: {
|
|
285
|
+
approver: string;
|
|
286
|
+
approvedAt: string;
|
|
287
|
+
comment?: string;
|
|
288
|
+
}[];
|
|
289
|
+
}[];
|
|
290
|
+
releasePlan: {
|
|
291
|
+
strategy: "immediate" | "gradual" | "scheduled" | "gated";
|
|
292
|
+
stages?: {
|
|
293
|
+
name: string;
|
|
294
|
+
percentage: number;
|
|
295
|
+
duration?: number;
|
|
296
|
+
criteria?: string[];
|
|
297
|
+
}[];
|
|
298
|
+
schedule?: string;
|
|
299
|
+
rollbackPlan?: string;
|
|
300
|
+
};
|
|
301
|
+
status: import("@lovelybunch/core").TaskStatus;
|
|
302
|
+
comments?: {
|
|
303
|
+
id: string;
|
|
304
|
+
author: string;
|
|
305
|
+
content: string;
|
|
306
|
+
createdAt: string;
|
|
307
|
+
}[];
|
|
308
|
+
metadata: {
|
|
309
|
+
createdAt: string;
|
|
310
|
+
updatedAt: string;
|
|
311
|
+
reviewers: string[];
|
|
312
|
+
aiInteractions: {
|
|
313
|
+
id: string;
|
|
314
|
+
timestamp: string;
|
|
315
|
+
model: string;
|
|
316
|
+
prompt: string;
|
|
317
|
+
response: string;
|
|
318
|
+
tokens: {
|
|
319
|
+
prompt: number;
|
|
320
|
+
completion: number;
|
|
321
|
+
total: number;
|
|
322
|
+
};
|
|
323
|
+
cost?: number;
|
|
324
|
+
}[];
|
|
325
|
+
tags?: string[];
|
|
326
|
+
priority?: "low" | "medium" | "high" | "critical";
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
330
|
+
success: false;
|
|
331
|
+
error: {
|
|
332
|
+
code: string;
|
|
333
|
+
message: any;
|
|
334
|
+
};
|
|
335
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
336
|
+
success: false;
|
|
337
|
+
error: {
|
|
338
|
+
code: string;
|
|
339
|
+
message: any;
|
|
340
|
+
};
|
|
341
|
+
}, 500, "json">)>;
|
|
342
|
+
export declare function DELETE(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
343
|
+
success: true;
|
|
344
|
+
message: string;
|
|
345
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
346
|
+
success: false;
|
|
347
|
+
error: {
|
|
348
|
+
code: string;
|
|
349
|
+
message: any;
|
|
350
|
+
};
|
|
351
|
+
}, 500, "json">)>;
|