dot-studio 0.0.1 → 0.0.2
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/README.md +20 -200
- package/client/assets/ActFrame-BYOBkLYW.js +1 -0
- package/client/assets/ActFrame-C_WEt6bv.css +1 -0
- package/client/assets/ActInspectorPanel-C3VlS7tB.js +1 -0
- package/client/assets/ActInspectorPanel-CE6s6GYv.css +1 -0
- package/client/assets/AssistantChat-BOyW0K79.js +1 -0
- package/client/assets/AssistantChat-DoVmHvMJ.css +1 -0
- package/client/assets/CanvasTerminalFrame-BC-79q9U.css +1 -0
- package/client/assets/CanvasTerminalFrame-DxKbexK6.js +4 -0
- package/client/assets/CanvasTrackingFrame-DumxhNwg.js +1 -0
- package/client/assets/CanvasTrackingFrame-G4rRrfne.css +1 -0
- package/client/assets/CanvasWindowFrame-ziJeVfHG.js +1 -0
- package/client/assets/DanceBundleEditorFrame-CH8VDUMK.js +1 -0
- package/client/assets/DanceBundleEditorFrame-DaLqMflT.css +1 -0
- package/client/assets/MarkdownEditorFrame-DVecIZpZ.css +1 -0
- package/client/assets/MarkdownEditorFrame-Dwpgs2GX.js +2 -0
- package/client/assets/MarkdownRenderer-Cz8A4AgP.js +1 -0
- package/client/assets/PublishModal-DUlHz0fT.js +1 -0
- package/client/assets/TodoDock-DcVf7zQG.js +1 -0
- package/client/assets/WorkspaceToolbar-CXYi_sMD.js +2 -0
- package/client/assets/WorkspaceToolbar-CiQvVocC.css +1 -0
- package/client/assets/chat-message-visibility-YwJ-AQno.js +11 -0
- package/client/assets/dnd-vendor-CIAZE2P2.js +5 -0
- package/client/assets/flow-vendor-BZV40eAE.css +1 -0
- package/client/assets/flow-vendor-C868rU-6.js +23 -0
- package/client/assets/icon-vendor-I2JVIi1s.js +501 -0
- package/client/assets/index-BMY4hrBP.js +3 -0
- package/client/assets/index-C-vnj9y3.js +1 -0
- package/client/assets/index-C9HTqfZw.css +1 -0
- package/client/assets/index-CWrv6O3o.js +64 -0
- package/client/assets/index-DMS12-Q2.js +8 -0
- package/client/assets/index-Dn7t_Y7G.js +1 -0
- package/client/assets/index-p-wk7iGH.css +1 -0
- package/client/assets/markdown-vendor-BSTcku12.css +10 -0
- package/client/assets/markdown-vendor-DnTJ9hmR.js +35 -0
- package/client/assets/participant-labels-Cf3qP3GB.js +1 -0
- package/client/assets/queries-Dm1jEHfc.js +1 -0
- package/client/assets/query-vendor-_taqgrbn.js +1 -0
- package/client/assets/react-vendor-DzpMUNDT.js +49 -0
- package/client/assets/settings-utils-l7KCS3Ev.js +1 -0
- package/client/assets/terminal-vendor-6GBZ9nXN.css +32 -0
- package/client/assets/terminal-vendor-D0xRnmbI.js +112 -0
- package/client/index.html +13 -3
- package/dist/cli.js +25 -3
- package/dist/server/app.js +72 -0
- package/dist/server/index.js +2 -62
- package/dist/server/lib/act-session-policy.js +31 -0
- package/dist/server/lib/chat-session.js +101 -0
- package/dist/server/lib/config.js +18 -4
- package/dist/server/lib/dot-authoring.js +171 -102
- package/dist/server/lib/dot-loader.js +9 -8
- package/dist/server/lib/dot-login.js +8 -190
- package/dist/server/lib/dot-source.js +11 -0
- package/dist/server/lib/model-catalog.js +74 -15
- package/dist/server/lib/opencode-auth.js +4 -1
- package/dist/server/lib/opencode-errors.js +70 -38
- package/dist/server/lib/opencode-sidecar.js +5 -2
- package/dist/server/lib/project-config.js +8 -0
- package/dist/server/lib/runtime-tools.js +46 -8
- package/dist/server/lib/safe-mode.js +410 -0
- package/dist/server/lib/session-execution.js +81 -0
- package/dist/server/lib/sse.js +22 -0
- package/dist/server/routes/act-runtime-threads.js +156 -0
- package/dist/server/routes/act-runtime-tools.js +157 -0
- package/dist/server/routes/act-runtime.js +7 -0
- package/dist/server/routes/adapter.js +32 -0
- package/dist/server/routes/assets-collection.js +16 -0
- package/dist/server/routes/assets-detail.js +38 -0
- package/dist/server/routes/assets.js +4 -158
- package/dist/server/routes/chat-messages.js +104 -0
- package/dist/server/routes/chat-sessions.js +104 -0
- package/dist/server/routes/chat-stream.js +15 -0
- package/dist/server/routes/chat.js +6 -353
- package/dist/server/routes/compile.js +5 -91
- package/dist/server/routes/dot-assets.js +77 -0
- package/dist/server/routes/dot-core.js +62 -0
- package/dist/server/routes/dot-performer.js +80 -0
- package/dist/server/routes/dot.js +6 -267
- package/dist/server/routes/drafts-collection.js +40 -0
- package/dist/server/routes/drafts-dance-bundle.js +113 -0
- package/dist/server/routes/drafts-item.js +86 -0
- package/dist/server/routes/drafts.js +9 -0
- package/dist/server/routes/health.js +18 -33
- package/dist/server/routes/opencode-core.js +120 -0
- package/dist/server/routes/opencode-file.js +67 -0
- package/dist/server/routes/opencode-mcp.js +74 -0
- package/dist/server/routes/opencode-provider.js +41 -0
- package/dist/server/routes/opencode.js +8 -418
- package/dist/server/routes/route-errors.js +10 -0
- package/dist/server/routes/safe-actions.js +60 -0
- package/dist/server/routes/safe-summary.js +20 -0
- package/dist/server/routes/safe.js +7 -0
- package/dist/server/routes/workspaces.js +47 -0
- package/dist/server/services/act-runtime/act-context-builder.js +81 -0
- package/dist/server/services/act-runtime/act-runtime-service.js +313 -0
- package/dist/server/services/act-runtime/act-runtime-utils.js +10 -0
- package/dist/server/services/act-runtime/act-tool-projection.js +26 -0
- package/dist/server/services/act-runtime/act-tools.js +151 -0
- package/dist/server/services/act-runtime/board-persistence.js +38 -0
- package/dist/server/services/act-runtime/event-logger.js +73 -0
- package/dist/server/services/act-runtime/event-router.js +102 -0
- package/dist/server/services/act-runtime/mailbox.js +149 -0
- package/dist/server/services/act-runtime/safety-guard.js +162 -0
- package/dist/server/services/act-runtime/session-queue.js +114 -0
- package/dist/server/services/act-runtime/thread-manager.js +351 -0
- package/dist/server/services/act-runtime/wake-cascade.js +306 -0
- package/dist/server/services/act-runtime/wake-evaluator.js +43 -0
- package/dist/server/services/act-runtime/wake-performer-resolver.js +68 -0
- package/dist/server/services/act-runtime/wake-prompt-builder.js +77 -0
- package/dist/server/services/adapter-view-service.js +6 -0
- package/dist/server/services/asset-service.js +366 -0
- package/dist/server/services/chat-event-stream-service.js +157 -0
- package/dist/server/services/chat-service.js +207 -0
- package/dist/server/services/chat-session-service.js +203 -0
- package/dist/server/services/compile-service.js +4 -0
- package/dist/server/services/dance-bundle-service.js +222 -0
- package/dist/server/services/dot-add-service.js +59 -0
- package/dist/server/services/dot-service.js +178 -0
- package/dist/server/services/draft-service.js +367 -0
- package/dist/server/services/opencode-projection/dance-compiler.js +164 -0
- package/dist/server/services/opencode-projection/performer-compiler.js +195 -0
- package/dist/server/services/opencode-projection/preview-service.js +31 -0
- package/dist/server/services/opencode-projection/projection-manifest.js +98 -0
- package/dist/server/services/opencode-projection/stage-projection-service.js +188 -0
- package/dist/server/services/opencode-service.js +338 -0
- package/dist/server/services/safe-service.js +33 -0
- package/dist/server/services/studio-assistant/assistant-service.js +172 -0
- package/dist/server/services/studio-service.js +69 -0
- package/dist/server/services/workspace-service.js +224 -0
- package/dist/server/terminal.js +57 -11
- package/dist/shared/act-types.js +4 -0
- package/dist/shared/adapter-view.js +1 -0
- package/dist/shared/asset-contracts.js +1 -0
- package/dist/shared/assistant-actions.js +1 -0
- package/dist/shared/chat-contracts.js +1 -0
- package/dist/shared/dot-contracts.js +1 -0
- package/dist/shared/dot-types.js +4 -0
- package/dist/shared/draft-contracts.js +2 -0
- package/dist/shared/model-types.js +2 -0
- package/dist/shared/performer-mcp-portability.js +10 -0
- package/dist/shared/safe-mode.js +1 -0
- package/dist/shared/session-metadata.js +4 -3
- package/package.json +6 -4
- package/client/assets/index-C2eIILoa.css +0 -41
- package/client/assets/index-DUPZ_Lw5.js +0 -616
- package/dist/server/lib/act-runtime.js +0 -1282
- package/dist/server/lib/prompt.js +0 -222
- package/dist/server/routes/stages.js +0 -137
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { createHash } from 'crypto';
|
|
4
|
-
import { getAssetPayload, readAsset } from 'dance-of-tal/lib/registry';
|
|
5
|
-
import { resolveRuntimeModel } from './model-catalog.js';
|
|
6
|
-
import { findRuntimeModelVariant } from '../../shared/model-variants.js';
|
|
7
|
-
import { StudioValidationError } from './opencode-errors.js';
|
|
8
|
-
const CAPABILITY_LOADER_TOOL_NAME = 'read';
|
|
9
|
-
function extractDraftTextContent(draft) {
|
|
10
|
-
if (!draft) {
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
if (typeof draft.content === 'string') {
|
|
14
|
-
return draft.content;
|
|
15
|
-
}
|
|
16
|
-
if (draft.content && typeof draft.content === 'object') {
|
|
17
|
-
const content = draft.content;
|
|
18
|
-
if (typeof content.content === 'string') {
|
|
19
|
-
return content.content;
|
|
20
|
-
}
|
|
21
|
-
if (typeof content.body === 'string') {
|
|
22
|
-
return content.body;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
function extractDraftDescription(draft) {
|
|
28
|
-
if (!draft) {
|
|
29
|
-
return '';
|
|
30
|
-
}
|
|
31
|
-
if (typeof draft.description === 'string') {
|
|
32
|
-
return draft.description;
|
|
33
|
-
}
|
|
34
|
-
if (draft.content && typeof draft.content === 'object') {
|
|
35
|
-
const content = draft.content;
|
|
36
|
-
if (typeof content.description === 'string') {
|
|
37
|
-
return content.description;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return '';
|
|
41
|
-
}
|
|
42
|
-
async function resolveTalContent(cwd, ref, drafts) {
|
|
43
|
-
if (!ref) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
if (ref.kind === 'registry') {
|
|
47
|
-
return getAssetPayload(cwd, ref.urn);
|
|
48
|
-
}
|
|
49
|
-
return extractDraftTextContent(drafts[ref.draftId]);
|
|
50
|
-
}
|
|
51
|
-
function draftDisplayName(ref, drafts) {
|
|
52
|
-
const draft = drafts[ref.draftId];
|
|
53
|
-
return draft?.name || draft?.description || `draft:${ref.draftId}`;
|
|
54
|
-
}
|
|
55
|
-
function runtimeCapabilityDir(cwd) {
|
|
56
|
-
return path.join(path.resolve(cwd), '.dot-studio', 'runtime-capabilities');
|
|
57
|
-
}
|
|
58
|
-
async function writeCapabilityDocument(cwd, refKey, payload) {
|
|
59
|
-
const dir = runtimeCapabilityDir(cwd);
|
|
60
|
-
await fs.mkdir(dir, { recursive: true });
|
|
61
|
-
const fileHash = createHash('sha1').update(refKey).digest('hex').slice(0, 16);
|
|
62
|
-
const filePath = path.join(dir, `${fileHash}.md`);
|
|
63
|
-
const doc = [
|
|
64
|
-
`# ${payload.title}`,
|
|
65
|
-
payload.description ? `Description: ${payload.description}` : 'Description: No description provided.',
|
|
66
|
-
'',
|
|
67
|
-
'---',
|
|
68
|
-
'',
|
|
69
|
-
payload.body,
|
|
70
|
-
].join('\n');
|
|
71
|
-
await fs.writeFile(filePath, doc, 'utf-8');
|
|
72
|
-
return filePath;
|
|
73
|
-
}
|
|
74
|
-
async function materializeCapabilityDocument(cwd, ref, drafts) {
|
|
75
|
-
if (ref.kind === 'registry') {
|
|
76
|
-
const asset = await readAsset(cwd, ref.urn);
|
|
77
|
-
const body = await getAssetPayload(cwd, ref.urn);
|
|
78
|
-
if (!body) {
|
|
79
|
-
throw new StudioValidationError(`Capability '${ref.urn}' was not found or has no content.`, 'fix_input');
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
urn: ref.urn,
|
|
83
|
-
description: typeof asset?.description === 'string' ? asset.description : '',
|
|
84
|
-
path: await writeCapabilityDocument(cwd, `registry:${ref.urn}`, {
|
|
85
|
-
title: ref.urn,
|
|
86
|
-
description: typeof asset?.description === 'string' ? asset.description : '',
|
|
87
|
-
body,
|
|
88
|
-
}),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
const draft = drafts[ref.draftId];
|
|
92
|
-
const body = extractDraftTextContent(draft);
|
|
93
|
-
if (!draft || !body) {
|
|
94
|
-
throw new StudioValidationError(`Capability draft '${draftDisplayName(ref, drafts)}' was not found or has no content.`, 'fix_input');
|
|
95
|
-
}
|
|
96
|
-
return {
|
|
97
|
-
urn: `draft/${ref.draftId}`,
|
|
98
|
-
description: extractDraftDescription(draft) || draft.name || 'Draft capability',
|
|
99
|
-
path: await writeCapabilityDocument(cwd, `draft:${ref.draftId}`, {
|
|
100
|
-
title: draft.name || `draft/${ref.draftId}`,
|
|
101
|
-
description: extractDraftDescription(draft) || draft.name || 'Draft capability',
|
|
102
|
-
body,
|
|
103
|
-
}),
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function buildSystemPreamble(toolName) {
|
|
107
|
-
const lines = [
|
|
108
|
-
'# Runtime Instructions',
|
|
109
|
-
'The section named Core Instructions is the always-on instruction layer for your role, rules, and operating logic.',
|
|
110
|
-
'The section named Optional Capability Catalog lists extra modules you may consult only when the task actually needs them.',
|
|
111
|
-
'Prefer the minimum capability context needed to complete the task well.',
|
|
112
|
-
'Do not mention internal runtime wiring, capability loading, or system sections unless the user asks about them directly.',
|
|
113
|
-
];
|
|
114
|
-
if (toolName) {
|
|
115
|
-
lines.push(`When optional capability context is needed, use the '${toolName}' tool to inspect the capability document path listed in the catalog.`);
|
|
116
|
-
}
|
|
117
|
-
return lines.join('\n');
|
|
118
|
-
}
|
|
119
|
-
function buildTalSection(talContent) {
|
|
120
|
-
if (!talContent) {
|
|
121
|
-
return [
|
|
122
|
-
'# Core Instructions',
|
|
123
|
-
'No core instruction asset is configured. Follow the user request directly and stay consistent with the current session context.',
|
|
124
|
-
].join('\n');
|
|
125
|
-
}
|
|
126
|
-
return [
|
|
127
|
-
'# Core Instructions',
|
|
128
|
-
talContent,
|
|
129
|
-
].join('\n\n');
|
|
130
|
-
}
|
|
131
|
-
function buildDanceSection(catalog, toolName) {
|
|
132
|
-
if (catalog.length === 0) {
|
|
133
|
-
return [
|
|
134
|
-
'# Optional Capability Catalog',
|
|
135
|
-
'No optional capability assets are configured.',
|
|
136
|
-
].join('\n');
|
|
137
|
-
}
|
|
138
|
-
const lines = [
|
|
139
|
-
'# Optional Capability Usage',
|
|
140
|
-
`Capability bodies are available on demand through '${toolName}'. Use the catalog below to decide whether to load one.`,
|
|
141
|
-
'',
|
|
142
|
-
'# Optional Capability Catalog',
|
|
143
|
-
];
|
|
144
|
-
for (const entry of catalog) {
|
|
145
|
-
lines.push(`- ${entry.urn}: ${entry.description || 'No description provided.'}${entry.path ? ` (path: ${entry.path})` : ''}`);
|
|
146
|
-
}
|
|
147
|
-
return lines.join('\n');
|
|
148
|
-
}
|
|
149
|
-
function buildRuntimePreferencesSection(input) {
|
|
150
|
-
if (!input.variantId) {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
const lines = [
|
|
154
|
-
'# Runtime Preferences',
|
|
155
|
-
`Preferred model variant: ${input.variantId}`,
|
|
156
|
-
];
|
|
157
|
-
if (input.variantSummary) {
|
|
158
|
-
lines.push(`Variant settings: ${input.variantSummary}`);
|
|
159
|
-
}
|
|
160
|
-
lines.push('Apply this preferred runtime profile when supported by the current host and model.');
|
|
161
|
-
return lines.join('\n');
|
|
162
|
-
}
|
|
163
|
-
export async function buildPromptEnvelope(input) {
|
|
164
|
-
if (!input.model) {
|
|
165
|
-
throw new Error('A model is required for this performer. Select a model before compiling or sending prompts.');
|
|
166
|
-
}
|
|
167
|
-
const runtimeModel = await resolveRuntimeModel(input.cwd, input.model);
|
|
168
|
-
const capabilitySnapshot = runtimeModel
|
|
169
|
-
? {
|
|
170
|
-
toolCall: runtimeModel.toolCall,
|
|
171
|
-
reasoning: runtimeModel.reasoning,
|
|
172
|
-
attachment: runtimeModel.attachment,
|
|
173
|
-
temperature: runtimeModel.temperature,
|
|
174
|
-
modalities: runtimeModel.modalities,
|
|
175
|
-
}
|
|
176
|
-
: null;
|
|
177
|
-
const selectedVariant = runtimeModel
|
|
178
|
-
? findRuntimeModelVariant([runtimeModel], input.model.provider, input.model.modelId, input.modelVariant || null)
|
|
179
|
-
: null;
|
|
180
|
-
const resolvedVariantId = runtimeModel
|
|
181
|
-
? selectedVariant?.id || null
|
|
182
|
-
: input.modelVariant || null;
|
|
183
|
-
const drafts = input.drafts || {};
|
|
184
|
-
if (input.danceRefs.length > 0 && !capabilitySnapshot?.toolCall) {
|
|
185
|
-
throw new StudioValidationError('The selected model does not support runtime capability loading. Choose a tool-capable model or remove saved capabilities.', 'choose_model');
|
|
186
|
-
}
|
|
187
|
-
const talContent = await resolveTalContent(input.cwd, input.talRef, drafts);
|
|
188
|
-
let deliveryMode = 'tool';
|
|
189
|
-
let toolName;
|
|
190
|
-
if (input.danceRefs.length > 0) {
|
|
191
|
-
toolName = CAPABILITY_LOADER_TOOL_NAME;
|
|
192
|
-
}
|
|
193
|
-
const danceCatalog = [];
|
|
194
|
-
for (const ref of input.danceRefs) {
|
|
195
|
-
const document = await materializeCapabilityDocument(input.cwd, ref, drafts);
|
|
196
|
-
danceCatalog.push({
|
|
197
|
-
urn: document.urn,
|
|
198
|
-
description: document.description,
|
|
199
|
-
loadMode: deliveryMode,
|
|
200
|
-
path: document.path,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
if (deliveryMode === 'tool' && danceCatalog.some((entry) => entry.loadMode === 'tool')) {
|
|
204
|
-
toolName = toolName || CAPABILITY_LOADER_TOOL_NAME;
|
|
205
|
-
}
|
|
206
|
-
const sections = [
|
|
207
|
-
buildSystemPreamble(toolName),
|
|
208
|
-
buildTalSection(talContent),
|
|
209
|
-
buildDanceSection(danceCatalog, toolName),
|
|
210
|
-
buildRuntimePreferencesSection({
|
|
211
|
-
variantId: resolvedVariantId,
|
|
212
|
-
variantSummary: selectedVariant?.summary || null,
|
|
213
|
-
}),
|
|
214
|
-
];
|
|
215
|
-
return {
|
|
216
|
-
system: sections.filter(Boolean).join('\n\n').trim() || '// No core instructions or optional capabilities configured',
|
|
217
|
-
danceCatalog,
|
|
218
|
-
deliveryMode,
|
|
219
|
-
capabilitySnapshot,
|
|
220
|
-
...(toolName ? { toolName } : {}),
|
|
221
|
-
};
|
|
222
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
// Stage CRUD Routes — with path validation
|
|
2
|
-
import { Hono } from 'hono';
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { createHash } from 'crypto';
|
|
6
|
-
import { stagesDir } from '../lib/config.js';
|
|
7
|
-
const stages = new Hono();
|
|
8
|
-
// ── Helpers ─────────────────────────────────────────────
|
|
9
|
-
function sanitizeStageId(id) {
|
|
10
|
-
return id
|
|
11
|
-
.replace(/\.\./g, '')
|
|
12
|
-
.replace(/[/\\:*?"<>|\x00-\x1f]/g, '')
|
|
13
|
-
.trim();
|
|
14
|
-
}
|
|
15
|
-
function validateStageId(id) {
|
|
16
|
-
const clean = sanitizeStageId(id);
|
|
17
|
-
if (!clean || clean.length === 0)
|
|
18
|
-
return null;
|
|
19
|
-
if (clean.length > 128)
|
|
20
|
-
return null;
|
|
21
|
-
return clean;
|
|
22
|
-
}
|
|
23
|
-
function normalizeWorkingDir(input) {
|
|
24
|
-
const trimmed = input.trim().replace(/\/+$/, '');
|
|
25
|
-
if (!trimmed)
|
|
26
|
-
return null;
|
|
27
|
-
return path.resolve(trimmed);
|
|
28
|
-
}
|
|
29
|
-
function stageIdForWorkingDir(workingDir) {
|
|
30
|
-
return createHash('sha1').update(workingDir).digest('hex').slice(0, 16);
|
|
31
|
-
}
|
|
32
|
-
function stagePathForId(id) {
|
|
33
|
-
return path.join(stagesDir(), `${id}.json`);
|
|
34
|
-
}
|
|
35
|
-
// ── List Stages ─────────────────────────────────────────
|
|
36
|
-
stages.get('/api/stages', async (c) => {
|
|
37
|
-
const dir = stagesDir();
|
|
38
|
-
try {
|
|
39
|
-
await fs.mkdir(dir, { recursive: true });
|
|
40
|
-
const files = await fs.readdir(dir);
|
|
41
|
-
const entries = await Promise.all(files
|
|
42
|
-
.filter((f) => f.endsWith('.json'))
|
|
43
|
-
.map(async (file) => {
|
|
44
|
-
const filePath = path.join(dir, file);
|
|
45
|
-
try {
|
|
46
|
-
const [raw, stat] = await Promise.all([
|
|
47
|
-
fs.readFile(filePath, 'utf-8'),
|
|
48
|
-
fs.stat(filePath),
|
|
49
|
-
]);
|
|
50
|
-
const parsed = JSON.parse(raw);
|
|
51
|
-
const workingDir = normalizeWorkingDir(parsed.workingDir || '') || '';
|
|
52
|
-
if (!workingDir) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
return {
|
|
56
|
-
id: file.replace('.json', ''),
|
|
57
|
-
workingDir,
|
|
58
|
-
updatedAt: stat.mtimeMs,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}));
|
|
65
|
-
return c.json(entries
|
|
66
|
-
.filter((entry) => entry !== null)
|
|
67
|
-
.sort((a, b) => b.updatedAt - a.updatedAt));
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return c.json([]);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
// ── Get Stage ───────────────────────────────────────────
|
|
74
|
-
stages.get('/api/stages/:id', async (c) => {
|
|
75
|
-
const rawId = c.req.param('id');
|
|
76
|
-
const id = validateStageId(rawId);
|
|
77
|
-
if (!id)
|
|
78
|
-
return c.json({ error: 'Invalid stage id' }, 400);
|
|
79
|
-
const filePath = stagePathForId(id);
|
|
80
|
-
// Ensure resolved path is within stages dir (prevent traversal)
|
|
81
|
-
if (!filePath.startsWith(stagesDir())) {
|
|
82
|
-
return c.json({ error: 'Invalid stage id' }, 400);
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
const raw = await fs.readFile(filePath, 'utf-8');
|
|
86
|
-
return c.json(JSON.parse(raw));
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return c.json({ error: 'Stage not found' }, 404);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
// ── Save Stage ──────────────────────────────────────────
|
|
93
|
-
stages.put('/api/stages', async (c) => {
|
|
94
|
-
const body = await c.req.json();
|
|
95
|
-
const workingDir = normalizeWorkingDir(body.workingDir || '');
|
|
96
|
-
if (!workingDir) {
|
|
97
|
-
return c.json({ error: 'workingDir is required' }, 400);
|
|
98
|
-
}
|
|
99
|
-
const id = stageIdForWorkingDir(workingDir);
|
|
100
|
-
const stage = {
|
|
101
|
-
...body,
|
|
102
|
-
workingDir,
|
|
103
|
-
};
|
|
104
|
-
const dir = stagesDir();
|
|
105
|
-
await fs.mkdir(dir, { recursive: true });
|
|
106
|
-
const filePath = stagePathForId(id);
|
|
107
|
-
if (!filePath.startsWith(dir)) {
|
|
108
|
-
return c.json({ error: 'Invalid stage id' }, 400);
|
|
109
|
-
}
|
|
110
|
-
await fs.writeFile(filePath, JSON.stringify(stage, null, 2), 'utf-8');
|
|
111
|
-
const stat = await fs.stat(filePath);
|
|
112
|
-
return c.json({
|
|
113
|
-
ok: true,
|
|
114
|
-
id,
|
|
115
|
-
workingDir,
|
|
116
|
-
updatedAt: stat.mtimeMs,
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
// ── Delete Stage ────────────────────────────────────────
|
|
120
|
-
stages.delete('/api/stages/:id', async (c) => {
|
|
121
|
-
const rawId = c.req.param('id');
|
|
122
|
-
const id = validateStageId(rawId);
|
|
123
|
-
if (!id)
|
|
124
|
-
return c.json({ error: 'Invalid stage id' }, 400);
|
|
125
|
-
const filePath = stagePathForId(id);
|
|
126
|
-
if (!filePath.startsWith(stagesDir())) {
|
|
127
|
-
return c.json({ error: 'Invalid stage id' }, 400);
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
await fs.unlink(filePath);
|
|
131
|
-
return c.json({ ok: true });
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
return c.json({ error: 'Stage not found' }, 404);
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
export default stages;
|