@vendoai/vendo 0.4.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 +28 -0
- package/bin/vendo.mjs +4 -0
- package/dist/ai-sdk.d.ts +35 -0
- package/dist/ai-sdk.js +62 -0
- package/dist/auth-presets/auth-js.d.ts +14 -0
- package/dist/auth-presets/auth-js.js +91 -0
- package/dist/auth-presets/auth0.d.ts +23 -0
- package/dist/auth-presets/auth0.js +98 -0
- package/dist/auth-presets/clerk.d.ts +23 -0
- package/dist/auth-presets/clerk.js +65 -0
- package/dist/auth-presets/conformance.d.ts +42 -0
- package/dist/auth-presets/conformance.js +139 -0
- package/dist/auth-presets/identity.d.ts +56 -0
- package/dist/auth-presets/identity.js +152 -0
- package/dist/auth-presets/index.d.ts +14 -0
- package/dist/auth-presets/index.js +12 -0
- package/dist/auth-presets/jwt.d.ts +16 -0
- package/dist/auth-presets/jwt.js +54 -0
- package/dist/auth-presets/shared.d.ts +35 -0
- package/dist/auth-presets/shared.js +1 -0
- package/dist/auth-presets/supabase.d.ts +42 -0
- package/dist/auth-presets/supabase.js +229 -0
- package/dist/byo-approvals.d.ts +37 -0
- package/dist/byo-approvals.js +189 -0
- package/dist/capability-misses.d.ts +38 -0
- package/dist/capability-misses.js +172 -0
- package/dist/catalog.d.ts +19 -0
- package/dist/catalog.js +152 -0
- package/dist/cli/cloud/args.d.ts +3 -0
- package/dist/cli/cloud/args.js +32 -0
- package/dist/cli/cloud/auth.d.ts +17 -0
- package/dist/cli/cloud/auth.js +123 -0
- package/dist/cli/cloud/client.d.ts +28 -0
- package/dist/cli/cloud/client.js +133 -0
- package/dist/cli/cloud/command.d.ts +22 -0
- package/dist/cli/cloud/command.js +73 -0
- package/dist/cli/cloud/deploy.d.ts +26 -0
- package/dist/cli/cloud/deploy.js +187 -0
- package/dist/cli/cloud/device-login.d.ts +32 -0
- package/dist/cli/cloud/device-login.js +259 -0
- package/dist/cli/cloud/index.d.ts +5 -0
- package/dist/cli/cloud/index.js +59 -0
- package/dist/cli/cloud/keys.d.ts +2 -0
- package/dist/cli/cloud/keys.js +31 -0
- package/dist/cli/cloud/members.d.ts +3 -0
- package/dist/cli/cloud/members.js +25 -0
- package/dist/cli/cloud/output.d.ts +4 -0
- package/dist/cli/cloud/output.js +12 -0
- package/dist/cli/cloud/pending-claim.d.ts +29 -0
- package/dist/cli/cloud/pending-claim.js +38 -0
- package/dist/cli/cloud/read.d.ts +3 -0
- package/dist/cli/cloud/read.js +12 -0
- package/dist/cli/cloud/services.d.ts +2 -0
- package/dist/cli/cloud/services.js +9 -0
- package/dist/cli/cloud/session.d.ts +13 -0
- package/dist/cli/cloud/session.js +41 -0
- package/dist/cli/cloud-init.d.ts +60 -0
- package/dist/cli/cloud-init.js +149 -0
- package/dist/cli/dep-versions.d.ts +26 -0
- package/dist/cli/dep-versions.js +77 -0
- package/dist/cli/doctor-codes.d.ts +58 -0
- package/dist/cli/doctor-codes.js +60 -0
- package/dist/cli/doctor-live.d.ts +67 -0
- package/dist/cli/doctor-live.js +169 -0
- package/dist/cli/doctor.d.ts +52 -0
- package/dist/cli/doctor.js +467 -0
- package/dist/cli/eject.d.ts +29 -0
- package/dist/cli/eject.js +141 -0
- package/dist/cli/extract/apply.d.ts +26 -0
- package/dist/cli/extract/apply.js +123 -0
- package/dist/cli/extract/claude-cli-harness.d.ts +18 -0
- package/dist/cli/extract/claude-cli-harness.js +132 -0
- package/dist/cli/extract/claude-harness.d.ts +14 -0
- package/dist/cli/extract/claude-harness.js +127 -0
- package/dist/cli/extract/codex-cli-harness.d.ts +31 -0
- package/dist/cli/extract/codex-cli-harness.js +154 -0
- package/dist/cli/extract/delegate.d.ts +21 -0
- package/dist/cli/extract/delegate.js +77 -0
- package/dist/cli/extract/extraction.d.ts +65 -0
- package/dist/cli/extract/extraction.js +300 -0
- package/dist/cli/extract/gateway-fuel.d.ts +69 -0
- package/dist/cli/extract/gateway-fuel.js +69 -0
- package/dist/cli/extract/harness.d.ts +114 -0
- package/dist/cli/extract/harness.js +84 -0
- package/dist/cli/extract/index.d.ts +9 -0
- package/dist/cli/extract/index.js +9 -0
- package/dist/cli/extract/npx-engine-harness.d.ts +73 -0
- package/dist/cli/extract/npx-engine-harness.js +225 -0
- package/dist/cli/extract/stages.d.ts +149 -0
- package/dist/cli/extract/stages.js +398 -0
- package/dist/cli/framework.d.ts +8 -0
- package/dist/cli/framework.js +36 -0
- package/dist/cli/init-auth.d.ts +67 -0
- package/dist/cli/init-auth.js +142 -0
- package/dist/cli/init-scaffolds.d.ts +28 -0
- package/dist/cli/init-scaffolds.js +235 -0
- package/dist/cli/init.d.ts +115 -0
- package/dist/cli/init.js +1008 -0
- package/dist/cli/mcp/index.d.ts +7 -0
- package/dist/cli/mcp/index.js +59 -0
- package/dist/cli/mcp/registry.d.ts +9 -0
- package/dist/cli/mcp/registry.js +117 -0
- package/dist/cli/mcp/server-json.d.ts +12 -0
- package/dist/cli/mcp/server-json.js +65 -0
- package/dist/cli/mcp/server.schema.json +574 -0
- package/dist/cli/mcp/verify-domain.d.ts +11 -0
- package/dist/cli/mcp/verify-domain.js +48 -0
- package/dist/cli/playground/app/embed-entry.d.ts +20 -0
- package/dist/cli/playground/app/embed-entry.js +46 -0
- package/dist/cli/playground/app/fake-client.d.ts +3 -0
- package/dist/cli/playground/app/fake-client.js +215 -0
- package/dist/cli/playground/app/fixtures.d.ts +47 -0
- package/dist/cli/playground/app/fixtures.js +472 -0
- package/dist/cli/playground/app/main.d.ts +1 -0
- package/dist/cli/playground/app/main.js +108 -0
- package/dist/cli/playground/app/scenario-mount.d.ts +13 -0
- package/dist/cli/playground/app/scenario-mount.js +48 -0
- package/dist/cli/playground/app/scenarios.d.ts +25 -0
- package/dist/cli/playground/app/scenarios.js +152 -0
- package/dist/cli/playground/app/theme-editor.d.ts +13 -0
- package/dist/cli/playground/app/theme-editor.js +149 -0
- package/dist/cli/playground/app/theme-state.d.ts +29 -0
- package/dist/cli/playground/app/theme-state.js +151 -0
- package/dist/cli/playground/bundle.gen.d.ts +2 -0
- package/dist/cli/playground/bundle.gen.js +2 -0
- package/dist/cli/playground/embed-bundle.gen.d.ts +2 -0
- package/dist/cli/playground/embed-bundle.gen.js +2 -0
- package/dist/cli/playground.d.ts +33 -0
- package/dist/cli/playground.js +120 -0
- package/dist/cli/pretty.d.ts +48 -0
- package/dist/cli/pretty.js +330 -0
- package/dist/cli/refine.d.ts +43 -0
- package/dist/cli/refine.js +181 -0
- package/dist/cli/semantics.d.ts +31 -0
- package/dist/cli/semantics.js +122 -0
- package/dist/cli/shared.d.ts +68 -0
- package/dist/cli/shared.js +184 -0
- package/dist/cli/sync.d.ts +36 -0
- package/dist/cli/sync.js +202 -0
- package/dist/cli/theme/color.d.ts +15 -0
- package/dist/cli/theme/color.js +188 -0
- package/dist/cli/theme/css-vars.d.ts +13 -0
- package/dist/cli/theme/css-vars.js +40 -0
- package/dist/cli/theme/entry-candidates.d.ts +5 -0
- package/dist/cli/theme/entry-candidates.js +22 -0
- package/dist/cli/theme/extract-theme.d.ts +184 -0
- package/dist/cli/theme/extract-theme.js +358 -0
- package/dist/cli/theme/walk.d.ts +2 -0
- package/dist/cli/theme/walk.js +24 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +333 -0
- package/dist/cloud-apps.d.ts +17 -0
- package/dist/cloud-apps.js +46 -0
- package/dist/cloud-console.d.ts +32 -0
- package/dist/cloud-console.js +72 -0
- package/dist/cloud-tools.d.ts +23 -0
- package/dist/cloud-tools.js +196 -0
- package/dist/connections.d.ts +71 -0
- package/dist/connections.js +192 -0
- package/dist/deployment-identity.d.ts +12 -0
- package/dist/deployment-identity.js +69 -0
- package/dist/dev-creds/model.d.ts +65 -0
- package/dist/dev-creds/model.js +159 -0
- package/dist/dev-creds/resolve.d.ts +36 -0
- package/dist/dev-creds/resolve.js +57 -0
- package/dist/hosted-store.d.ts +52 -0
- package/dist/hosted-store.js +322 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1 -0
- package/dist/mastra.d.ts +46 -0
- package/dist/mastra.js +88 -0
- package/dist/react.d.ts +10 -0
- package/dist/react.js +32 -0
- package/dist/refine.d.ts +244 -0
- package/dist/refine.js +599 -0
- package/dist/remixable.d.ts +18 -0
- package/dist/remixable.js +42 -0
- package/dist/runtime-capture.d.ts +18 -0
- package/dist/runtime-capture.js +111 -0
- package/dist/sandbox-wire.d.ts +101 -0
- package/dist/sandbox-wire.js +101 -0
- package/dist/sandbox.d.ts +38 -0
- package/dist/sandbox.js +389 -0
- package/dist/server.d.ts +216 -0
- package/dist/server.js +1320 -0
- package/dist/sync-impact.d.ts +14 -0
- package/dist/sync-impact.js +75 -0
- package/dist/turn-liveness.d.ts +33 -0
- package/dist/turn-liveness.js +105 -0
- package/dist/wire/approvals.d.ts +10 -0
- package/dist/wire/approvals.js +59 -0
- package/dist/wire/apps.d.ts +5 -0
- package/dist/wire/apps.js +155 -0
- package/dist/wire/automations.d.ts +5 -0
- package/dist/wire/automations.js +57 -0
- package/dist/wire/box.d.ts +3 -0
- package/dist/wire/box.js +268 -0
- package/dist/wire/connections.d.ts +5 -0
- package/dist/wire/connections.js +47 -0
- package/dist/wire/context.d.ts +22 -0
- package/dist/wire/context.js +190 -0
- package/dist/wire/doctor.d.ts +19 -0
- package/dist/wire/doctor.js +116 -0
- package/dist/wire/misc.d.ts +18 -0
- package/dist/wire/misc.js +227 -0
- package/dist/wire/shared.d.ts +158 -0
- package/dist/wire/shared.js +134 -0
- package/dist/wire/threads.d.ts +3 -0
- package/dist/wire/threads.js +68 -0
- package/package.json +151 -0
- package/skills/vendo-setup/SKILL.md +128 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { stdin, stdout } from "node:process";
|
|
6
|
+
import { DevModelController } from "../dev-creds/model.js";
|
|
7
|
+
import { runRefine } from "../refine.js";
|
|
8
|
+
import { consoleOutput, exists, withCommandRun, writeText } from "./shared.js";
|
|
9
|
+
const INTERVIEW_QUESTIONS = [
|
|
10
|
+
"What should users be able to ask the agent for that today's tools can't do in one step? (Enter to skip)",
|
|
11
|
+
"Any tools that look mislabeled, misdescribed, or that should be disabled? (Enter to skip)",
|
|
12
|
+
];
|
|
13
|
+
async function importHostModule(root, specifier) {
|
|
14
|
+
if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.startsWith("file:")) {
|
|
15
|
+
const url = specifier.startsWith("file:") ? specifier : pathToFileURL(resolve(root, specifier)).href;
|
|
16
|
+
return await import(url);
|
|
17
|
+
}
|
|
18
|
+
const require = createRequire(join(root, "package.json"));
|
|
19
|
+
return await import(pathToFileURL(require.resolve(specifier)).href);
|
|
20
|
+
}
|
|
21
|
+
/** BYO key through the existing provider-agnostic seam: `--model-import` loads
|
|
22
|
+
* the host's own ai-SDK model module; otherwise the shared dev-credential
|
|
23
|
+
* ladder composes (the same resolver init, doctor, and createVendo ride):
|
|
24
|
+
* provider env keys first, then VENDO_API_KEY via the Cloud model gateway.
|
|
25
|
+
* Resolved EAGERLY so a credential problem fails here, with instructions,
|
|
26
|
+
* instead of mid-run. */
|
|
27
|
+
export async function resolveRefineModel(options) {
|
|
28
|
+
if (options.modelImport !== undefined) {
|
|
29
|
+
let loaded;
|
|
30
|
+
try {
|
|
31
|
+
loaded = await importHostModule(options.root, options.modelImport);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error(`could not import ${options.modelImport}: ${error instanceof Error ? error.message : "unknown error"}. `
|
|
35
|
+
+ "Pass a runnable JS module (a TypeScript source module needs your runtime to strip types) "
|
|
36
|
+
+ "exporting your ai-SDK model as `model` (or default).");
|
|
37
|
+
}
|
|
38
|
+
const model = (loaded["model"] ?? loaded["default"]);
|
|
39
|
+
if (model === undefined) {
|
|
40
|
+
throw new Error(`${options.modelImport} does not export an ai-SDK model as \`model\` (or default)`);
|
|
41
|
+
}
|
|
42
|
+
return model;
|
|
43
|
+
}
|
|
44
|
+
const controller = new DevModelController({
|
|
45
|
+
root: options.root,
|
|
46
|
+
env: options.env,
|
|
47
|
+
...(options.importModule === undefined ? {} : { importModule: options.importModule }),
|
|
48
|
+
});
|
|
49
|
+
const resolution = await controller.resolve();
|
|
50
|
+
if (resolution.mode === "unavailable") {
|
|
51
|
+
throw new Error(`${resolution.message} Or pass --model-import <specifier> pointing at a module that exports your ai-SDK \`model\`.`);
|
|
52
|
+
}
|
|
53
|
+
return resolution.model;
|
|
54
|
+
}
|
|
55
|
+
async function defaultInterview(questions) {
|
|
56
|
+
if (!stdin.isTTY)
|
|
57
|
+
return [];
|
|
58
|
+
const readline = createInterface({ input: stdin, output: stdout });
|
|
59
|
+
const answers = [];
|
|
60
|
+
try {
|
|
61
|
+
for (const question of questions) {
|
|
62
|
+
const answer = (await readline.question(`${question}\n> `)).trim();
|
|
63
|
+
if (answer !== "")
|
|
64
|
+
answers.push(answer);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
readline.close();
|
|
69
|
+
}
|
|
70
|
+
return answers;
|
|
71
|
+
}
|
|
72
|
+
async function defaultConfirm(change, output) {
|
|
73
|
+
if (!stdin.isTTY) {
|
|
74
|
+
output.error(`skipped ${change.path}; re-run interactively or with --yes to apply it`);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const readline = createInterface({ input: stdin, output: stdout });
|
|
78
|
+
try {
|
|
79
|
+
const answer = await readline.question(`Apply ${change.path}? [y/N] `);
|
|
80
|
+
return ["y", "yes"].includes(answer.trim().toLowerCase());
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
readline.close();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function writeTranscript(root, transcript) {
|
|
87
|
+
const stamp = transcript.startedAt.replace(/[:.]/g, "-");
|
|
88
|
+
const path = join(root, ".vendo", "data", "refine", `${stamp}.json`);
|
|
89
|
+
await writeText(path, `${JSON.stringify(transcript, null, 2)}\n`);
|
|
90
|
+
return path;
|
|
91
|
+
}
|
|
92
|
+
function printRun(result, output) {
|
|
93
|
+
for (const probe of result.probes) {
|
|
94
|
+
output.log(`probe ${probe.tool}: ${probe.status}`);
|
|
95
|
+
for (const check of probe.checks) {
|
|
96
|
+
output.log(` ${check.ok ? "ok" : "failed"}: ${check.name} — ${check.detail}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const drop of result.dropped) {
|
|
100
|
+
output.error(`dropped ${drop.kind} ${drop.target}: ${drop.reason}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export async function runRefineCommand(options) {
|
|
104
|
+
const root = resolve(options.targetDir);
|
|
105
|
+
const output = options.output ?? consoleOutput;
|
|
106
|
+
return withCommandRun({
|
|
107
|
+
command: "refine",
|
|
108
|
+
root,
|
|
109
|
+
...(options.telemetry === undefined ? {} : { telemetry: options.telemetry }),
|
|
110
|
+
}, (failure) => refineCommand(options, output, root, failure));
|
|
111
|
+
}
|
|
112
|
+
async function refineCommand(options, output, root, failure) {
|
|
113
|
+
const env = options.env ?? process.env;
|
|
114
|
+
if (!await exists(join(root, ".vendo", "tools.json"))) {
|
|
115
|
+
failure.failedStep = "tools";
|
|
116
|
+
output.error("refine needs .vendo/tools.json — run `vendo init` (or `vendo sync`) first");
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
let model;
|
|
120
|
+
try {
|
|
121
|
+
model = options.model ?? await resolveRefineModel({
|
|
122
|
+
root,
|
|
123
|
+
...(options.modelImport === undefined ? {} : { modelImport: options.modelImport }),
|
|
124
|
+
env,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
failure.failedStep = "model";
|
|
129
|
+
output.error(error instanceof Error ? error.message : "model resolution failed");
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
let interview = options.asks ?? [];
|
|
133
|
+
if (interview.length === 0 && options.yes !== true) {
|
|
134
|
+
interview = await (options.interview ?? defaultInterview)(INTERVIEW_QUESTIONS);
|
|
135
|
+
}
|
|
136
|
+
let result;
|
|
137
|
+
try {
|
|
138
|
+
result = await runRefine({
|
|
139
|
+
root,
|
|
140
|
+
model,
|
|
141
|
+
interview,
|
|
142
|
+
...(options.url === undefined ? {} : { url: options.url }),
|
|
143
|
+
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
failure.failedStep = "run";
|
|
148
|
+
output.error(error instanceof Error ? error.message : "vendo refine failed");
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
printRun(result, output);
|
|
152
|
+
if (result.changes.length === 0) {
|
|
153
|
+
output.log("No changes proposed.");
|
|
154
|
+
const transcriptPath = await writeTranscript(root, result.transcript);
|
|
155
|
+
output.log(`Transcript: ${transcriptPath}`);
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
let applied = 0;
|
|
159
|
+
for (const change of result.changes) {
|
|
160
|
+
output.log(`\nProposed change:\n${change.diff}\n`);
|
|
161
|
+
for (const warning of change.warnings)
|
|
162
|
+
output.error(`warning: ${warning}`);
|
|
163
|
+
const approved = options.yes === true
|
|
164
|
+
|| await (options.confirm ?? ((candidate) => defaultConfirm(candidate, output)))(change);
|
|
165
|
+
if (approved) {
|
|
166
|
+
await writeText(join(root, ...change.path.split("/")), change.after);
|
|
167
|
+
applied += 1;
|
|
168
|
+
output.log(`wrote ${change.path}`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
output.error(`skipped ${change.path}; run \`vendo refine\` again to re-propose`);
|
|
172
|
+
}
|
|
173
|
+
result.transcript.decisions.push({ path: change.path, applied: approved });
|
|
174
|
+
}
|
|
175
|
+
const transcriptPath = await writeTranscript(root, result.transcript);
|
|
176
|
+
output.log(`\nApplied ${applied} of ${result.changes.length} proposed change(s). Transcript: ${transcriptPath}`);
|
|
177
|
+
if (applied > 0) {
|
|
178
|
+
output.log("Review the diffs with `git diff .vendo` and commit them; the registry loads capabilities and overrides on the next boot.");
|
|
179
|
+
}
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* W3 (v3 spec §Context) — `.vendo/semantics.json`, written by `vendo sync`.
|
|
3
|
+
*
|
|
4
|
+
* Per tool response field: `money(cents|dollars)`, `date(iso|epoch)`,
|
|
5
|
+
* `enum(value→label)`, `id(entity)`, `percent(ratio|0-100)` — everything else
|
|
6
|
+
* stays plain and is omitted. Priority per field:
|
|
7
|
+
*
|
|
8
|
+
* 1. host annotation (`overrides.json.tools[name].semantics`)
|
|
9
|
+
* 2. what this file already says (inference runs ONCE — a host edit here
|
|
10
|
+
* is never overwritten, and an inference never churns)
|
|
11
|
+
* 3. fresh inference (the dev server samples each zero-input read tool at
|
|
12
|
+
* POST /sync/semantics and returns classifications, never values)
|
|
13
|
+
*
|
|
14
|
+
* The `domains` manifest (has / has-NOT) is derived from tool names on FIRST
|
|
15
|
+
* sync and host-owned afterwards.
|
|
16
|
+
*/
|
|
17
|
+
export interface SemanticsSyncOptions {
|
|
18
|
+
vendoDir: string;
|
|
19
|
+
/** The dev-server wire base (same seam as /sync/impact). */
|
|
20
|
+
url: string;
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
|
+
/** CLI-note sink (unreachable server, malformed file). */
|
|
23
|
+
note: (message: string) => void;
|
|
24
|
+
}
|
|
25
|
+
/** host_listAccountTransactions → "account transactions". */
|
|
26
|
+
export declare const domainFromToolName: (name: string) => string | undefined;
|
|
27
|
+
export declare const deriveDomains: (toolNames: readonly string[]) => string[];
|
|
28
|
+
/** Read tools + annotations + the existing file, fetch one-time inference,
|
|
29
|
+
* merge by priority, and write `.vendo/semantics.json`. Fail-soft: any
|
|
30
|
+
* problem becomes a note, never a sync failure. */
|
|
31
|
+
export declare function syncSemantics(options: SemanticsSyncOptions): Promise<void>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { fieldSemanticSchema, semanticsFileSchema, VENDO_SEMANTICS_FORMAT, } from "@vendoai/core";
|
|
4
|
+
import { overridesFileSchema, toolsFileSchema } from "@vendoai/actions";
|
|
5
|
+
const readJson = async (file) => {
|
|
6
|
+
try {
|
|
7
|
+
return JSON.parse(await fs.readFile(file, "utf8"));
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
/** Verb tokens dropped from a tool name when deriving its data domain. */
|
|
14
|
+
const NAME_VERBS = /^(get|list|create|update|delete|set|send|reset|simulate|search|fetch|read|add|remove|post|put|patch)$/i;
|
|
15
|
+
/** Whole tools that are plumbing, not a data domain. */
|
|
16
|
+
const NOISE_TOKENS = /^(auth|demo|voice|session|sessions|webhook|webhooks|health|ping)$/i;
|
|
17
|
+
/** host_listAccountTransactions → "account transactions". */
|
|
18
|
+
export const domainFromToolName = (name) => {
|
|
19
|
+
const tokens = name
|
|
20
|
+
.replace(/^host_/, "")
|
|
21
|
+
.split(/[_.]/)
|
|
22
|
+
.flatMap((part) => part.split(/(?=[A-Z])/))
|
|
23
|
+
.map((token) => token.toLowerCase())
|
|
24
|
+
.filter((token) => token.length > 0);
|
|
25
|
+
if (tokens.some((token) => NOISE_TOKENS.test(token)))
|
|
26
|
+
return undefined;
|
|
27
|
+
const nouns = tokens.filter((token) => !NAME_VERBS.test(token));
|
|
28
|
+
if (nouns.length === 0)
|
|
29
|
+
return undefined;
|
|
30
|
+
return nouns.join(" ");
|
|
31
|
+
};
|
|
32
|
+
export const deriveDomains = (toolNames) => [...new Set(toolNames.map(domainFromToolName).filter((domain) => domain !== undefined))].sort();
|
|
33
|
+
const fetchInferred = async (options) => {
|
|
34
|
+
try {
|
|
35
|
+
const response = await (options.fetchImpl ?? fetch)(`${options.url}/sync/semantics`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
38
|
+
body: "{}",
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok)
|
|
41
|
+
throw new Error(`sync semantics returned ${response.status}`);
|
|
42
|
+
const body = await response.json();
|
|
43
|
+
if (typeof body !== "object" || body === null || typeof body.tools !== "object" || body.tools === null) {
|
|
44
|
+
throw new Error("invalid sync semantics response");
|
|
45
|
+
}
|
|
46
|
+
// Validate every entry; a malformed one drops rather than poisoning the file.
|
|
47
|
+
const tools = {};
|
|
48
|
+
for (const [tool, fields] of Object.entries(body.tools)) {
|
|
49
|
+
const cleaned = {};
|
|
50
|
+
for (const [path, semantic] of Object.entries(fields)) {
|
|
51
|
+
const parsed = fieldSemanticSafe(semantic);
|
|
52
|
+
if (parsed !== undefined)
|
|
53
|
+
cleaned[path] = parsed;
|
|
54
|
+
}
|
|
55
|
+
if (Object.keys(cleaned).length > 0)
|
|
56
|
+
tools[tool] = cleaned;
|
|
57
|
+
}
|
|
58
|
+
return tools;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
options.note(`semantics inference skipped — dev server not reachable at ${options.url}`);
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const fieldSemanticSafe = (value) => {
|
|
66
|
+
const result = fieldSemanticSchema.safeParse(value);
|
|
67
|
+
return result.success ? result.data : undefined;
|
|
68
|
+
};
|
|
69
|
+
/** Read tools + annotations + the existing file, fetch one-time inference,
|
|
70
|
+
* merge by priority, and write `.vendo/semantics.json`. Fail-soft: any
|
|
71
|
+
* problem becomes a note, never a sync failure. */
|
|
72
|
+
export async function syncSemantics(options) {
|
|
73
|
+
const toolsRaw = await readJson(join(options.vendoDir, "tools.json"));
|
|
74
|
+
const toolsParse = toolsFileSchema.safeParse(toolsRaw);
|
|
75
|
+
if (!toolsParse.success)
|
|
76
|
+
return; // no tools file → nothing to describe
|
|
77
|
+
const toolNames = toolsParse.data.tools.map((tool) => tool.name);
|
|
78
|
+
const overridesRaw = await readJson(join(options.vendoDir, "overrides.json"));
|
|
79
|
+
const overridesParse = overridesFileSchema.safeParse(overridesRaw);
|
|
80
|
+
const annotations = {};
|
|
81
|
+
if (overridesParse.success) {
|
|
82
|
+
for (const [tool, override] of Object.entries(overridesParse.data.tools)) {
|
|
83
|
+
if (override.semantics !== undefined)
|
|
84
|
+
annotations[tool] = override.semantics;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const file = join(options.vendoDir, "semantics.json");
|
|
88
|
+
const existingRaw = await readJson(file);
|
|
89
|
+
const existingParse = semanticsFileSchema.safeParse(existingRaw);
|
|
90
|
+
const existing = existingParse.success ? existingParse.data : undefined;
|
|
91
|
+
if (existingRaw !== undefined && existing === undefined) {
|
|
92
|
+
options.note("semantics.json is malformed; regenerating it (host edits in the old file are not carried over)");
|
|
93
|
+
}
|
|
94
|
+
const inferred = await fetchInferred(options) ?? {};
|
|
95
|
+
const tools = {};
|
|
96
|
+
for (const name of toolNames) {
|
|
97
|
+
const merged = {
|
|
98
|
+
...inferred[name],
|
|
99
|
+
...existing?.tools[name],
|
|
100
|
+
...annotations[name],
|
|
101
|
+
};
|
|
102
|
+
if (Object.keys(merged).length > 0)
|
|
103
|
+
tools[name] = merged;
|
|
104
|
+
}
|
|
105
|
+
const next = {
|
|
106
|
+
format: VENDO_SEMANTICS_FORMAT,
|
|
107
|
+
note: "Generated by `vendo sync` and safe to edit: field entries are inferred ONCE and your edits are preserved; overrides.json tools[name].semantics wins over everything. `domains` is derived from tool names on first sync and yours afterwards — keep has/hasNot honest, generation treats them as fact.",
|
|
108
|
+
tools,
|
|
109
|
+
// First sync derives the positive list; afterwards the manifest is
|
|
110
|
+
// host-owned (curation survives every sync).
|
|
111
|
+
domains: existing?.domains ?? { has: deriveDomains(toolNames), hasNot: [] },
|
|
112
|
+
};
|
|
113
|
+
const bytes = `${JSON.stringify(next, null, 2)}\n`;
|
|
114
|
+
try {
|
|
115
|
+
if (await fs.readFile(file, "utf8") === bytes)
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// absent — write below
|
|
120
|
+
}
|
|
121
|
+
await fs.writeFile(file, bytes, "utf8");
|
|
122
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type Telemetry } from "@vendoai/telemetry";
|
|
2
|
+
export declare const CLI_VERSION = "0.4.0";
|
|
3
|
+
export interface Output {
|
|
4
|
+
log(message: string): void;
|
|
5
|
+
error(message: string): void;
|
|
6
|
+
}
|
|
7
|
+
export declare const consoleOutput: Output;
|
|
8
|
+
export declare function askYesNo(question: string, defaultYes?: boolean): Promise<boolean>;
|
|
9
|
+
export declare function exists(path: string): Promise<boolean>;
|
|
10
|
+
export declare function readOptional(path: string): Promise<string | null>;
|
|
11
|
+
export declare function writeText(path: string, content: string): Promise<void>;
|
|
12
|
+
/** The injectable telemetry deps every CLI command's options carry
|
|
13
|
+
(init/doctor already ride this exact shape). */
|
|
14
|
+
export interface TelemetryOptions {
|
|
15
|
+
home?: string;
|
|
16
|
+
env?: Record<string, string | undefined>;
|
|
17
|
+
posthogKey?: string;
|
|
18
|
+
fetchImpl?: typeof fetch;
|
|
19
|
+
/** The command's TARGET project dir: projectIdHash/packageManager derive
|
|
20
|
+
from it (not the shell cwd — `vendo sync ../app` must attribute to
|
|
21
|
+
../app), and it is where the .env.local cloud-key read looks. Defaults
|
|
22
|
+
to process.cwd(). */
|
|
23
|
+
cwd?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The value of one NAME=value line in `<root>/.env.local`. Matches dotenv
|
|
27
|
+
* semantics for hand-authored entries: surrounding quotes are stripped, and
|
|
28
|
+
* unquoted values lose their ` #…` inline comment. Non-throwing: a missing
|
|
29
|
+
* or unreadable file is null. Sync on purpose — telemetry client creation is
|
|
30
|
+
* synchronous.
|
|
31
|
+
*/
|
|
32
|
+
export declare function envLocalValueSync(root: string, name: string): string | null;
|
|
33
|
+
/** One value grammar for every CLI dotenv reader (envLocalValueSync, doctor's
|
|
34
|
+
* readDotEnvFallback): matching surrounding quotes are stripped; unquoted
|
|
35
|
+
* values lose their ` #…` inline comment. */
|
|
36
|
+
export declare function normalizeDotEnvValue(value: string): string;
|
|
37
|
+
export declare function toolingTelemetry(options?: TelemetryOptions & {
|
|
38
|
+
log?: (message: string) => void;
|
|
39
|
+
}): Telemetry;
|
|
40
|
+
export declare function errorClass(error: unknown): string;
|
|
41
|
+
/** The closed `command_run.command` enum (TELEMETRY.md). init keeps its own
|
|
42
|
+
richer events; "theme" is reserved — no `vendo theme` entrypoint exists
|
|
43
|
+
yet. "login" is the top-level claim ceremony; init's embedded run of the
|
|
44
|
+
same ceremony stays "cloud-init". */
|
|
45
|
+
export type CommandName = "login" | "extract" | "theme" | "eject" | "playground" | "refine" | "sync" | "cloud-init" | "mcp";
|
|
46
|
+
/** Cloud-lane project identity (projectName + repoHost) for commands that
|
|
47
|
+
have a target project dir. Anonymous-lane sends strip both keys. */
|
|
48
|
+
export declare function cloudProjectProps(root: string | undefined): Promise<Record<string, unknown>>;
|
|
49
|
+
/**
|
|
50
|
+
* Run a CLI command body with one `command_run` telemetry row: ok is the
|
|
51
|
+
* exit code (0 = true), a throw records the error class and rethrows, and a
|
|
52
|
+
* body can name the step it failed at via the mutable `failure` argument.
|
|
53
|
+
* The body also receives the telemetry client for extra events (extract's
|
|
54
|
+
* `extract_completed`). Telemetry NEVER changes command behavior or exit
|
|
55
|
+
* codes — the client never throws, and this wrapper's own prop assembly is
|
|
56
|
+
* guarded too.
|
|
57
|
+
*/
|
|
58
|
+
export declare function withCommandRun(input: {
|
|
59
|
+
command: CommandName;
|
|
60
|
+
telemetry?: TelemetryOptions;
|
|
61
|
+
/** Host project dir for the cloud lane's projectName/repoHost; omitted
|
|
62
|
+
for commands without a target project (playground, mcp). */
|
|
63
|
+
root?: string;
|
|
64
|
+
}, body: (failure: {
|
|
65
|
+
failedStep?: string;
|
|
66
|
+
}, telemetry: Telemetry) => Promise<number>): Promise<number>;
|
|
67
|
+
/** Lockfile-derived package manager for `run dev` (doctor's probe starter). */
|
|
68
|
+
export declare function detectPackageManager(root: string): Promise<"pnpm" | "yarn" | "bun" | "npm">;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline/promises";
|
|
5
|
+
import { stdin, stdout } from "node:process";
|
|
6
|
+
import { initTelemetry, repoHost } from "@vendoai/telemetry";
|
|
7
|
+
export const CLI_VERSION = "0.4.0";
|
|
8
|
+
export const consoleOutput = {
|
|
9
|
+
log: (message) => console.log(message),
|
|
10
|
+
error: (message) => console.error(message),
|
|
11
|
+
};
|
|
12
|
+
export async function askYesNo(question, defaultYes = false) {
|
|
13
|
+
if (!stdin.isTTY || !stdout.isTTY)
|
|
14
|
+
return false;
|
|
15
|
+
const prompt = createInterface({ input: stdin, output: stdout });
|
|
16
|
+
try {
|
|
17
|
+
const answer = (await prompt.question(`${question} ${defaultYes ? "[Y/n]" : "[y/N]"} `)).trim().toLowerCase();
|
|
18
|
+
if (answer === "")
|
|
19
|
+
return defaultYes;
|
|
20
|
+
return ["y", "yes"].includes(answer);
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
prompt.close();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function exists(path) {
|
|
27
|
+
return access(path).then(() => true, () => false);
|
|
28
|
+
}
|
|
29
|
+
export async function readOptional(path) {
|
|
30
|
+
try {
|
|
31
|
+
return await readFile(path, "utf8");
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.code === "ENOENT")
|
|
35
|
+
return null;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function writeText(path, content) {
|
|
40
|
+
await mkdir(dirname(path), { recursive: true });
|
|
41
|
+
await writeFile(path, content, "utf8");
|
|
42
|
+
}
|
|
43
|
+
function noTelemetry() {
|
|
44
|
+
return { async track() { } };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The value of one NAME=value line in `<root>/.env.local`. Matches dotenv
|
|
48
|
+
* semantics for hand-authored entries: surrounding quotes are stripped, and
|
|
49
|
+
* unquoted values lose their ` #…` inline comment. Non-throwing: a missing
|
|
50
|
+
* or unreadable file is null. Sync on purpose — telemetry client creation is
|
|
51
|
+
* synchronous.
|
|
52
|
+
*/
|
|
53
|
+
export function envLocalValueSync(root, name) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = readFileSync(join(root, ".env.local"), "utf8");
|
|
56
|
+
const match = raw.match(new RegExp(`^\\s*${name}\\s*=\\s*(.+?)\\s*$`, "m"));
|
|
57
|
+
const value = match?.[1];
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return null;
|
|
60
|
+
return normalizeDotEnvValue(value);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** One value grammar for every CLI dotenv reader (envLocalValueSync, doctor's
|
|
67
|
+
* readDotEnvFallback): matching surrounding quotes are stripped; unquoted
|
|
68
|
+
* values lose their ` #…` inline comment. */
|
|
69
|
+
export function normalizeDotEnvValue(value) {
|
|
70
|
+
const quoted = value.match(/^(["'])(.*)\1$/);
|
|
71
|
+
if (quoted?.[2] !== undefined)
|
|
72
|
+
return quoted[2];
|
|
73
|
+
return value.replace(/\s+#.*$/, "").trimEnd();
|
|
74
|
+
}
|
|
75
|
+
export function toolingTelemetry(options = {}) {
|
|
76
|
+
try {
|
|
77
|
+
let env = options.env ?? process.env;
|
|
78
|
+
// Cloud-lane key sourcing widens to the project's .env.local — exactly
|
|
79
|
+
// where `vendo login` / cloud-init / --cloud-key land the key — because
|
|
80
|
+
// a dev-mode key almost never lives in the process env. Only
|
|
81
|
+
// VENDO_API_KEY widens: consent vars (DO_NOT_TRACK, CI, …) keep coming
|
|
82
|
+
// from the caller's env untouched, and an explicit non-blank env value
|
|
83
|
+
// always wins over .env.local (the same precedence init's credential
|
|
84
|
+
// merge uses).
|
|
85
|
+
if ((env.VENDO_API_KEY ?? "").trim() === "") {
|
|
86
|
+
const stored = envLocalValueSync(options.cwd ?? process.cwd(), "VENDO_API_KEY");
|
|
87
|
+
if (stored !== null)
|
|
88
|
+
env = { ...env, VENDO_API_KEY: stored };
|
|
89
|
+
}
|
|
90
|
+
return initTelemetry({
|
|
91
|
+
version: CLI_VERSION,
|
|
92
|
+
runtime: false,
|
|
93
|
+
home: options.home,
|
|
94
|
+
env,
|
|
95
|
+
cwd: options.cwd,
|
|
96
|
+
posthogKey: options.posthogKey ?? process.env.VENDO_POSTHOG_KEY,
|
|
97
|
+
fetchImpl: options.fetchImpl,
|
|
98
|
+
log: options.log,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return noTelemetry();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function errorClass(error) {
|
|
106
|
+
if (error instanceof Error && error.name)
|
|
107
|
+
return error.name.slice(0, 64);
|
|
108
|
+
return "unknown";
|
|
109
|
+
}
|
|
110
|
+
/** Cloud-lane project identity (projectName + repoHost) for commands that
|
|
111
|
+
have a target project dir. Anonymous-lane sends strip both keys. */
|
|
112
|
+
export async function cloudProjectProps(root) {
|
|
113
|
+
if (root === undefined)
|
|
114
|
+
return {};
|
|
115
|
+
const props = {};
|
|
116
|
+
try {
|
|
117
|
+
const name = JSON.parse((await readOptional(join(root, "package.json"))) ?? "{}").name;
|
|
118
|
+
if (typeof name === "string" && name.length > 0)
|
|
119
|
+
props.projectName = name;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// No usable package.json — the cloud lane just omits projectName.
|
|
123
|
+
}
|
|
124
|
+
const forge = repoHost(root);
|
|
125
|
+
if (forge !== undefined)
|
|
126
|
+
props.repoHost = forge;
|
|
127
|
+
return props;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Run a CLI command body with one `command_run` telemetry row: ok is the
|
|
131
|
+
* exit code (0 = true), a throw records the error class and rethrows, and a
|
|
132
|
+
* body can name the step it failed at via the mutable `failure` argument.
|
|
133
|
+
* The body also receives the telemetry client for extra events (extract's
|
|
134
|
+
* `extract_completed`). Telemetry NEVER changes command behavior or exit
|
|
135
|
+
* codes — the client never throws, and this wrapper's own prop assembly is
|
|
136
|
+
* guarded too.
|
|
137
|
+
*/
|
|
138
|
+
export async function withCommandRun(input, body) {
|
|
139
|
+
const started = Date.now();
|
|
140
|
+
// The first-run notice keeps its console.error default — several wrapped
|
|
141
|
+
// commands (sync --json, mcp server-json) own their stdout byte-for-byte.
|
|
142
|
+
// The target root rides in as the client's cwd so projectIdHash and the
|
|
143
|
+
// .env.local cloud-key read attribute to the project being operated on,
|
|
144
|
+
// not the shell cwd (an explicit seam cwd still wins).
|
|
145
|
+
const telemetry = toolingTelemetry({
|
|
146
|
+
...(input.root === undefined ? {} : { cwd: input.root }),
|
|
147
|
+
...(input.telemetry ?? {}),
|
|
148
|
+
});
|
|
149
|
+
const failure = {};
|
|
150
|
+
const track = async (ok, thrown) => {
|
|
151
|
+
try {
|
|
152
|
+
await telemetry.track("command_run", {
|
|
153
|
+
command: input.command,
|
|
154
|
+
ok,
|
|
155
|
+
durationMs: Date.now() - started,
|
|
156
|
+
...(failure.failedStep === undefined ? {} : { failedStep: failure.failedStep }),
|
|
157
|
+
...(thrown === undefined ? {} : { errorClass: errorClass(thrown.error) }),
|
|
158
|
+
...(await cloudProjectProps(input.root)),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Telemetry must never break a command. Intentional silent failure.
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
try {
|
|
166
|
+
const exit = await body(failure, telemetry);
|
|
167
|
+
await track(exit === 0);
|
|
168
|
+
return exit;
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
await track(false, { error });
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Lockfile-derived package manager for `run dev` (doctor's probe starter). */
|
|
176
|
+
export async function detectPackageManager(root) {
|
|
177
|
+
if (await exists(join(root, "pnpm-lock.yaml")))
|
|
178
|
+
return "pnpm";
|
|
179
|
+
if (await exists(join(root, "yarn.lock")))
|
|
180
|
+
return "yarn";
|
|
181
|
+
if (await exists(join(root, "bun.lockb")) || await exists(join(root, "bun.lock")))
|
|
182
|
+
return "bun";
|
|
183
|
+
return "npm";
|
|
184
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { vendoSync, type SyncReportWithWarnings } from "@vendoai/actions";
|
|
2
|
+
import type { ToolImpact } from "../sync-impact.js";
|
|
3
|
+
import { type Output, type TelemetryOptions } from "./shared.js";
|
|
4
|
+
export interface SyncReportPayload {
|
|
5
|
+
report: SyncReportWithWarnings;
|
|
6
|
+
impact?: ToolImpact[];
|
|
7
|
+
at: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SyncOptions {
|
|
10
|
+
targetDir: string;
|
|
11
|
+
strict?: boolean;
|
|
12
|
+
output?: Output;
|
|
13
|
+
sync?: typeof vendoSync;
|
|
14
|
+
url?: string;
|
|
15
|
+
fetchImpl?: typeof fetch;
|
|
16
|
+
report?: boolean;
|
|
17
|
+
push?: (report: SyncReportPayload) => Promise<void>;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
apiUrl?: string;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
/** Injectable telemetry deps (matches init/doctor). */
|
|
22
|
+
telemetry?: TelemetryOptions;
|
|
23
|
+
}
|
|
24
|
+
/** `sync --json` — the one machine-readable object printed on stdout. */
|
|
25
|
+
export interface SyncJsonResult {
|
|
26
|
+
ok: boolean;
|
|
27
|
+
exitCode: 0 | 2 | 3;
|
|
28
|
+
report: SyncReportWithWarnings;
|
|
29
|
+
/** [] = nothing referenced the changed tools; null = impact unknown (dev server unreachable). */
|
|
30
|
+
impact: ToolImpact[] | null;
|
|
31
|
+
/** CLI-level events not carried by the report (unreachable impact endpoint, report-push problems). */
|
|
32
|
+
notes: string[];
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
/** 04-actions §1 / 09-vendo §5 — fail-soft extraction, strict CI gate. */
|
|
36
|
+
export declare function runSync(options: SyncOptions): Promise<number>;
|