@mindstudio-ai/local-model-tunnel 0.5.9 → 0.5.11
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/{chunk-253MU7UA.js → chunk-U2KITVPK.js} +2 -2
- package/dist/{chunk-4CMGJFH3.js → chunk-XP4GPID6.js} +756 -228
- package/dist/chunk-XP4GPID6.js.map +1 -0
- package/dist/chunk-YYWJE5O6.js +500 -0
- package/dist/chunk-YYWJE5O6.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/headless.d.ts +2 -66
- package/dist/headless.js +2 -2
- package/dist/index.js +3 -3
- package/dist/{tui-343LXUFQ.js → tui-YCLMVYAJ.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-4CMGJFH3.js.map +0 -1
- package/dist/chunk-UNG63WXC.js +0 -368
- package/dist/chunk-UNG63WXC.js.map +0 -1
- /package/dist/{chunk-253MU7UA.js.map → chunk-U2KITVPK.js.map} +0 -0
- /package/dist/{tui-343LXUFQ.js.map → tui-YCLMVYAJ.js.map} +0 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DevProxy,
|
|
3
|
+
DevRunner,
|
|
4
|
+
closeBrowserLog,
|
|
5
|
+
closeRequestLog,
|
|
6
|
+
detectAppConfig,
|
|
7
|
+
detectGitBranch,
|
|
8
|
+
devRequestEvents,
|
|
9
|
+
fetchCallbackToken,
|
|
10
|
+
getApiBaseUrl,
|
|
11
|
+
getApiKey,
|
|
12
|
+
getConfigPath,
|
|
13
|
+
getEnvironment,
|
|
14
|
+
getUserId,
|
|
15
|
+
getWebInterfaceConfig,
|
|
16
|
+
initBrowserLog,
|
|
17
|
+
initLoggerHeadless,
|
|
18
|
+
initRequestLog,
|
|
19
|
+
log,
|
|
20
|
+
readTableSources,
|
|
21
|
+
stablePort,
|
|
22
|
+
syncSchema,
|
|
23
|
+
watchConfigFile,
|
|
24
|
+
watchTableFiles
|
|
25
|
+
} from "./chunk-XP4GPID6.js";
|
|
26
|
+
|
|
27
|
+
// src/dev/session-events.ts
|
|
28
|
+
function subscribeDevEvents(emit2, shutdown) {
|
|
29
|
+
const unsubs = [];
|
|
30
|
+
unsubs.push(
|
|
31
|
+
devRequestEvents.onStart((event) => {
|
|
32
|
+
emit2("method-started", { id: event.id, method: event.method });
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
unsubs.push(
|
|
36
|
+
devRequestEvents.onComplete((event) => {
|
|
37
|
+
emit2("method-completed", {
|
|
38
|
+
id: event.id,
|
|
39
|
+
success: event.success,
|
|
40
|
+
duration: event.duration,
|
|
41
|
+
...event.error ? { error: event.error } : {}
|
|
42
|
+
});
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
unsubs.push(
|
|
46
|
+
devRequestEvents.onConnectionWarning((message) => {
|
|
47
|
+
emit2("connection-lost", { message });
|
|
48
|
+
})
|
|
49
|
+
);
|
|
50
|
+
unsubs.push(
|
|
51
|
+
devRequestEvents.onConnectionRestored(() => {
|
|
52
|
+
emit2("connection-restored");
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
unsubs.push(
|
|
56
|
+
devRequestEvents.onSessionExpired(() => {
|
|
57
|
+
emit2("session-expired");
|
|
58
|
+
shutdown().then(() => process.exit(1));
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
unsubs.push(
|
|
62
|
+
devRequestEvents.onAuthRefreshStart((url) => {
|
|
63
|
+
emit2("auth-refresh-start", { url });
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
unsubs.push(
|
|
67
|
+
devRequestEvents.onAuthRefreshSuccess(() => {
|
|
68
|
+
emit2("auth-refresh-success");
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
unsubs.push(
|
|
72
|
+
devRequestEvents.onAuthRefreshFailed(() => {
|
|
73
|
+
emit2("auth-refresh-failed");
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
unsubs.push(
|
|
77
|
+
devRequestEvents.onImpersonate((event) => {
|
|
78
|
+
emit2("impersonation-changed", { roles: event.roles });
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
unsubs.push(
|
|
82
|
+
devRequestEvents.onScenarioStart((event) => {
|
|
83
|
+
emit2("scenario-started", { id: event.id, name: event.name });
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
unsubs.push(
|
|
87
|
+
devRequestEvents.onScenarioComplete((event) => {
|
|
88
|
+
emit2("scenario-completed", {
|
|
89
|
+
id: event.id,
|
|
90
|
+
success: event.success,
|
|
91
|
+
duration: event.duration,
|
|
92
|
+
roles: event.roles,
|
|
93
|
+
...event.error ? { error: event.error } : {}
|
|
94
|
+
});
|
|
95
|
+
})
|
|
96
|
+
);
|
|
97
|
+
return unsubs;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/dev/stdin-commands/run-scenario.ts
|
|
101
|
+
async function handleRunScenario(state, cwd, cmd, emit2) {
|
|
102
|
+
if (!state.runner) {
|
|
103
|
+
emit2("command-error", { message: "No active session" });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const freshConfig = detectAppConfig(cwd) ?? state.appConfig;
|
|
107
|
+
const scenario = freshConfig?.scenarios.find((s) => s.id === cmd.scenarioId);
|
|
108
|
+
if (!scenario) {
|
|
109
|
+
emit2("command-error", { message: `Unknown scenario: ${cmd.scenarioId}` });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await state.runner.runScenario(scenario);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/dev/stdin-commands/run-method.ts
|
|
116
|
+
async function handleRunMethod(state, cwd, cmd, emit2) {
|
|
117
|
+
if (!state.runner) {
|
|
118
|
+
emit2("command-error", { message: "No active session" });
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const methodName = cmd.method;
|
|
122
|
+
if (!methodName) {
|
|
123
|
+
emit2("command-error", { message: 'run-method requires "method" (export name or ID)' });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const freshConfig = detectAppConfig(cwd) ?? state.appConfig;
|
|
127
|
+
const method = freshConfig?.methods.find((m) => m.export === methodName) ?? freshConfig?.methods.find((m) => m.id === methodName);
|
|
128
|
+
if (!method) {
|
|
129
|
+
emit2("command-error", { message: `Unknown method: ${methodName}` });
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const result = await state.runner.runMethod({
|
|
133
|
+
methodExport: method.export,
|
|
134
|
+
methodPath: method.path,
|
|
135
|
+
input: cmd.input ?? {}
|
|
136
|
+
});
|
|
137
|
+
emit2("method-run-completed", {
|
|
138
|
+
method: method.export,
|
|
139
|
+
success: result.success,
|
|
140
|
+
output: result.output ?? null,
|
|
141
|
+
error: result.error ?? null,
|
|
142
|
+
stdout: result.stdout ?? [],
|
|
143
|
+
duration: result.duration
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/dev/stdin-commands/impersonate.ts
|
|
148
|
+
async function handleImpersonate(state, cmd, emit2) {
|
|
149
|
+
if (!state.runner) {
|
|
150
|
+
emit2("command-error", { message: "No active session" });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const roles = cmd.roles;
|
|
154
|
+
if (!Array.isArray(roles)) {
|
|
155
|
+
emit2("command-error", { message: "impersonate requires roles array" });
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
await state.runner.setImpersonation(roles);
|
|
159
|
+
}
|
|
160
|
+
async function handleClearImpersonation(state, emit2) {
|
|
161
|
+
if (!state.runner) {
|
|
162
|
+
emit2("command-error", { message: "No active session" });
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await state.runner.clearImpersonation();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/dev/stdin-commands/browser.ts
|
|
169
|
+
async function handleBrowser(state, cmd, emit2) {
|
|
170
|
+
if (!state.proxy) {
|
|
171
|
+
emit2("command-error", { message: "No active proxy \u2014 browser commands require a web interface" });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const steps = cmd.steps;
|
|
175
|
+
if (!Array.isArray(steps) || steps.length === 0) {
|
|
176
|
+
emit2("command-error", { message: 'browser action requires a non-empty "steps" array' });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
const result = await state.proxy.dispatchBrowserCommand(steps);
|
|
181
|
+
emit2("browser-completed", {
|
|
182
|
+
steps: result.steps,
|
|
183
|
+
snapshot: result.snapshot,
|
|
184
|
+
logs: result.logs,
|
|
185
|
+
duration: result.duration
|
|
186
|
+
});
|
|
187
|
+
} catch (err) {
|
|
188
|
+
emit2("command-error", {
|
|
189
|
+
message: err instanceof Error ? err.message : "Browser command failed"
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/dev/stdin-commands/screenshot.ts
|
|
195
|
+
async function handleScreenshot(state, emit2) {
|
|
196
|
+
if (!state.proxy) {
|
|
197
|
+
emit2("command-error", { message: "No active proxy" });
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!state.proxy.isBrowserConnected()) {
|
|
201
|
+
emit2("command-error", { message: "No browser connected, please refresh the MindStudio preview" });
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (!state.runner?.getSession() || !state.appConfig?.appId) {
|
|
205
|
+
emit2("command-error", { message: "No active session" });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const startTime = Date.now();
|
|
210
|
+
const result = await state.proxy.dispatchBrowserCommand([{ command: "screenshot" }]);
|
|
211
|
+
const stepResult = result.steps?.[0]?.result;
|
|
212
|
+
if (!stepResult?.image) {
|
|
213
|
+
emit2("command-error", { message: "Screenshot capture returned no image data" });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const session = state.runner.getSession();
|
|
217
|
+
const token = await fetchCallbackToken(state.appConfig.appId, session.sessionId);
|
|
218
|
+
const uploadResponse = await fetch(
|
|
219
|
+
`${getApiBaseUrl()}/_internal/v2/sandbox/upload`,
|
|
220
|
+
{
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: {
|
|
223
|
+
Authorization: `Bearer ${token}`,
|
|
224
|
+
"Content-Type": "application/json"
|
|
225
|
+
},
|
|
226
|
+
body: JSON.stringify({ extension: "png", contentType: "image/png" })
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
if (!uploadResponse.ok) {
|
|
230
|
+
const err = await uploadResponse.text();
|
|
231
|
+
emit2("command-error", { message: `Failed to get upload URL: ${uploadResponse.status} ${err}` });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const { uploadUrl, uploadFields, publicUrl } = await uploadResponse.json();
|
|
235
|
+
const imageBuffer = Buffer.from(stepResult.image, "base64");
|
|
236
|
+
const form = new FormData();
|
|
237
|
+
for (const [key, value] of Object.entries(uploadFields)) {
|
|
238
|
+
form.append(key, value);
|
|
239
|
+
}
|
|
240
|
+
form.append("file", new Blob([imageBuffer], { type: "image/png" }), "screenshot.png");
|
|
241
|
+
const uploadResult = await fetch(uploadUrl, { method: "POST", body: form });
|
|
242
|
+
if (!uploadResult.ok) {
|
|
243
|
+
emit2("command-error", { message: `S3 upload failed: ${uploadResult.status}` });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
emit2("screenshot-completed", {
|
|
247
|
+
url: publicUrl,
|
|
248
|
+
width: stepResult.width,
|
|
249
|
+
height: stepResult.height,
|
|
250
|
+
duration: Date.now() - startTime
|
|
251
|
+
});
|
|
252
|
+
} catch (err) {
|
|
253
|
+
emit2("command-error", {
|
|
254
|
+
message: err instanceof Error ? err.message : "Screenshot failed"
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/dev/stdin-commands/index.ts
|
|
260
|
+
function setupStdinCommands(state, cwd, emit2) {
|
|
261
|
+
if (!process.stdin.readable) return;
|
|
262
|
+
let buffer = "";
|
|
263
|
+
process.stdin.setEncoding("utf-8");
|
|
264
|
+
process.stdin.on("data", (chunk) => {
|
|
265
|
+
buffer += chunk;
|
|
266
|
+
let idx;
|
|
267
|
+
while ((idx = buffer.indexOf("\n")) !== -1) {
|
|
268
|
+
const line = buffer.slice(0, idx).trim();
|
|
269
|
+
buffer = buffer.slice(idx + 1);
|
|
270
|
+
if (!line) continue;
|
|
271
|
+
try {
|
|
272
|
+
const cmd = JSON.parse(line);
|
|
273
|
+
handleStdinCommand(cmd, state, cwd, emit2);
|
|
274
|
+
} catch {
|
|
275
|
+
emit2("command-error", { message: `Invalid JSON on stdin: ${line.slice(0, 100)}` });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
async function handleStdinCommand(cmd, state, cwd, emit2) {
|
|
281
|
+
switch (cmd.action) {
|
|
282
|
+
case "run-scenario":
|
|
283
|
+
return handleRunScenario(state, cwd, cmd, emit2);
|
|
284
|
+
case "run-method":
|
|
285
|
+
return handleRunMethod(state, cwd, cmd, emit2);
|
|
286
|
+
case "impersonate":
|
|
287
|
+
return handleImpersonate(state, cmd, emit2);
|
|
288
|
+
case "clear-impersonation":
|
|
289
|
+
return handleClearImpersonation(state, emit2);
|
|
290
|
+
case "browser":
|
|
291
|
+
return handleBrowser(state, cmd, emit2);
|
|
292
|
+
case "screenshot":
|
|
293
|
+
return handleScreenshot(state, emit2);
|
|
294
|
+
default:
|
|
295
|
+
emit2("command-error", { message: `Unknown action: ${cmd.action}` });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/headless.ts
|
|
300
|
+
function emit(event, data) {
|
|
301
|
+
process.stdout.write(JSON.stringify({ event, ...data }) + "\n");
|
|
302
|
+
}
|
|
303
|
+
async function startSession(cwd, opts, state, shutdown) {
|
|
304
|
+
const bindAddress = opts.bindAddress ?? "127.0.0.1";
|
|
305
|
+
const appConfig = detectAppConfig(cwd);
|
|
306
|
+
if (!appConfig) {
|
|
307
|
+
emit("config-error", { message: "No valid mindstudio.json found in " + cwd });
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
if (!appConfig.appId) {
|
|
311
|
+
emit("config-error", { message: 'Missing "appId" in mindstudio.json' });
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
state.appConfig = appConfig;
|
|
315
|
+
let devPort = opts.devPort ?? null;
|
|
316
|
+
if (devPort === null) {
|
|
317
|
+
const webConfig = getWebInterfaceConfig(appConfig, cwd);
|
|
318
|
+
devPort = webConfig?.devPort ?? null;
|
|
319
|
+
}
|
|
320
|
+
emit("session-starting", { appId: appConfig.appId, name: appConfig.name });
|
|
321
|
+
try {
|
|
322
|
+
const branch = detectGitBranch();
|
|
323
|
+
const runner = new DevRunner(appConfig.appId, cwd, {
|
|
324
|
+
branch,
|
|
325
|
+
methods: appConfig.methods.map((m) => ({ id: m.id, export: m.export, path: m.path }))
|
|
326
|
+
});
|
|
327
|
+
const session = await runner.start();
|
|
328
|
+
state.runner = runner;
|
|
329
|
+
initRequestLog(cwd);
|
|
330
|
+
initBrowserLog(cwd);
|
|
331
|
+
if (appConfig.tables.length > 0) {
|
|
332
|
+
try {
|
|
333
|
+
const tableSources = readTableSources(appConfig, cwd);
|
|
334
|
+
if (tableSources.length > 0) {
|
|
335
|
+
const syncResult = await syncSchema(appConfig.appId, session.sessionId, tableSources);
|
|
336
|
+
session.databases = syncResult.databases;
|
|
337
|
+
emit("schema-sync-completed", {
|
|
338
|
+
created: syncResult.created,
|
|
339
|
+
altered: syncResult.altered,
|
|
340
|
+
errors: syncResult.errors
|
|
341
|
+
});
|
|
342
|
+
} else {
|
|
343
|
+
log.warn("No table source files found, skipping schema sync", {
|
|
344
|
+
expected: appConfig.tables.map((t) => t.path)
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
} catch (err) {
|
|
348
|
+
emit("schema-sync-completed", {
|
|
349
|
+
created: [],
|
|
350
|
+
altered: [],
|
|
351
|
+
errors: [err instanceof Error ? err.message : "Schema sync failed"]
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
let proxyPort = null;
|
|
356
|
+
if (devPort !== null && session.clientContext) {
|
|
357
|
+
const proxy = new DevProxy(devPort, session.clientContext, bindAddress, opts.browserAgentUrl);
|
|
358
|
+
const preferred = opts.proxyPort ?? stablePort(appConfig.appId);
|
|
359
|
+
proxyPort = await proxy.start(preferred);
|
|
360
|
+
runner.setProxyUrl(`http://${bindAddress === "0.0.0.0" ? "localhost" : bindAddress}:${proxyPort}`);
|
|
361
|
+
runner.setProxy(proxy);
|
|
362
|
+
state.proxy = proxy;
|
|
363
|
+
}
|
|
364
|
+
state.proxyPort = proxyPort;
|
|
365
|
+
emit("session-started", {
|
|
366
|
+
sessionId: session.sessionId,
|
|
367
|
+
releaseId: session.releaseId,
|
|
368
|
+
branch: session.branch,
|
|
369
|
+
proxyPort,
|
|
370
|
+
proxyUrl: proxyPort ? `http://${bindAddress === "0.0.0.0" ? "localhost" : bindAddress}:${proxyPort}/` : null,
|
|
371
|
+
webInterfaceUrl: session.webInterfaceUrl,
|
|
372
|
+
roles: appConfig.roles.map((r) => ({ id: r.id, name: r.name ?? r.id, description: r.description })),
|
|
373
|
+
scenarios: appConfig.scenarios.map((s) => ({
|
|
374
|
+
id: s.id,
|
|
375
|
+
name: s.name ?? s.export,
|
|
376
|
+
description: s.description,
|
|
377
|
+
path: s.path,
|
|
378
|
+
roles: s.roles
|
|
379
|
+
}))
|
|
380
|
+
});
|
|
381
|
+
state.unsubscribers.push(...subscribeDevEvents(emit, shutdown));
|
|
382
|
+
setupTableWatchers(cwd, state);
|
|
383
|
+
return true;
|
|
384
|
+
} catch (err) {
|
|
385
|
+
emit("config-error", {
|
|
386
|
+
message: err instanceof Error ? err.message : "Failed to start session"
|
|
387
|
+
});
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function setupTableWatchers(cwd, state) {
|
|
392
|
+
if (!state.appConfig || state.appConfig.tables.length === 0) return;
|
|
393
|
+
const cleanup = watchTableFiles(state.appConfig.tables, cwd, async () => {
|
|
394
|
+
if (!state.runner || !state.appConfig?.appId) return;
|
|
395
|
+
const session = state.runner.getSession();
|
|
396
|
+
if (!session) return;
|
|
397
|
+
emit("schema-sync-started");
|
|
398
|
+
log.info("Table source file changed, syncing schema");
|
|
399
|
+
try {
|
|
400
|
+
const tableSources = readTableSources(state.appConfig, cwd);
|
|
401
|
+
if (tableSources.length > 0) {
|
|
402
|
+
const result = await syncSchema(state.appConfig.appId, session.sessionId, tableSources);
|
|
403
|
+
session.databases = result.databases;
|
|
404
|
+
emit("schema-sync-completed", {
|
|
405
|
+
created: result.created,
|
|
406
|
+
altered: result.altered,
|
|
407
|
+
errors: result.errors
|
|
408
|
+
});
|
|
409
|
+
log.info("Schema sync complete", { created: result.created, altered: result.altered });
|
|
410
|
+
} else {
|
|
411
|
+
log.warn("Table source file change detected but file(s) still missing", {
|
|
412
|
+
expected: state.appConfig.tables.map((t) => t.path)
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
} catch (err) {
|
|
416
|
+
const message = err instanceof Error ? err.message : "Schema sync failed";
|
|
417
|
+
emit("command-error", { message });
|
|
418
|
+
log.warn("Schema sync failed", { error: message });
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
state.unsubscribers.push(cleanup);
|
|
422
|
+
}
|
|
423
|
+
async function teardownSession(state) {
|
|
424
|
+
for (const unsub of state.unsubscribers) unsub();
|
|
425
|
+
state.unsubscribers = [];
|
|
426
|
+
state.proxy?.stop();
|
|
427
|
+
state.proxy = null;
|
|
428
|
+
state.proxyPort = null;
|
|
429
|
+
if (state.runner) {
|
|
430
|
+
await state.runner.stop().catch(() => {
|
|
431
|
+
});
|
|
432
|
+
state.runner = null;
|
|
433
|
+
}
|
|
434
|
+
closeRequestLog();
|
|
435
|
+
closeBrowserLog();
|
|
436
|
+
}
|
|
437
|
+
async function startHeadless(opts = {}) {
|
|
438
|
+
initLoggerHeadless(opts.logLevel ?? "info");
|
|
439
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
440
|
+
const apiKey = getApiKey();
|
|
441
|
+
const userId = getUserId();
|
|
442
|
+
log.info("Startup config", {
|
|
443
|
+
configPath: getConfigPath(),
|
|
444
|
+
environment: getEnvironment(),
|
|
445
|
+
apiBaseUrl: getApiBaseUrl(),
|
|
446
|
+
hasApiKey: !!apiKey,
|
|
447
|
+
apiKeyPrefix: apiKey ? apiKey.slice(0, 8) + "..." : null,
|
|
448
|
+
hasUserId: !!userId,
|
|
449
|
+
userId: userId ?? null,
|
|
450
|
+
cwd
|
|
451
|
+
});
|
|
452
|
+
const state = {
|
|
453
|
+
runner: null,
|
|
454
|
+
proxy: null,
|
|
455
|
+
appConfig: null,
|
|
456
|
+
proxyPort: null,
|
|
457
|
+
unsubscribers: []
|
|
458
|
+
};
|
|
459
|
+
let restarting = false;
|
|
460
|
+
let cleanupConfigWatcher;
|
|
461
|
+
let stopping = false;
|
|
462
|
+
const shutdown = async () => {
|
|
463
|
+
if (stopping) return;
|
|
464
|
+
stopping = true;
|
|
465
|
+
emit("session-stopping");
|
|
466
|
+
cleanupConfigWatcher?.();
|
|
467
|
+
await teardownSession(state);
|
|
468
|
+
emit("session-stopped");
|
|
469
|
+
};
|
|
470
|
+
process.on("SIGTERM", () => {
|
|
471
|
+
shutdown().then(() => process.exit(0));
|
|
472
|
+
});
|
|
473
|
+
process.on("SIGINT", () => {
|
|
474
|
+
shutdown().then(() => process.exit(0));
|
|
475
|
+
});
|
|
476
|
+
const ok = await startSession(cwd, opts, state, shutdown);
|
|
477
|
+
if (!ok && !state.appConfig) {
|
|
478
|
+
emit("error", { message: "No valid mindstudio.json found in " + cwd });
|
|
479
|
+
}
|
|
480
|
+
setupStdinCommands(state, cwd, emit);
|
|
481
|
+
cleanupConfigWatcher = watchConfigFile(cwd, async () => {
|
|
482
|
+
if (stopping || restarting) return;
|
|
483
|
+
restarting = true;
|
|
484
|
+
try {
|
|
485
|
+
log.info("mindstudio.json changed, restarting dev session");
|
|
486
|
+
emit("config-changed");
|
|
487
|
+
await teardownSession(state);
|
|
488
|
+
await startSession(cwd, opts, state, shutdown);
|
|
489
|
+
} finally {
|
|
490
|
+
restarting = false;
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
await new Promise(() => {
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
export {
|
|
498
|
+
startHeadless
|
|
499
|
+
};
|
|
500
|
+
//# sourceMappingURL=chunk-YYWJE5O6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dev/session-events.ts","../src/dev/stdin-commands/run-scenario.ts","../src/dev/stdin-commands/run-method.ts","../src/dev/stdin-commands/impersonate.ts","../src/dev/stdin-commands/browser.ts","../src/dev/stdin-commands/screenshot.ts","../src/dev/stdin-commands/index.ts","../src/headless.ts"],"sourcesContent":["/**\n * Subscribe to DevRunner events and relay them as JSON to stdout.\n * Returns an array of unsubscribe functions for cleanup on teardown.\n */\n\nimport { devRequestEvents } from './events';\n\ntype EmitFn = (event: string, data?: Record<string, unknown>) => void;\n\nexport function subscribeDevEvents(\n emit: EmitFn,\n shutdown: () => Promise<void>,\n): Array<() => void> {\n const unsubs: Array<() => void> = [];\n\n unsubs.push(\n devRequestEvents.onStart((event) => {\n emit('method-started', { id: event.id, method: event.method });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onComplete((event) => {\n emit('method-completed', {\n id: event.id,\n success: event.success,\n duration: event.duration,\n ...(event.error ? { error: event.error } : {}),\n });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onConnectionWarning((message) => {\n emit('connection-lost', { message });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onConnectionRestored(() => {\n emit('connection-restored');\n }),\n );\n\n unsubs.push(\n devRequestEvents.onSessionExpired(() => {\n emit('session-expired');\n shutdown().then(() => process.exit(1));\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshStart((url) => {\n emit('auth-refresh-start', { url });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshSuccess(() => {\n emit('auth-refresh-success');\n }),\n );\n\n unsubs.push(\n devRequestEvents.onAuthRefreshFailed(() => {\n emit('auth-refresh-failed');\n }),\n );\n\n unsubs.push(\n devRequestEvents.onImpersonate((event) => {\n emit('impersonation-changed', { roles: event.roles });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onScenarioStart((event) => {\n emit('scenario-started', { id: event.id, name: event.name });\n }),\n );\n\n unsubs.push(\n devRequestEvents.onScenarioComplete((event) => {\n emit('scenario-completed', {\n id: event.id,\n success: event.success,\n duration: event.duration,\n roles: event.roles,\n ...(event.error ? { error: event.error } : {}),\n });\n }),\n );\n\n return unsubs;\n}\n","import { detectAppConfig } from '../app-config';\nimport type { SessionState, EmitFn } from './types';\n\nexport async function handleRunScenario(\n state: SessionState,\n cwd: string,\n cmd: Record<string, unknown>,\n emit: EmitFn,\n): Promise<void> {\n if (!state.runner) {\n emit('command-error', { message: 'No active session' });\n return;\n }\n const freshConfig = detectAppConfig(cwd) ?? state.appConfig;\n const scenario = freshConfig?.scenarios.find((s) => s.id === cmd.scenarioId);\n if (!scenario) {\n emit('command-error', { message: `Unknown scenario: ${cmd.scenarioId}` });\n return;\n }\n await state.runner.runScenario(scenario);\n}\n","import { detectAppConfig } from '../app-config';\nimport type { SessionState, EmitFn } from './types';\n\nexport async function handleRunMethod(\n state: SessionState,\n cwd: string,\n cmd: Record<string, unknown>,\n emit: EmitFn,\n): Promise<void> {\n if (!state.runner) {\n emit('command-error', { message: 'No active session' });\n return;\n }\n const methodName = cmd.method as string;\n if (!methodName) {\n emit('command-error', { message: 'run-method requires \"method\" (export name or ID)' });\n return;\n }\n const freshConfig = detectAppConfig(cwd) ?? state.appConfig;\n const method =\n freshConfig?.methods.find((m) => m.export === methodName) ??\n freshConfig?.methods.find((m) => m.id === methodName);\n if (!method) {\n emit('command-error', { message: `Unknown method: ${methodName}` });\n return;\n }\n const result = await state.runner.runMethod({\n methodExport: method.export,\n methodPath: method.path,\n input: cmd.input ?? {},\n });\n emit('method-run-completed', {\n method: method.export,\n success: result.success,\n output: result.output ?? null,\n error: result.error ?? null,\n stdout: result.stdout ?? [],\n duration: result.duration,\n });\n}\n","import type { SessionState, EmitFn } from './types';\n\nexport async function handleImpersonate(\n state: SessionState,\n cmd: Record<string, unknown>,\n emit: EmitFn,\n): Promise<void> {\n if (!state.runner) {\n emit('command-error', { message: 'No active session' });\n return;\n }\n const roles = cmd.roles as string[];\n if (!Array.isArray(roles)) {\n emit('command-error', { message: 'impersonate requires roles array' });\n return;\n }\n await state.runner.setImpersonation(roles);\n}\n\nexport async function handleClearImpersonation(\n state: SessionState,\n emit: EmitFn,\n): Promise<void> {\n if (!state.runner) {\n emit('command-error', { message: 'No active session' });\n return;\n }\n await state.runner.clearImpersonation();\n}\n","import type { SessionState, EmitFn } from './types';\n\nexport async function handleBrowser(\n state: SessionState,\n cmd: Record<string, unknown>,\n emit: EmitFn,\n): Promise<void> {\n if (!state.proxy) {\n emit('command-error', { message: 'No active proxy — browser commands require a web interface' });\n return;\n }\n const steps = cmd.steps as Array<Record<string, unknown>>;\n if (!Array.isArray(steps) || steps.length === 0) {\n emit('command-error', { message: 'browser action requires a non-empty \"steps\" array' });\n return;\n }\n try {\n const result = await state.proxy.dispatchBrowserCommand(steps);\n emit('browser-completed', {\n steps: result.steps,\n snapshot: result.snapshot,\n logs: result.logs,\n duration: result.duration,\n });\n } catch (err) {\n emit('command-error', {\n message: err instanceof Error ? err.message : 'Browser command failed',\n });\n }\n}\n","import { fetchCallbackToken } from '../api';\nimport { getApiBaseUrl } from '../../config';\nimport type { SessionState, EmitFn } from './types';\n\nexport async function handleScreenshot(\n state: SessionState,\n emit: EmitFn,\n): Promise<void> {\n if (!state.proxy) {\n emit('command-error', { message: 'No active proxy' });\n return;\n }\n if (!state.proxy.isBrowserConnected()) {\n emit('command-error', { message: 'No browser connected, please refresh the MindStudio preview' });\n return;\n }\n if (!state.runner?.getSession() || !state.appConfig?.appId) {\n emit('command-error', { message: 'No active session' });\n return;\n }\n\n try {\n const startTime = Date.now();\n\n // 1. Dispatch screenshot command to browser\n const result = await state.proxy.dispatchBrowserCommand([{ command: 'screenshot' }]);\n const stepResult = (result.steps as Array<Record<string, unknown>>)?.[0]\n ?.result as { image: string; width: number; height: number };\n\n if (!stepResult?.image) {\n emit('command-error', { message: 'Screenshot capture returned no image data' });\n return;\n }\n\n // 2. Fetch a callback token for S3 upload auth\n const session = state.runner.getSession()!;\n const token = await fetchCallbackToken(state.appConfig.appId, session.sessionId);\n\n // 3. Get presigned upload URL\n const uploadResponse = await fetch(\n `${getApiBaseUrl()}/_internal/v2/sandbox/upload`,\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ extension: 'png', contentType: 'image/png' }),\n },\n );\n\n if (!uploadResponse.ok) {\n const err = await uploadResponse.text();\n emit('command-error', { message: `Failed to get upload URL: ${uploadResponse.status} ${err}` });\n return;\n }\n\n const { uploadUrl, uploadFields, publicUrl } = (await uploadResponse.json()) as {\n uploadUrl: string;\n uploadFields: Record<string, string>;\n publicUrl: string;\n };\n\n // 4. Upload to S3\n const imageBuffer = Buffer.from(stepResult.image, 'base64');\n const form = new FormData();\n for (const [key, value] of Object.entries(uploadFields)) {\n form.append(key, value);\n }\n form.append('file', new Blob([imageBuffer], { type: 'image/png' }), 'screenshot.png');\n const uploadResult = await fetch(uploadUrl, { method: 'POST', body: form });\n\n if (!uploadResult.ok) {\n emit('command-error', { message: `S3 upload failed: ${uploadResult.status}` });\n return;\n }\n\n // 5. Emit result\n emit('screenshot-completed', {\n url: publicUrl,\n width: stepResult.width,\n height: stepResult.height,\n duration: Date.now() - startTime,\n });\n } catch (err) {\n emit('command-error', {\n message: err instanceof Error ? err.message : 'Screenshot failed',\n });\n }\n}\n","/**\n * Stdin command router for headless mode.\n *\n * Reads NDJSON commands from stdin and dispatches to individual handlers.\n * Each command lives in its own file for isolation and testability.\n */\n\nimport { handleRunScenario } from './run-scenario';\nimport { handleRunMethod } from './run-method';\nimport { handleImpersonate, handleClearImpersonation } from './impersonate';\nimport { handleBrowser } from './browser';\nimport { handleScreenshot } from './screenshot';\nimport type { SessionState, EmitFn } from './types';\n\nexport type { SessionState, EmitFn } from './types';\n\nexport function setupStdinCommands(\n state: SessionState,\n cwd: string,\n emit: EmitFn,\n): void {\n if (!process.stdin.readable) return;\n\n let buffer = '';\n process.stdin.setEncoding('utf-8');\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk;\n let idx: number;\n while ((idx = buffer.indexOf('\\n')) !== -1) {\n const line = buffer.slice(0, idx).trim();\n buffer = buffer.slice(idx + 1);\n if (!line) continue;\n\n try {\n const cmd = JSON.parse(line) as { action: string; [key: string]: unknown };\n handleStdinCommand(cmd, state, cwd, emit);\n } catch {\n emit('command-error', { message: `Invalid JSON on stdin: ${line.slice(0, 100)}` });\n }\n }\n });\n}\n\nasync function handleStdinCommand(\n cmd: { action: string; [key: string]: unknown },\n state: SessionState,\n cwd: string,\n emit: EmitFn,\n): Promise<void> {\n switch (cmd.action) {\n case 'run-scenario':\n return handleRunScenario(state, cwd, cmd, emit);\n case 'run-method':\n return handleRunMethod(state, cwd, cmd, emit);\n case 'impersonate':\n return handleImpersonate(state, cmd, emit);\n case 'clear-impersonation':\n return handleClearImpersonation(state, emit);\n case 'browser':\n return handleBrowser(state, cmd, emit);\n case 'screenshot':\n return handleScreenshot(state, emit);\n default:\n emit('command-error', { message: `Unknown action: ${cmd.action}` });\n }\n}\n","/**\n * Headless Dev Mode\n *\n * Runs the MindStudio dev tunnel without a TUI. Designed for programmatic\n * control by a parent process (e.g., a sandbox C&C server or CI pipeline).\n *\n * Outputs structured JSON events to stdout (one per line, newline-delimited).\n * The parent process reads these to track session state, method execution,\n * errors, and connection health.\n *\n * Does NOT start a dev server — the parent process manages that separately.\n * The tunnel just needs to know which port to proxy to.\n *\n * @module\n */\n\nimport { DevRunner } from './dev/runner';\nimport { DevProxy } from './dev/proxy';\nimport { syncSchema } from './dev/api';\nimport {\n detectAppConfig,\n getWebInterfaceConfig,\n readTableSources,\n} from './dev/app-config';\nimport { initRequestLog, closeRequestLog } from './dev/request-log';\nimport { initBrowserLog, closeBrowserLog } from './dev/browser-log';\nimport { subscribeDevEvents } from './dev/session-events';\nimport { setupStdinCommands, type SessionState } from './dev/stdin-commands';\nimport {\n getApiKey,\n getApiBaseUrl,\n getUserId,\n getEnvironment,\n getConfigPath,\n} from './config';\nimport { initLoggerHeadless, log, type LogLevel } from './dev/logger';\nimport { stablePort, detectGitBranch } from './dev/utils';\nimport { watchTableFiles } from './dev/table-watcher';\nimport { watchConfigFile } from './dev/config-watcher';\n\n/**\n * Options for headless dev mode.\n */\nexport interface HeadlessOptions {\n /** Working directory containing mindstudio.json. Defaults to process.cwd(). */\n cwd?: string;\n /** Port the dev server is running on. If omitted, reads from web.json. If neither, proxy is skipped. */\n devPort?: number;\n /** Preferred port for the local proxy. Defaults to a stable port derived from the app ID. */\n proxyPort?: number;\n /** Bind address for the proxy server. Use '0.0.0.0' for hosted sandboxes. Defaults to '127.0.0.1'. */\n bindAddress?: string;\n /** Log level for stderr output. Defaults to 'info'. */\n logLevel?: LogLevel;\n /** URL for the browser agent script. Defaults to unpkg latest. Set to an ngrok URL for development. */\n browserAgentUrl?: string;\n}\n\n/** Write a JSON event to stdout. */\nfunction emit(event: string, data?: Record<string, unknown>): void {\n process.stdout.write(JSON.stringify({ event, ...data }) + '\\n');\n}\n\n// ---------------------------------------------------------------------------\n// Session lifecycle\n// ---------------------------------------------------------------------------\n\nasync function startSession(\n cwd: string,\n opts: HeadlessOptions,\n state: SessionState,\n shutdown: () => Promise<void>,\n): Promise<boolean> {\n const bindAddress = opts.bindAddress ?? '127.0.0.1';\n\n // Read fresh config\n const appConfig = detectAppConfig(cwd);\n if (!appConfig) {\n emit('config-error', { message: 'No valid mindstudio.json found in ' + cwd });\n return false;\n }\n\n if (!appConfig.appId) {\n emit('config-error', { message: 'Missing \"appId\" in mindstudio.json' });\n return false;\n }\n\n state.appConfig = appConfig;\n\n // Resolve dev port\n let devPort = opts.devPort ?? null;\n if (devPort === null) {\n const webConfig = getWebInterfaceConfig(appConfig, cwd);\n devPort = webConfig?.devPort ?? null;\n }\n\n emit('session-starting', { appId: appConfig.appId, name: appConfig.name });\n\n try {\n // Start platform session\n const branch = detectGitBranch();\n const runner = new DevRunner(appConfig.appId, cwd, {\n branch,\n methods: appConfig.methods.map((m) => ({ id: m.id, export: m.export, path: m.path })),\n });\n const session = await runner.start();\n state.runner = runner;\n\n // Initialize logs\n initRequestLog(cwd);\n initBrowserLog(cwd);\n\n // Sync schema\n if (appConfig.tables.length > 0) {\n try {\n const tableSources = readTableSources(appConfig, cwd);\n if (tableSources.length > 0) {\n const syncResult = await syncSchema(appConfig.appId, session.sessionId, tableSources);\n session.databases = syncResult.databases;\n emit('schema-sync-completed', {\n created: syncResult.created,\n altered: syncResult.altered,\n errors: syncResult.errors,\n });\n } else {\n log.warn('No table source files found, skipping schema sync', {\n expected: appConfig.tables.map((t) => t.path),\n });\n }\n } catch (err) {\n emit('schema-sync-completed', {\n created: [],\n altered: [],\n errors: [err instanceof Error ? err.message : 'Schema sync failed'],\n });\n }\n }\n\n // Start proxy\n let proxyPort: number | null = null;\n if (devPort !== null && session.clientContext) {\n const proxy = new DevProxy(devPort, session.clientContext, bindAddress, opts.browserAgentUrl);\n const preferred = opts.proxyPort ?? stablePort(appConfig.appId);\n proxyPort = await proxy.start(preferred);\n runner.setProxyUrl(`http://${bindAddress === '0.0.0.0' ? 'localhost' : bindAddress}:${proxyPort}`);\n runner.setProxy(proxy);\n state.proxy = proxy;\n }\n state.proxyPort = proxyPort;\n\n emit('session-started', {\n sessionId: session.sessionId,\n releaseId: session.releaseId,\n branch: session.branch,\n proxyPort,\n proxyUrl: proxyPort\n ? `http://${bindAddress === '0.0.0.0' ? 'localhost' : bindAddress}:${proxyPort}/`\n : null,\n webInterfaceUrl: session.webInterfaceUrl,\n roles: appConfig.roles.map((r) => ({ id: r.id, name: r.name ?? r.id, description: r.description })),\n scenarios: appConfig.scenarios.map((s) => ({\n id: s.id,\n name: s.name ?? s.export,\n description: s.description,\n path: s.path,\n roles: s.roles,\n })),\n });\n\n // Subscribe to runner events\n state.unsubscribers.push(...subscribeDevEvents(emit, shutdown));\n\n // Watch table source files for changes — auto-sync without session restart\n setupTableWatchers(cwd, state);\n\n return true;\n } catch (err) {\n emit('config-error', {\n message: err instanceof Error ? err.message : 'Failed to start session',\n });\n return false;\n }\n}\n\nfunction setupTableWatchers(cwd: string, state: SessionState): void {\n if (!state.appConfig || state.appConfig.tables.length === 0) return;\n\n const cleanup = watchTableFiles(state.appConfig.tables, cwd, async () => {\n if (!state.runner || !state.appConfig?.appId) return;\n const session = state.runner.getSession();\n if (!session) return;\n\n emit('schema-sync-started');\n log.info('Table source file changed, syncing schema');\n\n try {\n const tableSources = readTableSources(state.appConfig, cwd);\n if (tableSources.length > 0) {\n const result = await syncSchema(state.appConfig.appId, session.sessionId, tableSources);\n session.databases = result.databases;\n emit('schema-sync-completed', {\n created: result.created,\n altered: result.altered,\n errors: result.errors,\n });\n log.info('Schema sync complete', { created: result.created, altered: result.altered });\n } else {\n log.warn('Table source file change detected but file(s) still missing', {\n expected: state.appConfig.tables.map((t) => t.path),\n });\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Schema sync failed';\n emit('command-error', { message });\n log.warn('Schema sync failed', { error: message });\n }\n });\n\n state.unsubscribers.push(cleanup);\n}\n\nasync function teardownSession(state: SessionState): Promise<void> {\n for (const unsub of state.unsubscribers) unsub();\n state.unsubscribers = [];\n\n state.proxy?.stop();\n state.proxy = null;\n state.proxyPort = null;\n\n if (state.runner) {\n await state.runner.stop().catch(() => {});\n state.runner = null;\n }\n\n closeRequestLog();\n closeBrowserLog();\n}\n\n// ---------------------------------------------------------------------------\n// Entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Start the dev tunnel in headless mode.\n */\nexport async function startHeadless(opts: HeadlessOptions = {}): Promise<void> {\n initLoggerHeadless(opts.logLevel ?? 'info');\n\n const cwd = opts.cwd ?? process.cwd();\n\n const apiKey = getApiKey();\n const userId = getUserId();\n log.info('Startup config', {\n configPath: getConfigPath(),\n environment: getEnvironment(),\n apiBaseUrl: getApiBaseUrl(),\n hasApiKey: !!apiKey,\n apiKeyPrefix: apiKey ? apiKey.slice(0, 8) + '...' : null,\n hasUserId: !!userId,\n userId: userId ?? null,\n cwd,\n });\n\n const state: SessionState = {\n runner: null,\n proxy: null,\n appConfig: null,\n proxyPort: null,\n unsubscribers: [],\n };\n\n let restarting = false;\n let cleanupConfigWatcher: (() => void) | undefined;\n\n let stopping = false;\n const shutdown = async () => {\n if (stopping) return;\n stopping = true;\n emit('session-stopping');\n cleanupConfigWatcher?.();\n await teardownSession(state);\n emit('session-stopped');\n };\n\n process.on('SIGTERM', () => { shutdown().then(() => process.exit(0)); });\n process.on('SIGINT', () => { shutdown().then(() => process.exit(0)); });\n\n // Initial session start\n const ok = await startSession(cwd, opts, state, shutdown);\n if (!ok && !state.appConfig) {\n emit('error', { message: 'No valid mindstudio.json found in ' + cwd });\n }\n\n // Stdin command loop\n setupStdinCommands(state, cwd, emit);\n\n // Watch mindstudio.json for changes\n cleanupConfigWatcher = watchConfigFile(cwd, async () => {\n if (stopping || restarting) return;\n restarting = true;\n try {\n log.info('mindstudio.json changed, restarting dev session');\n emit('config-changed');\n await teardownSession(state);\n await startSession(cwd, opts, state, shutdown);\n } finally {\n restarting = false;\n }\n });\n\n // Keep the process alive — the poll loop runs in DevRunner\n await new Promise<void>(() => {});\n}\n"],"mappings":"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","names":["emit","emit","emit","emit","emit","emit","emit"]}
|
package/dist/cli.js
CHANGED
package/dist/headless.d.ts
CHANGED
|
@@ -21,49 +21,6 @@ type LogLevel = 'error' | 'warn' | 'info' | 'debug';
|
|
|
21
21
|
* Does NOT start a dev server — the parent process manages that separately.
|
|
22
22
|
* The tunnel just needs to know which port to proxy to.
|
|
23
23
|
*
|
|
24
|
-
* ## JSON Event Protocol
|
|
25
|
-
*
|
|
26
|
-
* Every line written to stdout is a JSON object with an `event` field:
|
|
27
|
-
*
|
|
28
|
-
* | Event | When | Key Fields |
|
|
29
|
-
* |------------------------|-----------------------------------------|-----------------------------------------------|
|
|
30
|
-
* | `session-starting` | Session initializing | `appId`, `name` |
|
|
31
|
-
* | `session-started` | Platform session active, proxy running | `sessionId`, `branch`, `proxyPort`, `proxyUrl`|
|
|
32
|
-
* | `session-stopping` | Graceful shutdown initiated | |
|
|
33
|
-
* | `session-stopped` | All resources cleaned up | |
|
|
34
|
-
* | `session-expired` | Platform expired the dev session | |
|
|
35
|
-
* | `method-started` | Method execution request received | `id`, `method` |
|
|
36
|
-
* | `method-completed` | Method execution finished | `id`, `success`, `duration`, `error?` |
|
|
37
|
-
* | `scenario-started` | Scenario execution started | `id`, `name` |
|
|
38
|
-
* | `scenario-completed` | Scenario execution finished | `id`, `success`, `duration`, `roles`, `error?`|
|
|
39
|
-
* | `schema-sync-started` | Table file changed, syncing schema | |
|
|
40
|
-
* | `schema-sync-completed`| Schema synced to platform | `created`, `altered`, `errors` |
|
|
41
|
-
* | `impersonation-changed`| Role override set or cleared | `roles` |
|
|
42
|
-
* | `connection-lost` | Lost connection, retrying with backoff | `message` |
|
|
43
|
-
* | `connection-restored` | Reconnected after connection loss | |
|
|
44
|
-
* | `config-changed` | mindstudio.json changed, restarting | |
|
|
45
|
-
* | `config-error` | Config invalid (non-fatal) | `message` |
|
|
46
|
-
* | `command-error` | Stdin command failed (non-fatal) | `message` |
|
|
47
|
-
* | `error` | Fatal startup error | `message` |
|
|
48
|
-
*
|
|
49
|
-
* ## Usage
|
|
50
|
-
*
|
|
51
|
-
* CLI:
|
|
52
|
-
* ```bash
|
|
53
|
-
* mindstudio-local --headless --port 5173 --bind 0.0.0.0
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* Programmatic:
|
|
57
|
-
* ```typescript
|
|
58
|
-
* import { startHeadless } from '@mindstudio-ai/local-model-tunnel';
|
|
59
|
-
*
|
|
60
|
-
* await startHeadless({
|
|
61
|
-
* cwd: '/path/to/project',
|
|
62
|
-
* devPort: 5173,
|
|
63
|
-
* bindAddress: '0.0.0.0',
|
|
64
|
-
* });
|
|
65
|
-
* ```
|
|
66
|
-
*
|
|
67
24
|
* @module
|
|
68
25
|
*/
|
|
69
26
|
|
|
@@ -81,32 +38,11 @@ interface HeadlessOptions {
|
|
|
81
38
|
bindAddress?: string;
|
|
82
39
|
/** Log level for stderr output. Defaults to 'info'. */
|
|
83
40
|
logLevel?: LogLevel;
|
|
41
|
+
/** URL for the browser agent script. Defaults to unpkg latest. Set to an ngrok URL for development. */
|
|
42
|
+
browserAgentUrl?: string;
|
|
84
43
|
}
|
|
85
44
|
/**
|
|
86
45
|
* Start the dev tunnel in headless mode.
|
|
87
|
-
*
|
|
88
|
-
* Reads mindstudio.json, starts a platform session, syncs schema,
|
|
89
|
-
* starts the local proxy, and enters the poll loop. Outputs JSON
|
|
90
|
-
* events to stdout. Does not return until shutdown (SIGTERM/SIGINT).
|
|
91
|
-
*
|
|
92
|
-
* Watches mindstudio.json for changes and automatically restarts the
|
|
93
|
-
* session when the config is updated (same behavior as the TUI).
|
|
94
|
-
*
|
|
95
|
-
* Does NOT start a dev server — the caller is responsible for that.
|
|
96
|
-
*
|
|
97
|
-
* @param opts - Configuration options
|
|
98
|
-
*
|
|
99
|
-
* @example
|
|
100
|
-
* ```typescript
|
|
101
|
-
* // From a C&C server — spawn and read events
|
|
102
|
-
* import { startHeadless } from '@mindstudio-ai/local-model-tunnel';
|
|
103
|
-
*
|
|
104
|
-
* await startHeadless({
|
|
105
|
-
* cwd: '/workspace/my-app',
|
|
106
|
-
* devPort: 5173,
|
|
107
|
-
* bindAddress: '0.0.0.0',
|
|
108
|
-
* });
|
|
109
|
-
* ```
|
|
110
46
|
*/
|
|
111
47
|
declare function startHeadless(opts?: HeadlessOptions): Promise<void>;
|
|
112
48
|
|