@vibecodr/cli 1.0.13 → 1.0.15
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/CHANGELOG.md +9 -1
- package/README.md +7 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +28 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/dryrun-logpush-check/README.md +1 -0
- package/dist/dryrun-logpush-check/worker.js +41512 -0
- package/dist/dryrun-logpush-check/worker.js.map +8 -0
- package/dist/legacy/cli/run.d.ts.map +1 -1
- package/dist/legacy/cli/run.js +420 -22
- package/dist/legacy/cli/run.js.map +1 -1
- package/dist/legacy/core/contracts.d.ts +76 -4
- package/dist/legacy/core/contracts.d.ts.map +1 -1
- package/dist/legacy/core/contracts.js +54 -3
- package/dist/legacy/core/contracts.js.map +1 -1
- package/dist/legacy/core/validators.js +1 -1
- package/dist/legacy/core/validators.js.map +1 -1
- package/dist/legacy/core/version.d.ts +2 -2
- package/dist/legacy/core/version.js +1 -1
- package/docs/API-CONTRACT.md +76 -10
- package/docs/CLOUDFLARE-PRIMITIVE-FIT.md +59 -4
- package/docs/VALIDATION-MATRIX.md +3 -3
- package/docs/commands.md +15 -2
- package/docs/legacy/COMPLETION-AUDIT.md +3 -3
- package/docs/legacy/vc-tools-finetune.md +1 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/legacy/cli/run.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/legacy/cli/run.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAgDtD,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,MAAM,CAAC,EAAE,QAAQ,CAAC;IAClB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAoED,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8CzF"}
|
package/dist/legacy/cli/run.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
3
4
|
import os from "node:os";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { ConfigStore, DEFAULT_API_URL, resolveConfigDir } from "../config/store.js";
|
|
@@ -18,6 +19,7 @@ const DEFAULT_AUTH_API_URL = "https://api.vibecodr.space";
|
|
|
18
19
|
const GRANT_REFRESH_SKEW_SECONDS = 60;
|
|
19
20
|
const ARTIFACT_OUTPUT_WORKSPACE_MESSAGE = "Artifact output is workspace-bounded so downloaded bytes can only be written to files you intentionally target inside this workspace. Use --local for ./vibecodr-proof, --out ./artifacts, --out ./artifacts/report.pdf, or cd to the intended workspace and use --out .";
|
|
20
21
|
const ARTIFACT_INPUT_WORKSPACE_MESSAGE = "Artifact upload sources are workspace-bounded so the CLI only reads files you intentionally target inside this workspace. Move the file into this workspace, or cd to the workspace that contains it.";
|
|
22
|
+
const WORKSPACE_SCRATCH_PROOF_DIR = ".vibecodr/browser-artifacts";
|
|
21
23
|
export async function runCli(argv, options = {}) {
|
|
22
24
|
const stdout = options.stdout ?? process.stdout;
|
|
23
25
|
const stderr = options.stderr ?? process.stderr;
|
|
@@ -190,11 +192,19 @@ async function commandTry(context, parsed) {
|
|
|
190
192
|
const { profile } = await context.store.getProfile(context.globals.profile);
|
|
191
193
|
const client = createClient(context, profile, await resolveToken(context, true));
|
|
192
194
|
const proofDir = getStringFlag(parsed.flags, "out") ?? "vibecodr-proof";
|
|
195
|
+
const proofOutput = await prepareAutoProofOutput(context, {
|
|
196
|
+
positionals: [],
|
|
197
|
+
flags: {
|
|
198
|
+
...parsed.flags,
|
|
199
|
+
out: proofDir
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
const resolvedProofDir = getStringFlag(proofOutput.parsed.flags, "out") ?? proofDir;
|
|
193
203
|
const browserParsed = {
|
|
194
204
|
positionals: ["https://example.com"],
|
|
195
205
|
flags: {
|
|
196
206
|
...parsed.flags,
|
|
197
|
-
out:
|
|
207
|
+
out: resolvedProofDir,
|
|
198
208
|
filename: "browser-read.md",
|
|
199
209
|
pollIntervalMs: getStringFlag(parsed.flags, "pollIntervalMs") ?? "250"
|
|
200
210
|
}
|
|
@@ -204,7 +214,7 @@ async function commandTry(context, parsed) {
|
|
|
204
214
|
flags: {
|
|
205
215
|
...parsed.flags,
|
|
206
216
|
command: "node -e \"console.log('vibecodr computer ok')\"",
|
|
207
|
-
out:
|
|
217
|
+
out: resolvedProofDir,
|
|
208
218
|
filename: "computer-run.json",
|
|
209
219
|
pollIntervalMs: getStringFlag(parsed.flags, "pollIntervalMs") ?? "250"
|
|
210
220
|
}
|
|
@@ -217,7 +227,7 @@ async function commandTry(context, parsed) {
|
|
|
217
227
|
proof: "failed",
|
|
218
228
|
usage: "failed"
|
|
219
229
|
};
|
|
220
|
-
const warnings = [];
|
|
230
|
+
const warnings = [...proofOutput.warnings];
|
|
221
231
|
const browserPayload = buildToolTestPayload("browser.extract_markdown", browserParsed.positionals[0], browserParsed);
|
|
222
232
|
const browserWork = await client.request("POST", "tools/test", { body: browserPayload });
|
|
223
233
|
const browserResult = await followSubmittedWork(context, client, "browser.extract_markdown", browserWork, browserParsed);
|
|
@@ -253,12 +263,12 @@ async function commandTry(context, parsed) {
|
|
|
253
263
|
const ready = Object.values(checks).every((status) => status === "ok");
|
|
254
264
|
return {
|
|
255
265
|
message: ready
|
|
256
|
-
? `Vibecodr Agent Computer check passed.\nProof saved: ${path.resolve(context.cwd,
|
|
257
|
-
: `Vibecodr Agent Computer check finished with attention needed.\nProof path: ${path.resolve(context.cwd,
|
|
266
|
+
? `Vibecodr Agent Computer check passed.\nProof saved: ${path.resolve(context.cwd, resolvedProofDir)}`
|
|
267
|
+
: `Vibecodr Agent Computer check finished with attention needed.\nProof path: ${path.resolve(context.cwd, resolvedProofDir)}`,
|
|
258
268
|
data: {
|
|
259
269
|
ready,
|
|
260
270
|
checks,
|
|
261
|
-
proofPath: path.resolve(context.cwd,
|
|
271
|
+
proofPath: path.resolve(context.cwd, resolvedProofDir)
|
|
262
272
|
},
|
|
263
273
|
warnings,
|
|
264
274
|
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
@@ -650,12 +660,145 @@ async function commandBrowser(context, subcommand, rest) {
|
|
|
650
660
|
const normalized = normalizeBrowserSnapshotOptions(parsed);
|
|
651
661
|
return submitHostedCapability(context, "browser.snapshot", normalized, "Captured a hosted Browser snapshot.", { autoFollow: true });
|
|
652
662
|
}
|
|
663
|
+
case "notes":
|
|
653
664
|
case "ask": {
|
|
654
|
-
const normalized =
|
|
655
|
-
return submitHostedCapability(context, "browser.
|
|
665
|
+
const normalized = normalizeBrowserNotesOptions(parsed);
|
|
666
|
+
return submitHostedCapability(context, "browser.notes", normalized, "Captured a hosted Browser snapshot with your note attached.", { autoFollow: true });
|
|
667
|
+
}
|
|
668
|
+
case "session":
|
|
669
|
+
return commandBrowserSession(context, rest[0], rest.slice(1));
|
|
670
|
+
default:
|
|
671
|
+
throw unknownSubcommandError("browser", subcommand, ["render", "screenshot", "read", "markdown", "pdf", "crawl", "snapshot", "notes", "session"], "Use vibecodr browser screenshot <https-url>, browser read <https-url>, browser snapshot <https-url> --local, or browser session open <https-url>.");
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
async function commandBrowserSession(context, subcommand, rest) {
|
|
675
|
+
const parsed = parseCommandOptions(rest);
|
|
676
|
+
const { profile } = await context.store.getProfile(context.globals.profile);
|
|
677
|
+
const client = createClient(context, profile, await resolveToken(context, true));
|
|
678
|
+
switch (subcommand) {
|
|
679
|
+
case "open": {
|
|
680
|
+
const target = parsed.positionals[0];
|
|
681
|
+
if (!target) {
|
|
682
|
+
throw new CliError("input.url_required", "browser session open requires an HTTPS URL target.", 2);
|
|
683
|
+
}
|
|
684
|
+
const body = { url: validateBrowserUrl(target) };
|
|
685
|
+
const timeoutMs = validatePositiveInt(getStringFlag(parsed.flags, "timeoutMs"), "--timeout-ms", 1000, 3_600_000);
|
|
686
|
+
const idleTimeoutMs = validatePositiveInt(getStringFlag(parsed.flags, "idleTimeoutMs"), "--idle-timeout-ms", 1000, 600_000);
|
|
687
|
+
if (timeoutMs !== undefined)
|
|
688
|
+
body.timeoutMs = timeoutMs;
|
|
689
|
+
if (idleTimeoutMs !== undefined)
|
|
690
|
+
body.idleTimeoutMs = idleTimeoutMs;
|
|
691
|
+
const response = await client.request("POST", "browser/sessions", { body });
|
|
692
|
+
return {
|
|
693
|
+
message: browserSessionMessage(response, "Opened Agent Browser session."),
|
|
694
|
+
data: response,
|
|
695
|
+
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
case "observe": {
|
|
699
|
+
const sessionId = validateBrowserSessionId(parsed.positionals[0]);
|
|
700
|
+
const response = await client.request("GET", `browser/sessions/${encodePathSegment(sessionId)}`);
|
|
701
|
+
return {
|
|
702
|
+
message: browserSessionMessage(response, "Observed Agent Browser session."),
|
|
703
|
+
data: response,
|
|
704
|
+
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
case "goto":
|
|
708
|
+
case "navigate":
|
|
709
|
+
case "click":
|
|
710
|
+
case "type":
|
|
711
|
+
case "scroll":
|
|
712
|
+
case "wait": {
|
|
713
|
+
const sessionId = validateBrowserSessionId(parsed.positionals[0]);
|
|
714
|
+
const body = browserSessionActionBody(subcommand, parsed);
|
|
715
|
+
const response = await client.request("POST", `browser/sessions/${encodePathSegment(sessionId)}/actions`, { body });
|
|
716
|
+
return {
|
|
717
|
+
message: browserSessionMessage(response, `Ran Agent Browser ${subcommand}.`),
|
|
718
|
+
data: response,
|
|
719
|
+
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
case "auth":
|
|
723
|
+
case "auth-request": {
|
|
724
|
+
const sessionId = validateBrowserSessionId(parsed.positionals[0]);
|
|
725
|
+
const response = await client.request("POST", `browser/sessions/${encodePathSegment(sessionId)}/auth${browserSessionLiveViewQuery(parsed, context.globals.debug)}`);
|
|
726
|
+
const handoffUrl = browserSessionHandoffUrlFromResponse(response);
|
|
727
|
+
const skipOpen = getBooleanFlag(parsed.flags, "noOpen") ||
|
|
728
|
+
parsed.flags.open === false ||
|
|
729
|
+
context.globals.json ||
|
|
730
|
+
context.globals.quiet ||
|
|
731
|
+
context.globals.noInput;
|
|
732
|
+
let opened = false;
|
|
733
|
+
if (handoffUrl && !skipOpen) {
|
|
734
|
+
opened = await maybeOpenBrowser(context, parsed, handoffUrl);
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
message: browserSessionMessage(response, opened ? "Opened Agent Browser for you to control." : "Agent Browser control link ready."),
|
|
738
|
+
data: response,
|
|
739
|
+
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
case "live":
|
|
743
|
+
case "watch":
|
|
744
|
+
case "view": {
|
|
745
|
+
const sessionId = validateBrowserSessionId(parsed.positionals[0]);
|
|
746
|
+
const response = await client.request("POST", `browser/sessions/${encodePathSegment(sessionId)}/live${browserSessionLiveViewQuery(parsed, context.globals.debug)}`);
|
|
747
|
+
const handoffUrl = browserSessionHandoffUrlFromResponse(response);
|
|
748
|
+
const skipOpen = getBooleanFlag(parsed.flags, "noOpen") ||
|
|
749
|
+
parsed.flags.open === false ||
|
|
750
|
+
context.globals.json ||
|
|
751
|
+
context.globals.quiet ||
|
|
752
|
+
context.globals.noInput;
|
|
753
|
+
let opened = false;
|
|
754
|
+
if (handoffUrl && !skipOpen) {
|
|
755
|
+
opened = await maybeOpenBrowser(context, parsed, handoffUrl);
|
|
756
|
+
}
|
|
757
|
+
return {
|
|
758
|
+
message: browserSessionMessage(response, opened ? "Opened Agent Browser live view." : "Agent Browser live view ready."),
|
|
759
|
+
data: response,
|
|
760
|
+
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
case "auth-status": {
|
|
764
|
+
const sessionId = validateBrowserSessionId(parsed.positionals[0]);
|
|
765
|
+
const response = await client.request("GET", `browser/sessions/${encodePathSegment(sessionId)}/auth`);
|
|
766
|
+
return {
|
|
767
|
+
message: browserSessionMessage(response, "Read Agent Browser live-control status."),
|
|
768
|
+
data: response,
|
|
769
|
+
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
case "auth-complete":
|
|
773
|
+
case "auth-done": {
|
|
774
|
+
const sessionId = validateBrowserSessionId(parsed.positionals[0]);
|
|
775
|
+
const response = await client.request("POST", `browser/sessions/${encodePathSegment(sessionId)}/auth/complete`);
|
|
776
|
+
return {
|
|
777
|
+
message: browserSessionMessage(response, "Gave Agent Browser control back to the agent."),
|
|
778
|
+
data: response,
|
|
779
|
+
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
case "auth-revoke": {
|
|
783
|
+
const sessionId = validateBrowserSessionId(parsed.positionals[0]);
|
|
784
|
+
const response = await client.request("POST", `browser/sessions/${encodePathSegment(sessionId)}/auth/revoke`);
|
|
785
|
+
return {
|
|
786
|
+
message: browserSessionMessage(response, "Ended Agent Browser live link."),
|
|
787
|
+
data: response,
|
|
788
|
+
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
case "close": {
|
|
792
|
+
const sessionId = validateBrowserSessionId(parsed.positionals[0]);
|
|
793
|
+
const response = await client.request("DELETE", `browser/sessions/${encodePathSegment(sessionId)}`);
|
|
794
|
+
return {
|
|
795
|
+
message: "Closed Agent Browser session.",
|
|
796
|
+
data: response,
|
|
797
|
+
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
798
|
+
};
|
|
656
799
|
}
|
|
657
800
|
default:
|
|
658
|
-
throw unknownSubcommandError("browser", subcommand, ["
|
|
801
|
+
throw unknownSubcommandError("browser session", subcommand, ["open", "observe", "goto", "click", "type", "scroll", "wait", "live", "auth", "auth-status", "auth-complete", "auth-revoke", "close"], "Use vibecodr browser session open <https-url>, live <sessionId>, observe <sessionId>, click <sessionId> --selector <css>, or close <sessionId>.");
|
|
659
802
|
}
|
|
660
803
|
}
|
|
661
804
|
async function commandComputer(context, subcommand, rest) {
|
|
@@ -735,13 +878,16 @@ async function submitHostedCapability(context, capabilityInput, parsed, successM
|
|
|
735
878
|
throw new CliError("input.local_requires_wait", "--local saves the completed output, so it cannot be combined with --no-wait.", 2);
|
|
736
879
|
}
|
|
737
880
|
const payload = buildToolTestPayload(capability, parsed.positionals[0], parsed, context.globals.timeoutMs === 30_000 ? undefined : context.globals.timeoutMs);
|
|
881
|
+
const proofOutput = options.autoFollow === true && !shouldSkipWait(parsed)
|
|
882
|
+
? await prepareAutoProofOutput(context, parsed)
|
|
883
|
+
: { parsed, warnings: [] };
|
|
738
884
|
const { profile } = await context.store.getProfile(context.globals.profile);
|
|
739
885
|
const client = createClient(context, profile, await resolveToken(context, true));
|
|
740
886
|
const response = await client.request("POST", "tools/test", {
|
|
741
887
|
body: payload
|
|
742
888
|
});
|
|
743
889
|
if (options.autoFollow === true && !shouldSkipWait(parsed)) {
|
|
744
|
-
return followSubmittedWork(context, client, capability, response, parsed);
|
|
890
|
+
return followSubmittedWork(context, client, capability, response, proofOutput.parsed, proofOutput.warnings);
|
|
745
891
|
}
|
|
746
892
|
return {
|
|
747
893
|
message: successMessage ?? (capability === "usage.read" ? "Read usage and limits from hosted Vibecodr." : `Submitted ${capability} test to hosted Vibecodr.`),
|
|
@@ -751,12 +897,13 @@ async function submitHostedCapability(context, capabilityInput, parsed, successM
|
|
|
751
897
|
function shouldSkipWait(parsed) {
|
|
752
898
|
return getBooleanFlag(parsed.flags, "noWait") || parsed.flags.wait === false;
|
|
753
899
|
}
|
|
754
|
-
async function followSubmittedWork(context, client, capability, submitted, parsed) {
|
|
900
|
+
async function followSubmittedWork(context, client, capability, submitted, parsed, warnings = []) {
|
|
755
901
|
const jobId = jobIdFromWork(submitted);
|
|
756
902
|
if (!jobId) {
|
|
757
903
|
return {
|
|
758
904
|
message: completedCapabilityMessage(capability),
|
|
759
905
|
data: publicWorkResult(capability, submitted, parsed),
|
|
906
|
+
warnings,
|
|
760
907
|
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
761
908
|
};
|
|
762
909
|
}
|
|
@@ -770,21 +917,24 @@ async function followSubmittedWork(context, client, capability, submitted, parse
|
|
|
770
917
|
return {
|
|
771
918
|
message: formatCompletedWorkMessage(capability, terminal, proof),
|
|
772
919
|
data: publicWorkResult(capability, terminal, parsed, proof),
|
|
920
|
+
warnings,
|
|
773
921
|
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
774
922
|
};
|
|
775
923
|
}
|
|
776
924
|
async function commandWorkFollow(context, parsed) {
|
|
777
925
|
const jobId = validateEntityId(requiredPositional(parsed, 0, "work follow requires a job id."), "job id");
|
|
926
|
+
const proofOutput = await prepareAutoProofOutput(context, parsed);
|
|
778
927
|
const { profile } = await context.store.getProfile(context.globals.profile);
|
|
779
928
|
const client = createClient(context, profile, await resolveToken(context, true));
|
|
780
|
-
const job = await pollWorkUntilTerminal(client, jobId, parsed);
|
|
929
|
+
const job = await pollWorkUntilTerminal(client, jobId, proofOutput.parsed);
|
|
781
930
|
const artifactId = artifactIdFromWork(job);
|
|
782
|
-
const proof = artifactId && shouldSaveArtifact(parsed)
|
|
783
|
-
? await saveArtifact(context, client, artifactId,
|
|
931
|
+
const proof = artifactId && shouldSaveArtifact(proofOutput.parsed)
|
|
932
|
+
? await saveArtifact(context, client, artifactId, proofOutput.parsed)
|
|
784
933
|
: undefined;
|
|
785
934
|
return {
|
|
786
935
|
message: formatCompletedWorkMessage(undefined, job, proof),
|
|
787
|
-
data: publicWorkResult(undefined, job, parsed, proof),
|
|
936
|
+
data: publicWorkResult(undefined, job, proofOutput.parsed, proof),
|
|
937
|
+
warnings: proofOutput.warnings,
|
|
788
938
|
humanData: getBooleanFlag(parsed.flags, "details") ? "show" : "hide"
|
|
789
939
|
};
|
|
790
940
|
}
|
|
@@ -880,6 +1030,60 @@ function parsedWithLocalOutput(parsed) {
|
|
|
880
1030
|
}
|
|
881
1031
|
};
|
|
882
1032
|
}
|
|
1033
|
+
async function prepareAutoProofOutput(context, parsed) {
|
|
1034
|
+
if (!shouldSaveArtifact(parsed)) {
|
|
1035
|
+
return { parsed, warnings: [] };
|
|
1036
|
+
}
|
|
1037
|
+
const withLocalOutput = parsedWithLocalOutput(parsed);
|
|
1038
|
+
const out = getStringFlag(withLocalOutput.flags, "out") ?? ".";
|
|
1039
|
+
const outPath = path.resolve(context.cwd, out);
|
|
1040
|
+
try {
|
|
1041
|
+
await ensureOutputPathAllowed(context.cwd, outPath);
|
|
1042
|
+
return { parsed: withLocalOutput, warnings: [] };
|
|
1043
|
+
}
|
|
1044
|
+
catch (error) {
|
|
1045
|
+
const cliError = toCliError(error);
|
|
1046
|
+
if (cliError.code !== "file.outside_workspace" || !isPathOutside(path.resolve(context.cwd), outPath)) {
|
|
1047
|
+
throw error;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const fallbackOut = await allocateScratchProofOutputDir(context.cwd);
|
|
1051
|
+
return {
|
|
1052
|
+
parsed: {
|
|
1053
|
+
...withLocalOutput,
|
|
1054
|
+
flags: {
|
|
1055
|
+
...withLocalOutput.flags,
|
|
1056
|
+
out: fallbackOut
|
|
1057
|
+
}
|
|
1058
|
+
},
|
|
1059
|
+
warnings: [
|
|
1060
|
+
`Output path is outside this workspace, so hosted artifacts cannot be written there. Writing to ${formatCliPath(fallbackOut)} instead. This scratch folder is gitignored; use --out ./path-inside-workspace to choose another workspace path.`
|
|
1061
|
+
]
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
async function allocateScratchProofOutputDir(cwd) {
|
|
1065
|
+
const root = path.join(cwd, WORKSPACE_SCRATCH_PROOF_DIR);
|
|
1066
|
+
await fs.mkdir(root, { recursive: true });
|
|
1067
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
1068
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
1069
|
+
const relative = `${WORKSPACE_SCRATCH_PROOF_DIR}/${timestamp}-${randomUUID().slice(0, 8)}`;
|
|
1070
|
+
const absolute = path.resolve(cwd, relative);
|
|
1071
|
+
try {
|
|
1072
|
+
await fs.mkdir(absolute, { recursive: false });
|
|
1073
|
+
return relative;
|
|
1074
|
+
}
|
|
1075
|
+
catch (error) {
|
|
1076
|
+
if (error.code !== "EEXIST") {
|
|
1077
|
+
throw error;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
throw new CliError("file.scratch_output_failed", "Could not create a workspace scratch output directory for hosted artifacts.", 5);
|
|
1082
|
+
}
|
|
1083
|
+
function formatCliPath(input) {
|
|
1084
|
+
const normalized = input.split(path.sep).join("/");
|
|
1085
|
+
return normalized.startsWith("./") || normalized.startsWith("../") ? normalized : `./${normalized}`;
|
|
1086
|
+
}
|
|
883
1087
|
function formatCompletedWorkMessage(capability, work, proof) {
|
|
884
1088
|
const status = workStatus(work) ?? "completed";
|
|
885
1089
|
if (status === "completed") {
|
|
@@ -999,14 +1203,14 @@ function userToolName(capability) {
|
|
|
999
1203
|
function normalizeBrowserSnapshotOptions(parsed) {
|
|
1000
1204
|
const [url, ...extraParts] = parsed.positionals;
|
|
1001
1205
|
if (extraParts.length > 0 || parsed.flags.instructions !== undefined || parsed.flags.note !== undefined) {
|
|
1002
|
-
throw new CliError("input.snapshot_is_not_prompted", "browser snapshot captures the page state; it does not prompt an agent or model. Remove the note/instructions, or use `vibecodr browser
|
|
1206
|
+
throw new CliError("input.snapshot_is_not_prompted", "browser snapshot captures the page state; it does not prompt an agent or model. Remove the note/instructions, or use `vibecodr browser notes <url> --note \"...\"` to save a note with the snapshot.", 2);
|
|
1003
1207
|
}
|
|
1004
1208
|
return {
|
|
1005
1209
|
positionals: url === undefined ? [] : [url],
|
|
1006
1210
|
flags: parsed.flags
|
|
1007
1211
|
};
|
|
1008
1212
|
}
|
|
1009
|
-
function
|
|
1213
|
+
function normalizeBrowserNotesOptions(parsed) {
|
|
1010
1214
|
const [url, ...instructionParts] = parsed.positionals;
|
|
1011
1215
|
const flags = { ...parsed.flags };
|
|
1012
1216
|
const note = getStringFlag(flags, "note");
|
|
@@ -1019,13 +1223,132 @@ function normalizeBrowserAskOptions(parsed) {
|
|
|
1019
1223
|
flags.instructions = instructionParts.join(" ");
|
|
1020
1224
|
}
|
|
1021
1225
|
if (getStringFlag(flags, "instructions") === undefined) {
|
|
1022
|
-
throw new CliError("input.
|
|
1226
|
+
throw new CliError("input.notes_note_required", "browser notes needs a note. For a normal capture, use `vibecodr browser snapshot <url> --local`.", 2);
|
|
1023
1227
|
}
|
|
1024
1228
|
return {
|
|
1025
1229
|
positionals: url === undefined ? [] : [url],
|
|
1026
1230
|
flags
|
|
1027
1231
|
};
|
|
1028
1232
|
}
|
|
1233
|
+
function validateBrowserSessionId(input) {
|
|
1234
|
+
return validateEntityId(input ?? "", "Agent Browser session id");
|
|
1235
|
+
}
|
|
1236
|
+
function browserSessionActionBody(subcommand, parsed) {
|
|
1237
|
+
if (subcommand === "goto" || subcommand === "navigate") {
|
|
1238
|
+
const target = getStringFlag(parsed.flags, "url") ?? parsed.positionals[1];
|
|
1239
|
+
if (!target) {
|
|
1240
|
+
throw new CliError("input.url_required", `browser session ${subcommand} requires an HTTPS URL target.`, 2);
|
|
1241
|
+
}
|
|
1242
|
+
return { action: "navigate", url: validateBrowserUrl(target) };
|
|
1243
|
+
}
|
|
1244
|
+
if (subcommand === "click") {
|
|
1245
|
+
return { action: "click", selector: requiredBrowserSessionSelector(parsed, "browser session click requires --selector <css>.") };
|
|
1246
|
+
}
|
|
1247
|
+
if (subcommand === "type") {
|
|
1248
|
+
const text = getStringFlag(parsed.flags, "text") ?? parsed.positionals.slice(2).join(" ").trim();
|
|
1249
|
+
if (!text) {
|
|
1250
|
+
throw new CliError("input.text_required", "browser session type requires --text <text>.", 2);
|
|
1251
|
+
}
|
|
1252
|
+
return {
|
|
1253
|
+
action: "type",
|
|
1254
|
+
selector: requiredBrowserSessionSelector(parsed, "browser session type requires --selector <css>."),
|
|
1255
|
+
text: text.slice(0, 2_000)
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
if (subcommand === "scroll") {
|
|
1259
|
+
const deltaY = validateIntegerRange(getStringFlag(parsed.flags, "deltaY") ?? parsed.positionals[1], "--delta-y", -10_000, 10_000);
|
|
1260
|
+
return deltaY === undefined ? { action: "scroll" } : { action: "scroll", deltaY };
|
|
1261
|
+
}
|
|
1262
|
+
if (subcommand === "wait") {
|
|
1263
|
+
const ms = validateIntegerRange(getStringFlag(parsed.flags, "ms") ?? parsed.positionals[1], "--ms", 1, 30_000);
|
|
1264
|
+
return ms === undefined ? { action: "wait" } : { action: "wait", ms };
|
|
1265
|
+
}
|
|
1266
|
+
throw new CliError("input.invalid_browser_session_action", "Unknown Agent Browser session action.", 2);
|
|
1267
|
+
}
|
|
1268
|
+
function browserSessionLiveViewQuery(parsed, globalDebug = false) {
|
|
1269
|
+
const mode = browserSessionLiveViewMode(parsed, globalDebug);
|
|
1270
|
+
return mode === "devtools" ? "?view=devtools" : "";
|
|
1271
|
+
}
|
|
1272
|
+
function browserSessionLiveViewMode(parsed, globalDebug = false) {
|
|
1273
|
+
const raw = (getStringFlag(parsed.flags, "view") ?? getStringFlag(parsed.flags, "mode") ?? "").trim().toLowerCase();
|
|
1274
|
+
const debug = globalDebug || getBooleanFlag(parsed.flags, "debug") || getBooleanFlag(parsed.flags, "devtools");
|
|
1275
|
+
if (debug && (raw === "tab" || raw === "browser")) {
|
|
1276
|
+
throw new CliError("input.invalid_live_view_mode", "Use either --debug/--devtools or --view tab, not both.", 2);
|
|
1277
|
+
}
|
|
1278
|
+
if (debug || raw === "devtools" || raw === "debug" || raw === "inspector") {
|
|
1279
|
+
return "devtools";
|
|
1280
|
+
}
|
|
1281
|
+
if (!raw || raw === "tab" || raw === "browser") {
|
|
1282
|
+
return "tab";
|
|
1283
|
+
}
|
|
1284
|
+
throw new CliError("input.invalid_live_view_mode", "Agent Browser live view mode must be tab or devtools.", 2);
|
|
1285
|
+
}
|
|
1286
|
+
function requiredBrowserSessionSelector(parsed, message) {
|
|
1287
|
+
const selector = getStringFlag(parsed.flags, "selector") ?? parsed.positionals[1];
|
|
1288
|
+
if (!selector || selector.trim().length === 0) {
|
|
1289
|
+
throw new CliError("input.selector_required", message, 2);
|
|
1290
|
+
}
|
|
1291
|
+
return selector.trim().slice(0, 500);
|
|
1292
|
+
}
|
|
1293
|
+
function validateIntegerRange(input, label, min, max) {
|
|
1294
|
+
if (input === undefined) {
|
|
1295
|
+
return undefined;
|
|
1296
|
+
}
|
|
1297
|
+
const value = Number(input);
|
|
1298
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
1299
|
+
throw new CliError("input.invalid_number", `${label} must be an integer from ${min} to ${max}.`, 2);
|
|
1300
|
+
}
|
|
1301
|
+
return value;
|
|
1302
|
+
}
|
|
1303
|
+
function browserSessionMessage(response, fallback) {
|
|
1304
|
+
if (!isRecord(response)) {
|
|
1305
|
+
return fallback;
|
|
1306
|
+
}
|
|
1307
|
+
const session = isRecord(response.session) ? response.session : undefined;
|
|
1308
|
+
const id = typeof session?.id === "string" ? session.id : typeof response.id === "string" ? response.id : undefined;
|
|
1309
|
+
const observation = isRecord(response.observation) ? response.observation : undefined;
|
|
1310
|
+
const screenshot = isRecord(observation?.screenshot) ? observation?.screenshot : undefined;
|
|
1311
|
+
const auth = isRecord(response.auth) ? response.auth : isRecord(session?.auth) ? session?.auth : undefined;
|
|
1312
|
+
const lines = [fallback];
|
|
1313
|
+
if (id) {
|
|
1314
|
+
lines.push(`Session: ${id}`);
|
|
1315
|
+
lines.push(`Observe: vibecodr browser session observe ${id}`);
|
|
1316
|
+
lines.push(`Watch live: vibecodr browser session live ${id}`);
|
|
1317
|
+
lines.push(`Need to sign in or steer? vibecodr browser session auth ${id}`);
|
|
1318
|
+
lines.push(`Close: vibecodr browser session close ${id}`);
|
|
1319
|
+
}
|
|
1320
|
+
if (auth && typeof auth.status === "string") {
|
|
1321
|
+
lines.push(`Control: ${browserSessionControlLabel(auth.status)}`);
|
|
1322
|
+
}
|
|
1323
|
+
const handoffUrl = browserSessionHandoffUrlFromResponse(response);
|
|
1324
|
+
if (handoffUrl) {
|
|
1325
|
+
lines.push(`Open live page: ${handoffUrl}`);
|
|
1326
|
+
}
|
|
1327
|
+
if (typeof screenshot?.id === "string") {
|
|
1328
|
+
lines.push(`Screenshot proof: vibecodr proof show ${screenshot.id}`);
|
|
1329
|
+
}
|
|
1330
|
+
return lines.join("\n");
|
|
1331
|
+
}
|
|
1332
|
+
function browserSessionControlLabel(status) {
|
|
1333
|
+
if (status === "human_control")
|
|
1334
|
+
return "you are controlling the browser";
|
|
1335
|
+
if (status === "auth_ready")
|
|
1336
|
+
return "agent can continue";
|
|
1337
|
+
if (status === "revoked")
|
|
1338
|
+
return "live link ended";
|
|
1339
|
+
if (status === "handoff_expired")
|
|
1340
|
+
return "watch link expired";
|
|
1341
|
+
if (status === "public")
|
|
1342
|
+
return "agent is browsing";
|
|
1343
|
+
return status;
|
|
1344
|
+
}
|
|
1345
|
+
function browserSessionHandoffUrlFromResponse(response) {
|
|
1346
|
+
if (!isRecord(response)) {
|
|
1347
|
+
return undefined;
|
|
1348
|
+
}
|
|
1349
|
+
const auth = isRecord(response.auth) ? response.auth : undefined;
|
|
1350
|
+
return typeof auth?.handoffUrl === "string" ? auth.handoffUrl : undefined;
|
|
1351
|
+
}
|
|
1029
1352
|
function normalizeComputerCommandOptions(parsed, missingMessage) {
|
|
1030
1353
|
const command = getStringFlag(parsed.flags, "command") ?? parsed.positionals.join(" ").trim();
|
|
1031
1354
|
if (!command) {
|
|
@@ -1894,6 +2217,26 @@ function usageBar(percent) {
|
|
|
1894
2217
|
return `[${"#".repeat(filled)}${"-".repeat(width - filled)}]`;
|
|
1895
2218
|
}
|
|
1896
2219
|
function buildToolTestPayload(capability, target, parsed, globalToolTimeoutMs) {
|
|
2220
|
+
if (capability.startsWith("browser.session_")) {
|
|
2221
|
+
const input = {};
|
|
2222
|
+
if (capability === "browser.session_open") {
|
|
2223
|
+
if (!target) {
|
|
2224
|
+
throw new CliError("input.url_required", "browser.session_open requires an HTTPS URL target.", 2);
|
|
2225
|
+
}
|
|
2226
|
+
input.url = validateBrowserUrl(target);
|
|
2227
|
+
const timeoutMs = validatePositiveInt(getStringFlag(parsed.flags, "timeoutMs"), "--timeout-ms", 1000, 3_600_000);
|
|
2228
|
+
const idleTimeoutMs = validatePositiveInt(getStringFlag(parsed.flags, "idleTimeoutMs"), "--idle-timeout-ms", 1000, 600_000);
|
|
2229
|
+
if (timeoutMs !== undefined)
|
|
2230
|
+
input.timeoutMs = timeoutMs;
|
|
2231
|
+
if (idleTimeoutMs !== undefined)
|
|
2232
|
+
input.idleTimeoutMs = idleTimeoutMs;
|
|
2233
|
+
}
|
|
2234
|
+
else {
|
|
2235
|
+
input.sessionId = validateBrowserSessionId(target);
|
|
2236
|
+
Object.assign(input, browserSessionToolTestActionInput(capability, parsed));
|
|
2237
|
+
}
|
|
2238
|
+
return { capability, input };
|
|
2239
|
+
}
|
|
1897
2240
|
if (capability.startsWith("browser.")) {
|
|
1898
2241
|
if (!target) {
|
|
1899
2242
|
throw new CliError("input.url_required", `${capability} requires an HTTPS URL target.`, 2);
|
|
@@ -1975,6 +2318,37 @@ function buildToolTestPayload(capability, target, parsed, globalToolTimeoutMs) {
|
|
|
1975
2318
|
}
|
|
1976
2319
|
return { capability, input: {} };
|
|
1977
2320
|
}
|
|
2321
|
+
function browserSessionToolTestActionInput(capability, parsed) {
|
|
2322
|
+
if (capability === "browser.session_observe" || capability === "browser.session_close") {
|
|
2323
|
+
return {};
|
|
2324
|
+
}
|
|
2325
|
+
if (capability === "browser.session_navigate") {
|
|
2326
|
+
const target = getStringFlag(parsed.flags, "url") ?? parsed.positionals[1];
|
|
2327
|
+
if (!target) {
|
|
2328
|
+
throw new CliError("input.url_required", "browser.session_navigate requires an HTTPS URL target.", 2);
|
|
2329
|
+
}
|
|
2330
|
+
return { url: validateBrowserUrl(target) };
|
|
2331
|
+
}
|
|
2332
|
+
if (capability === "browser.session_click") {
|
|
2333
|
+
return { selector: requiredBrowserSessionSelector(parsed, "browser.session_click requires --selector <css>.") };
|
|
2334
|
+
}
|
|
2335
|
+
if (capability === "browser.session_type") {
|
|
2336
|
+
const text = getStringFlag(parsed.flags, "text") ?? parsed.positionals.slice(2).join(" ").trim();
|
|
2337
|
+
if (!text) {
|
|
2338
|
+
throw new CliError("input.text_required", "browser.session_type requires --text <text>.", 2);
|
|
2339
|
+
}
|
|
2340
|
+
return { selector: requiredBrowserSessionSelector(parsed, "browser.session_type requires --selector <css>."), text };
|
|
2341
|
+
}
|
|
2342
|
+
if (capability === "browser.session_scroll") {
|
|
2343
|
+
const deltaY = validateIntegerRange(getStringFlag(parsed.flags, "deltaY") ?? parsed.positionals[1], "--delta-y", -10_000, 10_000);
|
|
2344
|
+
return deltaY === undefined ? {} : { deltaY };
|
|
2345
|
+
}
|
|
2346
|
+
if (capability === "browser.session_wait") {
|
|
2347
|
+
const ms = validateIntegerRange(getStringFlag(parsed.flags, "ms") ?? parsed.positionals[1], "--ms", 1, 30_000);
|
|
2348
|
+
return ms === undefined ? {} : { ms };
|
|
2349
|
+
}
|
|
2350
|
+
return {};
|
|
2351
|
+
}
|
|
1978
2352
|
function buildScheduledQaPayload(target, parsed, globalToolTimeoutMs) {
|
|
1979
2353
|
const capability = normalizeScheduledQaCliCapability(getStringFlag(parsed.flags, "capability") ?? getStringFlag(parsed.flags, "tool") ?? "browser.render_url");
|
|
1980
2354
|
const timeoutInput = getStringFlag(parsed.flags, "timeoutMs") ?? (globalToolTimeoutMs === undefined ? undefined : String(globalToolTimeoutMs));
|
|
@@ -2796,6 +3170,9 @@ Usage:
|
|
|
2796
3170
|
vibecodr computer status
|
|
2797
3171
|
vibecodr computer run "<command>" [--timeout-ms <ms>] [--network public|off] [--local|--out ./proof] [--no-wait] [--details]
|
|
2798
3172
|
vibecodr computer test "<command>" [--timeout-ms <ms>] [--network public|off] [--local|--out ./proof] [--no-wait] [--details]
|
|
3173
|
+
|
|
3174
|
+
Notes:
|
|
3175
|
+
Automatic output saves are workspace-bounded. If --out points outside this workspace, vibecodr writes to ./.vibecodr/browser-artifacts/<run> instead.
|
|
2799
3176
|
`;
|
|
2800
3177
|
case "browser":
|
|
2801
3178
|
return `vibecodr browser
|
|
@@ -2809,14 +3186,32 @@ Usage:
|
|
|
2809
3186
|
vibecodr browser pdf <https-url> [--local|--out ./proof] [--no-wait] [--details]
|
|
2810
3187
|
vibecodr browser crawl <https-url> [--max-pages n] [--max-depth n] [--local|--out ./proof]
|
|
2811
3188
|
vibecodr browser snapshot <https-url> [--local|--out ./proof]
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
vibecodr browser
|
|
3189
|
+
vibecodr browser session open <https-url> [--timeout-ms <ms>] [--idle-timeout-ms <ms>]
|
|
3190
|
+
vibecodr browser session observe <sessionId>
|
|
3191
|
+
vibecodr browser session goto <sessionId> <https-url>
|
|
3192
|
+
vibecodr browser session click <sessionId> --selector <css>
|
|
3193
|
+
vibecodr browser session type <sessionId> --selector <css> --text <text>
|
|
3194
|
+
vibecodr browser session scroll <sessionId> [--delta-y 800]
|
|
3195
|
+
vibecodr browser session wait <sessionId> [--ms 1000]
|
|
3196
|
+
vibecodr browser session live <sessionId> [--no-open] [--debug|--view devtools]
|
|
3197
|
+
vibecodr browser session auth <sessionId> [--no-open] [--debug|--view devtools]
|
|
3198
|
+
vibecodr browser session auth-status <sessionId>
|
|
3199
|
+
vibecodr browser session auth-complete <sessionId>
|
|
3200
|
+
vibecodr browser session auth-revoke <sessionId>
|
|
3201
|
+
vibecodr browser session close <sessionId>
|
|
3202
|
+
|
|
3203
|
+
Attach a note:
|
|
3204
|
+
vibecodr browser notes <https-url> --note <text> [--local|--out ./proof]
|
|
2815
3205
|
|
|
2816
3206
|
Notes:
|
|
2817
3207
|
Add --local to save completed output into ./vibecodr-proof automatically.
|
|
3208
|
+
Automatic output saves are workspace-bounded. If --out points outside this workspace, vibecodr writes to ./.vibecodr/browser-artifacts/<run> instead.
|
|
2818
3209
|
browser snapshot captures page state; it does not prompt an agent or model.
|
|
2819
|
-
browser
|
|
3210
|
+
browser notes saves your note with the snapshot.
|
|
3211
|
+
browser session opens a real hosted Agent Browser the agent can observe and control until it is closed or idle.
|
|
3212
|
+
browser session live opens the watch-and-intercede page without pausing the agent.
|
|
3213
|
+
browser session auth opens the same live page with human control already active for login, MFA, CAPTCHA, or other human-only steps.
|
|
3214
|
+
Add --debug or --view devtools only when an agent/developer needs the inspector panel instead of the plain browser tab.
|
|
2820
3215
|
`;
|
|
2821
3216
|
case "work":
|
|
2822
3217
|
return `vibecodr work
|
|
@@ -2828,6 +3223,9 @@ Usage:
|
|
|
2828
3223
|
vibecodr work show <jobId>
|
|
2829
3224
|
vibecodr work follow <jobId> [--local|--out ./proof] [--details]
|
|
2830
3225
|
vibecodr work cancel <jobId> --yes
|
|
3226
|
+
|
|
3227
|
+
Notes:
|
|
3228
|
+
Automatic output saves are workspace-bounded. If --out points outside this workspace, vibecodr writes to ./.vibecodr/browser-artifacts/<run> instead.
|
|
2831
3229
|
`;
|
|
2832
3230
|
case "proof":
|
|
2833
3231
|
return `vibecodr proof
|