@vendoai/vendo 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capability-misses.js +2 -3
- package/dist/cli/cloud/client.d.ts +2 -5
- package/dist/cli/cloud/client.js +4 -4
- package/dist/cli/dep-versions.d.ts +1 -1
- package/dist/cli/dep-versions.js +1 -1
- package/dist/cli/doctor-codes.d.ts +5 -0
- package/dist/cli/doctor-codes.js +5 -0
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.js +86 -2
- package/dist/cli/extract/apply.js +1 -1
- package/dist/cli/extract/delegate.js +9 -1
- package/dist/cli/extract/extraction.d.ts +4 -0
- package/dist/cli/extract/extraction.js +29 -2
- package/dist/cli/extract/harness.d.ts +12 -0
- package/dist/cli/extract/harness.js +3 -0
- package/dist/cli/extract/stages.js +5 -1
- package/dist/cli/init-scaffolds.d.ts +10 -1
- package/dist/cli/init-scaffolds.js +93 -1
- package/dist/cli/init.d.ts +3 -3
- package/dist/cli/init.js +53 -5
- package/dist/cli/playground/app/fake-client.js +11 -5
- package/dist/cli/playground/app/main.js +23 -13
- package/dist/cli/playground/app/scenario-mount.js +4 -1
- package/dist/cli/playground/app/theme-editor.js +27 -27
- package/dist/cli/playground/bundle.gen.d.ts +1 -1
- package/dist/cli/playground/bundle.gen.js +1 -1
- package/dist/cli/playground/embed-bundle.gen.d.ts +1 -1
- package/dist/cli/playground/embed-bundle.gen.js +1 -1
- package/dist/cli/shared.d.ts +1 -1
- package/dist/cli/shared.js +1 -1
- package/dist/cli/sync.d.ts +1 -1
- package/dist/cli/sync.js +1 -1
- package/dist/cloud-apps.js +2 -1
- package/dist/cloud-key-fetch.d.ts +16 -0
- package/dist/cloud-key-fetch.js +40 -0
- package/dist/cloud-tools.js +2 -1
- package/dist/connections.js +2 -2
- package/dist/dev-creds/model-edge.d.ts +21 -0
- package/dist/dev-creds/model-edge.js +24 -0
- package/dist/hosted-store.js +2 -1
- package/dist/refine.js +2 -1
- package/dist/sandbox.js +2 -2
- package/dist/server.d.ts +1 -1
- package/dist/server.js +56 -22
- package/dist/wire/shared.d.ts +2 -2
- package/dist/wire/shared.js +1 -1
- package/package.json +20 -11
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalJson, sha256Hex, } from "@vendoai/core";
|
|
2
2
|
import { envOptOut, loadConfig } from "@vendoai/telemetry";
|
|
3
|
-
import {
|
|
3
|
+
import { cloudKeyFetch } from "./cloud-key-fetch.js";
|
|
4
4
|
const DEFAULT_DATA_DIR = ".vendo/data";
|
|
5
5
|
const DEFAULT_BATCH_SIZE = 100;
|
|
6
6
|
const DEFAULT_QUEUE_LIMIT = 1_000;
|
|
@@ -60,8 +60,7 @@ function createMissUploader(options) {
|
|
|
60
60
|
unrefTimer(timeout);
|
|
61
61
|
try {
|
|
62
62
|
const surface = await options.surface;
|
|
63
|
-
const response = await
|
|
64
|
-
auth: "key",
|
|
63
|
+
const response = await cloudKeyFetch("/api/v1/misses", {
|
|
65
64
|
apiKey: options.apiKey,
|
|
66
65
|
env: options.env,
|
|
67
66
|
fetchImpl: options.fetchImpl,
|
|
@@ -5,11 +5,8 @@ export declare class CloudError extends Error {
|
|
|
5
5
|
readonly status: number;
|
|
6
6
|
constructor(code: string, message: string, status: number);
|
|
7
7
|
}
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
env?: Record<string, string | undefined>;
|
|
11
|
-
}
|
|
12
|
-
export declare function resolveCloudBaseUrl(options?: CloudUrlOptions): string;
|
|
8
|
+
export { resolveCloudBaseUrl, type CloudUrlOptions } from "../../cloud-key-fetch.js";
|
|
9
|
+
import { type CloudUrlOptions } from "../../cloud-key-fetch.js";
|
|
13
10
|
export interface SessionStore {
|
|
14
11
|
read(): Promise<CloudSession | null>;
|
|
15
12
|
write(session: CloudSession): Promise<void>;
|
package/dist/cli/cloud/client.js
CHANGED
|
@@ -15,10 +15,10 @@ export class CloudError extends Error {
|
|
|
15
15
|
this.status = status;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
18
|
+
// Base-URL resolution lives with the portable runtime helper so server-side
|
|
19
|
+
// code (capability misses) never imports this Node/CLI module.
|
|
20
|
+
export { resolveCloudBaseUrl } from "../../cloud-key-fetch.js";
|
|
21
|
+
import { resolveCloudBaseUrl } from "../../cloud-key-fetch.js";
|
|
22
22
|
function requestUrl(path, options) {
|
|
23
23
|
const base = resolveCloudBaseUrl(options);
|
|
24
24
|
const suffix = base.endsWith("/api/v1") && path.startsWith("/api/v1/")
|
|
@@ -23,4 +23,4 @@ export declare function installedAiVersion(root: string): Promise<string | null>
|
|
|
23
23
|
* Read the host project's dependency versions (deps + devDeps) for telemetry.
|
|
24
24
|
* Non-throwing: a missing or malformed package.json returns {}.
|
|
25
25
|
*/
|
|
26
|
-
export declare function detectDepVersions(root: string, framework: HostFramework): Promise<DepVersions>;
|
|
26
|
+
export declare function detectDepVersions(root: string, framework: HostFramework | "custom"): Promise<DepVersions>;
|
package/dist/cli/dep-versions.js
CHANGED
|
@@ -56,7 +56,7 @@ export async function detectDepVersions(root, framework) {
|
|
|
56
56
|
const manifest = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
57
57
|
const dependencies = { ...manifest.devDependencies, ...manifest.dependencies };
|
|
58
58
|
const out = {};
|
|
59
|
-
const frameworkPackage = framework === "unknown" ? undefined : FRAMEWORK_PACKAGE[framework];
|
|
59
|
+
const frameworkPackage = framework === "unknown" || framework === "custom" ? undefined : FRAMEWORK_PACKAGE[framework];
|
|
60
60
|
const frameworkVersion = frameworkPackage === undefined ? undefined : bareVersion(dependencies[frameworkPackage]);
|
|
61
61
|
if (frameworkVersion !== undefined)
|
|
62
62
|
out.frameworkVersion = frameworkVersion;
|
|
@@ -18,6 +18,8 @@ export declare const DOCTOR_ERROR_CODES: {
|
|
|
18
18
|
readonly "E-WIRE-004": "the Next.js root layout is not wrapped in <VendoRoot>";
|
|
19
19
|
readonly "E-WIRE-005": "the @vendoai/vendo (or vendoai alias) dependency is not declared";
|
|
20
20
|
readonly "E-WIRE-006": "no visible agent surface is mounted (<VendoRoot> alone renders nothing)";
|
|
21
|
+
readonly "E-WIRE-007": "no createVendo server wiring found in an unknown-framework host";
|
|
22
|
+
readonly "E-WIRE-008": "no <VendoRoot> found in an unknown-framework host's source";
|
|
21
23
|
readonly "E-CFG-001": "a required .vendo/ config file is missing";
|
|
22
24
|
readonly "E-CFG-002": ".vendo/data/.gitignore is missing";
|
|
23
25
|
readonly "E-DEP-001": "the installed ai package is a major version @vendoai/vendo does not support";
|
|
@@ -30,6 +32,7 @@ export declare const DOCTOR_ERROR_CODES: {
|
|
|
30
32
|
readonly "E-LIVE-004": "no execution venue is configured";
|
|
31
33
|
readonly "E-LIVE-005": "the host /status does not report an execution venue (version skew)";
|
|
32
34
|
readonly "E-LIVE-006": "the app's root page returns a server error while the wire answers";
|
|
35
|
+
readonly "E-LIVE-007": "the selected e2b execution venue is unusable (E2B_API_KEY or the e2b package is missing)";
|
|
33
36
|
readonly "E-AUTH-001": "present credentials did not reach the host API";
|
|
34
37
|
readonly "E-AUTH-002": "the present credential probe is unreachable";
|
|
35
38
|
readonly "E-AUTH-003": "the present credential probe cannot run while the dev server is down";
|
|
@@ -49,6 +52,8 @@ export declare const DOCTOR_ERROR_CODES: {
|
|
|
49
52
|
readonly "E-TURN-001": "the live model turn did not answer";
|
|
50
53
|
readonly "E-TURN-002": "the live model turn cannot run while the dev server is down";
|
|
51
54
|
readonly "E-CLOUD-001": "VENDO_API_KEY is set but not usable";
|
|
55
|
+
readonly "E-TOOLS-001": "every extracted host tool is disabled or excluded (zero live host tools)";
|
|
56
|
+
readonly "E-TOOLS-002": "the extracted tool surface is empty (zero host tools)";
|
|
52
57
|
};
|
|
53
58
|
export type DoctorErrorCode = keyof typeof DOCTOR_ERROR_CODES;
|
|
54
59
|
/** Complete list of every code doctor can emit, for CI enumeration. */
|
package/dist/cli/doctor-codes.js
CHANGED
|
@@ -19,6 +19,8 @@ export const DOCTOR_ERROR_CODES = {
|
|
|
19
19
|
"E-WIRE-004": "the Next.js root layout is not wrapped in <VendoRoot>",
|
|
20
20
|
"E-WIRE-005": "the @vendoai/vendo (or vendoai alias) dependency is not declared",
|
|
21
21
|
"E-WIRE-006": "no visible agent surface is mounted (<VendoRoot> alone renders nothing)",
|
|
22
|
+
"E-WIRE-007": "no createVendo server wiring found in an unknown-framework host",
|
|
23
|
+
"E-WIRE-008": "no <VendoRoot> found in an unknown-framework host's source",
|
|
22
24
|
"E-CFG-001": "a required .vendo/ config file is missing",
|
|
23
25
|
"E-CFG-002": ".vendo/data/.gitignore is missing",
|
|
24
26
|
"E-DEP-001": "the installed ai package is a major version @vendoai/vendo does not support",
|
|
@@ -31,6 +33,7 @@ export const DOCTOR_ERROR_CODES = {
|
|
|
31
33
|
"E-LIVE-004": "no execution venue is configured",
|
|
32
34
|
"E-LIVE-005": "the host /status does not report an execution venue (version skew)",
|
|
33
35
|
"E-LIVE-006": "the app's root page returns a server error while the wire answers",
|
|
36
|
+
"E-LIVE-007": "the selected e2b execution venue is unusable (E2B_API_KEY or the e2b package is missing)",
|
|
34
37
|
"E-AUTH-001": "present credentials did not reach the host API",
|
|
35
38
|
"E-AUTH-002": "the present credential probe is unreachable",
|
|
36
39
|
"E-AUTH-003": "the present credential probe cannot run while the dev server is down",
|
|
@@ -50,6 +53,8 @@ export const DOCTOR_ERROR_CODES = {
|
|
|
50
53
|
"E-TURN-001": "the live model turn did not answer",
|
|
51
54
|
"E-TURN-002": "the live model turn cannot run while the dev server is down",
|
|
52
55
|
"E-CLOUD-001": "VENDO_API_KEY is set but not usable",
|
|
56
|
+
"E-TOOLS-001": "every extracted host tool is disabled or excluded (zero live host tools)",
|
|
57
|
+
"E-TOOLS-002": "the extracted tool surface is empty (zero host tools)",
|
|
53
58
|
};
|
|
54
59
|
/** Complete list of every code doctor can emit, for CI enumeration. */
|
|
55
60
|
export const doctorErrorCodes = Object.keys(DOCTOR_ERROR_CODES);
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface DoctorOptions {
|
|
|
34
34
|
ok: boolean;
|
|
35
35
|
stop: () => void;
|
|
36
36
|
}>;
|
|
37
|
+
e2bResolvable?: (root: string) => boolean;
|
|
37
38
|
}
|
|
38
39
|
/** Doctor runs standalone, so unlike the dev server it gets no framework
|
|
39
40
|
* dotenv loading — without this, `VENDO_API_KEY` sitting in `.env.local`
|
package/dist/cli/doctor.js
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import { join, resolve } from "node:path";
|
|
3
4
|
import { stdin, stdout } from "node:process";
|
|
4
5
|
import { cloudDoctor, liveModelTurn, startDevServerForProbe, } from "./doctor-live.js";
|
|
5
6
|
import { installedAiVersion } from "./dep-versions.js";
|
|
6
7
|
import { doctorFixRef } from "./doctor-codes.js";
|
|
7
8
|
import { EJECT_MANIFEST_FILE } from "./eject.js";
|
|
9
|
+
import { overridesFileSchema, toolsFileSchema } from "@vendoai/actions";
|
|
8
10
|
import { detectFramework, detectVendoWiring } from "./framework.js";
|
|
9
11
|
import { walk } from "./theme/walk.js";
|
|
10
12
|
import { remoteUrls, sameUrl, validateRegistryServer } from "./mcp/registry.js";
|
|
11
13
|
import { askYesNo, CLI_VERSION, consoleOutput, exists, normalizeDotEnvValue, readOptional, toolingTelemetry } from "./shared.js";
|
|
14
|
+
/** Whether the optional `e2b` SDK resolves from the target project — the same
|
|
15
|
+
* node_modules walk the running wire's dynamic `import("e2b")` performs, so
|
|
16
|
+
* doctor certifies the venue against the resolution that will actually be
|
|
17
|
+
* asked to load it (0.4.4 defect C: /status said e2b on a host without the
|
|
18
|
+
* SDK, and the first build died in an unusable venue). */
|
|
19
|
+
function e2bResolvableFrom(root) {
|
|
20
|
+
try {
|
|
21
|
+
createRequire(join(root, "__vendo-doctor-probe__.js")).resolve("e2b");
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
12
28
|
async function hasDependency(root) {
|
|
13
29
|
try {
|
|
14
30
|
const manifest = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
@@ -112,7 +128,21 @@ export async function runDoctor(options) {
|
|
|
112
128
|
}
|
|
113
129
|
const scanned = await detectVendoWiring(root, { exclude: wrapperCandidates });
|
|
114
130
|
const wiring = { ...scanned, surface: scanned.surface || (scanned.client && wrapperWithOverlay) };
|
|
115
|
-
if (framework === "
|
|
131
|
+
if (framework === "unknown") {
|
|
132
|
+
// No framework to pattern-match (field case: a Cloudflare Worker + Vite
|
|
133
|
+
// host failed E-WIRE-003/004 forever) — judge the wiring by the same
|
|
134
|
+
// bounded source scan init uses, never by another framework's file
|
|
135
|
+
// layout. The surface check below still runs; it is source-generic.
|
|
136
|
+
if (scanned.server)
|
|
137
|
+
pass("wiring/server", "createVendo server wiring found");
|
|
138
|
+
else
|
|
139
|
+
fail("wiring/server", "E-WIRE-007", "no createVendo server wiring found — import createVendo from @vendoai/vendo/server and mount vendo.handler on your runtime's request entry");
|
|
140
|
+
if (scanned.client)
|
|
141
|
+
pass("wiring/client", "<VendoRoot> wraps the client");
|
|
142
|
+
else
|
|
143
|
+
warn("wiring/client", "E-WIRE-008", "no <VendoRoot> found in the host source — the @vendoai/ui hooks and embeds need it; ignore this if the host renders a fully custom surface");
|
|
144
|
+
}
|
|
145
|
+
else if (framework === "express") {
|
|
116
146
|
if (wiring.server)
|
|
117
147
|
pass("wiring/express-server", "Express server is wired");
|
|
118
148
|
else
|
|
@@ -184,6 +214,42 @@ export async function runDoctor(options) {
|
|
|
184
214
|
}
|
|
185
215
|
if (!await exists(join(root, ".vendo", "data", ".gitignore")))
|
|
186
216
|
warn("config/data-gitignore", "E-CFG-002", ".vendo/data/.gitignore is missing");
|
|
217
|
+
// The core promise, statically checkable: does the agent have any HOST
|
|
218
|
+
// tool it may actually call? All-disabled is an explicit misconfiguration
|
|
219
|
+
// (fail); an empty extraction is a strong warning — connector-only hosts
|
|
220
|
+
// are legitimate, but a fresh install landing here means extraction found
|
|
221
|
+
// nothing user-facing (field case: an infra product whose surface was all
|
|
222
|
+
// internal endpoints ended with tools: [] and a silently useless agent).
|
|
223
|
+
const toolsRaw = await readOptional(join(root, ".vendo", "tools.json"));
|
|
224
|
+
const overridesRaw = await readOptional(join(root, ".vendo", "overrides.json"));
|
|
225
|
+
if (toolsRaw !== null) {
|
|
226
|
+
try {
|
|
227
|
+
const toolsFile = toolsFileSchema.parse(JSON.parse(toolsRaw));
|
|
228
|
+
let overridesTools = {};
|
|
229
|
+
if (overridesRaw !== null) {
|
|
230
|
+
try {
|
|
231
|
+
overridesTools = overridesFileSchema.parse(JSON.parse(overridesRaw)).tools;
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Malformed overrides are their own (pre-existing) failure surface.
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const live = toolsFile.tools.filter((tool) => (overridesTools[tool.name]?.disabled ?? tool.disabled ?? false) !== true);
|
|
238
|
+
if (toolsFile.tools.length === 0) {
|
|
239
|
+
warn("tools/live-surface", "E-TOOLS-002", "the extracted tool surface is empty — the agent cannot act on this product's API; re-run `vendo init` extraction (or ignore if this deployment is connector-only)");
|
|
240
|
+
}
|
|
241
|
+
else if (live.length === 0) {
|
|
242
|
+
fail("tools/live-surface", "E-TOOLS-001", `zero live host tools — all ${toolsFile.tools.length} extracted tools are disabled or excluded; review the audience exclusions in .vendo/overrides.json and re-enable the end-user surface (disabled: false)`);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
pass("tools/live-surface", `${live.length} live host tool${live.length === 1 ? "" : "s"}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
// Not the vendo/tools@1 shape (e.g. a placeholder {}) — the config
|
|
250
|
+
// checks above already govern presence; nothing to grade here.
|
|
251
|
+
}
|
|
252
|
+
}
|
|
187
253
|
// §4 customization ladder — ejected chrome drift. The ejected pixels are the
|
|
188
254
|
// host's code, so a version gap is awareness (warn), never breakage (fail):
|
|
189
255
|
// the hooks/wire dependency keeps working; only new presentation is missed.
|
|
@@ -279,7 +345,25 @@ export async function runDoctor(options) {
|
|
|
279
345
|
// 10-mcp §1 — the door flag lives under blocks.mcp.
|
|
280
346
|
mcpEnabled = body.blocks.mcp === true;
|
|
281
347
|
sandboxVenue = body.blocks.sandbox;
|
|
282
|
-
if (sandboxVenue === "e2b"
|
|
348
|
+
if (sandboxVenue === "e2b") {
|
|
349
|
+
// 0.4.4 defect C — "ok: execution venue: e2b" on a host that cannot
|
|
350
|
+
// actually run e2b is a false blessing: the venue must be USABLE
|
|
351
|
+
// (key set and SDK resolvable from this project), or every server-app
|
|
352
|
+
// build dies in it instead of riding the Cloud sandbox.
|
|
353
|
+
const keyPresent = typeof env.E2B_API_KEY === "string" && env.E2B_API_KEY.trim() !== "";
|
|
354
|
+
const installed = (options.e2bResolvable ?? e2bResolvableFrom)(root);
|
|
355
|
+
if (keyPresent && installed) {
|
|
356
|
+
pass("live/venue", "execution venue: e2b");
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
const missing = [
|
|
360
|
+
...(keyPresent ? [] : ["E2B_API_KEY is not set"]),
|
|
361
|
+
...(installed ? [] : ["the e2b package does not resolve from this project"]),
|
|
362
|
+
].join(" and ");
|
|
363
|
+
fail("live/venue", "E-LIVE-007", `the running wire selected the e2b execution venue but ${missing}; server-app builds will fail in an unusable sandbox. Fix: install the e2b package and set E2B_API_KEY, or remove E2B_API_KEY from the server env (with VENDO_API_KEY set, the managed Cloud sandbox takes over), then restart the dev server and re-run doctor`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else if (sandboxVenue === "cloud" || sandboxVenue === "custom") {
|
|
283
367
|
pass("live/venue", `execution venue: ${sandboxVenue}`);
|
|
284
368
|
}
|
|
285
369
|
else if (sandboxVenue === false) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { vendoSync } from "@vendoai/actions";
|
|
3
|
+
import { vendoSync } from "@vendoai/actions/sync";
|
|
4
4
|
import { detectDepVersions } from "../dep-versions.js";
|
|
5
5
|
import { detectFramework } from "../framework.js";
|
|
6
6
|
import { applyDraft, reportApplied } from "./extraction.js";
|
|
@@ -41,6 +41,10 @@ export const EXTRACTION_DRAFT_JSON_SCHEMA = {
|
|
|
41
41
|
type: "boolean",
|
|
42
42
|
description: "false = wake a statically-unclassifiable tool (requires reasoning and risk).",
|
|
43
43
|
},
|
|
44
|
+
audience: {
|
|
45
|
+
enum: ["end-user", "operator", "internal"],
|
|
46
|
+
description: "Who the handler's own auth admits; non-end-user grades exclude the tool by default.",
|
|
47
|
+
},
|
|
44
48
|
reasoning: { type: "string", maxLength: 500 },
|
|
45
49
|
},
|
|
46
50
|
},
|
|
@@ -66,10 +70,14 @@ export function composeDelegatedInstructions(tools, appName) {
|
|
|
66
70
|
"",
|
|
67
71
|
"Rules:",
|
|
68
72
|
"- Produce ONE json document matching the provided draftSchema:",
|
|
69
|
-
' { "brief": string, "tools": [{ "name", "description", "risk"?, "critical"?, "disabled"?, "reasoning"? }], "missedSurfaces"?: string[] }',
|
|
73
|
+
' { "brief": string, "tools": [{ "name", "description", "risk"?, "critical"?, "disabled"?, "audience"?, "reasoning"? }], "missedSurfaces"?: string[] }',
|
|
70
74
|
"- tools: include ONLY names from the list above. Rewrite each description so an agent choosing tools understands what it actually does (read the handler source). <= 200 chars each.",
|
|
71
75
|
"- risk: you may RAISE risk (read->write->destructive) when the handler is more dangerous than labeled; never lower it. Mark irreversible operations critical: true.",
|
|
72
76
|
"- A tool listed as disabled was statically unclassifiable. If you can read its handler and grade it, set disabled: false WITH a risk and one-line reasoning. Leave it out otherwise.",
|
|
77
|
+
"- audience: who the handler's own auth admits — \"end-user\" (a signed-in customer acting on their own data),",
|
|
78
|
+
" \"operator\" (admin/staff/support consoles), or \"internal\" (machine-to-machine: webhooks, cron, reconciliation,",
|
|
79
|
+
" service tokens). Read the auth checks, not the route name. When unsure, default to internal — non-end-user",
|
|
80
|
+
" tools are excluded from the embedded agent by default, and a wrong \"end-user\" grade exposes a privileged surface.",
|
|
73
81
|
"- missedSurfaces: API surfaces you found that the list is missing (path + one line). Do not invent tools for them.",
|
|
74
82
|
"- brief: one paragraph — what the product does, who uses it, the jobs the agent should help with. Written from the actual code, no marketing fluff.",
|
|
75
83
|
`- Save the draft to a json file and apply it with \`${APPLY_COMMAND}\` — deterministic guards validate every entry before anything lands in .vendo/.`,
|
|
@@ -8,6 +8,10 @@ export interface AppliedSummary {
|
|
|
8
8
|
riskRaised: number;
|
|
9
9
|
critical: number;
|
|
10
10
|
woken: number;
|
|
11
|
+
/** Non-end-user-audience tools excluded (disabled) this apply. */
|
|
12
|
+
excluded: number;
|
|
13
|
+
/** Tools still live after this apply — zero means the agent cannot act on the host API at all. */
|
|
14
|
+
enabledAfter: number;
|
|
11
15
|
briefWritten: boolean;
|
|
12
16
|
refused: string[];
|
|
13
17
|
missedSurfaces: string[];
|
|
@@ -50,7 +50,7 @@ export async function applyDraft(input) {
|
|
|
50
50
|
? { format: "vendo/overrides@1", tools: {} }
|
|
51
51
|
: overridesSchema.parse(JSON.parse(raw));
|
|
52
52
|
const summary = {
|
|
53
|
-
described: 0, riskRaised: 0, critical: 0, woken: 0,
|
|
53
|
+
described: 0, riskRaised: 0, critical: 0, woken: 0, excluded: 0, enabledAfter: 0,
|
|
54
54
|
briefWritten: false, refused: [], missedSurfaces: input.draft.missedSurfaces ?? [],
|
|
55
55
|
};
|
|
56
56
|
for (const entry of input.draft.tools) {
|
|
@@ -70,7 +70,10 @@ export async function applyDraft(input) {
|
|
|
70
70
|
// evidence, so a reasoned wake carries the model's grade without tripping
|
|
71
71
|
// the downgrade guard (Greptile P1 / Devin on #363). A human-set risk or
|
|
72
72
|
// disabled decision always wins.
|
|
73
|
-
|
|
73
|
+
// A wake claiming a non-end-user audience is contradictory; audience
|
|
74
|
+
// exclusion (fail-closed) wins and the wake leg never runs.
|
|
75
|
+
const nonEndUser = entry.audience === "operator" || entry.audience === "internal";
|
|
76
|
+
const isWake = entry.disabled === false && fact.disabled === true && !nonEndUser;
|
|
74
77
|
if (isWake && existing.disabled === undefined) {
|
|
75
78
|
if (entry.reasoning === undefined || entry.risk === undefined) {
|
|
76
79
|
summary.refused.push(`${entry.name}: waking a disabled tool needs reasoning and a risk grade`);
|
|
@@ -96,9 +99,27 @@ export async function applyDraft(input) {
|
|
|
96
99
|
next.critical = true;
|
|
97
100
|
summary.critical += 1;
|
|
98
101
|
}
|
|
102
|
+
// Audience: recorded as provenance; a non-end-user grade excludes the
|
|
103
|
+
// tool by default. A human decision on either field always wins — a
|
|
104
|
+
// hand-set disabled (true OR false) or audience is never touched.
|
|
105
|
+
if (entry.audience !== undefined && existing.audience === undefined) {
|
|
106
|
+
next.audience = entry.audience;
|
|
107
|
+
if (nonEndUser && existing.disabled === undefined && next.disabled !== false) {
|
|
108
|
+
if (next.disabled !== true)
|
|
109
|
+
summary.excluded += 1;
|
|
110
|
+
next.disabled = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
99
113
|
if (JSON.stringify(next) !== JSON.stringify(existing))
|
|
100
114
|
overrides.tools[entry.name] = next;
|
|
101
115
|
}
|
|
116
|
+
// The number that actually matters to an end user: how many tools the
|
|
117
|
+
// agent can still call once static grades and overrides settle. Zero means
|
|
118
|
+
// the product's core promise silently degraded — reportApplied warns.
|
|
119
|
+
summary.enabledAfter = input.tools.filter((tool) => {
|
|
120
|
+
const override = overrides.tools[tool.name] ?? {};
|
|
121
|
+
return (override.disabled ?? tool.disabled ?? false) !== true;
|
|
122
|
+
}).length;
|
|
102
123
|
await writeText(overridesPath, `${JSON.stringify(overrides, null, 2)}\n`);
|
|
103
124
|
const briefPath = join(input.root, ".vendo", "brief.md");
|
|
104
125
|
const currentBrief = ((await readOptional(briefPath)) ?? "").trim();
|
|
@@ -117,6 +138,7 @@ export function reportApplied(input) {
|
|
|
117
138
|
...(applied.riskRaised > 0 ? [`${applied.riskRaised} risk raises`] : []),
|
|
118
139
|
...(applied.critical > 0 ? [`${applied.critical} critical marks`] : []),
|
|
119
140
|
...(applied.woken > 0 ? [`${applied.woken} tools woken`] : []),
|
|
141
|
+
...(applied.excluded > 0 ? [`${applied.excluded} operator/internal tools excluded`] : []),
|
|
120
142
|
...(input.briefDrafted ? ["brief drafted"] : []),
|
|
121
143
|
];
|
|
122
144
|
output.log(`AI polish applied: ${parts.join(" · ")} → .vendo/overrides.json, .vendo/brief.md${input.artifactsNote ?? ""}`);
|
|
@@ -124,6 +146,11 @@ export function reportApplied(input) {
|
|
|
124
146
|
output.error(` ${note}`);
|
|
125
147
|
for (const refused of applied.refused)
|
|
126
148
|
output.error(` refused: ${refused}`);
|
|
149
|
+
if (applied.enabledAfter === 0) {
|
|
150
|
+
output.error(" WARNING: zero live tools after apply — every extracted tool is disabled or excluded, so the embedded "
|
|
151
|
+
+ "agent cannot act on this product's API. Review the audience exclusions in .vendo/overrides.json "
|
|
152
|
+
+ "(re-enable the end-user surface with disabled: false) or re-run extraction.");
|
|
153
|
+
}
|
|
127
154
|
for (const missed of applied.missedSurfaces)
|
|
128
155
|
output.log(` missed surface (not extracted yet): ${missed}`);
|
|
129
156
|
}
|
|
@@ -15,10 +15,14 @@ export declare const draftToolSchema: z.ZodObject<{
|
|
|
15
15
|
critical: z.ZodOptional<z.ZodBoolean>;
|
|
16
16
|
/** false = wake a statically-unclassifiable tool (needs reasoning). */
|
|
17
17
|
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
+
/** Who the handler's own auth admits; non-end-user grades exclude the tool
|
|
19
|
+
* by default (applyDraft). */
|
|
20
|
+
audience: z.ZodOptional<z.ZodEnum<["end-user", "operator", "internal"]>>;
|
|
18
21
|
reasoning: z.ZodOptional<z.ZodString>;
|
|
19
22
|
}, "strip", z.ZodTypeAny, {
|
|
20
23
|
name: string;
|
|
21
24
|
description: string;
|
|
25
|
+
audience?: "end-user" | "operator" | "internal" | undefined;
|
|
22
26
|
risk?: "read" | "write" | "destructive" | undefined;
|
|
23
27
|
critical?: boolean | undefined;
|
|
24
28
|
disabled?: boolean | undefined;
|
|
@@ -26,6 +30,7 @@ export declare const draftToolSchema: z.ZodObject<{
|
|
|
26
30
|
}, {
|
|
27
31
|
name: string;
|
|
28
32
|
description: string;
|
|
33
|
+
audience?: "end-user" | "operator" | "internal" | undefined;
|
|
29
34
|
risk?: "read" | "write" | "destructive" | undefined;
|
|
30
35
|
critical?: boolean | undefined;
|
|
31
36
|
disabled?: boolean | undefined;
|
|
@@ -42,10 +47,14 @@ export declare const extractionDraftSchema: z.ZodObject<{
|
|
|
42
47
|
critical: z.ZodOptional<z.ZodBoolean>;
|
|
43
48
|
/** false = wake a statically-unclassifiable tool (needs reasoning). */
|
|
44
49
|
disabled: z.ZodOptional<z.ZodBoolean>;
|
|
50
|
+
/** Who the handler's own auth admits; non-end-user grades exclude the tool
|
|
51
|
+
* by default (applyDraft). */
|
|
52
|
+
audience: z.ZodOptional<z.ZodEnum<["end-user", "operator", "internal"]>>;
|
|
45
53
|
reasoning: z.ZodOptional<z.ZodString>;
|
|
46
54
|
}, "strip", z.ZodTypeAny, {
|
|
47
55
|
name: string;
|
|
48
56
|
description: string;
|
|
57
|
+
audience?: "end-user" | "operator" | "internal" | undefined;
|
|
49
58
|
risk?: "read" | "write" | "destructive" | undefined;
|
|
50
59
|
critical?: boolean | undefined;
|
|
51
60
|
disabled?: boolean | undefined;
|
|
@@ -53,6 +62,7 @@ export declare const extractionDraftSchema: z.ZodObject<{
|
|
|
53
62
|
}, {
|
|
54
63
|
name: string;
|
|
55
64
|
description: string;
|
|
65
|
+
audience?: "end-user" | "operator" | "internal" | undefined;
|
|
56
66
|
risk?: "read" | "write" | "destructive" | undefined;
|
|
57
67
|
critical?: boolean | undefined;
|
|
58
68
|
disabled?: boolean | undefined;
|
|
@@ -65,6 +75,7 @@ export declare const extractionDraftSchema: z.ZodObject<{
|
|
|
65
75
|
tools: {
|
|
66
76
|
name: string;
|
|
67
77
|
description: string;
|
|
78
|
+
audience?: "end-user" | "operator" | "internal" | undefined;
|
|
68
79
|
risk?: "read" | "write" | "destructive" | undefined;
|
|
69
80
|
critical?: boolean | undefined;
|
|
70
81
|
disabled?: boolean | undefined;
|
|
@@ -76,6 +87,7 @@ export declare const extractionDraftSchema: z.ZodObject<{
|
|
|
76
87
|
tools: {
|
|
77
88
|
name: string;
|
|
78
89
|
description: string;
|
|
90
|
+
audience?: "end-user" | "operator" | "internal" | undefined;
|
|
79
91
|
risk?: "read" | "write" | "destructive" | undefined;
|
|
80
92
|
critical?: boolean | undefined;
|
|
81
93
|
disabled?: boolean | undefined;
|
|
@@ -15,6 +15,9 @@ export const draftToolSchema = z.object({
|
|
|
15
15
|
critical: z.boolean().optional(),
|
|
16
16
|
/** false = wake a statically-unclassifiable tool (needs reasoning). */
|
|
17
17
|
disabled: z.boolean().optional(),
|
|
18
|
+
/** Who the handler's own auth admits; non-end-user grades exclude the tool
|
|
19
|
+
* by default (applyDraft). */
|
|
20
|
+
audience: z.enum(["end-user", "operator", "internal"]).optional(),
|
|
18
21
|
reasoning: z.string().max(500).optional(),
|
|
19
22
|
});
|
|
20
23
|
export const extractionDraftSchema = z.object({
|
|
@@ -115,10 +115,14 @@ export function composeInstructions(tools, appName, surface) {
|
|
|
115
115
|
"",
|
|
116
116
|
"Rules:",
|
|
117
117
|
"- Reply with ONLY one fenced json block matching:",
|
|
118
|
-
' { "tools": [{ "name", "description", "risk"?, "critical"?, "disabled"?, "reasoning"? }], "missedSurfaces"?: string[] }',
|
|
118
|
+
' { "tools": [{ "name", "description", "risk"?, "critical"?, "disabled"?, "audience"?, "reasoning"? }], "missedSurfaces"?: string[] }',
|
|
119
119
|
"- tools: include ONLY names from the list above. Rewrite each description so an agent choosing tools understands what it actually does (read the handler source). <= 200 chars each.",
|
|
120
120
|
"- risk: you may RAISE risk (read->write->destructive) when the handler is more dangerous than labeled; never lower it. Mark irreversible operations critical: true.",
|
|
121
121
|
"- A tool listed as disabled was statically unclassifiable. If you can read its handler and grade it, set disabled: false WITH a risk and one-line reasoning. Leave it out otherwise.",
|
|
122
|
+
"- audience: who the handler's own auth admits — \"end-user\" (a signed-in customer acting on their own data),",
|
|
123
|
+
" \"operator\" (admin/staff/support consoles), or \"internal\" (machine-to-machine: webhooks, cron, reconciliation,",
|
|
124
|
+
" service tokens). Read the auth checks, not the route name. When unsure, default to internal — non-end-user",
|
|
125
|
+
" tools are excluded from the embedded agent by default, and a wrong \"end-user\" grade exposes a privileged surface.",
|
|
122
126
|
"- missedSurfaces: API surfaces you found that the list is missing (path + one line). Do not invent tools for them.",
|
|
123
127
|
].join("\n");
|
|
124
128
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ServerActionRegistration } from "@vendoai/actions";
|
|
1
|
+
import { type ServerActionRegistration } from "@vendoai/actions/sync";
|
|
2
2
|
import { type AuthMatch } from "./init-auth.js";
|
|
3
3
|
/** The wired preset line plus its escape-hatch comment. The lead-in stays
|
|
4
4
|
honest about how the preset got here: detection cites the found
|
|
@@ -46,5 +46,14 @@ export declare function wiringServerActions(root: string): Promise<ServerActionR
|
|
|
46
46
|
* sorted registrations, stable aliases — so re-init stays idempotent.
|
|
47
47
|
*/
|
|
48
48
|
export declare function serverActionsModuleSource(root: string, wiringDir: string, registrations: ServerActionRegistration[]): string;
|
|
49
|
+
/** The runtime-neutral composition (`--framework custom`): plain Request →
|
|
50
|
+
* Response with env passed per call, so ONE generated module serves any
|
|
51
|
+
* Web-standard host — Cloudflare Workers, Bun, Deno, Hono, Lambda adapters.
|
|
52
|
+
* Construction is lazy (first request): the safe shape everywhere and the
|
|
53
|
+
* only legal one at Workers module scope. With a Vendo Cloud key the four
|
|
54
|
+
* infrastructure seams wire the Cloud adapters explicitly per the adapter
|
|
55
|
+
* rule (reference shape: the vendo-on-Workers field integration,
|
|
56
|
+
* 2026-07-21). */
|
|
57
|
+
export declare function customServerSource(typescript: boolean, auth?: AuthMatch | null): string;
|
|
49
58
|
export declare function expressServerSource(typescript: boolean, auth?: AuthMatch | null): string;
|
|
50
59
|
export declare const VENDO_ENV_EXAMPLE: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join, relative, sep } from "node:path";
|
|
2
|
-
import { extractServerActions, serverActionRegistrations, } from "@vendoai/actions";
|
|
2
|
+
import { extractServerActions, serverActionRegistrations, } from "@vendoai/actions/sync";
|
|
3
3
|
import { AUTH_FAMILY_INFO, AUTH_PRESET_SPECIFIER } from "./init-auth.js";
|
|
4
4
|
/** The wired preset line plus its escape-hatch comment. The lead-in stays
|
|
5
5
|
honest about how the preset got here: detection cites the found
|
|
@@ -161,6 +161,98 @@ export function serverActionsModuleSource(root, wiringDir, registrations) {
|
|
|
161
161
|
return `${header}${imports.join("\n")}\n\n` +
|
|
162
162
|
`export const serverActions = {\n${entries.join("\n")}\n};\n`;
|
|
163
163
|
}
|
|
164
|
+
/** The runtime-neutral composition (`--framework custom`): plain Request →
|
|
165
|
+
* Response with env passed per call, so ONE generated module serves any
|
|
166
|
+
* Web-standard host — Cloudflare Workers, Bun, Deno, Hono, Lambda adapters.
|
|
167
|
+
* Construction is lazy (first request): the safe shape everywhere and the
|
|
168
|
+
* only legal one at Workers module scope. With a Vendo Cloud key the four
|
|
169
|
+
* infrastructure seams wire the Cloud adapters explicitly per the adapter
|
|
170
|
+
* rule (reference shape: the vendo-on-Workers field integration,
|
|
171
|
+
* 2026-07-21). */
|
|
172
|
+
export function customServerSource(typescript, auth = null) {
|
|
173
|
+
const registrySpecifier = typescript ? "./registry" : "./registry.mjs";
|
|
174
|
+
const envType = typescript
|
|
175
|
+
? `\nexport interface VendoEnv {\n` +
|
|
176
|
+
` VENDO_API_KEY?: string;\n` +
|
|
177
|
+
` VENDO_CLOUD_URL?: string;\n` +
|
|
178
|
+
` VENDO_BASE_URL?: string;\n` +
|
|
179
|
+
`}\n`
|
|
180
|
+
: "";
|
|
181
|
+
const signatures = typescript
|
|
182
|
+
? {
|
|
183
|
+
vendoVar: `let vendo: ReturnType<typeof createVendo> | null = null;`,
|
|
184
|
+
getVendo: `(env: VendoEnv = {})`,
|
|
185
|
+
handle: `(request: Request, env: VendoEnv = {}): Promise<Response>`,
|
|
186
|
+
}
|
|
187
|
+
: {
|
|
188
|
+
vendoVar: `let vendo = null;`,
|
|
189
|
+
getVendo: `(env = {})`,
|
|
190
|
+
handle: `(request, env = {})`,
|
|
191
|
+
};
|
|
192
|
+
const clientHint = typescript
|
|
193
|
+
? ` * // in the client entry — theme.json adopts the host brand (08 §4);\n` +
|
|
194
|
+
` * // the cast narrows TypeScript's widened JSON-module string literals;\n` +
|
|
195
|
+
` * // <VendoOverlay /> is the visible surface (launcher pill + panel):\n` +
|
|
196
|
+
` * import { VendoOverlay, VendoRoot } from "@vendoai/vendo/react";\n` +
|
|
197
|
+
` * import { registry } from "<path-to>/vendo/registry";\n` +
|
|
198
|
+
` * import theme from "<path-to>/.vendo/theme.json";\n` +
|
|
199
|
+
` * import type { VendoTheme } from "@vendoai/vendo";\n` +
|
|
200
|
+
` * root.render(<VendoRoot components={registry} theme={theme as VendoTheme}><App /><VendoOverlay /></VendoRoot>);\n`
|
|
201
|
+
: ` * // in the client entry — theme.json adopts the host brand (08 §4);\n` +
|
|
202
|
+
` * // <VendoOverlay /> is the visible surface (launcher pill + panel):\n` +
|
|
203
|
+
` * import { VendoOverlay, VendoRoot } from "@vendoai/vendo/react";\n` +
|
|
204
|
+
` * import { registry } from "<path-to>/vendo/registry.mjs";\n` +
|
|
205
|
+
` * import theme from "<path-to>/.vendo/theme.json";\n` +
|
|
206
|
+
` * root.render(<VendoRoot components={registry} theme={theme}><App /><VendoOverlay /></VendoRoot>);\n`;
|
|
207
|
+
return `/**\n` +
|
|
208
|
+
` * Route your runtime's requests through this module:\n` +
|
|
209
|
+
` * // Cloudflare Workers:\n` +
|
|
210
|
+
` * // export default { fetch: (request, env) => handleVendoRequest(request, env) };\n` +
|
|
211
|
+
` * // Bun / Deno / Hono / Node: serve your /api/vendo routes through\n` +
|
|
212
|
+
` * // handleVendoRequest(request)\n` +
|
|
213
|
+
clientHint +
|
|
214
|
+
` * Deployed hosts must set VENDO_BASE_URL to their public origin\n` +
|
|
215
|
+
` * (credential forwarding fails closed without it — vendo doctor checks).\n` +
|
|
216
|
+
` */\n` +
|
|
217
|
+
`import { createAnthropic } from "@ai-sdk/anthropic";\n` +
|
|
218
|
+
authImportLine(auth) +
|
|
219
|
+
`import { cloudConnections, cloudSandbox, cloudTools, createVendo, hostedStore } from "@vendoai/vendo/server";\n` +
|
|
220
|
+
`import { registry } from ${JSON.stringify(registrySpecifier)};\n` +
|
|
221
|
+
envType +
|
|
222
|
+
`\n${signatures.vendoVar}\n` +
|
|
223
|
+
`\n/** Lazy singleton: constructed on the first request, never at module\n` +
|
|
224
|
+
` scope — Workers forbids I/O and timers there, and lazy is correct on\n` +
|
|
225
|
+
` every other runtime too. */\n` +
|
|
226
|
+
`function getVendo${signatures.getVendo} {\n` +
|
|
227
|
+
` if (vendo === null) {\n` +
|
|
228
|
+
` const processEnv = globalThis.process?.env ?? {};\n` +
|
|
229
|
+
` const apiKey = env.VENDO_API_KEY ?? processEnv.VENDO_API_KEY;\n` +
|
|
230
|
+
` const baseUrl = (env.VENDO_CLOUD_URL ?? processEnv.VENDO_CLOUD_URL ?? "https://console.vendo.run").replace(/\\/+$/, "");\n` +
|
|
231
|
+
` const cloud = apiKey === undefined || apiKey === "" ? undefined : { apiKey, baseUrl };\n` +
|
|
232
|
+
` vendo = createVendo({\n` +
|
|
233
|
+
(auth === null ? anonymousPrincipalLines() : authConfigLines(auth))
|
|
234
|
+
.split("\n").map((line) => (line === "" ? line : ` ${line}`)).join("\n") +
|
|
235
|
+
` catalog: registry,\n` +
|
|
236
|
+
` policy: {}, // .vendo/policy.json: destructive asks, reads run\n` +
|
|
237
|
+
` // With a Vendo Cloud key the infrastructure seams wire the Cloud\n` +
|
|
238
|
+
` // adapters EXPLICITLY (composition decides; blocks never read the\n` +
|
|
239
|
+
` // environment). Without one, pass your own adapters here — model,\n` +
|
|
240
|
+
` // store, connections, sandbox all accept custom implementations.\n` +
|
|
241
|
+
` ...(cloud === undefined ? {} : {\n` +
|
|
242
|
+
` model: createAnthropic({ apiKey: cloud.apiKey, baseURL: \`\${cloud.baseUrl}/api/v1\` })("vendo-default"),\n` +
|
|
243
|
+
` store: hostedStore(cloud),\n` +
|
|
244
|
+
` connections: cloudConnections(cloud),\n` +
|
|
245
|
+
` connectors: [cloudTools(cloud)],\n` +
|
|
246
|
+
` sandbox: cloudSandbox(cloud),\n` +
|
|
247
|
+
` }),\n` +
|
|
248
|
+
` });\n` +
|
|
249
|
+
` }\n` +
|
|
250
|
+
` return vendo;\n` +
|
|
251
|
+
`}\n` +
|
|
252
|
+
`\nexport function handleVendoRequest${signatures.handle} {\n` +
|
|
253
|
+
` return getVendo(env).handler(request);\n` +
|
|
254
|
+
`}\n`;
|
|
255
|
+
}
|
|
164
256
|
export function expressServerSource(typescript, auth = null) {
|
|
165
257
|
const imports = typescript
|
|
166
258
|
? `import { once } from "node:events";\n` +
|
package/dist/cli/init.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ExtractedTool } from "@vendoai/actions";
|
|
2
2
|
import { type CloudStepOptions } from "./cloud-init.js";
|
|
3
3
|
import { type AiExtractionOptions } from "./extract/extraction.js";
|
|
4
4
|
import { type DevCredential } from "../dev-creds/resolve.js";
|
|
@@ -14,7 +14,7 @@ export interface RiskRecommendation {
|
|
|
14
14
|
recommendation: string;
|
|
15
15
|
}
|
|
16
16
|
export interface InitPlan {
|
|
17
|
-
framework: HostFramework;
|
|
17
|
+
framework: Exclude<HostFramework, "unknown"> | "custom";
|
|
18
18
|
root: string;
|
|
19
19
|
writes: string[];
|
|
20
20
|
codeChanges: Array<{
|
|
@@ -54,7 +54,7 @@ export interface InitOptions {
|
|
|
54
54
|
detection comes back "unknown" (there is no safe default to guess).
|
|
55
55
|
"unknown" is excluded: an override that answers nothing would silently
|
|
56
56
|
bypass the non-interactive framework guard. */
|
|
57
|
-
framework?: Exclude<HostFramework, "unknown"
|
|
57
|
+
framework?: Exclude<HostFramework, "unknown"> | "custom";
|
|
58
58
|
/** --cloud-key: answer the cloud-login offer with an existing key — landed
|
|
59
59
|
in .env.local exactly where the mint would put it. */
|
|
60
60
|
cloudKey?: string;
|