@userreason/cli 0.1.0 → 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/README.md +301 -41
- package/dist/authorize.d.ts +29 -0
- package/dist/authorize.js +23 -0
- package/dist/bin.js +565 -50
- package/dist/capture.d.ts +18 -0
- package/dist/capture.js +256 -0
- package/dist/catalog.d.ts +16 -0
- package/dist/catalog.js +206 -0
- package/dist/client.d.ts +79 -2
- package/dist/client.js +140 -17
- package/dist/config.d.ts +4 -0
- package/dist/config.js +35 -12
- package/dist/contracts.d.ts +13 -3
- package/dist/contracts.js +4 -4
- package/dist/credentials.d.ts +12 -2
- package/dist/credentials.js +110 -26
- package/dist/docs.d.ts +51 -0
- package/dist/docs.js +143 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +38 -0
- package/dist/event-policy.d.ts +2 -0
- package/dist/event-policy.js +9 -0
- package/dist/first-survey-preparation.d.ts +8 -0
- package/dist/first-survey-preparation.js +29 -0
- package/dist/first-survey-source.d.ts +60 -0
- package/dist/first-survey-source.js +289 -0
- package/dist/first-survey-terminal.d.ts +5 -0
- package/dist/first-survey-terminal.js +16 -0
- package/dist/first-survey.d.ts +88 -0
- package/dist/first-survey.js +227 -0
- package/dist/help.d.ts +13 -0
- package/dist/help.js +74 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +23 -0
- package/dist/lock.js +19 -5
- package/dist/profile.d.ts +20 -0
- package/dist/profile.js +57 -0
- package/dist/project.d.ts +9 -0
- package/dist/project.js +87 -0
- package/dist/recommended-surveys.d.ts +30 -0
- package/dist/recommended-surveys.js +201 -0
- package/dist/setup-executor.d.ts +35 -0
- package/dist/setup-executor.js +329 -0
- package/dist/setup-files.d.ts +11 -0
- package/dist/setup-files.js +96 -0
- package/dist/setup-runner.d.ts +14 -0
- package/dist/setup-runner.js +142 -0
- package/dist/setup-source.d.ts +31 -0
- package/dist/setup-source.js +48 -0
- package/dist/setup-store.d.ts +37 -0
- package/dist/setup-store.js +256 -0
- package/dist/setup-terminal.d.ts +3 -0
- package/dist/setup-terminal.js +6 -0
- package/dist/setup-wire.d.ts +1237 -0
- package/dist/setup-wire.js +175 -0
- package/dist/setup.d.ts +1223 -0
- package/dist/setup.js +258 -0
- package/dist/starter-survey.d.ts +22 -0
- package/dist/starter-survey.js +457 -0
- package/dist/telemetry.d.ts +67 -0
- package/dist/telemetry.js +304 -0
- package/dist/ui.d.ts +75 -0
- package/dist/ui.js +107 -0
- package/dist/views.d.ts +51 -0
- package/dist/views.js +93 -0
- package/dist/workos.d.ts +1 -1
- package/dist/workos.js +9 -4
- package/package.json +29 -12
- package/skills/userreason/SKILL.md +88 -8
- package/skills/userreason/references/management.md +67 -0
package/dist/project.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import { lstat, readFile, realpath } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { apiUrl, uuid } from './contracts.js';
|
|
5
|
+
const explicit = 'Supply --workspace and --site (and --api-url or --dev for that environment).';
|
|
6
|
+
async function source(root, file) {
|
|
7
|
+
const path = resolve(root, file), rel = relative(root, path);
|
|
8
|
+
if (rel.startsWith('..') || !rel)
|
|
9
|
+
return;
|
|
10
|
+
let current = root;
|
|
11
|
+
for (const part of rel.split(sep)) {
|
|
12
|
+
current = join(current, part);
|
|
13
|
+
try {
|
|
14
|
+
if ((await lstat(current)).isSymbolicLink())
|
|
15
|
+
throw new Error(`Cannot inspect symlinked project file ${file}. ${explicit}`);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error.code === 'ENOENT')
|
|
19
|
+
return;
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const info = await lstat(path);
|
|
24
|
+
if (!info.isFile() || info.size > 2_000_000)
|
|
25
|
+
throw new Error(`Cannot inspect project file ${file}. ${explicit}`);
|
|
26
|
+
return readFile(path, 'utf8');
|
|
27
|
+
}
|
|
28
|
+
/** Parse static SDK initialization only. Never execute application code or select an arbitrary account site. */
|
|
29
|
+
export async function detectProjectTarget(directory = process.cwd()) {
|
|
30
|
+
let root = await realpath(directory);
|
|
31
|
+
while (!(await source(root, 'package.json'))) {
|
|
32
|
+
const parent = dirname(root);
|
|
33
|
+
if (parent === root)
|
|
34
|
+
return;
|
|
35
|
+
root = parent;
|
|
36
|
+
}
|
|
37
|
+
const candidates = new Set();
|
|
38
|
+
for (const folder of ['', 'src/'])
|
|
39
|
+
for (const name of ['main', 'index', 'instrumentation-client', 'userreason.client', 'userreason.generated'])
|
|
40
|
+
for (const extension of ['ts', 'tsx', 'js', 'jsx', 'mjs'])
|
|
41
|
+
candidates.add(`${folder}${name}.${extension}`);
|
|
42
|
+
const html = await source(root, 'index.html');
|
|
43
|
+
for (const match of html?.matchAll(/<script\b([^>]*)>/gi) ?? []) {
|
|
44
|
+
const src = /\bsrc\s*=\s*['"]([^'"]+)['"]/i.exec(match[1])?.[1];
|
|
45
|
+
if (src && /\btype\s*=\s*['"]module['"]/i.test(match[1]) && !/^(?:[a-z]+:|\/\/)|[?#]/i.test(src))
|
|
46
|
+
candidates.add(src.replace(/^\//, ''));
|
|
47
|
+
}
|
|
48
|
+
let found;
|
|
49
|
+
for (const file of candidates) {
|
|
50
|
+
const text = await source(root, file);
|
|
51
|
+
if (!text)
|
|
52
|
+
continue;
|
|
53
|
+
const ast = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, /x$/.test(file) ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
54
|
+
const bindings = new Set(ast.statements.filter(ts.isImportDeclaration)
|
|
55
|
+
.filter(node => ts.isStringLiteral(node.moduleSpecifier) && node.moduleSpecifier.text === '@userreason/js')
|
|
56
|
+
.map(node => node.importClause?.name?.text).filter(Boolean));
|
|
57
|
+
const visit = (node) => {
|
|
58
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'init' && bindings.has(node.expression.expression.getText(ast))) {
|
|
59
|
+
if (ast.parseDiagnostics?.length)
|
|
60
|
+
throw new Error(`Invalid SDK initialization in ${file}. ${explicit}`);
|
|
61
|
+
const options = node.arguments[0];
|
|
62
|
+
if (!options || !ts.isObjectLiteralExpression(options))
|
|
63
|
+
throw new Error(`Dynamic SDK initialization in ${file}. ${explicit}`);
|
|
64
|
+
const values = {};
|
|
65
|
+
for (const prop of options.properties) {
|
|
66
|
+
if (ts.isSpreadAssignment(prop))
|
|
67
|
+
throw new Error(`Spread SDK options in ${file}. ${explicit}`);
|
|
68
|
+
const name = prop.name && (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) ? prop.name.text : '';
|
|
69
|
+
if (!['siteId', 'workspaceId', 'apiBaseUrl'].includes(name))
|
|
70
|
+
continue;
|
|
71
|
+
if (!ts.isPropertyAssignment(prop) || !(ts.isStringLiteral(prop.initializer) || ts.isNoSubstitutionTemplateLiteral(prop.initializer)) || name in values)
|
|
72
|
+
throw new Error(`Dynamic SDK option ${name} in ${file}. ${explicit}`);
|
|
73
|
+
values[name] = prop.initializer.text;
|
|
74
|
+
}
|
|
75
|
+
if (values.siteId) {
|
|
76
|
+
const target = { directory: root, file, siteId: uuid(values.siteId, 'SDK siteId'), apiUrl: apiUrl(values.apiBaseUrl ?? 'https://api.userreason.com'), ...(values.workspaceId ? { workspaceId: uuid(values.workspaceId) } : {}) };
|
|
77
|
+
if (found && (found.siteId !== target.siteId || found.apiUrl !== target.apiUrl || found.workspaceId !== target.workspaceId))
|
|
78
|
+
throw new Error(`Multiple SDK targets in this project. ${explicit}`);
|
|
79
|
+
found = target;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
ts.forEachChild(node, visit);
|
|
83
|
+
};
|
|
84
|
+
visit(ast);
|
|
85
|
+
}
|
|
86
|
+
return found;
|
|
87
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { UserReasonClient } from "./client.js";
|
|
2
|
+
import { type FirstSurveyPreparation } from "./first-survey-preparation.js";
|
|
3
|
+
import type { FirstSurveyUI } from "./first-survey.js";
|
|
4
|
+
type Receipt = {
|
|
5
|
+
prompt: string;
|
|
6
|
+
request: string;
|
|
7
|
+
definition: Record<string, unknown>;
|
|
8
|
+
result?: unknown;
|
|
9
|
+
};
|
|
10
|
+
/** One optional selection; all drafts stay unpublished and match no audience. */
|
|
11
|
+
export declare function recommendedSurveys(client: UserReasonClient, root: string, workspace: string, site: string, ui: FirstSurveyUI, options?: {
|
|
12
|
+
preparation?: FirstSurveyPreparation;
|
|
13
|
+
sourceConsent?: boolean;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
state: string;
|
|
16
|
+
error?: undefined;
|
|
17
|
+
count?: undefined;
|
|
18
|
+
receipts?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
state: string;
|
|
21
|
+
error: string;
|
|
22
|
+
count?: undefined;
|
|
23
|
+
receipts?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
state: string;
|
|
26
|
+
count: number;
|
|
27
|
+
receipts: Record<string, Receipt>;
|
|
28
|
+
error?: undefined;
|
|
29
|
+
}>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { mkdir, open, rename, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { configDirectory } from "./config.js";
|
|
6
|
+
import { sourceHash, verifyFirstSurveySource, } from "./first-survey-source.js";
|
|
7
|
+
import { prepareFirstSurveyLocally, } from "./first-survey-preparation.js";
|
|
8
|
+
const record = (v) => v && typeof v === "object" && !Array.isArray(v)
|
|
9
|
+
? v
|
|
10
|
+
: {};
|
|
11
|
+
/** One optional selection; all drafts stay unpublished and match no audience. */
|
|
12
|
+
export async function recommendedSurveys(client, root, workspace, site, ui, options = {}) {
|
|
13
|
+
if (!options.sourceConsent)
|
|
14
|
+
return { state: "skipped" };
|
|
15
|
+
if (!ui.chooseMany)
|
|
16
|
+
throw Error("Survey selection requires a checkbox interface");
|
|
17
|
+
const scope = {
|
|
18
|
+
api: client.config.apiUrl,
|
|
19
|
+
workspace_id: workspace,
|
|
20
|
+
site_id: site,
|
|
21
|
+
};
|
|
22
|
+
const scopeKey = sourceHash(JSON.stringify([root, scope]));
|
|
23
|
+
const dir = join(configDirectory(), "recommended-surveys");
|
|
24
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
25
|
+
const path = join(dir, scopeKey + ".json");
|
|
26
|
+
const lock = await open(path + ".lock", "wx", 0o600);
|
|
27
|
+
const preparation = options.preparation ?? prepareFirstSurveyLocally(root);
|
|
28
|
+
let state = {
|
|
29
|
+
version: 1,
|
|
30
|
+
scope: scopeKey,
|
|
31
|
+
expires: Date.now() + 7 * 86400000,
|
|
32
|
+
candidates: [],
|
|
33
|
+
sources: [],
|
|
34
|
+
receipts: {},
|
|
35
|
+
};
|
|
36
|
+
const save = async () => {
|
|
37
|
+
const tmp = path + "." + randomUUID() + ".tmp";
|
|
38
|
+
await writeFile(tmp, JSON.stringify(state), { flag: "wx", mode: 0o600 });
|
|
39
|
+
await rename(tmp, path);
|
|
40
|
+
};
|
|
41
|
+
const check = () => ui.signal?.throwIfAborted();
|
|
42
|
+
try {
|
|
43
|
+
try {
|
|
44
|
+
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
45
|
+
try {
|
|
46
|
+
if ((await file.stat()).size > 128000)
|
|
47
|
+
throw Error("Invalid recommendation cache");
|
|
48
|
+
const old = JSON.parse(await file.readFile("utf8"));
|
|
49
|
+
if (old.version !== 1 || old.scope !== scopeKey)
|
|
50
|
+
throw Error("Recommendation scope changed");
|
|
51
|
+
if (old.expires > Date.now())
|
|
52
|
+
state = old;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await file.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (e) {
|
|
59
|
+
if (e.code !== "ENOENT")
|
|
60
|
+
throw e;
|
|
61
|
+
}
|
|
62
|
+
check();
|
|
63
|
+
// A previous selected save can be retried without changing its definitions or IDs.
|
|
64
|
+
if (!state.selected) {
|
|
65
|
+
if (state.candidates.length) {
|
|
66
|
+
try {
|
|
67
|
+
await verifyFirstSurveySource(root, state.sources);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
state.candidates = [];
|
|
71
|
+
state.sources = [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!state.candidates.length) {
|
|
75
|
+
const generate = async () => {
|
|
76
|
+
const preview = await preparation.take(ui.signal);
|
|
77
|
+
if (!preview.evidence.length)
|
|
78
|
+
throw Error("No product source available");
|
|
79
|
+
const metadata = record(await client.request("/onboarding/v1/planning/describe", { schema_version: 1, session_id: randomUUID(), scope }, ui.signal));
|
|
80
|
+
if (!record(metadata.recommendations).version)
|
|
81
|
+
throw Error("Development recommendation pipeline is unavailable");
|
|
82
|
+
await verifyFirstSurveySource(root, preview.evidence);
|
|
83
|
+
const response = record(await client.request("/onboarding/v1/planning/analyze", {
|
|
84
|
+
schema_version: 1,
|
|
85
|
+
session_id: randomUUID(),
|
|
86
|
+
request_id: randomUUID(),
|
|
87
|
+
scope,
|
|
88
|
+
packages: preview.packages,
|
|
89
|
+
capabilities: [
|
|
90
|
+
...preview.capabilities,
|
|
91
|
+
"recommended_surveys:v1",
|
|
92
|
+
],
|
|
93
|
+
evidence: preview.evidence,
|
|
94
|
+
previous_plan_digest: null,
|
|
95
|
+
limits: {
|
|
96
|
+
input_tokens: 12000,
|
|
97
|
+
output_tokens: 4000,
|
|
98
|
+
deadline_ms: 40000,
|
|
99
|
+
},
|
|
100
|
+
consent: {
|
|
101
|
+
packet_digest: sourceHash(JSON.stringify(preview.evidence)),
|
|
102
|
+
destination: metadata.destination,
|
|
103
|
+
configuration_fingerprint: metadata.configuration_fingerprint,
|
|
104
|
+
policy_version: "first-survey-v1",
|
|
105
|
+
authorized_at: new Date().toISOString(),
|
|
106
|
+
git_authorized: false,
|
|
107
|
+
},
|
|
108
|
+
}, ui.signal));
|
|
109
|
+
await verifyFirstSurveySource(root, preview.evidence);
|
|
110
|
+
const candidates = record(response.result).candidates;
|
|
111
|
+
if (!Array.isArray(candidates))
|
|
112
|
+
throw Error("Invalid recommendations");
|
|
113
|
+
state.candidates = candidates.slice(0, 3);
|
|
114
|
+
state.sources = preview.evidence.map(({ path, file_hash }) => ({
|
|
115
|
+
path,
|
|
116
|
+
file_hash,
|
|
117
|
+
}));
|
|
118
|
+
await save();
|
|
119
|
+
};
|
|
120
|
+
try {
|
|
121
|
+
if (ui.loading)
|
|
122
|
+
await ui.loading("Finding recommended surveys", generate);
|
|
123
|
+
else
|
|
124
|
+
await generate();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
check();
|
|
128
|
+
ui.show("Recommendations are unavailable right now. Setup is complete; rerun the wizard to try again.");
|
|
129
|
+
return {
|
|
130
|
+
state: "unavailable",
|
|
131
|
+
error: error instanceof Error ? error.message : "unavailable",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!state.candidates.length) {
|
|
136
|
+
ui.show("No supported survey recommendations yet. Setup is complete.");
|
|
137
|
+
return { state: "empty" };
|
|
138
|
+
}
|
|
139
|
+
ui.show("Select surveys to get started. We’ll create drafts in your workspace. Nothing will be published.");
|
|
140
|
+
const selected = await ui.chooseMany("Recommended surveys", state.candidates.map((c) => ({
|
|
141
|
+
value: c.id,
|
|
142
|
+
label: `${String(c.definition.name)} — ${c.question}`,
|
|
143
|
+
hint: `${c.opportunity.claim} Delivery can be configured later.`,
|
|
144
|
+
})), []);
|
|
145
|
+
check();
|
|
146
|
+
if (!selected.length)
|
|
147
|
+
return { state: "skipped" };
|
|
148
|
+
if (new Set(selected).size !== selected.length ||
|
|
149
|
+
selected.some((id) => !state.candidates.some((c) => c.id === id)))
|
|
150
|
+
throw Error("Invalid survey selection");
|
|
151
|
+
await verifyFirstSurveySource(root, state.sources);
|
|
152
|
+
// Validate all selected drafts before writing any of them.
|
|
153
|
+
for (const id of selected) {
|
|
154
|
+
const c = state.candidates.find((c) => c.id === id);
|
|
155
|
+
const validation = record(await client.read("prompts.validate", {
|
|
156
|
+
workspaceId: workspace,
|
|
157
|
+
siteId: site,
|
|
158
|
+
definition: c.definition,
|
|
159
|
+
}));
|
|
160
|
+
if (validation.valid !== true)
|
|
161
|
+
throw Error("A recommended draft did not validate");
|
|
162
|
+
}
|
|
163
|
+
state.selected = selected;
|
|
164
|
+
for (const id of selected)
|
|
165
|
+
state.receipts[id] = {
|
|
166
|
+
prompt: randomUUID(),
|
|
167
|
+
request: randomUUID(),
|
|
168
|
+
definition: state.candidates.find((c) => c.id === id).definition,
|
|
169
|
+
};
|
|
170
|
+
await save();
|
|
171
|
+
}
|
|
172
|
+
let count = 0;
|
|
173
|
+
for (const id of state.selected) {
|
|
174
|
+
check();
|
|
175
|
+
const receipt = state.receipts[id];
|
|
176
|
+
if (!receipt)
|
|
177
|
+
throw Error("Missing draft receipt");
|
|
178
|
+
if (!receipt.result) {
|
|
179
|
+
receipt.result = await client.promptCommand({
|
|
180
|
+
workspaceId: workspace,
|
|
181
|
+
siteId: site,
|
|
182
|
+
action: "create",
|
|
183
|
+
promptId: receipt.prompt,
|
|
184
|
+
requestId: receipt.request,
|
|
185
|
+
definition: receipt.definition,
|
|
186
|
+
});
|
|
187
|
+
await save();
|
|
188
|
+
}
|
|
189
|
+
const saved = record(receipt.result), link = record(saved.links).edit;
|
|
190
|
+
ui.show(`Draft saved: ${String(receipt.definition.name)}${typeof link === "string" ? `\n${link}` : ""}`);
|
|
191
|
+
count++;
|
|
192
|
+
}
|
|
193
|
+
ui.show(`${count} survey draft${count === 1 ? "" : "s"} saved. Nothing was published.`);
|
|
194
|
+
return { state: "saved", count, receipts: state.receipts };
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
preparation.cancel();
|
|
198
|
+
await lock.close();
|
|
199
|
+
await unlink(path + ".lock");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type BrowserSetupClient } from './setup.js';
|
|
2
|
+
import { type SetupLocalPlan, type SetupEffect, type SetupSessionView } from './setup-wire.js';
|
|
3
|
+
import { type SetupLocalFile } from './setup-files.js';
|
|
4
|
+
export interface PreparedSetupOperation {
|
|
5
|
+
effect: SetupEffect;
|
|
6
|
+
files: SetupLocalFile[];
|
|
7
|
+
guards?: {
|
|
8
|
+
path: string;
|
|
9
|
+
before_hash: string | null;
|
|
10
|
+
}[];
|
|
11
|
+
}
|
|
12
|
+
export interface PreparedSetupPlan {
|
|
13
|
+
wire: SetupLocalPlan;
|
|
14
|
+
operations: PreparedSetupOperation[];
|
|
15
|
+
}
|
|
16
|
+
export interface SetupExecutorRuntime {
|
|
17
|
+
prepare(session: SetupSessionView): Promise<PreparedSetupPlan>;
|
|
18
|
+
/** Only a locally prepared allowlisted operation is passed here, never a server script. */
|
|
19
|
+
apply(operation: PreparedSetupOperation, signal: AbortSignal): Promise<'verified' | 'already_present'>;
|
|
20
|
+
/** Installed package versions require a runtime check in addition to file hashes. */
|
|
21
|
+
verify?(operation: PreparedSetupOperation): Promise<boolean>;
|
|
22
|
+
}
|
|
23
|
+
export interface SetupExecutorOptions {
|
|
24
|
+
signal?: AbortSignal;
|
|
25
|
+
pollMs?: number;
|
|
26
|
+
heartbeatMs?: number;
|
|
27
|
+
maxPolls?: number;
|
|
28
|
+
sleep?: (ms: number) => Promise<void>;
|
|
29
|
+
onSession?: (session: SetupSessionView) => void;
|
|
30
|
+
}
|
|
31
|
+
export type SetupExecutorClient = Pick<BrowserSetupClient, 'identity' | 'poll' | 'plan' | 'claim' | 'startCommand' | 'report' | 'heartbeat' | 'reconcile'>;
|
|
32
|
+
/** Bounded local cleanup never restores authority or follows an unknown file/link. */
|
|
33
|
+
export declare function pruneSetupExecutionReceipts(now?: number): Promise<number>;
|
|
34
|
+
/** Runs only after browser approval. Durable intents precede effects and durable outcomes precede reports. */
|
|
35
|
+
export declare function runSetupExecutor(client: SetupExecutorClient, directory: string, runtime: SetupExecutorRuntime, options?: SetupExecutorOptions): Promise<SetupSessionView>;
|