dot-studio 0.0.1
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/LICENSE +21 -0
- package/README.md +214 -0
- package/client/assets/index-C2eIILoa.css +41 -0
- package/client/assets/index-DUPZ_Lw5.js +616 -0
- package/client/assets/index.es-Btlrnc3g.js +1 -0
- package/client/index.html +14 -0
- package/dist/cli.js +196 -0
- package/dist/server/index.js +79 -0
- package/dist/server/lib/act-runtime.js +1282 -0
- package/dist/server/lib/cache.js +31 -0
- package/dist/server/lib/config.js +53 -0
- package/dist/server/lib/dot-authoring.js +245 -0
- package/dist/server/lib/dot-loader.js +61 -0
- package/dist/server/lib/dot-login.js +190 -0
- package/dist/server/lib/model-catalog.js +111 -0
- package/dist/server/lib/opencode-auth.js +69 -0
- package/dist/server/lib/opencode-errors.js +220 -0
- package/dist/server/lib/opencode-sidecar.js +144 -0
- package/dist/server/lib/opencode.js +12 -0
- package/dist/server/lib/package-bin.js +63 -0
- package/dist/server/lib/project-config.js +39 -0
- package/dist/server/lib/prompt.js +222 -0
- package/dist/server/lib/request-context.js +27 -0
- package/dist/server/lib/runtime-tools.js +208 -0
- package/dist/server/routes/assets.js +161 -0
- package/dist/server/routes/chat.js +356 -0
- package/dist/server/routes/compile.js +105 -0
- package/dist/server/routes/dot.js +270 -0
- package/dist/server/routes/health.js +56 -0
- package/dist/server/routes/opencode.js +421 -0
- package/dist/server/routes/stages.js +137 -0
- package/dist/server/start.js +23 -0
- package/dist/server/terminal.js +282 -0
- package/dist/shared/mcp-config.js +19 -0
- package/dist/shared/model-variants.js +50 -0
- package/dist/shared/project-mcp.js +22 -0
- package/dist/shared/session-metadata.js +26 -0
- package/package.json +103 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// Terminal WebSocket handler — proxies PTY sessions via OpenCode SDK v2 PTY API
|
|
2
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
3
|
+
import { OPENCODE_URL } from './lib/config.js';
|
|
4
|
+
const MAX_BUFFER = 500;
|
|
5
|
+
const sessions = new Map();
|
|
6
|
+
let sessionCounter = 0;
|
|
7
|
+
async function opencodePtyRequest(path, init, directory) {
|
|
8
|
+
const url = new URL(path, OPENCODE_URL);
|
|
9
|
+
if (directory) {
|
|
10
|
+
url.searchParams.set('directory', directory);
|
|
11
|
+
}
|
|
12
|
+
const res = await fetch(url, {
|
|
13
|
+
...init,
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
...(init.headers || {}),
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
const raw = await res.text();
|
|
20
|
+
const payload = raw ? JSON.parse(raw) : null;
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
const message = payload?.error?.message
|
|
23
|
+
|| payload?.error
|
|
24
|
+
|| payload?.data?.message
|
|
25
|
+
|| payload?.message
|
|
26
|
+
|| raw
|
|
27
|
+
|| `OpenCode PTY request failed (${res.status})`;
|
|
28
|
+
throw new Error(String(message));
|
|
29
|
+
}
|
|
30
|
+
return payload;
|
|
31
|
+
}
|
|
32
|
+
export function setupTerminalWs(server, defaultCwd) {
|
|
33
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
34
|
+
const resolveDefaultCwd = () => typeof defaultCwd === 'function' ? defaultCwd() : defaultCwd;
|
|
35
|
+
server.on('upgrade', (req, socket, head) => {
|
|
36
|
+
if (req.url?.startsWith('/ws/terminal')) {
|
|
37
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
38
|
+
wss.emit('connection', ws, req);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
wss.on('connection', (ws, req) => {
|
|
43
|
+
const url = new URL(req.url || '', 'http://localhost');
|
|
44
|
+
const action = url.searchParams.get('action') || 'create';
|
|
45
|
+
const targetId = url.searchParams.get('id') || '';
|
|
46
|
+
const cwd = url.searchParams.get('cwd') || resolveDefaultCwd();
|
|
47
|
+
const targetSession = targetId ? sessions.get(targetId) : null;
|
|
48
|
+
if (action === 'attach' && targetSession && targetSession.cwd === cwd) {
|
|
49
|
+
attachSession(ws, targetId);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
createSession(ws, cwd);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
async function createSession(ws, cwd) {
|
|
56
|
+
sessionCounter++;
|
|
57
|
+
const id = `term-${sessionCounter}`;
|
|
58
|
+
const shell = process.env.SHELL || '/bin/zsh';
|
|
59
|
+
const title = `Terminal ${sessionCounter}`;
|
|
60
|
+
try {
|
|
61
|
+
const ptyData = await opencodePtyRequest('/pty', {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
command: shell,
|
|
65
|
+
args: ['-l'],
|
|
66
|
+
cwd,
|
|
67
|
+
title,
|
|
68
|
+
env: {
|
|
69
|
+
TERM: 'xterm-256color',
|
|
70
|
+
COLORTERM: 'truecolor',
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
}, cwd);
|
|
74
|
+
const ptyID = ptyData?.id;
|
|
75
|
+
if (!ptyID) {
|
|
76
|
+
throw new Error('Failed to create PTY session: no ID returned');
|
|
77
|
+
}
|
|
78
|
+
const session = { id, ptyID, ws, title, buffer: [], cwd, ptyWs: null, initialized: false };
|
|
79
|
+
sessions.set(id, session);
|
|
80
|
+
// Connect to OpenCode PTY WebSocket for real-time I/O
|
|
81
|
+
connectPtyWebSocket(session);
|
|
82
|
+
ws.send(JSON.stringify({
|
|
83
|
+
type: 'connected',
|
|
84
|
+
id, title, shell, cwd,
|
|
85
|
+
sessions: getSessionList(cwd),
|
|
86
|
+
}));
|
|
87
|
+
setupWsHandlers(ws, session);
|
|
88
|
+
broadcastSessionList(session.cwd);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error('Failed to create PTY via OpenCode:', err.message);
|
|
92
|
+
ws.send(JSON.stringify({ type: 'error', message: `Failed: ${err.message}` }));
|
|
93
|
+
ws.close();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function connectPtyWebSocket(session) {
|
|
97
|
+
// Build OpenCode PTY WebSocket URL
|
|
98
|
+
const baseUrl = OPENCODE_URL.replace(/^http/, 'ws');
|
|
99
|
+
const ptyUrl = new URL(`/pty/${session.ptyID}/connect`, baseUrl);
|
|
100
|
+
if (session.cwd) {
|
|
101
|
+
ptyUrl.searchParams.set('directory', session.cwd);
|
|
102
|
+
}
|
|
103
|
+
const ptyWsUrl = ptyUrl.toString();
|
|
104
|
+
const ptyWs = new WebSocket(ptyWsUrl);
|
|
105
|
+
session.ptyWs = ptyWs;
|
|
106
|
+
ptyWs.on('message', (rawData) => {
|
|
107
|
+
const data = rawData.toString();
|
|
108
|
+
// Filter initial noise like {"cursor":0}% that zsh/bash emit before the first prompt.
|
|
109
|
+
// The data may arrive in multiple small chunks with ANSI sequences mixed in.
|
|
110
|
+
if (!session.initialized) {
|
|
111
|
+
// Strip ALL ANSI escape sequences (CSI, OSC, etc.)
|
|
112
|
+
const stripped = data
|
|
113
|
+
.replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, '') // OSC sequences
|
|
114
|
+
.replace(/\x1b\[[^a-zA-Z]*[a-zA-Z]/g, '') // CSI sequences
|
|
115
|
+
.replace(/\x1b[^[\]].?/g, '') // Other escapes
|
|
116
|
+
.replace(/[\r\n]/g, '')
|
|
117
|
+
.trim();
|
|
118
|
+
// Skip if it's cursor control noise, percent signs, or empty after stripping
|
|
119
|
+
if (!stripped || /\{"cursor":\d+\}/.test(stripped) || /^%+$/.test(stripped)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
session.initialized = true;
|
|
123
|
+
}
|
|
124
|
+
addToBuffer(session, data);
|
|
125
|
+
if (session.ws?.readyState === WebSocket.OPEN) {
|
|
126
|
+
session.ws.send(JSON.stringify({ type: 'output', data }));
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
// Poll PTY status to detect exit (OpenCode PTY WS may not close immediately)
|
|
130
|
+
const statusInterval = setInterval(async () => {
|
|
131
|
+
if (!sessions.has(session.id)) {
|
|
132
|
+
clearInterval(statusInterval);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const info = await opencodePtyRequest(`/pty/${session.ptyID}`, {
|
|
137
|
+
method: 'GET',
|
|
138
|
+
}, session.cwd);
|
|
139
|
+
if (info?.status === 'exited') {
|
|
140
|
+
clearInterval(statusInterval);
|
|
141
|
+
handleSessionExit(session);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// PTY not found (404) means it already exited
|
|
146
|
+
clearInterval(statusInterval);
|
|
147
|
+
handleSessionExit(session);
|
|
148
|
+
}
|
|
149
|
+
}, 2000);
|
|
150
|
+
ptyWs.on('close', () => {
|
|
151
|
+
clearInterval(statusInterval);
|
|
152
|
+
session.ptyWs = null;
|
|
153
|
+
handleSessionExit(session);
|
|
154
|
+
});
|
|
155
|
+
ptyWs.on('error', (err) => {
|
|
156
|
+
console.error(`PTY WebSocket error for ${session.id}:`, err.message);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function handleSessionExit(session) {
|
|
160
|
+
if (!sessions.has(session.id))
|
|
161
|
+
return; // Already handled
|
|
162
|
+
// Notify client that the session has exited
|
|
163
|
+
if (session.ws?.readyState === WebSocket.OPEN) {
|
|
164
|
+
session.ws.send(JSON.stringify({ type: 'exit', id: session.id }));
|
|
165
|
+
}
|
|
166
|
+
session.ptyWs?.close();
|
|
167
|
+
session.ptyWs = null;
|
|
168
|
+
sessions.delete(session.id);
|
|
169
|
+
broadcastSessionList(session.cwd);
|
|
170
|
+
}
|
|
171
|
+
function attachSession(ws, targetId) {
|
|
172
|
+
const session = sessions.get(targetId);
|
|
173
|
+
if (!session)
|
|
174
|
+
return;
|
|
175
|
+
if (session.ws && session.ws !== ws && session.ws.readyState === WebSocket.OPEN) {
|
|
176
|
+
session.ws.close();
|
|
177
|
+
}
|
|
178
|
+
session.ws = ws;
|
|
179
|
+
// Replay buffer
|
|
180
|
+
if (session.buffer.length > 0) {
|
|
181
|
+
ws.send(JSON.stringify({ type: 'output', data: session.buffer.join('') }));
|
|
182
|
+
}
|
|
183
|
+
ws.send(JSON.stringify({
|
|
184
|
+
type: 'attached',
|
|
185
|
+
id: session.id,
|
|
186
|
+
title: session.title,
|
|
187
|
+
sessions: getSessionList(session.cwd),
|
|
188
|
+
}));
|
|
189
|
+
setupWsHandlers(ws, session);
|
|
190
|
+
}
|
|
191
|
+
function setupWsHandlers(ws, session) {
|
|
192
|
+
ws.on('message', async (msg) => {
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(msg.toString());
|
|
195
|
+
switch (parsed.type) {
|
|
196
|
+
case 'input':
|
|
197
|
+
// Forward input to OpenCode PTY WebSocket
|
|
198
|
+
if (session.ptyWs?.readyState === WebSocket.OPEN) {
|
|
199
|
+
session.ptyWs.send(parsed.data);
|
|
200
|
+
}
|
|
201
|
+
break;
|
|
202
|
+
case 'resize':
|
|
203
|
+
try {
|
|
204
|
+
await opencodePtyRequest(`/pty/${session.ptyID}`, {
|
|
205
|
+
method: 'PUT',
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
size: { rows: parsed.rows, cols: parsed.cols },
|
|
208
|
+
}),
|
|
209
|
+
}, session.cwd);
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
console.error('PTY resize failed:', e.message);
|
|
213
|
+
}
|
|
214
|
+
break;
|
|
215
|
+
case 'create':
|
|
216
|
+
createSession(ws, parsed.cwd || session.cwd || resolveDefaultCwd());
|
|
217
|
+
break;
|
|
218
|
+
case 'kill': {
|
|
219
|
+
const target = sessions.get(parsed.id);
|
|
220
|
+
if (target && target.cwd === session.cwd) {
|
|
221
|
+
try {
|
|
222
|
+
await opencodePtyRequest(`/pty/${target.ptyID}`, {
|
|
223
|
+
method: 'DELETE',
|
|
224
|
+
}, target.cwd);
|
|
225
|
+
}
|
|
226
|
+
catch { /* ignore */ }
|
|
227
|
+
target.ptyWs?.close();
|
|
228
|
+
sessions.delete(parsed.id);
|
|
229
|
+
broadcastSessionList(session.cwd);
|
|
230
|
+
}
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case 'rename': {
|
|
234
|
+
const target = sessions.get(parsed.id);
|
|
235
|
+
if (target && target.cwd === session.cwd && parsed.title) {
|
|
236
|
+
target.title = parsed.title;
|
|
237
|
+
broadcastSessionList(session.cwd);
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case 'list':
|
|
242
|
+
ws.send(JSON.stringify({ type: 'sessions', sessions: getSessionList(session.cwd) }));
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Raw input fallback
|
|
248
|
+
if (session.ptyWs?.readyState === WebSocket.OPEN) {
|
|
249
|
+
session.ptyWs.send(msg.toString());
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
ws.on('close', () => {
|
|
254
|
+
if (session.ws === ws)
|
|
255
|
+
session.ws = null;
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function addToBuffer(session, data) {
|
|
259
|
+
session.buffer.push(data);
|
|
260
|
+
if (session.buffer.length > MAX_BUFFER) {
|
|
261
|
+
session.buffer.splice(0, session.buffer.length - MAX_BUFFER);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function getSessionList(cwd) {
|
|
265
|
+
return Array.from(sessions.values())
|
|
266
|
+
.filter((session) => session.cwd === cwd)
|
|
267
|
+
.map(s => ({
|
|
268
|
+
id: s.id,
|
|
269
|
+
title: s.title,
|
|
270
|
+
connected: s.ws !== null && s.ws.readyState === WebSocket.OPEN,
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
function broadcastSessionList(cwd) {
|
|
274
|
+
const list = getSessionList(cwd);
|
|
275
|
+
for (const s of sessions.values()) {
|
|
276
|
+
if (s.cwd === cwd && s.ws?.readyState === WebSocket.OPEN) {
|
|
277
|
+
s.ws.send(JSON.stringify({ type: 'sessions', sessions: list }));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
console.log(' Terminal: WebSocket on /ws/terminal (via OpenCode PTY)');
|
|
282
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
function unique(values) {
|
|
2
|
+
return Array.from(new Set(values.filter(Boolean)));
|
|
3
|
+
}
|
|
4
|
+
export function extractMcpServerNamesFromConfig(value) {
|
|
5
|
+
if (!value || typeof value !== 'object') {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
const record = value;
|
|
9
|
+
const nestedServers = record.servers;
|
|
10
|
+
if (Array.isArray(nestedServers)) {
|
|
11
|
+
return unique(nestedServers.filter((item) => typeof item === 'string'));
|
|
12
|
+
}
|
|
13
|
+
if (nestedServers && typeof nestedServers === 'object') {
|
|
14
|
+
return unique(Object.keys(nestedServers));
|
|
15
|
+
}
|
|
16
|
+
return unique(Object.entries(record)
|
|
17
|
+
.filter(([, config]) => config !== null && typeof config === 'object')
|
|
18
|
+
.map(([name]) => name));
|
|
19
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
function flattenVariantOptions(value, prefix = '', acc = []) {
|
|
2
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
3
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
4
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
5
|
+
flattenVariantOptions(raw, path, acc);
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
if (Array.isArray(raw)) {
|
|
9
|
+
acc.push([path, raw.join(', ')]);
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
acc.push([path, String(raw)]);
|
|
13
|
+
}
|
|
14
|
+
return acc;
|
|
15
|
+
}
|
|
16
|
+
export function summarizeVariantOptions(options) {
|
|
17
|
+
const entries = flattenVariantOptions(options).slice(0, 4);
|
|
18
|
+
if (entries.length === 0) {
|
|
19
|
+
return 'Variant preset';
|
|
20
|
+
}
|
|
21
|
+
return entries.map(([key, value]) => `${key}=${value}`).join(' · ');
|
|
22
|
+
}
|
|
23
|
+
export function normalizeRuntimeVariants(raw) {
|
|
24
|
+
if (!raw || typeof raw !== 'object') {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
return Object.entries(raw).map(([id, options]) => {
|
|
28
|
+
const normalizedOptions = options && typeof options === 'object' && !Array.isArray(options)
|
|
29
|
+
? options
|
|
30
|
+
: {};
|
|
31
|
+
return {
|
|
32
|
+
id,
|
|
33
|
+
options: normalizedOptions,
|
|
34
|
+
summary: summarizeVariantOptions(normalizedOptions),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function findRuntimeModel(models, provider, modelId) {
|
|
39
|
+
if (!provider || !modelId) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return models.find((model) => model.provider === provider && model.id === modelId) || null;
|
|
43
|
+
}
|
|
44
|
+
export function findRuntimeModelVariant(models, provider, modelId, variantId) {
|
|
45
|
+
if (!variantId) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const model = findRuntimeModel(models, provider, modelId);
|
|
49
|
+
return model?.variants.find((variant) => variant.id === variantId) || null;
|
|
50
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function isProjectMcpCatalog(value) {
|
|
2
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
export function extractProjectMcpCatalog(config) {
|
|
5
|
+
if (!config || typeof config !== 'object') {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
const record = config;
|
|
9
|
+
return isProjectMcpCatalog(record.mcp) ? record.mcp : {};
|
|
10
|
+
}
|
|
11
|
+
export function projectMcpServerNames(config) {
|
|
12
|
+
return Object.keys(extractProjectMcpCatalog(config));
|
|
13
|
+
}
|
|
14
|
+
export function projectMcpEntryEnabled(entry) {
|
|
15
|
+
return entry?.enabled !== false;
|
|
16
|
+
}
|
|
17
|
+
export function projectMcpEntryType(entry) {
|
|
18
|
+
if (entry && typeof entry === 'object' && 'type' in entry) {
|
|
19
|
+
return entry.type === 'remote' ? 'remote' : 'local';
|
|
20
|
+
}
|
|
21
|
+
return 'toggle';
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const SESSION_TITLE_PREFIX = 'DOT Studio:';
|
|
2
|
+
const SESSION_METADATA_PATTERN = /^DOT Studio:\s*(.*?)\s*\[studio:([^:\]]+):([^\]]+)\]\s*$/;
|
|
3
|
+
export function buildStudioSessionTitle(performerId, performerName, configHash) {
|
|
4
|
+
return `${SESSION_TITLE_PREFIX} ${performerName} [studio:${performerId}:${configHash}]`;
|
|
5
|
+
}
|
|
6
|
+
export function parseStudioSessionTitle(title) {
|
|
7
|
+
if (!title || !title.startsWith(SESSION_TITLE_PREFIX)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const match = title.match(SESSION_METADATA_PATTERN);
|
|
11
|
+
if (!match) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
label: match[1].trim(),
|
|
16
|
+
performerId: match[2],
|
|
17
|
+
configHash: match[3],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function renameStudioSessionTitle(title, nextLabel) {
|
|
21
|
+
const parsed = parseStudioSessionTitle(title);
|
|
22
|
+
if (!parsed) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return buildStudioSessionTitle(parsed.performerId, nextLabel.trim(), parsed.configHash);
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dot-studio",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "DOT Studio visual workspace for composing and running Dance of Tal performers and acts on top of OpenCode.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "monarchjuno",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/dance-of-tal/dance-of-tal.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/dance-of-tal/dance-of-tal",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/dance-of-tal/dance-of-tal/issues"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"dance-of-tal",
|
|
20
|
+
"dot",
|
|
21
|
+
"studio",
|
|
22
|
+
"opencode",
|
|
23
|
+
"agentic-ai",
|
|
24
|
+
"visual-editor"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20.19.0"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"scripts": {
|
|
31
|
+
"start": "tsx server/start.ts",
|
|
32
|
+
"dev": "vite",
|
|
33
|
+
"kill-ports": "lsof -ti:5173,3001,4096 | xargs kill -9 2>/dev/null || true",
|
|
34
|
+
"dev:all": "npm run kill-ports && concurrently -k -n vite,server,opencode -c cyan,yellow,magenta \"vite\" \"tsx --watch server/index.ts\" \"opencode --port 4096 .\"",
|
|
35
|
+
"server": "tsx server/index.ts",
|
|
36
|
+
"server:dev": "tsx --watch server/index.ts",
|
|
37
|
+
"clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true }); require('fs').rmSync('client', { recursive: true, force: true })\"",
|
|
38
|
+
"build": "npm run build:all",
|
|
39
|
+
"build:client": "vite build --outDir client",
|
|
40
|
+
"build:server": "tsc -p tsconfig.server.json",
|
|
41
|
+
"build:all": "npm run clean && npm run build:client && npm run build:server",
|
|
42
|
+
"pack:check": "npm run build:all && npm pack --dry-run",
|
|
43
|
+
"type-check": "tsc -b --noEmit && tsc -p tsconfig.server.json --noEmit",
|
|
44
|
+
"lint": "eslint .",
|
|
45
|
+
"preview": "vite preview",
|
|
46
|
+
"opencode": "opencode --port 4096 .",
|
|
47
|
+
"prepublishOnly": "npm run build:all"
|
|
48
|
+
},
|
|
49
|
+
"bin": {
|
|
50
|
+
"dot-studio": "./dist/cli.js"
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist/",
|
|
54
|
+
"client/",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE"
|
|
57
|
+
],
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@dnd-kit/core": "^6.3.1",
|
|
60
|
+
"@dnd-kit/sortable": "^10.0.0",
|
|
61
|
+
"@dnd-kit/utilities": "^3.2.2",
|
|
62
|
+
"@hono/node-server": "^1.19.11",
|
|
63
|
+
"@opencode-ai/sdk": "^1.2.17",
|
|
64
|
+
"@tanstack/react-query": "^5.90.21",
|
|
65
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
66
|
+
"@xterm/addon-webgl": "^0.19.0",
|
|
67
|
+
"@xterm/xterm": "^6.0.0",
|
|
68
|
+
"@xyflow/react": "^12.10.1",
|
|
69
|
+
"dance-of-tal": "^3.2.3",
|
|
70
|
+
"elkjs": "^0.11.1",
|
|
71
|
+
"highlight.js": "^11.11.1",
|
|
72
|
+
"hono": "^4.12.5",
|
|
73
|
+
"lucide-react": "^0.577.0",
|
|
74
|
+
"material-file-icons": "^2.4.0",
|
|
75
|
+
"open": "^10.0.0",
|
|
76
|
+
"opencode-ai": "1.2.24",
|
|
77
|
+
"react": "^19.2.0",
|
|
78
|
+
"react-dom": "^19.2.0",
|
|
79
|
+
"react-markdown": "^10.1.0",
|
|
80
|
+
"rehype-highlight": "^7.0.2",
|
|
81
|
+
"remark-gfm": "^4.0.1",
|
|
82
|
+
"tsx": "^4.21.0",
|
|
83
|
+
"ws": "^8.19.0",
|
|
84
|
+
"xstate": "^5.28.0",
|
|
85
|
+
"zustand": "^5.0.11"
|
|
86
|
+
},
|
|
87
|
+
"devDependencies": {
|
|
88
|
+
"@eslint/js": "^9.39.1",
|
|
89
|
+
"@types/node": "^24.10.1",
|
|
90
|
+
"@types/react": "^19.2.7",
|
|
91
|
+
"@types/react-dom": "^19.2.3",
|
|
92
|
+
"@types/ws": "^8.18.1",
|
|
93
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
94
|
+
"concurrently": "^9.2.1",
|
|
95
|
+
"eslint": "^9.39.1",
|
|
96
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
97
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
98
|
+
"globals": "^16.5.0",
|
|
99
|
+
"typescript": "~5.9.3",
|
|
100
|
+
"typescript-eslint": "^8.48.0",
|
|
101
|
+
"vite": "^6.4.1"
|
|
102
|
+
}
|
|
103
|
+
}
|