@docsxai/engine 0.2.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 +202 -0
- package/README.md +130 -0
- package/dist/auth/api-login.d.ts +69 -0
- package/dist/auth/api-login.js +95 -0
- package/dist/auth/browser-session.d.ts +28 -0
- package/dist/auth/browser-session.js +43 -0
- package/dist/auth/cookie-jar.d.ts +58 -0
- package/dist/auth/cookie-jar.js +212 -0
- package/dist/auth/email-otp.d.ts +210 -0
- package/dist/auth/email-otp.js +166 -0
- package/dist/auth/http-basic.d.ts +5 -0
- package/dist/auth/http-basic.js +17 -0
- package/dist/auth/index.d.ts +47 -0
- package/dist/auth/index.js +137 -0
- package/dist/auth/jwt-injection.d.ts +153 -0
- package/dist/auth/jwt-injection.js +136 -0
- package/dist/auth/manual-capture.d.ts +35 -0
- package/dist/auth/manual-capture.js +30 -0
- package/dist/auth/mtls.d.ts +15 -0
- package/dist/auth/mtls.js +53 -0
- package/dist/auth/pat-header.d.ts +19 -0
- package/dist/auth/pat-header.js +34 -0
- package/dist/auth/storage-state-cache.d.ts +38 -0
- package/dist/auth/storage-state-cache.js +143 -0
- package/dist/auth/test-backdoor.d.ts +25 -0
- package/dist/auth/test-backdoor.js +51 -0
- package/dist/auth/totp.d.ts +39 -0
- package/dist/auth/totp.js +108 -0
- package/dist/auth/types.d.ts +86 -0
- package/dist/auth/types.js +57 -0
- package/dist/auth/ui-form.d.ts +204 -0
- package/dist/auth/ui-form.js +153 -0
- package/dist/auth/webauthn.d.ts +88 -0
- package/dist/auth/webauthn.js +67 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +3 -0
- package/dist/backend-client-contracts.d.ts +88 -0
- package/dist/backend-client-contracts.js +19 -0
- package/dist/backend-client-oauth-login.d.ts +7 -0
- package/dist/backend-client-oauth-login.js +90 -0
- package/dist/backend-client-state-cache.d.ts +73 -0
- package/dist/backend-client-state-cache.js +185 -0
- package/dist/backend-client-token.d.ts +18 -0
- package/dist/backend-client-token.js +94 -0
- package/dist/backend-client-transport.d.ts +66 -0
- package/dist/backend-client-transport.js +181 -0
- package/dist/backend-client.d.ts +5 -0
- package/dist/backend-client.js +18 -0
- package/dist/calibrate.d.ts +31 -0
- package/dist/calibrate.js +68 -0
- package/dist/cli-commands-authoring.d.ts +5 -0
- package/dist/cli-commands-authoring.js +403 -0
- package/dist/cli-commands-backend.d.ts +5 -0
- package/dist/cli-commands-backend.js +211 -0
- package/dist/cli-commands-docpack.d.ts +5 -0
- package/dist/cli-commands-docpack.js +280 -0
- package/dist/cli-commands-session.d.ts +4 -0
- package/dist/cli-commands-session.js +398 -0
- package/dist/cli-shared.d.ts +5 -0
- package/dist/cli-shared.js +45 -0
- package/dist/cli-usage.d.ts +1 -0
- package/dist/cli-usage.js +137 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +77 -0
- package/dist/diagnose.d.ts +50 -0
- package/dist/diagnose.js +168 -0
- package/dist/diff-compute.d.ts +13 -0
- package/dist/diff-compute.js +378 -0
- package/dist/diff-report.d.ts +7 -0
- package/dist/diff-report.js +125 -0
- package/dist/diff-types.d.ts +125 -0
- package/dist/diff-types.js +15 -0
- package/dist/diff.d.ts +3 -0
- package/dist/diff.js +16 -0
- package/dist/doc-pack-io.d.ts +30 -0
- package/dist/doc-pack-io.js +182 -0
- package/dist/doc-pack.d.ts +1814 -0
- package/dist/doc-pack.js +328 -0
- package/dist/doctor-checks-plugins.d.ts +2 -0
- package/dist/doctor-checks-plugins.js +136 -0
- package/dist/doctor-checks.d.ts +56 -0
- package/dist/doctor-checks.js +367 -0
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +62 -0
- package/dist/export/adf.d.ts +57 -0
- package/dist/export/adf.js +323 -0
- package/dist/export/playwright-test.d.ts +26 -0
- package/dist/export/playwright-test.js +221 -0
- package/dist/flow-file.d.ts +21 -0
- package/dist/flow-file.js +180 -0
- package/dist/flow-lint.d.ts +24 -0
- package/dist/flow-lint.js +203 -0
- package/dist/flow-runtime.d.ts +113 -0
- package/dist/flow-runtime.js +273 -0
- package/dist/flow-tree.d.ts +19 -0
- package/dist/flow-tree.js +104 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +31 -0
- package/dist/playwright-driver.d.ts +105 -0
- package/dist/playwright-driver.js +363 -0
- package/dist/playwright-instrumented-browser.d.ts +51 -0
- package/dist/playwright-instrumented-browser.js +189 -0
- package/dist/plugins/load.d.ts +22 -0
- package/dist/plugins/load.js +99 -0
- package/dist/plugins/lock.d.ts +40 -0
- package/dist/plugins/lock.js +122 -0
- package/dist/plugins/manifest.d.ts +70 -0
- package/dist/plugins/manifest.js +115 -0
- package/dist/plugins/plan.d.ts +51 -0
- package/dist/plugins/plan.js +279 -0
- package/dist/plugins/registry.d.ts +59 -0
- package/dist/plugins/registry.js +71 -0
- package/dist/plugins/runtime.d.ts +7 -0
- package/dist/plugins/runtime.js +27 -0
- package/dist/plugins/types.d.ts +58 -0
- package/dist/plugins/types.js +4 -0
- package/dist/plugins-cli.d.ts +1 -0
- package/dist/plugins-cli.js +191 -0
- package/dist/redact.d.ts +16 -0
- package/dist/redact.js +72 -0
- package/dist/style.d.ts +46 -0
- package/dist/style.js +151 -0
- package/dist/viewer-bin.d.ts +20 -0
- package/dist/viewer-bin.js +97 -0
- package/dist/workspace.d.ts +60 -0
- package/dist/workspace.js +172 -0
- package/dist/zip.d.ts +17 -0
- package/dist/zip.js +113 -0
- package/package.json +64 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// `docsxai plugins <list|info|sync>` — the operator surface over the plugin runtime.
|
|
2
|
+
//
|
|
3
|
+
// list — resolve + load the workspace's plugins; print the full status table.
|
|
4
|
+
// info — manifest + registered artifact names for one plugin (by namespace).
|
|
5
|
+
// sync — (re)write plugins-lock.json from the resolved manifests. Never executes plugin code.
|
|
6
|
+
//
|
|
7
|
+
// All three honour `--format json` for tooling.
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import { PLUGINS_LOCK_FILE, PLUGINS_LOCK_SCHEMA, PluginsConfigError, PluginsLockError, readPluginsLock, readWorkspacePluginsConfig, sha256Hex, writePluginsLock, } from "./plugins/lock.js";
|
|
10
|
+
import { parseFlags } from "./cli-shared.js";
|
|
11
|
+
import { resolvePlugins, resolvePluginSources } from "./plugins/runtime.js";
|
|
12
|
+
const PLUGINS_USAGE = `docsxai plugins — workspace plugin runtime
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
docsxai plugins list <workspace-dir> [--format text|json]
|
|
16
|
+
docsxai plugins info <workspace-dir> <namespace> [--format text|json]
|
|
17
|
+
docsxai plugins sync <workspace-dir> [--format text|json]
|
|
18
|
+
|
|
19
|
+
Notes:
|
|
20
|
+
• Plugins are declared in the workspace's .docsxai.json:
|
|
21
|
+
"plugins": [{ "package": "<npm-name>" } | { "path": "<dir>" }, …]
|
|
22
|
+
"plugin_capabilities": ["egress:<host-glob>", …]
|
|
23
|
+
• list resolves and loads the declared set, then prints every plugin's status (loaded, or the
|
|
24
|
+
disabled/error reason). Exit 1 if any declared plugin is not loaded.
|
|
25
|
+
• sync pins each plugin's register-module sha256 into ${PLUGINS_LOCK_FILE} next to the config.
|
|
26
|
+
When the lock exists, every resolve verifies the bytes before importing — a changed module
|
|
27
|
+
fails closed until you re-run sync. sync itself never executes plugin code.
|
|
28
|
+
`;
|
|
29
|
+
function parseFormat(flags) {
|
|
30
|
+
const format = typeof flags.get("format") === "string" ? flags.get("format") : "text";
|
|
31
|
+
return format === "text" || format === "json" ? format : null;
|
|
32
|
+
}
|
|
33
|
+
function formatRecordText(r, indent = " ") {
|
|
34
|
+
const id = r.namespace || r.name;
|
|
35
|
+
const kinds = r.manifest ? r.manifest.kinds.join(",") : "-";
|
|
36
|
+
let out = `${indent}${id} ${r.status} v${r.version} ${r.trust} ${kinds} ${r.source}\n`;
|
|
37
|
+
if (r.statusReason)
|
|
38
|
+
out += `${indent} ↳ ${r.statusReason}\n`;
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
async function loadRegistry(workspaceDir) {
|
|
42
|
+
const cfg = await readWorkspacePluginsConfig(workspaceDir);
|
|
43
|
+
const lock = await readPluginsLock(workspaceDir);
|
|
44
|
+
const registry = await resolvePlugins({
|
|
45
|
+
workspaceDir,
|
|
46
|
+
sources: cfg.sources,
|
|
47
|
+
enabledCapabilities: cfg.capabilities,
|
|
48
|
+
lock,
|
|
49
|
+
});
|
|
50
|
+
return { cfg, registry };
|
|
51
|
+
}
|
|
52
|
+
async function cmdList(workspaceDir, format) {
|
|
53
|
+
const { registry } = await loadRegistry(workspaceDir);
|
|
54
|
+
const records = registry.listPlugins();
|
|
55
|
+
if (format === "json") {
|
|
56
|
+
process.stdout.write(JSON.stringify(records, null, 2) + "\n");
|
|
57
|
+
}
|
|
58
|
+
else if (records.length === 0) {
|
|
59
|
+
process.stdout.write('plugins: none configured (add a "plugins" array to .docsxai.json)\n');
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const loaded = records.filter((r) => r.status === "loaded").length;
|
|
63
|
+
process.stdout.write(`plugins (${records.length} configured, ${loaded} loaded):\n`);
|
|
64
|
+
for (const r of records)
|
|
65
|
+
process.stdout.write(formatRecordText(r));
|
|
66
|
+
}
|
|
67
|
+
return records.every((r) => r.status === "loaded") ? 0 : 1;
|
|
68
|
+
}
|
|
69
|
+
async function cmdInfo(workspaceDir, namespace, format) {
|
|
70
|
+
const { registry } = await loadRegistry(workspaceDir);
|
|
71
|
+
const record = registry.pluginsInfo(namespace);
|
|
72
|
+
if (!record) {
|
|
73
|
+
const known = registry
|
|
74
|
+
.listPlugins()
|
|
75
|
+
.map((r) => r.namespace || r.name)
|
|
76
|
+
.join(", ");
|
|
77
|
+
process.stderr.write(`plugins info: no plugin "${namespace}"${known ? ` (configured: ${known})` : " (none configured)"}\n`);
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
if (format === "json") {
|
|
81
|
+
process.stdout.write(JSON.stringify(record, null, 2) + "\n");
|
|
82
|
+
return record.status === "loaded" ? 0 : 1;
|
|
83
|
+
}
|
|
84
|
+
process.stdout.write(formatRecordText(record, ""));
|
|
85
|
+
if (record.manifest) {
|
|
86
|
+
process.stdout.write(` apiVersion: ${record.manifest.apiVersion}\n`);
|
|
87
|
+
process.stdout.write(` capabilities: ${record.manifest.capabilities.join(", ") || "(none)"}\n`);
|
|
88
|
+
process.stdout.write(` dependsOn: ${record.manifest.dependsOn.map((d) => `${d.plugin}@${d.version}`).join(", ") || "(none)"}\n`);
|
|
89
|
+
}
|
|
90
|
+
process.stdout.write(` artifacts (${record.artifacts.length}):\n`);
|
|
91
|
+
for (const a of record.artifacts)
|
|
92
|
+
process.stdout.write(` ${a.kind} ${a.name}\n`);
|
|
93
|
+
return record.status === "loaded" ? 0 : 1;
|
|
94
|
+
}
|
|
95
|
+
async function cmdSync(workspaceDir, format) {
|
|
96
|
+
const cfg = await readWorkspacePluginsConfig(workspaceDir);
|
|
97
|
+
const resolutions = await resolvePluginSources(workspaceDir, cfg.sources);
|
|
98
|
+
const failures = [];
|
|
99
|
+
const lock = { schema: PLUGINS_LOCK_SCHEMA, plugins: {} };
|
|
100
|
+
for (const r of resolutions) {
|
|
101
|
+
if (!r.ok) {
|
|
102
|
+
failures.push({ source: r.record.source, reason: r.record.statusReason ?? "unresolvable" });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const ns = r.candidate.manifest.namespace;
|
|
106
|
+
if (lock.plugins[ns]) {
|
|
107
|
+
failures.push({
|
|
108
|
+
source: r.candidate.source,
|
|
109
|
+
reason: `namespace "${ns}" is claimed by more than one configured plugin — cannot lock`,
|
|
110
|
+
});
|
|
111
|
+
delete lock.plugins[ns];
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
let bytes;
|
|
115
|
+
try {
|
|
116
|
+
bytes = await fs.readFile(r.candidate.registerPath);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
failures.push({
|
|
120
|
+
source: r.candidate.source,
|
|
121
|
+
reason: `register module not found at ${r.candidate.registerPath}`,
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
lock.plugins[ns] = {
|
|
126
|
+
source: r.candidate.source,
|
|
127
|
+
version: r.candidate.version,
|
|
128
|
+
sha256: sha256Hex(bytes),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const lockPath = await writePluginsLock(workspaceDir, lock);
|
|
132
|
+
const entryCount = Object.keys(lock.plugins).length;
|
|
133
|
+
if (format === "json") {
|
|
134
|
+
process.stdout.write(JSON.stringify({ lockPath, lock, failures }, null, 2) + "\n");
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
process.stdout.write(`plugins sync: wrote ${lockPath} (${entryCount} plugin(s))\n`);
|
|
138
|
+
for (const [ns, entry] of Object.entries(lock.plugins)) {
|
|
139
|
+
process.stdout.write(` ${ns} v${entry.version} ${entry.sha256.slice(0, 12)}… ${entry.source}\n`);
|
|
140
|
+
}
|
|
141
|
+
for (const f of failures) {
|
|
142
|
+
process.stderr.write(`plugins sync: NOT locked ${f.source} — ${f.reason}\n`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return failures.length > 0 ? 1 : 0;
|
|
146
|
+
}
|
|
147
|
+
export async function pluginsCli(args) {
|
|
148
|
+
const [sub, ...rest] = args;
|
|
149
|
+
if (sub === undefined || sub === "--help" || sub === "-h" || sub === "help") {
|
|
150
|
+
process.stdout.write(PLUGINS_USAGE);
|
|
151
|
+
return sub === undefined ? 2 : 0;
|
|
152
|
+
}
|
|
153
|
+
if (sub !== "list" && sub !== "info" && sub !== "sync") {
|
|
154
|
+
process.stderr.write(`plugins: unknown subcommand "${sub}"\n\n${PLUGINS_USAGE}`);
|
|
155
|
+
return 2;
|
|
156
|
+
}
|
|
157
|
+
const { positionals, flags } = parseFlags(rest);
|
|
158
|
+
const workspaceDir = positionals[0];
|
|
159
|
+
if (!workspaceDir) {
|
|
160
|
+
process.stderr.write(`plugins ${sub}: missing <workspace-dir>\n\n${PLUGINS_USAGE}`);
|
|
161
|
+
return 2;
|
|
162
|
+
}
|
|
163
|
+
const format = parseFormat(flags);
|
|
164
|
+
if (!format) {
|
|
165
|
+
process.stderr.write(`plugins ${sub}: --format must be "text" or "json"\n`);
|
|
166
|
+
return 2;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
switch (sub) {
|
|
170
|
+
case "list":
|
|
171
|
+
return await cmdList(workspaceDir, format);
|
|
172
|
+
case "info": {
|
|
173
|
+
const namespace = positionals[1];
|
|
174
|
+
if (!namespace) {
|
|
175
|
+
process.stderr.write(`plugins info: missing <namespace>\n\n${PLUGINS_USAGE}`);
|
|
176
|
+
return 2;
|
|
177
|
+
}
|
|
178
|
+
return await cmdInfo(workspaceDir, namespace, format);
|
|
179
|
+
}
|
|
180
|
+
case "sync":
|
|
181
|
+
return await cmdSync(workspaceDir, format);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
if (e instanceof PluginsConfigError || e instanceof PluginsLockError) {
|
|
186
|
+
process.stderr.write(`plugins ${sub}: ${e.message}\n`);
|
|
187
|
+
return 1;
|
|
188
|
+
}
|
|
189
|
+
throw e;
|
|
190
|
+
}
|
|
191
|
+
}
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RedactionStyle } from "./doc-pack.js";
|
|
2
|
+
/** One rectangle to mask, in the PNG's own pixel space. */
|
|
3
|
+
export interface RedactionBox {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
style: RedactionStyle;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Apply redaction boxes to a PNG. `box` fills the rect with solid opaque black; `pixelate`
|
|
12
|
+
* replaces it with a {@link MOSAIC_TILE}-px mosaic (each tile becomes its average colour, tiles
|
|
13
|
+
* anchored at the box origin). Boxes are clamped to the image; a box that clamps to nothing is a
|
|
14
|
+
* no-op. Returns a freshly encoded PNG buffer; the input is not mutated.
|
|
15
|
+
*/
|
|
16
|
+
export declare function applyRedactions(png: Buffer, boxes: RedactionBox[]): Buffer;
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Deterministic screenshot redaction — pure pixel transforms over decoded PNGs.
|
|
2
|
+
//
|
|
3
|
+
// Boxes arrive in the screenshot's own pixel space (device pixels — selector bounding boxes are
|
|
4
|
+
// already devicePixelRatio-scaled by the driver; fixed regions are scaled the same way at capture
|
|
5
|
+
// time). Same PNG + same boxes → byte-identical output, which is what keeps redacted doc packs
|
|
6
|
+
// reproducible run-over-run.
|
|
7
|
+
import { PNG } from "pngjs";
|
|
8
|
+
const MOSAIC_TILE = 16;
|
|
9
|
+
/**
|
|
10
|
+
* Apply redaction boxes to a PNG. `box` fills the rect with solid opaque black; `pixelate`
|
|
11
|
+
* replaces it with a {@link MOSAIC_TILE}-px mosaic (each tile becomes its average colour, tiles
|
|
12
|
+
* anchored at the box origin). Boxes are clamped to the image; a box that clamps to nothing is a
|
|
13
|
+
* no-op. Returns a freshly encoded PNG buffer; the input is not mutated.
|
|
14
|
+
*/
|
|
15
|
+
export function applyRedactions(png, boxes) {
|
|
16
|
+
if (boxes.length === 0)
|
|
17
|
+
return png;
|
|
18
|
+
const img = PNG.sync.read(png);
|
|
19
|
+
for (const box of boxes) {
|
|
20
|
+
const x0 = Math.max(0, Math.floor(box.x));
|
|
21
|
+
const y0 = Math.max(0, Math.floor(box.y));
|
|
22
|
+
const x1 = Math.min(img.width, Math.ceil(box.x + box.width));
|
|
23
|
+
const y1 = Math.min(img.height, Math.ceil(box.y + box.height));
|
|
24
|
+
if (x1 <= x0 || y1 <= y0)
|
|
25
|
+
continue;
|
|
26
|
+
if (box.style === "pixelate")
|
|
27
|
+
pixelate(img, x0, y0, x1, y1);
|
|
28
|
+
else
|
|
29
|
+
fillBlack(img, x0, y0, x1, y1);
|
|
30
|
+
}
|
|
31
|
+
return PNG.sync.write(img);
|
|
32
|
+
}
|
|
33
|
+
function fillBlack(img, x0, y0, x1, y1) {
|
|
34
|
+
for (let y = y0; y < y1; y++) {
|
|
35
|
+
for (let x = x0; x < x1; x++) {
|
|
36
|
+
const i = (y * img.width + x) * 4;
|
|
37
|
+
img.data[i] = 0;
|
|
38
|
+
img.data[i + 1] = 0;
|
|
39
|
+
img.data[i + 2] = 0;
|
|
40
|
+
img.data[i + 3] = 255;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function pixelate(img, x0, y0, x1, y1) {
|
|
45
|
+
for (let ty = y0; ty < y1; ty += MOSAIC_TILE) {
|
|
46
|
+
for (let tx = x0; tx < x1; tx += MOSAIC_TILE) {
|
|
47
|
+
const tx1 = Math.min(tx + MOSAIC_TILE, x1);
|
|
48
|
+
const ty1 = Math.min(ty + MOSAIC_TILE, y1);
|
|
49
|
+
let r = 0, g = 0, b = 0, a = 0;
|
|
50
|
+
const n = (tx1 - tx) * (ty1 - ty);
|
|
51
|
+
for (let y = ty; y < ty1; y++) {
|
|
52
|
+
for (let x = tx; x < tx1; x++) {
|
|
53
|
+
const i = (y * img.width + x) * 4;
|
|
54
|
+
r += img.data[i];
|
|
55
|
+
g += img.data[i + 1];
|
|
56
|
+
b += img.data[i + 2];
|
|
57
|
+
a += img.data[i + 3];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const avg = [Math.round(r / n), Math.round(g / n), Math.round(b / n), Math.round(a / n)];
|
|
61
|
+
for (let y = ty; y < ty1; y++) {
|
|
62
|
+
for (let x = tx; x < tx1; x++) {
|
|
63
|
+
const i = (y * img.width + x) * 4;
|
|
64
|
+
img.data[i] = avg[0];
|
|
65
|
+
img.data[i + 1] = avg[1];
|
|
66
|
+
img.data[i + 2] = avg[2];
|
|
67
|
+
img.data[i + 3] = avg[3];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/dist/style.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { StyleArtifact } from "./doc-pack.js";
|
|
2
|
+
export declare class StyleError extends Error {
|
|
3
|
+
readonly cause?: unknown | undefined;
|
|
4
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
5
|
+
}
|
|
6
|
+
/** The seed style every workspace starts with — overwritable by the agent during calibration. */
|
|
7
|
+
export declare const DEFAULT_STYLE: StyleArtifact;
|
|
8
|
+
/**
|
|
9
|
+
* Regex patterns keyed by the pruning-rule category string. The style artifact's `pruning_rules`
|
|
10
|
+
* names categories from this catalogue; the lint reports any match.
|
|
11
|
+
*
|
|
12
|
+
* Each pattern is multiline-friendly. Add new categories here and reference them by string in
|
|
13
|
+
* `style.yaml`'s `pruning_rules:` — the engine reads the array, picks the matching patterns,
|
|
14
|
+
* and scans `docs/**\/*.md` for leaks.
|
|
15
|
+
*/
|
|
16
|
+
export declare const JARGON_PATTERNS: Record<string, RegExp>;
|
|
17
|
+
export interface JargonHit {
|
|
18
|
+
/** Path of the offending file, workspace-relative. */
|
|
19
|
+
file: string;
|
|
20
|
+
/** 1-based line number where the match starts. */
|
|
21
|
+
line: number;
|
|
22
|
+
/** The pruning-rule category that matched. */
|
|
23
|
+
category: string;
|
|
24
|
+
/** The literal substring that matched. */
|
|
25
|
+
snippet: string;
|
|
26
|
+
}
|
|
27
|
+
export interface StylePaths {
|
|
28
|
+
workspace: string;
|
|
29
|
+
yamlPath: string;
|
|
30
|
+
jsonPath: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function stylePathsFor(workspace: string): StylePaths;
|
|
33
|
+
/** Read + parse + validate `docs/style.yaml`. Returns null when the file doesn't exist. Throws on invalid YAML or schema mismatch. */
|
|
34
|
+
export declare function loadStyle(workspace: string): Promise<StyleArtifact | null>;
|
|
35
|
+
/** Write `docs/style.yaml` + derived `docs/style.json`. Creates `docs/` if missing. */
|
|
36
|
+
export declare function writeStyle(workspace: string, style: StyleArtifact): Promise<StylePaths>;
|
|
37
|
+
/** Init the style artifact with `DEFAULT_STYLE` if it doesn't already exist. */
|
|
38
|
+
export declare function initStyleIfAbsent(workspace: string): Promise<{
|
|
39
|
+
paths: StylePaths;
|
|
40
|
+
created: boolean;
|
|
41
|
+
}>;
|
|
42
|
+
/** Scan a single text body against the named jargon categories. Returns hits with line numbers. */
|
|
43
|
+
export declare function scanTextForJargon(text: string, file: string, categories: string[]): JargonHit[];
|
|
44
|
+
/** Scan every `<workspace>/docs/<flow>/<step>.md` user-facing write-up for jargon leakage. */
|
|
45
|
+
export declare function scanWorkspaceForJargon(workspace: string, style: StyleArtifact): Promise<JargonHit[]>;
|
|
46
|
+
export declare function formatJargonHitsText(hits: JargonHit[]): string;
|
package/dist/style.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Style artifact — load/write/derive `docs/style.yaml` + derived `docs/style.json`, plus the
|
|
2
|
+
// jargon-leak scanner that backs the semantic-reshape exit criterion (testing-jargon absent
|
|
3
|
+
// from user-facing step write-ups). The engine never re-shapes prose itself (LLM-agnostic) —
|
|
4
|
+
// the agent does that during calibration. This module is the engine's contribution: validate
|
|
5
|
+
// the schema, persist the artifact, and report when jargon leaks through.
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
9
|
+
import { StyleArtifact } from "./doc-pack.js";
|
|
10
|
+
import { resolveWorkspacePath } from "./workspace.js";
|
|
11
|
+
export class StyleError extends Error {
|
|
12
|
+
cause;
|
|
13
|
+
constructor(message, cause) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.cause = cause;
|
|
16
|
+
this.name = "StyleError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** The seed style every workspace starts with — overwritable by the agent during calibration. */
|
|
20
|
+
export const DEFAULT_STYLE = {
|
|
21
|
+
schema: "docsxai/style@1",
|
|
22
|
+
voice: {
|
|
23
|
+
tone: "concise, instructional, second-person ('you')",
|
|
24
|
+
audience: "end users (not engineers)",
|
|
25
|
+
},
|
|
26
|
+
structure: { per_step: "one short imperative sentence + a screenshot; no internal jargon" },
|
|
27
|
+
terminology: {},
|
|
28
|
+
pruning_rules: [
|
|
29
|
+
"VERIFY/EXPECT/ASSERT directives",
|
|
30
|
+
"WAIT directives",
|
|
31
|
+
"internal locator names",
|
|
32
|
+
"network-verification blocks",
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Regex patterns keyed by the pruning-rule category string. The style artifact's `pruning_rules`
|
|
37
|
+
* names categories from this catalogue; the lint reports any match.
|
|
38
|
+
*
|
|
39
|
+
* Each pattern is multiline-friendly. Add new categories here and reference them by string in
|
|
40
|
+
* `style.yaml`'s `pruning_rules:` — the engine reads the array, picks the matching patterns,
|
|
41
|
+
* and scans `docs/**\/*.md` for leaks.
|
|
42
|
+
*/
|
|
43
|
+
export const JARGON_PATTERNS = {
|
|
44
|
+
"VERIFY/EXPECT/ASSERT directives": /\b(VERIFY|EXPECT|ASSERT)\b/g,
|
|
45
|
+
"WAIT directives": /\bWAIT(?:\s+FOR)?\b/g,
|
|
46
|
+
"internal locator names": /\b(data-testid|data-test|data-cy|data-qa|querySelector|getByRole|getByText)\b/g,
|
|
47
|
+
"network-verification blocks": /\b(GET|POST|PUT|DELETE|PATCH)\s+\/(?:api|v\d)\/\S+|\bstatus[:\s]+[1-5]\d{2}\b/g,
|
|
48
|
+
};
|
|
49
|
+
export function stylePathsFor(workspace) {
|
|
50
|
+
return {
|
|
51
|
+
workspace,
|
|
52
|
+
yamlPath: resolveWorkspacePath(workspace, "docs", "style.yaml"),
|
|
53
|
+
jsonPath: resolveWorkspacePath(workspace, "docs", "style.json"),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Read + parse + validate `docs/style.yaml`. Returns null when the file doesn't exist. Throws on invalid YAML or schema mismatch. */
|
|
57
|
+
export async function loadStyle(workspace) {
|
|
58
|
+
const { yamlPath } = stylePathsFor(workspace);
|
|
59
|
+
let text;
|
|
60
|
+
try {
|
|
61
|
+
text = await fs.readFile(yamlPath, "utf8");
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
let raw;
|
|
67
|
+
try {
|
|
68
|
+
raw = parseYaml(text);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
throw new StyleError(`${yamlPath}: invalid YAML — ${e.message}`, e);
|
|
72
|
+
}
|
|
73
|
+
const parsed = StyleArtifact.safeParse(raw);
|
|
74
|
+
if (!parsed.success) {
|
|
75
|
+
throw new StyleError(`${yamlPath}: schema validation failed — ${parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
76
|
+
}
|
|
77
|
+
return parsed.data;
|
|
78
|
+
}
|
|
79
|
+
/** Write `docs/style.yaml` + derived `docs/style.json`. Creates `docs/` if missing. */
|
|
80
|
+
export async function writeStyle(workspace, style) {
|
|
81
|
+
const paths = stylePathsFor(workspace);
|
|
82
|
+
await fs.mkdir(path.dirname(paths.yamlPath), { recursive: true });
|
|
83
|
+
await fs.writeFile(paths.yamlPath, stringifyYaml(style, { lineWidth: 100 }), "utf8");
|
|
84
|
+
await fs.writeFile(paths.jsonPath, JSON.stringify(style, null, 2) + "\n", "utf8");
|
|
85
|
+
return paths;
|
|
86
|
+
}
|
|
87
|
+
/** Init the style artifact with `DEFAULT_STYLE` if it doesn't already exist. */
|
|
88
|
+
export async function initStyleIfAbsent(workspace) {
|
|
89
|
+
const paths = stylePathsFor(workspace);
|
|
90
|
+
try {
|
|
91
|
+
await fs.access(paths.yamlPath);
|
|
92
|
+
return { paths, created: false };
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
await writeStyle(workspace, DEFAULT_STYLE);
|
|
96
|
+
return { paths, created: true };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Scan a single text body against the named jargon categories. Returns hits with line numbers. */
|
|
100
|
+
export function scanTextForJargon(text, file, categories) {
|
|
101
|
+
const hits = [];
|
|
102
|
+
const lines = text.split(/\n/);
|
|
103
|
+
for (const category of categories) {
|
|
104
|
+
const pattern = JARGON_PATTERNS[category];
|
|
105
|
+
if (!pattern)
|
|
106
|
+
continue;
|
|
107
|
+
for (let i = 0; i < lines.length; i++) {
|
|
108
|
+
const line = lines[i];
|
|
109
|
+
// reset stateful flag-`g` regex each pass
|
|
110
|
+
const re = new RegExp(pattern.source, pattern.flags.replace(/g/g, "") + "g");
|
|
111
|
+
let m;
|
|
112
|
+
while ((m = re.exec(line)) !== null) {
|
|
113
|
+
hits.push({ file, line: i + 1, category, snippet: m[0] });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return hits;
|
|
118
|
+
}
|
|
119
|
+
/** Scan every `<workspace>/docs/<flow>/<step>.md` user-facing write-up for jargon leakage. */
|
|
120
|
+
export async function scanWorkspaceForJargon(workspace, style) {
|
|
121
|
+
const docsRoot = resolveWorkspacePath(workspace, "docs");
|
|
122
|
+
const flows = await fs.readdir(docsRoot, { withFileTypes: true }).catch(() => []);
|
|
123
|
+
const categories = style.pruning_rules ?? [];
|
|
124
|
+
if (categories.length === 0)
|
|
125
|
+
return [];
|
|
126
|
+
const hits = [];
|
|
127
|
+
for (const ent of flows) {
|
|
128
|
+
if (!ent.isDirectory())
|
|
129
|
+
continue;
|
|
130
|
+
const flowDir = resolveWorkspacePath(workspace, "docs", ent.name);
|
|
131
|
+
const files = await fs.readdir(flowDir).catch(() => []);
|
|
132
|
+
for (const f of files) {
|
|
133
|
+
if (!f.endsWith(".md"))
|
|
134
|
+
continue;
|
|
135
|
+
const abs = resolveWorkspacePath(workspace, "docs", ent.name, f);
|
|
136
|
+
const rel = path.relative(path.resolve(workspace), abs);
|
|
137
|
+
const text = await fs.readFile(abs, "utf8").catch(() => "");
|
|
138
|
+
hits.push(...scanTextForJargon(text, rel, categories));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return hits;
|
|
142
|
+
}
|
|
143
|
+
export function formatJargonHitsText(hits) {
|
|
144
|
+
if (hits.length === 0)
|
|
145
|
+
return "✓ no jargon leaks\n";
|
|
146
|
+
let out = `${hits.length} jargon leak${hits.length !== 1 ? "s" : ""}:\n`;
|
|
147
|
+
for (const h of hits) {
|
|
148
|
+
out += ` ${h.file}:${h.line} [${h.category}] ${h.snippet}\n`;
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const VIEWER_BIN_ENV = "DOCSX_VIEWER_BIN";
|
|
2
|
+
export declare const VIEWER_PACKAGE = "@docsxai/viewer";
|
|
3
|
+
export declare const VIEWER_BIN_NAME = "docsxai-viewer";
|
|
4
|
+
export interface ViewerBinResolution {
|
|
5
|
+
/** Command to spawn. */
|
|
6
|
+
command: string;
|
|
7
|
+
/** Args to pass before the caller's own (the bin script path when `command` is the Node binary). */
|
|
8
|
+
prefixArgs: string[];
|
|
9
|
+
source: "env" | "package" | "path";
|
|
10
|
+
/** Human-readable description of every resolution step tried, ending with the one that resolved. */
|
|
11
|
+
attempts: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface ResolveViewerBinOptions {
|
|
14
|
+
env?: Record<string, string | undefined>;
|
|
15
|
+
/** Directories whose `node_modules` to search for the viewer package. Defaults to the engine's own resolution chain. */
|
|
16
|
+
resolveFrom?: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare function resolveViewerBin(opts?: ResolveViewerBinOptions): Promise<ViewerBinResolution>;
|
|
19
|
+
/** Failure message for when the resolved command could not be launched (spawn ENOENT). */
|
|
20
|
+
export declare function formatViewerBinFailure(resolution: ViewerBinResolution): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Resolution of the `docsxai-viewer` bin for `docsxai render`. The viewer is its own package and
|
|
2
|
+
// the engine doesn't depend on it at build time, so the bin is located at run time, in order:
|
|
3
|
+
//
|
|
4
|
+
// 1. `DOCSX_VIEWER_BIN` — explicit operator override; a path to the viewer's bin script
|
|
5
|
+
// (run with the current Node when it's a `.js`/`.mjs`/`.cjs` file) or to an executable.
|
|
6
|
+
// 2. The `@docsxai/viewer` package installed next to the engine: its `package.json` is
|
|
7
|
+
// looked up along the engine's node_modules ancestor chain and the `bin` entry is run with
|
|
8
|
+
// the current Node — covers installs where the package is present but no PATH shim is.
|
|
9
|
+
// (A direct manifest read, not `require.resolve("…/package.json")` — the viewer's `exports`
|
|
10
|
+
// map doesn't export its package.json. The chain is walked explicitly rather than via
|
|
11
|
+
// `require.resolve.paths()`, which test runners extend with extra lookup dirs.)
|
|
12
|
+
// 3. `docsxai-viewer` on PATH (the npm bin shim) — the legacy behavior, kept as the fallback.
|
|
13
|
+
import { promises as fs } from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
export const VIEWER_BIN_ENV = "DOCSX_VIEWER_BIN";
|
|
17
|
+
export const VIEWER_PACKAGE = "@docsxai/viewer";
|
|
18
|
+
export const VIEWER_BIN_NAME = "docsxai-viewer";
|
|
19
|
+
async function isFile(p) {
|
|
20
|
+
try {
|
|
21
|
+
return (await fs.stat(p)).isFile();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function findViewerManifest(searchDirs) {
|
|
28
|
+
for (const dir of searchDirs) {
|
|
29
|
+
const manifest = path.join(dir, VIEWER_PACKAGE, "package.json");
|
|
30
|
+
if (await isFile(manifest))
|
|
31
|
+
return manifest;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
/** The `node_modules` dirs Node itself would search from this module's location. */
|
|
36
|
+
function defaultSearchDirs() {
|
|
37
|
+
const dirs = [];
|
|
38
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
for (;;) {
|
|
40
|
+
if (path.basename(dir) !== "node_modules")
|
|
41
|
+
dirs.push(path.join(dir, "node_modules"));
|
|
42
|
+
const parent = path.dirname(dir);
|
|
43
|
+
if (parent === dir)
|
|
44
|
+
return dirs;
|
|
45
|
+
dir = parent;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function resolveViewerBin(opts = {}) {
|
|
49
|
+
const env = opts.env ?? process.env;
|
|
50
|
+
const attempts = [];
|
|
51
|
+
const envBin = env[VIEWER_BIN_ENV];
|
|
52
|
+
if (envBin) {
|
|
53
|
+
if (await isFile(envBin)) {
|
|
54
|
+
attempts.push(`$${VIEWER_BIN_ENV} → ${envBin}`);
|
|
55
|
+
return /\.(c|m)?js$/.test(envBin)
|
|
56
|
+
? { command: process.execPath, prefixArgs: [envBin], source: "env", attempts }
|
|
57
|
+
: { command: envBin, prefixArgs: [], source: "env", attempts };
|
|
58
|
+
}
|
|
59
|
+
attempts.push(`$${VIEWER_BIN_ENV} → ${envBin} (no such file)`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
attempts.push(`$${VIEWER_BIN_ENV} — not set`);
|
|
63
|
+
}
|
|
64
|
+
const searchDirs = opts.resolveFrom
|
|
65
|
+
? opts.resolveFrom.map((d) => path.join(d, "node_modules"))
|
|
66
|
+
: defaultSearchDirs();
|
|
67
|
+
const manifestPath = await findViewerManifest(searchDirs);
|
|
68
|
+
if (manifestPath) {
|
|
69
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
|
70
|
+
const binRel = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.[VIEWER_BIN_NAME];
|
|
71
|
+
if (binRel) {
|
|
72
|
+
const binAbs = path.resolve(path.dirname(manifestPath), binRel);
|
|
73
|
+
if (await isFile(binAbs)) {
|
|
74
|
+
attempts.push(`${VIEWER_PACKAGE} (installed package) → ${binAbs}`);
|
|
75
|
+
return { command: process.execPath, prefixArgs: [binAbs], source: "package", attempts };
|
|
76
|
+
}
|
|
77
|
+
attempts.push(`${VIEWER_PACKAGE} (installed package) → ${binAbs} (bin file missing — is the package built?)`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
attempts.push(`${VIEWER_PACKAGE} (installed package) → ${manifestPath} (no "${VIEWER_BIN_NAME}" bin entry)`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
attempts.push(`${VIEWER_PACKAGE} (installed package) — not found next to the engine`);
|
|
85
|
+
}
|
|
86
|
+
attempts.push(`\`${VIEWER_BIN_NAME}\` on PATH`);
|
|
87
|
+
return { command: VIEWER_BIN_NAME, prefixArgs: [], source: "path", attempts };
|
|
88
|
+
}
|
|
89
|
+
/** Failure message for when the resolved command could not be launched (spawn ENOENT). */
|
|
90
|
+
export function formatViewerBinFailure(resolution) {
|
|
91
|
+
const lines = resolution.attempts.map((a, i, all) => ` ${i + 1}. ${a}${i === all.length - 1 ? " — not found" : ""}`);
|
|
92
|
+
return [
|
|
93
|
+
`\`${VIEWER_BIN_NAME}\` could not be launched. Tried, in order:`,
|
|
94
|
+
...lines,
|
|
95
|
+
`Install ${VIEWER_PACKAGE} next to the engine (or globally), or point ${VIEWER_BIN_ENV} at its bin script.`,
|
|
96
|
+
].join("\n");
|
|
97
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export declare const WORKSPACE_CONFIG_FILE = ".docsxai.json";
|
|
2
|
+
/** Thrown when a workspace-relative path resolves outside the workspace root. */
|
|
3
|
+
export declare class WorkspacePathEscapeError extends Error {
|
|
4
|
+
readonly workspaceDir: string;
|
|
5
|
+
readonly resolvedPath: string;
|
|
6
|
+
constructor(workspaceDir: string, resolvedPath: string);
|
|
7
|
+
}
|
|
8
|
+
/** Resolve path segments against a workspace root, guaranteeing containment. */
|
|
9
|
+
export declare function resolveWorkspacePath(workspaceDir: string, ...segments: string[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Like {@link resolveWorkspacePath}, but additionally defends against symlink escape: the deepest
|
|
12
|
+
* existing ancestor of the resolved path is realpath'd and containment re-verified against the
|
|
13
|
+
* realpath'd workspace root. Use before writing to a path whose segments are user-influenced
|
|
14
|
+
* (flow names, step ids, role names) — a symlink inside the workspace pointing outside must throw.
|
|
15
|
+
*/
|
|
16
|
+
export declare function resolveWorkspacePathReal(workspaceDir: string, ...segments: string[]): Promise<string>;
|
|
17
|
+
export interface WorkspaceConfig {
|
|
18
|
+
schema: "docsxai/workspace@1";
|
|
19
|
+
/** Base URL of the running app this workspace documents. Used as the default for `run`/`capture-auth`. */
|
|
20
|
+
app_url?: string;
|
|
21
|
+
/** Accept self-signed/invalid TLS for `app_url` (e.g. a local HTTPS dev cert). */
|
|
22
|
+
ignore_https_errors?: boolean;
|
|
23
|
+
/** Backend stub/service URL for `push`/`pull` (e.g. `http://localhost:4477`). Optional — workspaces operate fully locally without it. */
|
|
24
|
+
backend_url?: string;
|
|
25
|
+
/** Backend workspace ID, set by `push` after first round-trip. */
|
|
26
|
+
backend_workspace_id?: string;
|
|
27
|
+
/** Backend project ID, set by `push` after first round-trip. */
|
|
28
|
+
backend_project_id?: string;
|
|
29
|
+
created_at: string;
|
|
30
|
+
}
|
|
31
|
+
export interface InitWorkspaceOptions {
|
|
32
|
+
/** Target directory. Ignored (a temp dir is used) when `persistTmp` is true; otherwise required. */
|
|
33
|
+
dir?: string;
|
|
34
|
+
/** Create the workspace in a throwaway temp dir instead of `dir`. */
|
|
35
|
+
persistTmp?: boolean;
|
|
36
|
+
appUrl?: string;
|
|
37
|
+
/** `manual-capture` (default) writes an `auth/strategy.yaml`; `none` skips it. */
|
|
38
|
+
auth?: "manual-capture" | "none";
|
|
39
|
+
role?: string;
|
|
40
|
+
/** Cache TTL for the captured session (`session`, or a duration like `1h`/`30m`). Default `1h` — a *fallback* used only when `authCookie` isn't set/found. */
|
|
41
|
+
ttl?: string;
|
|
42
|
+
/** Name of the app's auth/session cookie — when set, the cached session's expiry tracks this cookie, not `ttl`. */
|
|
43
|
+
authCookie?: string;
|
|
44
|
+
captureTrigger?: "console" | "button";
|
|
45
|
+
ignoreHttpsErrors?: boolean;
|
|
46
|
+
/** Allow scaffolding into a non-empty directory. */
|
|
47
|
+
force?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface InitWorkspaceResult {
|
|
50
|
+
/** The (possibly temp) directory the workspace was created in. */
|
|
51
|
+
dir: string;
|
|
52
|
+
/** Paths created, relative to `dir`. */
|
|
53
|
+
created: string[];
|
|
54
|
+
/** True if `dir` is a throwaway temp dir (from `--persist tmp`). */
|
|
55
|
+
ephemeral: boolean;
|
|
56
|
+
}
|
|
57
|
+
/** Scaffold a workspace. Returns where it landed + what was created. */
|
|
58
|
+
export declare function initWorkspace(opts: InitWorkspaceOptions): Promise<InitWorkspaceResult>;
|
|
59
|
+
/** Read `<dir>/.docsxai.json` if present. Returns `null` if absent or unreadable. */
|
|
60
|
+
export declare function loadWorkspaceConfig(dir: string): Promise<WorkspaceConfig | null>;
|