@nikelyh/offscript 0.0.0-stage → 1.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/LICENSE +201 -0
- package/README.md +183 -2
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/dist/packages/application/assign-investigation/execute.js +34 -0
- package/dist/packages/application/assign-investigation/ports.js +1 -0
- package/dist/packages/application/assign-investigation/resolve-context.js +41 -0
- package/dist/packages/application/check-target.js +4 -0
- package/dist/packages/application/complete-investigation/execute.js +54 -0
- package/dist/packages/application/complete-investigation/ports.js +1 -0
- package/dist/packages/application/complete-investigation/resolve-context.js +48 -0
- package/dist/packages/application/configure-simulation/execute.js +29 -0
- package/dist/packages/application/configure-simulation/ports.js +1 -0
- package/dist/packages/application/configure-simulation/profiles.js +16 -0
- package/dist/packages/application/delete-configured-simulation.js +11 -0
- package/dist/packages/application/errors.js +8 -0
- package/dist/packages/application/index.js +11 -0
- package/dist/packages/application/outbox/dispatch.js +48 -0
- package/dist/packages/application/outbox/ports.js +16 -0
- package/dist/packages/application/recover-simulation/execute.js +31 -0
- package/dist/packages/application/recover-simulation/ports.js +1 -0
- package/dist/packages/application/request-investigation/execute.js +38 -0
- package/dist/packages/application/request-investigation/ports.js +1 -0
- package/dist/packages/application/request-investigation/resolve-context.js +41 -0
- package/dist/packages/browser/browser-manager.js +91 -0
- package/dist/packages/browser/dom-snapshot.js +186 -0
- package/dist/packages/browser/index.js +5 -0
- package/dist/packages/browser/scope-guard.js +73 -0
- package/dist/packages/browser/session.js +209 -0
- package/dist/packages/browser/tools/browser-tools.js +192 -0
- package/dist/packages/browser/types.js +1 -0
- package/dist/packages/contracts/errors.js +8 -0
- package/dist/packages/contracts/event-envelope.js +28 -0
- package/dist/packages/contracts/event-validation.js +44 -0
- package/dist/packages/contracts/events.js +1 -0
- package/dist/packages/contracts/finding-assessment.js +128 -0
- package/dist/packages/contracts/index.js +15 -0
- package/dist/packages/contracts/investigation-events.js +43 -0
- package/dist/packages/contracts/investigation-summary.js +60 -0
- package/dist/packages/contracts/mappers.js +17 -0
- package/dist/packages/contracts/observation-summary.js +25 -0
- package/dist/packages/contracts/semantic-events.js +41 -0
- package/dist/packages/contracts/serialization.js +18 -0
- package/dist/packages/contracts/simulation-configuration.js +17 -0
- package/dist/packages/contracts/simulation-execution.js +62 -0
- package/dist/packages/contracts/simulation-results.js +52 -0
- package/dist/packages/contracts/simulation-setup.js +60 -0
- package/dist/packages/contracts/summaries.js +42 -0
- package/dist/packages/contracts/target-connection.js +36 -0
- package/dist/packages/contracts/validation.js +42 -0
- package/dist/packages/domain/agent.js +23 -0
- package/dist/packages/domain/environment.js +1 -0
- package/dist/packages/domain/errors.js +14 -0
- package/dist/packages/domain/identity.js +16 -0
- package/dist/packages/domain/index.js +10 -0
- package/dist/packages/domain/investigation-result.js +19 -0
- package/dist/packages/domain/investigation.js +70 -0
- package/dist/packages/domain/observation.js +19 -0
- package/dist/packages/domain/profile.js +19 -0
- package/dist/packages/domain/simulation.js +61 -0
- package/dist/packages/domain/timestamp.js +9 -0
- package/dist/packages/orchestration/browser-exploration-factory.js +135 -0
- package/dist/packages/orchestration/controlled-demo-factory.js +26 -0
- package/dist/packages/orchestration/deterministic-browser-runner.js +92 -0
- package/dist/packages/orchestration/deterministic-runner.js +69 -0
- package/dist/packages/orchestration/event-bridge.js +27 -0
- package/dist/packages/orchestration/guard-inference.js +44 -0
- package/dist/packages/orchestration/index.js +7 -0
- package/dist/packages/orchestration/investigation-reaction.js +53 -0
- package/dist/packages/orchestration/investigation-store.js +96 -0
- package/dist/packages/orchestration/memory-outbox.js +63 -0
- package/dist/packages/orchestration/multi-mode-factory.js +12 -0
- package/dist/packages/orchestration/outbox-publisher.js +13 -0
- package/dist/packages/orchestration/session-storage.js +52 -0
- package/dist/packages/orchestration/session.js +176 -0
- package/dist/packages/orchestration/situations.js +26 -0
- package/dist/packages/orchestration/state.js +32 -0
- package/dist/packages/orchestration/tools/arguments.js +13 -0
- package/dist/packages/orchestration/tools/browser-exploration-tools.js +154 -0
- package/dist/packages/orchestration/tools/browser-interaction-tools.js +74 -0
- package/dist/packages/orchestration/tools/exploration-tools.js +119 -0
- package/dist/packages/persistence/codecs/investigation.js +30 -0
- package/dist/packages/persistence/codecs/simulation.js +69 -0
- package/dist/packages/persistence/codecs/values.js +24 -0
- package/dist/packages/persistence/connection.js +29 -0
- package/dist/packages/persistence/errors.js +8 -0
- package/dist/packages/persistence/index.js +22 -0
- package/dist/packages/persistence/migrate.js +40 -0
- package/dist/packages/persistence/migrations/0000_initial_domain_storage.sql +92 -0
- package/dist/packages/persistence/migrations/0001_transactional_outbox.sql +26 -0
- package/dist/packages/persistence/migrations/0002_simulation_configuration.sql +7 -0
- package/dist/packages/persistence/migrations/meta/0000_snapshot.json +637 -0
- package/dist/packages/persistence/migrations/meta/0001_snapshot.json +812 -0
- package/dist/packages/persistence/migrations/meta/0002_snapshot.json +859 -0
- package/dist/packages/persistence/migrations/meta/_journal.json +27 -0
- package/dist/packages/persistence/outbox/committer.js +54 -0
- package/dist/packages/persistence/outbox/store.js +66 -0
- package/dist/packages/persistence/repositories/configurations.js +41 -0
- package/dist/packages/persistence/repositories/delete-configuration.js +20 -0
- package/dist/packages/persistence/repositories/investigation-writes.js +72 -0
- package/dist/packages/persistence/repositories/investigations.js +27 -0
- package/dist/packages/persistence/repositories/observations.js +36 -0
- package/dist/packages/persistence/repositories/simulation-writes.js +39 -0
- package/dist/packages/persistence/repositories/simulations.js +23 -0
- package/dist/packages/persistence/schema/configurations.js +10 -0
- package/dist/packages/persistence/schema/index.js +5 -0
- package/dist/packages/persistence/schema/investigations.js +53 -0
- package/dist/packages/persistence/schema/observations.js +17 -0
- package/dist/packages/persistence/schema/outbox.js +26 -0
- package/dist/packages/persistence/schema/simulations.js +29 -0
- package/dist/packages/projects/index.js +2 -0
- package/dist/packages/projects/inspect.js +45 -0
- package/dist/packages/projects/suggest-target.js +34 -0
- package/dist/packages/projects/target-probe.js +61 -0
- package/dist/packages/runtime/deadline.js +11 -0
- package/dist/packages/runtime/evidence-store.js +76 -0
- package/dist/packages/runtime/execution-coordinator.js +223 -0
- package/dist/packages/runtime/execution-settlement.js +50 -0
- package/dist/packages/runtime/execution-types.js +1 -0
- package/dist/packages/runtime/host-config.js +26 -0
- package/dist/packages/runtime/host-storage.js +46 -0
- package/dist/packages/runtime/host.js +2 -0
- package/dist/packages/runtime/index.js +1 -0
- package/dist/packages/runtime/local-host.js +280 -0
- package/dist/packages/runtime/outbox-dispatcher.js +61 -0
- package/dist/packages/runtime/session-registry.js +42 -0
- package/dist/packages/server/configuration-routes.js +85 -0
- package/dist/packages/server/contracts.js +13 -0
- package/dist/packages/server/index.js +2 -0
- package/dist/packages/server/security.js +38 -0
- package/dist/packages/server/server.js +44 -0
- package/dist/packages/server/studio-assets.js +44 -0
- package/dist/packages/server/studio-session.js +28 -0
- package/dist/scripts/server/access-file.js +21 -0
- package/dist/scripts/server/cli-results.js +126 -0
- package/dist/scripts/server/open-browser.js +13 -0
- package/dist/scripts/server/run.js +76 -0
- package/dist/scripts/server/startup-error.js +17 -0
- package/dist/scripts/server/studio-entry-file.js +22 -0
- package/dist/studio/assets/index-DMnHlGCr.js +9 -0
- package/dist/studio/assets/index-Dh5KHl3J.css +1 -0
- package/dist/studio/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/studio/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/studio/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/studio/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/studio/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/studio/assets/space-grotesk-latin-ext-wght-normal-D9tNdqV9.woff2 +0 -0
- package/dist/studio/assets/space-grotesk-latin-wght-normal-BhU9QXUp.woff2 +0 -0
- package/dist/studio/assets/space-grotesk-vietnamese-wght-normal-D0rl6rjA.woff2 +0 -0
- package/dist/studio/brand/logo-perspectives.svg +1 -0
- package/dist/studio/favicon.svg +1 -0
- package/dist/studio/index.html +14 -0
- package/dist/studio/licenses/jetbrains-mono.txt +93 -0
- package/dist/studio/licenses/space-grotesk.txt +93 -0
- package/dist/studio/participants/badger.png +0 -0
- package/dist/studio/participants/compass.png +0 -0
- package/dist/studio/participants/fox.png +0 -0
- package/dist/studio/participants/owl.png +0 -0
- package/dist/studio/participants/raven.png +0 -0
- package/dist/studio/participants/sparrow.png +0 -0
- package/offscript.mjs +178 -0
- package/package.json +100 -4
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export function createHostAccessFile(directory, access) {
|
|
5
|
+
const path = join(directory, `.offscript-host-${process.pid}-${randomUUID()}.json`);
|
|
6
|
+
const content = JSON.stringify({ origin: access.origin, token: access.token }, null, 2) + "\n";
|
|
7
|
+
writeFileSync(path, content, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
8
|
+
return Object.freeze({ path,
|
|
9
|
+
remove() {
|
|
10
|
+
try {
|
|
11
|
+
if (readFileSync(path, "utf8") !== content)
|
|
12
|
+
throw new Error("Host access file was modified; it was preserved.");
|
|
13
|
+
unlinkSync(path);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { simulationId } from "../../packages/domain/index.js";
|
|
2
|
+
import { openPersistence } from "../../packages/persistence/index.js";
|
|
3
|
+
import { evaluateFindingAssessment } from "../../packages/contracts/index.js";
|
|
4
|
+
import { executeRecoverSimulation } from "../../packages/application/index.js";
|
|
5
|
+
export async function executeListCommand(options) {
|
|
6
|
+
const persistence = openPersistence({
|
|
7
|
+
projectRoot: options.projectRoot,
|
|
8
|
+
...(options.dataDirectory !== undefined ? { dataDirectory: options.dataDirectory } : {}),
|
|
9
|
+
});
|
|
10
|
+
try {
|
|
11
|
+
persistence.migrate();
|
|
12
|
+
const list = await persistence.configurations.listConfigurations();
|
|
13
|
+
if (list.length === 0) {
|
|
14
|
+
console.log("No saved simulations found in this project.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
console.log(`Saved simulations (${list.length}):\n`);
|
|
18
|
+
for (const sim of list) {
|
|
19
|
+
console.log(`• ${sim.name} (${sim.simulationId})`);
|
|
20
|
+
console.log(` Target: ${sim.targetUrl}`);
|
|
21
|
+
console.log(` Goal: ${sim.goal.split("\n")[0]}`);
|
|
22
|
+
console.log(` Users: ${sim.userProfileIds.join(", ")}`);
|
|
23
|
+
console.log("");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
persistence.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function executeResultsCommand(options) {
|
|
31
|
+
const persistence = openPersistence({
|
|
32
|
+
projectRoot: options.projectRoot,
|
|
33
|
+
...(options.dataDirectory !== undefined ? { dataDirectory: options.dataDirectory } : {}),
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
persistence.migrate();
|
|
37
|
+
const simId = simulationId(options.simulationId);
|
|
38
|
+
const configuration = await persistence.configurations.findConfiguration(simId);
|
|
39
|
+
if (!configuration) {
|
|
40
|
+
console.error(`OFFSCRIPT: Simulation not found: ${options.simulationId}`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const observations = await persistence.observations.listObservations(simId);
|
|
45
|
+
const investigations = await persistence.investigations.listInvestigations(simId);
|
|
46
|
+
const completed = investigations.filter((inv) => inv.lifecycle.status === "completed");
|
|
47
|
+
const findings = completed.map((inv) => evaluateFindingAssessment(inv, observations));
|
|
48
|
+
console.log(`Simulation: ${configuration.name} (${configuration.simulationId})`);
|
|
49
|
+
console.log(`Target: ${configuration.targetUrl}`);
|
|
50
|
+
console.log(`Goal: ${configuration.goal}\n`);
|
|
51
|
+
console.log(`--- Findings (${findings.length}) ---`);
|
|
52
|
+
if (findings.length === 0) {
|
|
53
|
+
console.log(" No completed findings recorded.");
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
for (const finding of findings) {
|
|
57
|
+
const badge = finding.isConfirmedFinding
|
|
58
|
+
? "[CONFIRMED FINDING]"
|
|
59
|
+
: finding.confidence === "provisional"
|
|
60
|
+
? "[PROVISIONAL]"
|
|
61
|
+
: finding.confidence === "refuted"
|
|
62
|
+
? "[REFUTED]"
|
|
63
|
+
: "[INCONCLUSIVE]";
|
|
64
|
+
console.log(` ${badge} ${finding.hypothesis}`);
|
|
65
|
+
console.log(` Outcome: ${finding.outcome} (${finding.confidence})`);
|
|
66
|
+
console.log(` Explanation: ${finding.explanation}`);
|
|
67
|
+
if (finding.uncertaintyReason) {
|
|
68
|
+
console.log(` Uncertainty: ${finding.uncertaintyReason}`);
|
|
69
|
+
}
|
|
70
|
+
console.log(` Participants: ${finding.participatingAgents.join(", ")}`);
|
|
71
|
+
if (finding.evidenceIds.length > 0) {
|
|
72
|
+
console.log(` Evidence: ${finding.evidenceIds.join(", ")}`);
|
|
73
|
+
}
|
|
74
|
+
console.log("");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
console.log(`--- Observations (${observations.length}) ---`);
|
|
78
|
+
if (observations.length === 0) {
|
|
79
|
+
console.log(" No observations recorded.");
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
for (const obs of observations) {
|
|
83
|
+
console.log(` • [${obs.id}] by ${obs.agentId} at ${obs.context.location}`);
|
|
84
|
+
console.log(` ${obs.description}`);
|
|
85
|
+
if (obs.evidenceIds.length > 0) {
|
|
86
|
+
console.log(` Evidence: ${obs.evidenceIds.join(", ")}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
console.log("");
|
|
90
|
+
}
|
|
91
|
+
console.log(`--- Investigations (${investigations.length}) ---`);
|
|
92
|
+
if (investigations.length === 0) {
|
|
93
|
+
console.log(" No investigations requested.");
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
for (const inv of investigations) {
|
|
97
|
+
const status = inv.lifecycle.status;
|
|
98
|
+
console.log(` • [${inv.id}] (${status}) "${inv.hypothesis}"`);
|
|
99
|
+
console.log(` Requested by: ${inv.requestedBy}`);
|
|
100
|
+
if (status !== "requested") {
|
|
101
|
+
console.log(` Assigned: ${inv.lifecycle.agentIds.join(", ")}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
console.log("");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
persistence.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export async function executeRecoverCommand(options) {
|
|
112
|
+
const persistence = openPersistence({
|
|
113
|
+
projectRoot: options.projectRoot,
|
|
114
|
+
...(options.dataDirectory !== undefined ? { dataDirectory: options.dataDirectory } : {}),
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
persistence.migrate();
|
|
118
|
+
const simId = simulationId(options.simulationId);
|
|
119
|
+
const action = options.cancel ? "cancel" : "fail";
|
|
120
|
+
const result = await executeRecoverSimulation({ simulationId: simId, action, reason: "INTERRUPTED_BY_HOST_RESTART" }, persistence.simulations);
|
|
121
|
+
console.log(`Simulation ${options.simulationId} recovered to ${result.simulation.lifecycle.status}.`);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
persistence.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
/** Launch a local entry without interpreting paths as shell commands. */
|
|
4
|
+
export function openBrowser(path, platform = process.platform, launch = spawn) {
|
|
5
|
+
const url = pathToFileURL(path).href;
|
|
6
|
+
const command = platform === "win32" ? "rundll32.exe" : platform === "darwin" ? "open" : "xdg-open";
|
|
7
|
+
const args = platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const child = launch(command, args, { shell: false, windowsHide: true, stdio: "ignore" });
|
|
10
|
+
child.once("error", reject);
|
|
11
|
+
child.once("exit", code => code === 0 ? resolve() : reject(new Error("Browser launcher failed.")));
|
|
12
|
+
});
|
|
13
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createLocalHost } from "../../packages/runtime/host.js";
|
|
2
|
+
import { createMultiModeSessionFactory } from "../../packages/orchestration/index.js";
|
|
3
|
+
import { createHostAccessFile } from "./access-file.js";
|
|
4
|
+
import { createStudioEntryFile } from "./studio-entry-file.js";
|
|
5
|
+
import { openBrowser } from "./open-browser.js";
|
|
6
|
+
import { startupError } from "./startup-error.js";
|
|
7
|
+
process.env.MOZAIK_API_KEY ??= "";
|
|
8
|
+
let requestStop = () => { };
|
|
9
|
+
const stopping = new Promise(resolve => { requestStop = resolve; });
|
|
10
|
+
let interrupted = false;
|
|
11
|
+
function stop() { interrupted = true; requestStop(); }
|
|
12
|
+
process.once("SIGINT", stop);
|
|
13
|
+
process.once("SIGTERM", stop);
|
|
14
|
+
let host;
|
|
15
|
+
let access;
|
|
16
|
+
let studioEntry;
|
|
17
|
+
try {
|
|
18
|
+
const portText = process.env.OFFSCRIPT_PORT;
|
|
19
|
+
if (portText !== undefined && !/^\d+$/.test(portText))
|
|
20
|
+
throw new Error("Invalid port configuration.");
|
|
21
|
+
host = createLocalHost({ projectRoot: process.env.OFFSCRIPT_PROJECT_ROOT ?? process.cwd(),
|
|
22
|
+
sessionFactory: createMultiModeSessionFactory(),
|
|
23
|
+
...(process.env.OFFSCRIPT_STUDIO_DIRECTORY ? { studioDirectory: process.env.OFFSCRIPT_STUDIO_DIRECTORY } : {}),
|
|
24
|
+
...(process.env.OFFSCRIPT_DATA_DIRECTORY === undefined ? {} : { dataDirectory: process.env.OFFSCRIPT_DATA_DIRECTORY }),
|
|
25
|
+
...(portText === undefined ? {} : { port: Number(portText) }),
|
|
26
|
+
onDiagnostic(code) { console.error(code); process.exitCode = 1; stop(); },
|
|
27
|
+
});
|
|
28
|
+
const origin = await host.start();
|
|
29
|
+
if (!interrupted) {
|
|
30
|
+
access = createHostAccessFile(host.config.dataDirectory, host.credentials());
|
|
31
|
+
console.log(`OFFSCRIPT host: ${origin}`);
|
|
32
|
+
console.log(`Local access credentials: ${access.path}`);
|
|
33
|
+
if (process.env.OFFSCRIPT_STUDIO_DIRECTORY) {
|
|
34
|
+
studioEntry = createStudioEntryFile(host.config.dataDirectory, host.studioEntry());
|
|
35
|
+
console.log(`Open this local file in your browser to enter Studio: ${studioEntry.path}`);
|
|
36
|
+
console.log("The entry is single-use and expires in five minutes. Refreshing Studio keeps the browser session.");
|
|
37
|
+
if (process.env.OFFSCRIPT_OPEN_BROWSER === "1") {
|
|
38
|
+
void openBrowser(studioEntry.path).catch(() => {
|
|
39
|
+
if (!interrupted)
|
|
40
|
+
console.error("Could not open a browser. Open the printed local HTML entry manually; the host is still running.");
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
console.log("Press Ctrl+C to stop. No exploration starts automatically.");
|
|
45
|
+
await stopping;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error(`OFFSCRIPT: ${startupError(error)}`);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
try {
|
|
54
|
+
await host?.close();
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
console.error("OFFSCRIPT host shutdown did not complete cleanly.");
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
access?.remove();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
console.error("OFFSCRIPT host access file could not be removed.");
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
studioEntry?.remove();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
console.error("Studio entry file could not be removed.");
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
}
|
|
74
|
+
process.off("SIGINT", stop);
|
|
75
|
+
process.off("SIGTERM", stop);
|
|
76
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Report actionable categories without exposing credentials or exception contents. */
|
|
2
|
+
export function startupError(error) {
|
|
3
|
+
const seen = new Set();
|
|
4
|
+
let current = error;
|
|
5
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
6
|
+
seen.add(current);
|
|
7
|
+
const code = "code" in current ? current.code : undefined;
|
|
8
|
+
if (code === "EADDRINUSE")
|
|
9
|
+
return "Port is already in use. Choose --port 0 or another port.";
|
|
10
|
+
if (code === "EACCES" || code === "EPERM")
|
|
11
|
+
return "Access denied. Check project/storage permissions and the selected port.";
|
|
12
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
13
|
+
return "Project, storage, or distribution path is unavailable. Check the selected directories.";
|
|
14
|
+
current = current.cause;
|
|
15
|
+
}
|
|
16
|
+
return "Host could not start. Check project, port, storage, and installed distribution compatibility.";
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
export function createStudioEntryFile(directory, access) {
|
|
5
|
+
const path = join(directory, `.offscript-studio-${process.pid}-${randomUUID()}.html`);
|
|
6
|
+
const escape = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<").replaceAll(">", ">");
|
|
7
|
+
const content = `<!doctype html><html lang="en"><meta charset="utf-8"><meta name="referrer" content="no-referrer"><title>Open OFFSCRIPT Studio</title>
|
|
8
|
+
<form method="post" action="${escape(access.origin)}/studio/session"><input type="hidden" name="entry" value="${escape(access.entry)}"><button>Open OFFSCRIPT Studio</button></form>
|
|
9
|
+
<script>document.querySelector('form').submit()</script></html>`;
|
|
10
|
+
writeFileSync(path, content, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
11
|
+
return Object.freeze({ path, remove() {
|
|
12
|
+
try {
|
|
13
|
+
if (readFileSync(path, "utf8") !== content)
|
|
14
|
+
throw new Error("Studio entry file changed; it was preserved.");
|
|
15
|
+
unlinkSync(path);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
} });
|
|
22
|
+
}
|