@go-go-golems/os-scripting 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -0
- package/app/createAppStore.d.ts +89 -0
- package/app/createAppStore.js +90 -0
- package/app/index.d.ts +2 -0
- package/app/index.js +2 -0
- package/app/runtimeSessionLifecycleMiddleware.d.ts +6 -0
- package/app/runtimeSessionLifecycleMiddleware.js +42 -0
- package/features/runtimeSessions/capabilityPolicy.d.ts +12 -0
- package/features/runtimeSessions/capabilityPolicy.js +36 -0
- package/features/runtimeSessions/index.d.ts +3 -0
- package/features/runtimeSessions/index.js +3 -0
- package/features/runtimeSessions/runtimeSessionsSlice.d.ts +90 -0
- package/features/runtimeSessions/runtimeSessionsSlice.js +210 -0
- package/features/runtimeSessions/selectors.d.ts +16 -0
- package/features/runtimeSessions/selectors.js +40 -0
- package/hypercard/artifacts/artifactProjectionMiddleware.d.ts +1 -0
- package/hypercard/artifacts/artifactProjectionMiddleware.js +66 -0
- package/hypercard/artifacts/artifactRuntime.d.ts +22 -0
- package/hypercard/artifacts/artifactRuntime.js +137 -0
- package/hypercard/artifacts/artifactsSelectors.d.ts +10 -0
- package/hypercard/artifacts/artifactsSelectors.js +2 -0
- package/hypercard/artifacts/artifactsSlice.d.ts +38 -0
- package/hypercard/artifacts/artifactsSlice.js +94 -0
- package/hypercard/debug/RuntimeSurfaceDebugWindow.d.ts +7 -0
- package/hypercard/debug/RuntimeSurfaceDebugWindow.js +181 -0
- package/hypercard/debug/jsSessionDebugRegistry.d.ts +11 -0
- package/hypercard/debug/jsSessionDebugRegistry.js +43 -0
- package/hypercard/debug/runtimeDebugApp.d.ts +19 -0
- package/hypercard/debug/runtimeDebugApp.js +25 -0
- package/hypercard/debug/runtimeDebugRegistry.d.ts +6 -0
- package/hypercard/debug/runtimeDebugRegistry.js +49 -0
- package/hypercard/editor/CodeEditorWindow.d.ts +7 -0
- package/hypercard/editor/CodeEditorWindow.js +128 -0
- package/hypercard/editor/editorLaunch.d.ts +16 -0
- package/hypercard/editor/editorLaunch.js +58 -0
- package/hypercard/editor/runtimeSurfaceRef.d.ts +8 -0
- package/hypercard/editor/runtimeSurfaceRef.js +60 -0
- package/hypercard/index.d.ts +12 -0
- package/hypercard/index.js +12 -0
- package/hypercard/task-manager/TaskManagerWindow.d.ts +1 -0
- package/hypercard/task-manager/TaskManagerWindow.js +102 -0
- package/hypercard/task-manager/index.d.ts +6 -0
- package/hypercard/task-manager/index.js +6 -0
- package/hypercard/task-manager/jsSessionSource.d.ts +10 -0
- package/hypercard/task-manager/jsSessionSource.js +49 -0
- package/hypercard/task-manager/runtimeSessionSource.d.ts +26 -0
- package/hypercard/task-manager/runtimeSessionSource.js +123 -0
- package/hypercard/task-manager/taskManagerApp.d.ts +16 -0
- package/hypercard/task-manager/taskManagerApp.js +25 -0
- package/hypercard/task-manager/taskManagerRegistry.d.ts +10 -0
- package/hypercard/task-manager/taskManagerRegistry.js +75 -0
- package/hypercard/task-manager/types.d.ts +25 -0
- package/hypercard/task-manager/types.js +1 -0
- package/hypercard/timeline/hypercardCard.d.ts +5 -0
- package/hypercard/timeline/hypercardCard.js +104 -0
- package/index.d.ts +18 -0
- package/index.js +18 -0
- package/package.json +72 -0
- package/plugin-runtime/contracts.d.ts +116 -0
- package/plugin-runtime/contracts.js +32 -0
- package/plugin-runtime/fixtures/column-stack.vm.js +19 -0
- package/plugin-runtime/fixtures/dynamic-card.vm.js +13 -0
- package/plugin-runtime/fixtures/inventory-stack.vm.js +29 -0
- package/plugin-runtime/fixtures/kanban-card.vm.js +55 -0
- package/plugin-runtime/fixtures/loop-stack.vm.js +16 -0
- package/plugin-runtime/fixtures/patched-low-stock-handler.vm.js +3 -0
- package/plugin-runtime/fixtures/patched-low-stock-render.vm.js +5 -0
- package/plugin-runtime/index.d.ts +6 -0
- package/plugin-runtime/index.js +6 -0
- package/plugin-runtime/intentSchema.d.ts +3 -0
- package/plugin-runtime/intentSchema.js +25 -0
- package/plugin-runtime/jsEvalSupport.d.ts +6 -0
- package/plugin-runtime/jsEvalSupport.js +93 -0
- package/plugin-runtime/jsSessionService.d.ts +55 -0
- package/plugin-runtime/jsSessionService.js +136 -0
- package/plugin-runtime/quickJsSessionCore.d.ts +24 -0
- package/plugin-runtime/quickJsSessionCore.js +92 -0
- package/plugin-runtime/runtimeService.d.ts +34 -0
- package/plugin-runtime/runtimeService.js +248 -0
- package/plugin-runtime/runtimeSurfaceRegistry.d.ts +45 -0
- package/plugin-runtime/runtimeSurfaceRegistry.js +73 -0
- package/plugin-runtime/stack-bootstrap.vm.js +236 -0
- package/repl/attachedJsSessionRegistry.d.ts +25 -0
- package/repl/attachedJsSessionRegistry.js +81 -0
- package/repl/attachedRuntimeSessionRegistry.d.ts +11 -0
- package/repl/attachedRuntimeSessionRegistry.js +107 -0
- package/repl/hypercardReplDriver.d.ts +54 -0
- package/repl/hypercardReplDriver.js +600 -0
- package/repl/jsReplDriver.d.ts +8 -0
- package/repl/jsReplDriver.js +348 -0
- package/repl/jsSessionBroker.d.ts +21 -0
- package/repl/jsSessionBroker.js +75 -0
- package/repl/runtimeBroker.d.ts +43 -0
- package/repl/runtimeBroker.js +117 -0
- package/runtime-host/RuntimeSurfaceSessionHost.d.ts +8 -0
- package/runtime-host/RuntimeSurfaceSessionHost.js +384 -0
- package/runtime-host/fixtures/CardSessionHost.chat.vm.js +61 -0
- package/runtime-host/fixtures/CardSessionHost.list.vm.js +29 -0
- package/runtime-host/fixtures/CardSessionHost.nav.vm.js +101 -0
- package/runtime-host/fixtures/CardSessionHost.report.vm.js +34 -0
- package/runtime-host/pluginIntentRouting.d.ts +13 -0
- package/runtime-host/pluginIntentRouting.js +83 -0
- package/runtime-packages/index.d.ts +1 -0
- package/runtime-packages/index.js +1 -0
- package/runtime-packages/runtimePackageRegistry.d.ts +14 -0
- package/runtime-packages/runtimePackageRegistry.js +42 -0
- package/runtime-packages/ui.package.vm.js +84 -0
- package/runtime-packs/index.d.ts +1 -0
- package/runtime-packs/index.js +1 -0
- package/runtime-packs/runtimeSurfaceTypeRegistry.d.ts +20 -0
- package/runtime-packs/runtimeSurfaceTypeRegistry.js +37 -0
- package/runtime-session-manager/index.d.ts +2 -0
- package/runtime-session-manager/index.js +2 -0
- package/runtime-session-manager/runtimeOwnership.d.ts +10 -0
- package/runtime-session-manager/runtimeOwnership.js +19 -0
- package/runtime-session-manager/runtimeSessionManager.d.ts +47 -0
- package/runtime-session-manager/runtimeSessionManager.js +214 -0
- package/testRuntimeUi.d.ts +4 -0
- package/testRuntimeUi.js +39 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
import { getAttachedRuntimeSession, listAttachedRuntimeSessions } from './attachedRuntimeSessionRegistry';
|
|
2
|
+
import { getRuntimePackageOrThrow, listRuntimePackages } from '../runtime-packages/runtimePackageRegistry';
|
|
3
|
+
import { listRuntimeSurfaceTypes } from '../runtime-packs/runtimeSurfaceTypeRegistry';
|
|
4
|
+
import { createRuntimeBroker } from './runtimeBroker';
|
|
5
|
+
const COMMAND_HELP = {
|
|
6
|
+
packages: {
|
|
7
|
+
title: 'packages',
|
|
8
|
+
detail: 'List registered runtime packages and their summaries.',
|
|
9
|
+
usage: 'packages',
|
|
10
|
+
},
|
|
11
|
+
'surface-types': {
|
|
12
|
+
title: 'surface-types',
|
|
13
|
+
detail: 'List registered runtime surface types.',
|
|
14
|
+
usage: 'surface-types',
|
|
15
|
+
},
|
|
16
|
+
bundles: {
|
|
17
|
+
title: 'bundles',
|
|
18
|
+
detail: 'List the available bundle library entries that the REPL can spawn.',
|
|
19
|
+
usage: 'bundles',
|
|
20
|
+
},
|
|
21
|
+
spawn: {
|
|
22
|
+
title: 'spawn',
|
|
23
|
+
detail: 'Spawn a runtime session from a named bundle library entry.',
|
|
24
|
+
usage: 'spawn <bundle-key> [session-id]',
|
|
25
|
+
},
|
|
26
|
+
attach: {
|
|
27
|
+
title: 'attach',
|
|
28
|
+
detail: 'Attach the REPL to a live host-owned runtime session in read-only mode.',
|
|
29
|
+
usage: 'attach <session-id>',
|
|
30
|
+
},
|
|
31
|
+
sessions: {
|
|
32
|
+
title: 'sessions',
|
|
33
|
+
detail: 'List spawned and attached runtime sessions visible to the REPL.',
|
|
34
|
+
usage: 'sessions',
|
|
35
|
+
},
|
|
36
|
+
help: {
|
|
37
|
+
title: 'help',
|
|
38
|
+
detail: 'Show HyperCard REPL command help or docs for a command, package symbol, or runtime surface.',
|
|
39
|
+
usage: 'help [topic]',
|
|
40
|
+
},
|
|
41
|
+
use: {
|
|
42
|
+
title: 'use',
|
|
43
|
+
detail: 'Select an active runtime session for later render/event commands.',
|
|
44
|
+
usage: 'use <session-id>',
|
|
45
|
+
},
|
|
46
|
+
surfaces: {
|
|
47
|
+
title: 'surfaces',
|
|
48
|
+
detail: 'List runtime surfaces for the active or provided session.',
|
|
49
|
+
usage: 'surfaces [session-id]',
|
|
50
|
+
},
|
|
51
|
+
render: {
|
|
52
|
+
title: 'render',
|
|
53
|
+
detail: 'Render a runtime surface and print the returned tree as JSON.',
|
|
54
|
+
usage: 'render <surface-id> [state-json]',
|
|
55
|
+
},
|
|
56
|
+
event: {
|
|
57
|
+
title: 'event',
|
|
58
|
+
detail: 'Dispatch a handler on a runtime surface and print the resulting actions.',
|
|
59
|
+
usage: 'event <surface-id> <handler> [args-json] [state-json]',
|
|
60
|
+
},
|
|
61
|
+
'define-surface': {
|
|
62
|
+
title: 'define-surface',
|
|
63
|
+
detail: 'Define a new runtime surface in the active session from inline factory code.',
|
|
64
|
+
usage: 'define-surface <surface-id> <surface-type> <factory-code>',
|
|
65
|
+
},
|
|
66
|
+
'define-render': {
|
|
67
|
+
title: 'define-render',
|
|
68
|
+
detail: 'Replace the render() function for a runtime surface with inline code.',
|
|
69
|
+
usage: 'define-render <surface-id> <render-code>',
|
|
70
|
+
},
|
|
71
|
+
'define-handler': {
|
|
72
|
+
title: 'define-handler',
|
|
73
|
+
detail: 'Replace or add a handler on a runtime surface with inline code.',
|
|
74
|
+
usage: 'define-handler <surface-id> <handler-name> <handler-code>',
|
|
75
|
+
},
|
|
76
|
+
'open-surface': {
|
|
77
|
+
title: 'open-surface',
|
|
78
|
+
detail: 'Emit a host effect requesting a window for a runtime surface.',
|
|
79
|
+
usage: 'open-surface <surface-id> [session-id]',
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
function asRecord(value) {
|
|
83
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
function formatJsonLines(value) {
|
|
89
|
+
return JSON.stringify(value, null, 2)
|
|
90
|
+
.split('\n')
|
|
91
|
+
.map((text) => ({ type: 'output', text }));
|
|
92
|
+
}
|
|
93
|
+
function listDocEntries() {
|
|
94
|
+
const entries = [];
|
|
95
|
+
for (const packageId of listRuntimePackages()) {
|
|
96
|
+
const runtimePackage = getRuntimePackageOrThrow(packageId);
|
|
97
|
+
const metadata = runtimePackage.docsMetadata;
|
|
98
|
+
for (const file of metadata?.docs?.files ?? []) {
|
|
99
|
+
if (file.package?.name) {
|
|
100
|
+
entries.push({
|
|
101
|
+
title: file.package.name,
|
|
102
|
+
detail: file.package.description ?? file.package.title ?? runtimePackage.summary ?? packageId,
|
|
103
|
+
usage: file.package.prose,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
for (const symbol of file.symbols ?? []) {
|
|
107
|
+
entries.push({
|
|
108
|
+
title: symbol.name,
|
|
109
|
+
detail: symbol.summary ?? `Documentation symbol from ${packageId}`,
|
|
110
|
+
usage: symbol.prose,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return entries;
|
|
116
|
+
}
|
|
117
|
+
function listBundleDocEntries(bundleLibrary) {
|
|
118
|
+
const entries = [];
|
|
119
|
+
for (const bundle of bundleLibrary.values()) {
|
|
120
|
+
const docs = bundle.docsMetadata?.docs;
|
|
121
|
+
for (const [symbolName, symbol] of Object.entries(docs?.by_symbol ?? {})) {
|
|
122
|
+
entries.push({
|
|
123
|
+
title: symbolName,
|
|
124
|
+
detail: symbol.summary ?? `Runtime surface doc from ${bundle.key}`,
|
|
125
|
+
usage: symbol.prose,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
for (const file of docs?.files ?? []) {
|
|
129
|
+
for (const symbol of file.symbols ?? []) {
|
|
130
|
+
if (docs?.by_symbol?.[symbol.name]) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
entries.push({
|
|
134
|
+
title: symbol.name,
|
|
135
|
+
detail: symbol.summary ?? `Runtime surface doc from ${bundle.key}`,
|
|
136
|
+
usage: symbol.prose,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return entries;
|
|
142
|
+
}
|
|
143
|
+
function listPackageCompletionItems() {
|
|
144
|
+
return listRuntimePackages().map((packageId) => {
|
|
145
|
+
const runtimePackage = getRuntimePackageOrThrow(packageId);
|
|
146
|
+
return {
|
|
147
|
+
value: packageId,
|
|
148
|
+
detail: runtimePackage.summary ?? runtimePackage.version,
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function parseJsonArg(raw, fallback = {}) {
|
|
153
|
+
if (!raw) {
|
|
154
|
+
return fallback;
|
|
155
|
+
}
|
|
156
|
+
return JSON.parse(raw);
|
|
157
|
+
}
|
|
158
|
+
function parseInlineAuthoringArgs(raw, count) {
|
|
159
|
+
const trimmed = raw.trim();
|
|
160
|
+
if (!trimmed) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
const tokens = [];
|
|
164
|
+
let cursor = trimmed;
|
|
165
|
+
for (let index = 0; index < count; index += 1) {
|
|
166
|
+
const match = cursor.match(/^(\S+)(?:\s+|$)/);
|
|
167
|
+
if (!match) {
|
|
168
|
+
return tokens;
|
|
169
|
+
}
|
|
170
|
+
tokens.push(match[1]);
|
|
171
|
+
cursor = cursor.slice(match[0].length);
|
|
172
|
+
}
|
|
173
|
+
if (cursor.trim().length > 0) {
|
|
174
|
+
tokens.push(cursor.trim());
|
|
175
|
+
}
|
|
176
|
+
return tokens;
|
|
177
|
+
}
|
|
178
|
+
function helpForTopic(topic, bundleLibrary) {
|
|
179
|
+
const docEntries = [
|
|
180
|
+
...listDocEntries(),
|
|
181
|
+
...listBundleDocEntries(bundleLibrary),
|
|
182
|
+
];
|
|
183
|
+
if (!topic) {
|
|
184
|
+
return [
|
|
185
|
+
...Object.values(COMMAND_HELP),
|
|
186
|
+
...docEntries,
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
const command = COMMAND_HELP[topic];
|
|
190
|
+
if (command) {
|
|
191
|
+
return [command];
|
|
192
|
+
}
|
|
193
|
+
return docEntries.filter((entry) => entry.title === topic);
|
|
194
|
+
}
|
|
195
|
+
function findBundleForStackId(bundleLibrary, stackId) {
|
|
196
|
+
for (const bundle of bundleLibrary.values()) {
|
|
197
|
+
if (bundle.stackId === stackId) {
|
|
198
|
+
return bundle;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
function describeSurface(bundleLibrary, stackId, surfaceId, fallbackSurfaceType) {
|
|
204
|
+
const bundle = findBundleForStackId(bundleLibrary, stackId);
|
|
205
|
+
const symbol = bundle?.docsMetadata?.docs?.by_symbol?.[surfaceId];
|
|
206
|
+
if (!symbol?.summary) {
|
|
207
|
+
return fallbackSurfaceType;
|
|
208
|
+
}
|
|
209
|
+
return `${symbol.summary} [${fallbackSurfaceType}]`;
|
|
210
|
+
}
|
|
211
|
+
function collectSessions(broker) {
|
|
212
|
+
const attached = listAttachedRuntimeSessions().map((entry) => entry.summary);
|
|
213
|
+
const spawned = broker.listSessions();
|
|
214
|
+
return [...spawned, ...attached].sort((left, right) => left.sessionId.localeCompare(right.sessionId));
|
|
215
|
+
}
|
|
216
|
+
function linesForSessions(broker, activeSessionId) {
|
|
217
|
+
const sessions = collectSessions(broker);
|
|
218
|
+
if (sessions.length === 0) {
|
|
219
|
+
return [{ type: 'system', text: 'No runtime sessions.' }];
|
|
220
|
+
}
|
|
221
|
+
return sessions.flatMap((session) => [
|
|
222
|
+
{
|
|
223
|
+
type: 'output',
|
|
224
|
+
text: `${session.sessionId}${session.sessionId === activeSessionId ? ' *' : ''} — ` +
|
|
225
|
+
`${session.stackId} (${session.packageIds.join(', ')}) ` +
|
|
226
|
+
`[${session.origin}${session.writable ? '' : ', read-only'}]`,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
type: 'system',
|
|
230
|
+
text: ` surfaces: ${session.surfaces.join(', ') || 'none'}`,
|
|
231
|
+
},
|
|
232
|
+
]);
|
|
233
|
+
}
|
|
234
|
+
function promptForSession(sessionId) {
|
|
235
|
+
return sessionId ? `hc[${sessionId}]>` : 'hc>';
|
|
236
|
+
}
|
|
237
|
+
export function createHypercardReplDriver(options = {}) {
|
|
238
|
+
const broker = options.broker ?? createRuntimeBroker();
|
|
239
|
+
const bundleLibrary = new Map(Object.values(options.bundleLibrary ?? {}).map((entry) => [entry.key, entry]));
|
|
240
|
+
let activeSessionId = null;
|
|
241
|
+
let sessionCounter = 1;
|
|
242
|
+
function nextSessionId(bundleKey) {
|
|
243
|
+
const candidate = `${bundleKey}@repl-${sessionCounter}`;
|
|
244
|
+
sessionCounter += 1;
|
|
245
|
+
return candidate;
|
|
246
|
+
}
|
|
247
|
+
function getSessionRecord(sessionId) {
|
|
248
|
+
const spawnedHandle = broker.getSession(sessionId);
|
|
249
|
+
if (spawnedHandle) {
|
|
250
|
+
const summary = broker.listSessions().find((entry) => entry.sessionId === sessionId);
|
|
251
|
+
if (!summary) {
|
|
252
|
+
throw new Error(`Missing runtime session summary for spawned session: ${sessionId}`);
|
|
253
|
+
}
|
|
254
|
+
return { handle: spawnedHandle, summary };
|
|
255
|
+
}
|
|
256
|
+
const attached = getAttachedRuntimeSession(sessionId);
|
|
257
|
+
if (attached) {
|
|
258
|
+
return { handle: attached.handle, summary: attached.summary };
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
function requireWritableSession(record, sessionId) {
|
|
263
|
+
if (record.summary.writable) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
throw new Error(`Attached runtime session ${sessionId} is read-only`);
|
|
267
|
+
}
|
|
268
|
+
function getActiveSession(sessionIdArg) {
|
|
269
|
+
const sessionId = sessionIdArg ?? activeSessionId;
|
|
270
|
+
if (!sessionId) {
|
|
271
|
+
throw new Error('No active runtime session. Use spawn or use <session-id> first.');
|
|
272
|
+
}
|
|
273
|
+
const record = getSessionRecord(sessionId);
|
|
274
|
+
if (!record) {
|
|
275
|
+
throw new Error(`Unknown runtime session: ${sessionId}`);
|
|
276
|
+
}
|
|
277
|
+
return { sessionId, ...record };
|
|
278
|
+
}
|
|
279
|
+
async function spawnBundle(bundleKey, sessionIdArg) {
|
|
280
|
+
const bundle = bundleLibrary.get(bundleKey);
|
|
281
|
+
if (!bundle) {
|
|
282
|
+
throw new Error(`Unknown bundle: ${bundleKey}`);
|
|
283
|
+
}
|
|
284
|
+
const request = {
|
|
285
|
+
stackId: bundle.stackId,
|
|
286
|
+
sessionId: sessionIdArg ?? nextSessionId(bundleKey),
|
|
287
|
+
packageIds: bundle.packageIds,
|
|
288
|
+
bundleCode: bundle.bundleCode,
|
|
289
|
+
};
|
|
290
|
+
const handle = await broker.spawnSession(request);
|
|
291
|
+
activeSessionId = handle.sessionId;
|
|
292
|
+
return {
|
|
293
|
+
lines: [
|
|
294
|
+
{
|
|
295
|
+
type: 'system',
|
|
296
|
+
text: `Spawned runtime session ${handle.sessionId} from ${bundleKey}`,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
type: 'output',
|
|
300
|
+
text: `surfaces: ${handle.getBundleMeta().surfaces.join(', ') || 'none'}`,
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
envVars: {
|
|
304
|
+
REPL_PROMPT: promptForSession(handle.sessionId),
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
async execute(raw, _context) {
|
|
310
|
+
const trimmed = raw.trim();
|
|
311
|
+
if (!trimmed) {
|
|
312
|
+
return { lines: [] };
|
|
313
|
+
}
|
|
314
|
+
const [command, ...rest] = trimmed.split(/\s+/);
|
|
315
|
+
const restRaw = trimmed.slice(command.length).trimStart();
|
|
316
|
+
switch (command) {
|
|
317
|
+
case 'packages':
|
|
318
|
+
return {
|
|
319
|
+
lines: listPackageCompletionItems().map((item) => ({
|
|
320
|
+
type: 'output',
|
|
321
|
+
text: `${item.value} — ${item.detail ?? ''}`.trim(),
|
|
322
|
+
})),
|
|
323
|
+
};
|
|
324
|
+
case 'surface-types':
|
|
325
|
+
return {
|
|
326
|
+
lines: listRuntimeSurfaceTypes().map((surfaceType) => ({
|
|
327
|
+
type: 'output',
|
|
328
|
+
text: surfaceType,
|
|
329
|
+
})),
|
|
330
|
+
};
|
|
331
|
+
case 'bundles':
|
|
332
|
+
return {
|
|
333
|
+
lines: bundleLibrary.size === 0
|
|
334
|
+
? [{ type: 'system', text: 'No bundle library entries configured.' }]
|
|
335
|
+
: Array.from(bundleLibrary.values()).map((bundle) => ({
|
|
336
|
+
type: 'output',
|
|
337
|
+
text: `${bundle.key} — ${bundle.title ?? bundle.stackId} [${bundle.packageIds.join(', ')}]`,
|
|
338
|
+
})),
|
|
339
|
+
};
|
|
340
|
+
case 'spawn':
|
|
341
|
+
if (rest.length === 0) {
|
|
342
|
+
throw new Error('Usage: spawn <bundle-key> [session-id]');
|
|
343
|
+
}
|
|
344
|
+
return await spawnBundle(rest[0], rest[1]);
|
|
345
|
+
case 'sessions':
|
|
346
|
+
return {
|
|
347
|
+
lines: linesForSessions(broker, activeSessionId),
|
|
348
|
+
};
|
|
349
|
+
case 'help': {
|
|
350
|
+
const topic = rest[0] ?? null;
|
|
351
|
+
const entries = helpForTopic(topic, bundleLibrary);
|
|
352
|
+
if (!entries || entries.length === 0) {
|
|
353
|
+
return {
|
|
354
|
+
lines: [
|
|
355
|
+
{
|
|
356
|
+
type: 'error',
|
|
357
|
+
text: topic ? `No help available for "${topic}"` : 'No help available.',
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
lines: entries.flatMap((entry) => [
|
|
364
|
+
{ type: 'output', text: `${entry.title} — ${entry.detail}` },
|
|
365
|
+
...(entry.usage ? [{ type: 'system', text: ` ${entry.usage}` }] : []),
|
|
366
|
+
]),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
case 'attach': {
|
|
370
|
+
const sessionId = rest[0];
|
|
371
|
+
if (!sessionId) {
|
|
372
|
+
throw new Error('Usage: attach <session-id>');
|
|
373
|
+
}
|
|
374
|
+
const attached = getAttachedRuntimeSession(sessionId);
|
|
375
|
+
if (!attached) {
|
|
376
|
+
throw new Error(`Unknown attached runtime session: ${sessionId}`);
|
|
377
|
+
}
|
|
378
|
+
activeSessionId = sessionId;
|
|
379
|
+
return {
|
|
380
|
+
lines: [{ type: 'system', text: `Attached to runtime session: ${sessionId} (read-only)` }],
|
|
381
|
+
envVars: {
|
|
382
|
+
REPL_PROMPT: promptForSession(sessionId),
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
case 'use': {
|
|
387
|
+
const sessionId = rest[0];
|
|
388
|
+
if (!sessionId) {
|
|
389
|
+
throw new Error('Usage: use <session-id>');
|
|
390
|
+
}
|
|
391
|
+
const record = getSessionRecord(sessionId);
|
|
392
|
+
if (!record) {
|
|
393
|
+
throw new Error(`Unknown runtime session: ${sessionId}`);
|
|
394
|
+
}
|
|
395
|
+
activeSessionId = sessionId;
|
|
396
|
+
return {
|
|
397
|
+
lines: [{ type: 'system', text: `Active runtime session: ${sessionId}${record.summary.writable ? '' : ' (read-only)'}` }],
|
|
398
|
+
envVars: {
|
|
399
|
+
REPL_PROMPT: promptForSession(sessionId),
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
case 'surfaces': {
|
|
404
|
+
const { handle, sessionId } = getActiveSession(rest[0]);
|
|
405
|
+
const meta = handle.getBundleMeta();
|
|
406
|
+
return {
|
|
407
|
+
lines: [
|
|
408
|
+
{ type: 'system', text: `Runtime surfaces for ${sessionId}` },
|
|
409
|
+
...meta.surfaces.map((surfaceId) => ({
|
|
410
|
+
type: 'output',
|
|
411
|
+
text: `${surfaceId} — ${describeSurface(bundleLibrary, handle.stackId, surfaceId, meta.surfaceTypes?.[surfaceId] ?? 'ui.card.v1')}`,
|
|
412
|
+
})),
|
|
413
|
+
],
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
case 'render': {
|
|
417
|
+
const [surfaceId, stateJson] = parseInlineAuthoringArgs(restRaw, 1);
|
|
418
|
+
if (!surfaceId) {
|
|
419
|
+
throw new Error('Usage: render <surface-id> [state-json]');
|
|
420
|
+
}
|
|
421
|
+
const { handle } = getActiveSession();
|
|
422
|
+
const tree = handle.renderSurface(surfaceId, parseJsonArg(stateJson, {}));
|
|
423
|
+
return { lines: formatJsonLines(tree) };
|
|
424
|
+
}
|
|
425
|
+
case 'event': {
|
|
426
|
+
const surfaceId = rest[0];
|
|
427
|
+
const handler = rest[1];
|
|
428
|
+
if (!surfaceId || !handler) {
|
|
429
|
+
throw new Error('Usage: event <surface-id> <handler> [args-json] [state-json]');
|
|
430
|
+
}
|
|
431
|
+
const active = getActiveSession();
|
|
432
|
+
requireWritableSession(active, active.sessionId);
|
|
433
|
+
const { handle } = active;
|
|
434
|
+
const actions = handle.eventSurface(surfaceId, handler, parseJsonArg(rest[2], {}), parseJsonArg(rest[3], {}));
|
|
435
|
+
return { lines: formatJsonLines(actions) };
|
|
436
|
+
}
|
|
437
|
+
case 'define-surface': {
|
|
438
|
+
const [surfaceId, surfaceType, code] = parseInlineAuthoringArgs(restRaw, 2);
|
|
439
|
+
if (!surfaceId || !surfaceType || !code) {
|
|
440
|
+
throw new Error('Usage: define-surface <surface-id> <surface-type> <factory-code>');
|
|
441
|
+
}
|
|
442
|
+
const active = getActiveSession();
|
|
443
|
+
requireWritableSession(active, active.sessionId);
|
|
444
|
+
const { handle, sessionId } = active;
|
|
445
|
+
const bundle = handle.defineSurface(surfaceId, code, surfaceType);
|
|
446
|
+
return {
|
|
447
|
+
lines: [
|
|
448
|
+
{ type: 'system', text: `Defined runtime surface ${surfaceId} in ${sessionId}` },
|
|
449
|
+
{ type: 'output', text: `surfaces: ${bundle.surfaces.join(', ') || 'none'}` },
|
|
450
|
+
],
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
case 'define-render': {
|
|
454
|
+
const [surfaceId, code] = parseInlineAuthoringArgs(restRaw, 1);
|
|
455
|
+
if (!surfaceId || !code) {
|
|
456
|
+
throw new Error('Usage: define-render <surface-id> <render-code>');
|
|
457
|
+
}
|
|
458
|
+
const active = getActiveSession();
|
|
459
|
+
requireWritableSession(active, active.sessionId);
|
|
460
|
+
const { handle, sessionId } = active;
|
|
461
|
+
handle.defineSurfaceRender(surfaceId, code);
|
|
462
|
+
return {
|
|
463
|
+
lines: [
|
|
464
|
+
{ type: 'system', text: `Updated render() for ${surfaceId} in ${sessionId}` },
|
|
465
|
+
],
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
case 'define-handler': {
|
|
469
|
+
const [surfaceId, handlerName, code] = parseInlineAuthoringArgs(restRaw, 2);
|
|
470
|
+
if (!surfaceId || !handlerName || !code) {
|
|
471
|
+
throw new Error('Usage: define-handler <surface-id> <handler-name> <handler-code>');
|
|
472
|
+
}
|
|
473
|
+
const active = getActiveSession();
|
|
474
|
+
requireWritableSession(active, active.sessionId);
|
|
475
|
+
const { handle, sessionId } = active;
|
|
476
|
+
handle.defineSurfaceHandler(surfaceId, handlerName, code);
|
|
477
|
+
return {
|
|
478
|
+
lines: [
|
|
479
|
+
{ type: 'system', text: `Updated handler ${surfaceId}.${handlerName} in ${sessionId}` },
|
|
480
|
+
],
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
case 'open-surface': {
|
|
484
|
+
const surfaceId = rest[0];
|
|
485
|
+
if (!surfaceId) {
|
|
486
|
+
throw new Error('Usage: open-surface <surface-id> [session-id]');
|
|
487
|
+
}
|
|
488
|
+
const active = getActiveSession(rest[1]);
|
|
489
|
+
const { handle, sessionId } = active;
|
|
490
|
+
return {
|
|
491
|
+
lines: [
|
|
492
|
+
{
|
|
493
|
+
type: 'system',
|
|
494
|
+
text: `Requested runtime surface window for ${sessionId}:${surfaceId}${active.summary.writable ? '' : ' (read-only attached view)'}`,
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
effects: [
|
|
498
|
+
{
|
|
499
|
+
type: 'open-window',
|
|
500
|
+
payload: {
|
|
501
|
+
kind: 'runtime-surface',
|
|
502
|
+
sessionId,
|
|
503
|
+
stackId: handle.stackId,
|
|
504
|
+
surfaceId,
|
|
505
|
+
title: `${handle.stackId}:${surfaceId}`,
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
default:
|
|
512
|
+
throw new Error(`Unknown HyperCard REPL command: ${command}`);
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
getCompletions(input) {
|
|
516
|
+
const trimmed = input.trimStart();
|
|
517
|
+
const tokens = trimmed.split(/\s+/).filter(Boolean);
|
|
518
|
+
if (tokens.length === 0) {
|
|
519
|
+
return Object.values(COMMAND_HELP).map((entry) => ({
|
|
520
|
+
value: entry.title,
|
|
521
|
+
detail: entry.detail,
|
|
522
|
+
}));
|
|
523
|
+
}
|
|
524
|
+
if (tokens.length === 1 && !trimmed.endsWith(' ')) {
|
|
525
|
+
return Object.values(COMMAND_HELP)
|
|
526
|
+
.filter((entry) => entry.title.startsWith(tokens[0]))
|
|
527
|
+
.map((entry) => ({ value: entry.title, detail: entry.detail }));
|
|
528
|
+
}
|
|
529
|
+
switch (tokens[0]) {
|
|
530
|
+
case 'spawn':
|
|
531
|
+
return Array.from(bundleLibrary.values())
|
|
532
|
+
.filter((bundle) => bundle.key.startsWith(tokens[1] ?? ''))
|
|
533
|
+
.map((bundle) => ({
|
|
534
|
+
value: bundle.key,
|
|
535
|
+
detail: bundle.title ?? bundle.stackId,
|
|
536
|
+
}));
|
|
537
|
+
case 'use':
|
|
538
|
+
case 'attach':
|
|
539
|
+
return collectSessions(broker)
|
|
540
|
+
.filter((session) => session.sessionId.startsWith(tokens[1] ?? ''))
|
|
541
|
+
.map((session) => ({
|
|
542
|
+
value: session.sessionId,
|
|
543
|
+
detail: `${session.stackId}${session.writable ? '' : ' (read-only)'}`,
|
|
544
|
+
}));
|
|
545
|
+
case 'render':
|
|
546
|
+
case 'event':
|
|
547
|
+
case 'define-render':
|
|
548
|
+
case 'define-handler':
|
|
549
|
+
case 'open-surface': {
|
|
550
|
+
const session = activeSessionId ? getSessionRecord(activeSessionId) : null;
|
|
551
|
+
const partial = tokens[1] ?? '';
|
|
552
|
+
return session
|
|
553
|
+
? session
|
|
554
|
+
.handle
|
|
555
|
+
.getBundleMeta()
|
|
556
|
+
.surfaces.filter((surfaceId) => surfaceId.startsWith(partial))
|
|
557
|
+
.map((surfaceId) => ({
|
|
558
|
+
value: surfaceId,
|
|
559
|
+
detail: session.handle.getBundleMeta().surfaceTypes?.[surfaceId] ?? 'ui.card.v1',
|
|
560
|
+
}))
|
|
561
|
+
: [];
|
|
562
|
+
}
|
|
563
|
+
case 'define-surface':
|
|
564
|
+
return listRuntimeSurfaceTypes()
|
|
565
|
+
.filter((surfaceType) => surfaceType.startsWith(tokens[2] ?? ''))
|
|
566
|
+
.map((surfaceType) => ({
|
|
567
|
+
value: surfaceType,
|
|
568
|
+
detail: 'registered runtime surface type',
|
|
569
|
+
}));
|
|
570
|
+
case 'help':
|
|
571
|
+
return helpForTopic(null, bundleLibrary)?.map((entry) => ({
|
|
572
|
+
value: entry.title,
|
|
573
|
+
detail: entry.detail,
|
|
574
|
+
})) ?? [];
|
|
575
|
+
default:
|
|
576
|
+
const lastToken = tokens.length > 0 ? tokens[tokens.length - 1] ?? '' : '';
|
|
577
|
+
return [
|
|
578
|
+
...listPackageCompletionItems(),
|
|
579
|
+
...listDocEntries().map((entry) => ({
|
|
580
|
+
value: entry.title,
|
|
581
|
+
detail: entry.detail,
|
|
582
|
+
})),
|
|
583
|
+
...listBundleDocEntries(bundleLibrary).map((entry) => ({
|
|
584
|
+
value: entry.title,
|
|
585
|
+
detail: entry.detail,
|
|
586
|
+
})),
|
|
587
|
+
].filter((entry) => entry.value.startsWith(lastToken));
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
getHelp(topic) {
|
|
591
|
+
return helpForTopic(topic, bundleLibrary);
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
export function getRuntimePackageDocsMetadata(packageId) {
|
|
596
|
+
return getRuntimePackageOrThrow(packageId).docsMetadata ?? null;
|
|
597
|
+
}
|
|
598
|
+
export function getHypercardReplDriverState(driver) {
|
|
599
|
+
return asRecord(driver);
|
|
600
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ReplDriver } from '@go-go-golems/os-repl';
|
|
2
|
+
import { type JsSessionBroker } from './jsSessionBroker';
|
|
3
|
+
export interface JsReplDriverOptions {
|
|
4
|
+
broker?: JsSessionBroker;
|
|
5
|
+
initialSessionId?: string | null;
|
|
6
|
+
initialOrigin?: 'spawned' | 'attached-runtime' | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function createJsReplDriver(options?: JsReplDriverOptions): ReplDriver;
|