libretto 0.5.5 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -10
- package/README.template.md +23 -10
- package/dist/cli/cli.js +10 -0
- package/dist/cli/commands/ai.js +77 -2
- package/dist/cli/commands/browser.js +98 -8
- package/dist/cli/commands/execution.js +152 -56
- package/dist/cli/commands/setup.js +390 -0
- package/dist/cli/commands/snapshot.js +2 -2
- package/dist/cli/commands/status.js +62 -0
- package/dist/cli/core/{snapshot-api-config.js → ai-model.js} +81 -7
- package/dist/cli/core/api-snapshot-analyzer.js +7 -5
- package/dist/cli/core/browser.js +202 -36
- package/dist/cli/core/{ai-config.js → config.js} +14 -79
- package/dist/cli/core/context.js +1 -25
- package/dist/cli/core/deploy-artifact.js +121 -61
- package/dist/cli/core/providers/browserbase.js +53 -0
- package/dist/cli/core/providers/index.js +48 -0
- package/dist/cli/core/providers/kernel.js +46 -0
- package/dist/cli/core/providers/libretto-cloud.js +58 -0
- package/dist/cli/core/readonly-exec.js +231 -0
- package/dist/{shared/llm/client.js → cli/core/resolve-model.js} +4 -68
- package/dist/cli/core/session.js +53 -0
- package/dist/cli/core/skill-version.js +73 -0
- package/dist/cli/core/telemetry.js +1 -54
- package/dist/cli/index.js +1 -7
- package/dist/cli/router.js +4 -4
- package/dist/cli/workers/run-integration-runtime.js +19 -13
- package/dist/cli/workers/run-integration-worker-protocol.js +5 -2
- package/dist/index.d.ts +2 -4
- package/dist/index.js +2 -2
- package/dist/runtime/extract/extract.d.ts +2 -2
- package/dist/runtime/extract/extract.js +4 -2
- package/dist/runtime/extract/index.d.ts +1 -1
- package/dist/runtime/recovery/agent.d.ts +2 -3
- package/dist/runtime/recovery/agent.js +5 -3
- package/dist/runtime/recovery/errors.d.ts +2 -3
- package/dist/runtime/recovery/errors.js +4 -2
- package/dist/runtime/recovery/index.d.ts +1 -2
- package/dist/runtime/recovery/recovery.d.ts +2 -3
- package/dist/runtime/recovery/recovery.js +3 -3
- package/dist/shared/debug/pause.js +4 -21
- package/dist/shared/run/api.d.ts +2 -0
- package/dist/shared/run/browser.d.ts +9 -1
- package/dist/shared/run/browser.js +43 -3
- package/dist/shared/state/index.d.ts +1 -1
- package/dist/shared/state/index.js +2 -0
- package/dist/shared/state/session-state.d.ts +20 -1
- package/dist/shared/state/session-state.js +12 -2
- package/dist/shared/workflow/workflow.d.ts +2 -1
- package/dist/shared/workflow/workflow.js +16 -9
- package/package.json +17 -16
- package/scripts/postinstall.mjs +13 -11
- package/scripts/skills-libretto.mjs +14 -4
- package/skills/AGENTS.md +11 -0
- package/skills/libretto/SKILL.md +30 -9
- package/skills/libretto/references/auth-profiles.md +1 -1
- package/skills/libretto/references/code-generation-rules.md +3 -3
- package/skills/libretto/references/configuration-file-reference.md +11 -6
- package/skills/libretto-readonly/SKILL.md +95 -0
- package/src/cli/cli.ts +10 -0
- package/src/cli/commands/ai.ts +111 -1
- package/src/cli/commands/browser.ts +111 -9
- package/src/cli/commands/execution.ts +181 -74
- package/src/cli/commands/setup.ts +516 -0
- package/src/cli/commands/snapshot.ts +2 -2
- package/src/cli/commands/status.ts +79 -0
- package/src/cli/core/{snapshot-api-config.ts → ai-model.ts} +154 -14
- package/src/cli/core/api-snapshot-analyzer.ts +7 -5
- package/src/cli/core/browser.ts +242 -35
- package/src/cli/core/{ai-config.ts → config.ts} +14 -108
- package/src/cli/core/context.ts +1 -45
- package/src/cli/core/deploy-artifact.ts +141 -71
- package/src/cli/core/providers/browserbase.ts +57 -0
- package/src/cli/core/providers/index.ts +62 -0
- package/src/cli/core/providers/kernel.ts +49 -0
- package/src/cli/core/providers/libretto-cloud.ts +61 -0
- package/src/cli/core/providers/types.ts +9 -0
- package/src/cli/core/readonly-exec.ts +284 -0
- package/src/{shared/llm/client.ts → cli/core/resolve-model.ts} +3 -85
- package/src/cli/core/session.ts +75 -2
- package/src/cli/core/skill-version.ts +93 -0
- package/src/cli/core/telemetry.ts +0 -52
- package/src/cli/index.ts +0 -6
- package/src/cli/router.ts +4 -4
- package/src/cli/workers/run-integration-runtime.ts +18 -16
- package/src/cli/workers/run-integration-worker-protocol.ts +4 -1
- package/src/index.ts +1 -7
- package/src/runtime/extract/extract.ts +6 -5
- package/src/runtime/recovery/agent.ts +5 -4
- package/src/runtime/recovery/errors.ts +4 -3
- package/src/runtime/recovery/recovery.ts +4 -4
- package/src/shared/debug/pause.ts +4 -23
- package/src/shared/run/browser.ts +50 -1
- package/src/shared/state/index.ts +2 -0
- package/src/shared/state/session-state.ts +10 -0
- package/src/shared/workflow/workflow.ts +24 -13
- package/dist/cli/commands/init.js +0 -286
- package/dist/cli/commands/logs.js +0 -117
- package/dist/shared/llm/ai-sdk-adapter.d.ts +0 -22
- package/dist/shared/llm/ai-sdk-adapter.js +0 -49
- package/dist/shared/llm/client.d.ts +0 -13
- package/dist/shared/llm/index.d.ts +0 -5
- package/dist/shared/llm/index.js +0 -6
- package/dist/shared/llm/types.d.ts +0 -67
- package/src/cli/commands/init.ts +0 -331
- package/src/cli/commands/logs.ts +0 -128
- package/src/shared/llm/ai-sdk-adapter.ts +0 -81
- package/src/shared/llm/index.ts +0 -3
- package/src/shared/llm/types.ts +0 -63
- /package/dist/{shared/llm → cli/core/providers}/types.js +0 -0
package/dist/cli/core/browser.js
CHANGED
|
@@ -19,16 +19,18 @@ import {
|
|
|
19
19
|
getSessionNetworkLogPath,
|
|
20
20
|
PROFILES_DIR
|
|
21
21
|
} from "./context.js";
|
|
22
|
-
import { readLibrettoConfig } from "./
|
|
22
|
+
import { readLibrettoConfig } from "./config.js";
|
|
23
23
|
import {
|
|
24
24
|
assertSessionAvailableForStart,
|
|
25
25
|
clearSessionState,
|
|
26
|
+
isPidRunning,
|
|
26
27
|
listSessionsWithStateFile,
|
|
27
28
|
readSessionStateOrThrow,
|
|
28
29
|
logFileForSession,
|
|
29
30
|
readSessionState,
|
|
30
31
|
writeSessionState
|
|
31
32
|
} from "./session.js";
|
|
33
|
+
import { getCloudProviderApi } from "./providers/index.js";
|
|
32
34
|
import { installSessionTelemetry } from "./session-telemetry.js";
|
|
33
35
|
const CLOSE_WAIT_MS = 1500;
|
|
34
36
|
const FORCE_CLOSE_WAIT_MS = 300;
|
|
@@ -178,6 +180,11 @@ async function connect(session, logger, timeoutMs = 1e4, options) {
|
|
|
178
180
|
endpoint,
|
|
179
181
|
pid: state.pid
|
|
180
182
|
});
|
|
183
|
+
if (state.provider) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Could not connect to ${state.provider.name} session for "${session}" at ${endpoint}. The remote session may still be active. Try again, or close with: libretto close --session ${session}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
181
188
|
if (state.pid == null || !isPidRunning(state.pid)) {
|
|
182
189
|
clearSessionState(session, logger);
|
|
183
190
|
throw new Error(
|
|
@@ -293,8 +300,16 @@ async function runOpen(rawUrl, headed, session, logger, options) {
|
|
|
293
300
|
const parsedUrl = normalizeUrl(rawUrl);
|
|
294
301
|
const url = parsedUrl.href;
|
|
295
302
|
const viewport = resolveViewport(options?.viewport, logger);
|
|
303
|
+
const accessMode = options?.accessMode ?? "write-access";
|
|
296
304
|
const windowPosition = headed ? resolveWindowPosition(logger) : void 0;
|
|
297
|
-
logger.info("open-start", {
|
|
305
|
+
logger.info("open-start", {
|
|
306
|
+
url,
|
|
307
|
+
headed,
|
|
308
|
+
session,
|
|
309
|
+
viewport,
|
|
310
|
+
windowPosition,
|
|
311
|
+
accessMode
|
|
312
|
+
});
|
|
298
313
|
assertSessionAvailableForStart(session, logger);
|
|
299
314
|
const port = await pickFreePort();
|
|
300
315
|
const runLogPath = logFileForSession(session);
|
|
@@ -523,6 +538,7 @@ await new Promise(() => {});
|
|
|
523
538
|
session,
|
|
524
539
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
525
540
|
status: "active",
|
|
541
|
+
mode: accessMode,
|
|
526
542
|
viewport
|
|
527
543
|
},
|
|
528
544
|
logger
|
|
@@ -549,6 +565,86 @@ await new Promise(() => {});
|
|
|
549
565
|
`Failed to connect to browser after ${Math.ceil(cdpStartupTimeoutMs / 1e3)}s. Check startup logs: ${runLogPath}`
|
|
550
566
|
);
|
|
551
567
|
}
|
|
568
|
+
async function runOpenWithProvider(rawUrl, providerName, provider, session, logger, accessMode = "write-access") {
|
|
569
|
+
const parsedUrl = normalizeUrl(rawUrl);
|
|
570
|
+
const url = parsedUrl.href;
|
|
571
|
+
logger.info("open-provider-start", { url, provider: providerName, session });
|
|
572
|
+
console.log(
|
|
573
|
+
`Creating ${providerName} browser session (session: ${session})...`
|
|
574
|
+
);
|
|
575
|
+
const providerSession = await provider.createSession();
|
|
576
|
+
logger.info("open-provider-session-created", {
|
|
577
|
+
provider: providerName,
|
|
578
|
+
sessionId: providerSession.sessionId,
|
|
579
|
+
cdpEndpoint: providerSession.cdpEndpoint
|
|
580
|
+
});
|
|
581
|
+
console.log(`Connecting to ${providerName} browser...`);
|
|
582
|
+
let browser = null;
|
|
583
|
+
try {
|
|
584
|
+
browser = await tryConnectToCDP(
|
|
585
|
+
providerSession.cdpEndpoint,
|
|
586
|
+
logger,
|
|
587
|
+
3e4
|
|
588
|
+
);
|
|
589
|
+
if (!browser) {
|
|
590
|
+
throw new Error(
|
|
591
|
+
`Could not connect to ${providerName} browser at ${providerSession.cdpEndpoint}. The remote session was created but CDP connection failed.`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
const contexts = browser.contexts();
|
|
595
|
+
let page;
|
|
596
|
+
if (contexts.length > 0 && contexts[0].pages().length > 0) {
|
|
597
|
+
page = contexts[0].pages()[0];
|
|
598
|
+
} else {
|
|
599
|
+
const context = contexts.length > 0 ? contexts[0] : await browser.newContext();
|
|
600
|
+
page = await context.newPage();
|
|
601
|
+
}
|
|
602
|
+
await page.goto(url);
|
|
603
|
+
logger.info("open-provider-navigated", { url, session });
|
|
604
|
+
writeSessionState(
|
|
605
|
+
{
|
|
606
|
+
port: 0,
|
|
607
|
+
cdpEndpoint: providerSession.cdpEndpoint,
|
|
608
|
+
session,
|
|
609
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
610
|
+
status: "active",
|
|
611
|
+
mode: accessMode,
|
|
612
|
+
provider: {
|
|
613
|
+
name: providerName,
|
|
614
|
+
sessionId: providerSession.sessionId
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
logger
|
|
618
|
+
);
|
|
619
|
+
disconnectBrowser(browser, logger, session);
|
|
620
|
+
} catch (err) {
|
|
621
|
+
if (browser) {
|
|
622
|
+
disconnectBrowser(browser, logger, session);
|
|
623
|
+
}
|
|
624
|
+
logger.warn("open-provider-cleanup-after-error", {
|
|
625
|
+
provider: providerName,
|
|
626
|
+
sessionId: providerSession.sessionId,
|
|
627
|
+
error: err
|
|
628
|
+
});
|
|
629
|
+
try {
|
|
630
|
+
await provider.closeSession(providerSession.sessionId);
|
|
631
|
+
} catch (cleanupErr) {
|
|
632
|
+
logger.warn("open-provider-cleanup-failed", {
|
|
633
|
+
provider: providerName,
|
|
634
|
+
sessionId: providerSession.sessionId,
|
|
635
|
+
error: cleanupErr
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
throw err;
|
|
639
|
+
}
|
|
640
|
+
logger.info("open-provider-success", {
|
|
641
|
+
url,
|
|
642
|
+
provider: providerName,
|
|
643
|
+
session,
|
|
644
|
+
sessionId: providerSession.sessionId
|
|
645
|
+
});
|
|
646
|
+
console.log(`Browser open (${providerName}): ${url}`);
|
|
647
|
+
}
|
|
552
648
|
async function runSave(urlOrDomain, session, logger) {
|
|
553
649
|
logger.info("save-start", { urlOrDomain, session });
|
|
554
650
|
const { browser, context, page } = await connect(session, logger);
|
|
@@ -621,10 +717,33 @@ async function runClose(session, logger) {
|
|
|
621
717
|
console.log(`No browser running for session "${session}".`);
|
|
622
718
|
return;
|
|
623
719
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
720
|
+
if (state.provider) {
|
|
721
|
+
logger.info("close-provider", {
|
|
722
|
+
session,
|
|
723
|
+
provider: state.provider.name,
|
|
724
|
+
sessionId: state.provider.sessionId
|
|
725
|
+
});
|
|
726
|
+
try {
|
|
727
|
+
const provider = getCloudProviderApi(state.provider.name);
|
|
728
|
+
await provider.closeSession(state.provider.sessionId);
|
|
729
|
+
} catch (err) {
|
|
730
|
+
logger.warn("close-provider-error", {
|
|
731
|
+
session,
|
|
732
|
+
provider: state.provider.name,
|
|
733
|
+
sessionId: state.provider.sessionId,
|
|
734
|
+
error: err
|
|
735
|
+
});
|
|
736
|
+
writeSessionState({ ...state, status: "cleanup-failed" }, logger);
|
|
737
|
+
throw new Error(
|
|
738
|
+
`Failed to close remote ${state.provider.name} session "${state.provider.sessionId}" for session "${session}". State preserved with status "cleanup-failed". Retry with: libretto close --session ${session}`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
} else {
|
|
742
|
+
logger.info("close-killing", { session, pid: state.pid, port: state.port });
|
|
743
|
+
if (state.pid != null) {
|
|
744
|
+
sendSignalToProcessGroupOrPid(state.pid, "SIGTERM", logger, session);
|
|
745
|
+
await waitForCloseSignalWindow(CLOSE_WAIT_MS);
|
|
746
|
+
}
|
|
628
747
|
}
|
|
629
748
|
clearSessionState(session, logger);
|
|
630
749
|
logger.info("close-success", { session });
|
|
@@ -633,14 +752,6 @@ async function runClose(session, logger) {
|
|
|
633
752
|
function waitForCloseSignalWindow(ms) {
|
|
634
753
|
return new Promise((r) => setTimeout(r, ms));
|
|
635
754
|
}
|
|
636
|
-
function isPidRunning(pid) {
|
|
637
|
-
try {
|
|
638
|
-
process.kill(pid, 0);
|
|
639
|
-
return true;
|
|
640
|
-
} catch {
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
755
|
function sendSignalToProcessGroupOrPid(pid, signal, logger, session) {
|
|
645
756
|
try {
|
|
646
757
|
process.kill(pid, signal);
|
|
@@ -674,14 +785,16 @@ function resolveClosableSessions(logger) {
|
|
|
674
785
|
closable.push({
|
|
675
786
|
session,
|
|
676
787
|
pid: state.pid,
|
|
677
|
-
port: state.port
|
|
788
|
+
port: state.port,
|
|
789
|
+
provider: state.provider
|
|
678
790
|
});
|
|
679
791
|
}
|
|
680
792
|
return { closable, clearedUnreadableStates };
|
|
681
793
|
}
|
|
682
|
-
function clearStoppedSessionStates(sessions, logger) {
|
|
794
|
+
function clearStoppedSessionStates(sessions, logger, skip) {
|
|
683
795
|
let cleared = 0;
|
|
684
796
|
for (const session of sessions) {
|
|
797
|
+
if (skip?.has(session.session)) continue;
|
|
685
798
|
if (session.pid == null || !isPidRunning(session.pid)) {
|
|
686
799
|
clearSessionState(session.session, logger);
|
|
687
800
|
cleared += 1;
|
|
@@ -702,7 +815,34 @@ async function runCloseAll(logger, options) {
|
|
|
702
815
|
console.log("No browser sessions found.");
|
|
703
816
|
return;
|
|
704
817
|
}
|
|
818
|
+
const failedProviderSessions = /* @__PURE__ */ new Set();
|
|
705
819
|
for (const target of closable) {
|
|
820
|
+
if (target.provider) {
|
|
821
|
+
logger.info("close-all-provider", {
|
|
822
|
+
session: target.session,
|
|
823
|
+
provider: target.provider.name,
|
|
824
|
+
sessionId: target.provider.sessionId
|
|
825
|
+
});
|
|
826
|
+
try {
|
|
827
|
+
const provider = getCloudProviderApi(target.provider.name);
|
|
828
|
+
await provider.closeSession(target.provider.sessionId);
|
|
829
|
+
} catch (err) {
|
|
830
|
+
logger.warn("close-all-provider-error", {
|
|
831
|
+
session: target.session,
|
|
832
|
+
provider: target.provider.name,
|
|
833
|
+
sessionId: target.provider.sessionId,
|
|
834
|
+
error: err
|
|
835
|
+
});
|
|
836
|
+
failedProviderSessions.add(target.session);
|
|
837
|
+
const state = readSessionState(target.session, logger);
|
|
838
|
+
if (state) {
|
|
839
|
+
writeSessionState({ ...state, status: "cleanup-failed" }, logger);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
for (const target of closable) {
|
|
845
|
+
if (target.provider) continue;
|
|
706
846
|
logger.info("close-all-sigterm", {
|
|
707
847
|
session: target.session,
|
|
708
848
|
pid: target.pid,
|
|
@@ -722,7 +862,11 @@ async function runCloseAll(logger, options) {
|
|
|
722
862
|
(target) => target.pid != null && isPidRunning(target.pid)
|
|
723
863
|
);
|
|
724
864
|
if (survivors.length > 0 && !force) {
|
|
725
|
-
const closed = clearStoppedSessionStates(
|
|
865
|
+
const closed = clearStoppedSessionStates(
|
|
866
|
+
closable,
|
|
867
|
+
logger,
|
|
868
|
+
failedProviderSessions
|
|
869
|
+
);
|
|
726
870
|
throw new Error(
|
|
727
871
|
[
|
|
728
872
|
`Failed to close ${survivors.length} session(s) gracefully: ${formatSessionList(survivors)}.`,
|
|
@@ -753,7 +897,11 @@ async function runCloseAll(logger, options) {
|
|
|
753
897
|
(target) => target.pid != null && isPidRunning(target.pid)
|
|
754
898
|
);
|
|
755
899
|
if (survivors.length > 0) {
|
|
756
|
-
const closed = clearStoppedSessionStates(
|
|
900
|
+
const closed = clearStoppedSessionStates(
|
|
901
|
+
closable,
|
|
902
|
+
logger,
|
|
903
|
+
failedProviderSessions
|
|
904
|
+
);
|
|
757
905
|
throw new Error(
|
|
758
906
|
[
|
|
759
907
|
`Failed to force-close ${survivors.length} session(s): ${formatSessionList(survivors)}.`,
|
|
@@ -762,19 +910,25 @@ async function runCloseAll(logger, options) {
|
|
|
762
910
|
);
|
|
763
911
|
}
|
|
764
912
|
}
|
|
765
|
-
clearStoppedSessionStates(closable, logger);
|
|
913
|
+
clearStoppedSessionStates(closable, logger, failedProviderSessions);
|
|
914
|
+
if (failedProviderSessions.size > 0) {
|
|
915
|
+
console.log(
|
|
916
|
+
`Warning: ${failedProviderSessions.size} provider session(s) failed remote cleanup and were preserved with status "cleanup-failed".`
|
|
917
|
+
);
|
|
918
|
+
}
|
|
766
919
|
if (clearedUnreadableStates > 0) {
|
|
767
920
|
console.log(
|
|
768
921
|
`Cleared ${clearedUnreadableStates} unreadable session state file(s).`
|
|
769
922
|
);
|
|
770
923
|
}
|
|
771
|
-
|
|
924
|
+
const closedCount = closable.length - failedProviderSessions.size;
|
|
925
|
+
console.log(`Closed ${closedCount} session(s).`);
|
|
772
926
|
if (forceKilled > 0) {
|
|
773
927
|
console.log(`Force-killed ${forceKilled} session(s).`);
|
|
774
928
|
}
|
|
775
929
|
}
|
|
776
|
-
async function runConnect(cdpUrl, session, logger) {
|
|
777
|
-
logger.info("connect-start", { cdpUrl, session });
|
|
930
|
+
async function runConnect(cdpUrl, session, logger, accessMode = "write-access") {
|
|
931
|
+
logger.info("connect-start", { cdpUrl, session, accessMode });
|
|
778
932
|
assertSessionAvailableForStart(session, logger);
|
|
779
933
|
let parsedUrl;
|
|
780
934
|
try {
|
|
@@ -784,28 +938,38 @@ async function runConnect(cdpUrl, session, logger) {
|
|
|
784
938
|
[
|
|
785
939
|
`Invalid CDP URL: ${cdpUrl}`,
|
|
786
940
|
``,
|
|
787
|
-
`Expected an HTTP URL pointing to a Chrome DevTools Protocol endpoint, for example:`,
|
|
941
|
+
`Expected an HTTP or WebSocket URL pointing to a Chrome DevTools Protocol endpoint, for example:`,
|
|
788
942
|
` libretto connect http://127.0.0.1:9222`,
|
|
789
943
|
` libretto connect http://remote-host:9222`,
|
|
790
|
-
` libretto connect http://remote-host:9222/devtools/browser/<id
|
|
944
|
+
` libretto connect http://remote-host:9222/devtools/browser/<id>`,
|
|
945
|
+
` libretto connect ws://remote-host:9222/devtools/browser/<id>`,
|
|
946
|
+
` libretto connect wss://remote-host/cdp-endpoint`
|
|
791
947
|
].join("\n")
|
|
792
948
|
);
|
|
793
949
|
}
|
|
794
950
|
const endpoint = parsedUrl.href;
|
|
795
|
-
const
|
|
951
|
+
const isWebSocket = parsedUrl.protocol === "ws:" || parsedUrl.protocol === "wss:";
|
|
952
|
+
const port = parsedUrl.port ? Number(parsedUrl.port) : parsedUrl.protocol === "https:" || parsedUrl.protocol === "wss:" ? 443 : 80;
|
|
796
953
|
console.log(
|
|
797
954
|
`Connecting to CDP endpoint at ${endpoint} (session: ${session})...`
|
|
798
955
|
);
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
956
|
+
if (!isWebSocket) {
|
|
957
|
+
const versionUrl = `${parsedUrl.protocol}//${parsedUrl.host}/json/version`;
|
|
958
|
+
try {
|
|
959
|
+
const resp = await fetch(versionUrl);
|
|
960
|
+
const versionInfo = await resp.json();
|
|
961
|
+
logger.info("connect-version-ok", { versionUrl, versionInfo });
|
|
962
|
+
} catch (err) {
|
|
963
|
+
logger.error("connect-version-failed", { versionUrl, error: err });
|
|
964
|
+
throw new Error(
|
|
965
|
+
`Cannot reach CDP endpoint at ${versionUrl}. Make sure the target is running and accessible at ${parsedUrl.host}.`
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
} else {
|
|
969
|
+
logger.info("connect-skip-version-check", {
|
|
970
|
+
reason: "WebSocket-only endpoint, skipping HTTP version check",
|
|
971
|
+
endpoint
|
|
972
|
+
});
|
|
809
973
|
}
|
|
810
974
|
const browser = await tryConnectToCDP(endpoint, logger, 1e4);
|
|
811
975
|
if (!browser) {
|
|
@@ -826,7 +990,8 @@ async function runConnect(cdpUrl, session, logger) {
|
|
|
826
990
|
cdpEndpoint: endpoint,
|
|
827
991
|
session,
|
|
828
992
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
829
|
-
status: "active"
|
|
993
|
+
status: "active",
|
|
994
|
+
mode: accessMode
|
|
830
995
|
},
|
|
831
996
|
logger
|
|
832
997
|
);
|
|
@@ -865,6 +1030,7 @@ export {
|
|
|
865
1030
|
runCloseAll,
|
|
866
1031
|
runConnect,
|
|
867
1032
|
runOpen,
|
|
1033
|
+
runOpenWithProvider,
|
|
868
1034
|
runPages,
|
|
869
1035
|
runSave
|
|
870
1036
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { SessionAccessModeSchema } from "../../shared/state/index.js";
|
|
4
5
|
import { LIBRETTO_CONFIG_PATH } from "./context.js";
|
|
5
6
|
const CURRENT_CONFIG_VERSION = 1;
|
|
6
7
|
const AiConfigSchema = z.object({
|
|
@@ -19,27 +20,10 @@ const LibrettoConfigSchema = z.object({
|
|
|
19
20
|
version: z.literal(CURRENT_CONFIG_VERSION),
|
|
20
21
|
ai: AiConfigSchema.optional(),
|
|
21
22
|
viewport: ViewportConfigSchema.optional(),
|
|
22
|
-
windowPosition: WindowPositionConfigSchema.optional()
|
|
23
|
+
windowPosition: WindowPositionConfigSchema.optional(),
|
|
24
|
+
provider: z.string().optional(),
|
|
25
|
+
sessionMode: SessionAccessModeSchema.optional()
|
|
23
26
|
}).passthrough();
|
|
24
|
-
const DEFAULT_MODELS = {
|
|
25
|
-
openai: "openai/gpt-5.4",
|
|
26
|
-
anthropic: "anthropic/claude-sonnet-4-6",
|
|
27
|
-
gemini: "google/gemini-3-flash-preview",
|
|
28
|
-
vertex: "vertex/gemini-2.5-pro"
|
|
29
|
-
};
|
|
30
|
-
const PROVIDER_ALIASES = {
|
|
31
|
-
claude: DEFAULT_MODELS.anthropic,
|
|
32
|
-
google: DEFAULT_MODELS.gemini
|
|
33
|
-
};
|
|
34
|
-
const CONFIGURE_PROVIDERS = [
|
|
35
|
-
"openai",
|
|
36
|
-
"anthropic",
|
|
37
|
-
"gemini",
|
|
38
|
-
"vertex"
|
|
39
|
-
];
|
|
40
|
-
function formatConfigureProviders(separator = " | ") {
|
|
41
|
-
return CONFIGURE_PROVIDERS.join(separator);
|
|
42
|
-
}
|
|
43
27
|
function formatConfigIssues(error) {
|
|
44
28
|
return error.issues.map((issue) => ` - ${issue.path.join(".") || "root"}: ${issue.message}`).join("\n");
|
|
45
29
|
}
|
|
@@ -58,7 +42,8 @@ function formatExpectedConfigExample() {
|
|
|
58
42
|
windowPosition: {
|
|
59
43
|
x: 1600,
|
|
60
44
|
y: 120
|
|
61
|
-
}
|
|
45
|
+
},
|
|
46
|
+
sessionMode: "write-access"
|
|
62
47
|
},
|
|
63
48
|
null,
|
|
64
49
|
2
|
|
@@ -73,10 +58,10 @@ ${detail}` : null,
|
|
|
73
58
|
"Expected config example:",
|
|
74
59
|
formatExpectedConfigExample(),
|
|
75
60
|
"Notes:",
|
|
76
|
-
' - "ai", "viewport", and "
|
|
61
|
+
' - "ai", "viewport", "windowPosition", and "sessionMode" are optional.',
|
|
77
62
|
' - "ai.model" must be a provider/model string like "openai/gpt-5.4" or "anthropic/claude-sonnet-4-6".',
|
|
78
63
|
"Fix the file to match this shape, or delete it and rerun:",
|
|
79
|
-
` npx libretto ai configure
|
|
64
|
+
` npx libretto ai configure openai | anthropic | gemini | vertex`
|
|
80
65
|
].filter(Boolean).join("\n")
|
|
81
66
|
);
|
|
82
67
|
}
|
|
@@ -112,7 +97,12 @@ function readAiConfig(configPath = LIBRETTO_CONFIG_PATH) {
|
|
|
112
97
|
return readLibrettoConfig(configPath).ai ?? null;
|
|
113
98
|
}
|
|
114
99
|
function writeAiConfig(model, configPath = LIBRETTO_CONFIG_PATH) {
|
|
115
|
-
|
|
100
|
+
let librettoConfig;
|
|
101
|
+
try {
|
|
102
|
+
librettoConfig = readLibrettoConfig(configPath);
|
|
103
|
+
} catch {
|
|
104
|
+
librettoConfig = { version: CURRENT_CONFIG_VERSION };
|
|
105
|
+
}
|
|
116
106
|
const ai = AiConfigSchema.parse({
|
|
117
107
|
model,
|
|
118
108
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -139,60 +129,6 @@ function clearAiConfig(configPath = LIBRETTO_CONFIG_PATH) {
|
|
|
139
129
|
);
|
|
140
130
|
return true;
|
|
141
131
|
}
|
|
142
|
-
function printAiConfig(config, configPath) {
|
|
143
|
-
console.log(`Model: ${config.model}`);
|
|
144
|
-
console.log(`Config file: ${configPath}`);
|
|
145
|
-
console.log(`Updated at: ${config.updatedAt}`);
|
|
146
|
-
}
|
|
147
|
-
function resolveModelFromInput(input) {
|
|
148
|
-
const trimmed = input.trim();
|
|
149
|
-
if (!trimmed) return null;
|
|
150
|
-
if (trimmed.includes("/")) return trimmed;
|
|
151
|
-
const normalized = trimmed.toLowerCase();
|
|
152
|
-
return DEFAULT_MODELS[normalized] ?? PROVIDER_ALIASES[normalized] ?? null;
|
|
153
|
-
}
|
|
154
|
-
function runAiConfigure(input, options = {}) {
|
|
155
|
-
const configureCommandName = options.configureCommandName ?? "npx libretto ai configure";
|
|
156
|
-
const configPath = options.configPath ?? LIBRETTO_CONFIG_PATH;
|
|
157
|
-
const presetArg = input.preset?.trim();
|
|
158
|
-
if (!presetArg && !input.clear) {
|
|
159
|
-
const config2 = readAiConfig(configPath);
|
|
160
|
-
if (!config2) {
|
|
161
|
-
console.log(
|
|
162
|
-
`No AI config set. Choose a default model: ${configureCommandName} ${formatConfigureProviders()}`
|
|
163
|
-
);
|
|
164
|
-
console.log(
|
|
165
|
-
"Provider credentials still come from your shell or .env file."
|
|
166
|
-
);
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
printAiConfig(config2, configPath);
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (input.clear) {
|
|
173
|
-
const removed = clearAiConfig(configPath);
|
|
174
|
-
if (removed) {
|
|
175
|
-
console.log(`Cleared AI config: ${configPath}`);
|
|
176
|
-
} else {
|
|
177
|
-
console.log("No AI config was set.");
|
|
178
|
-
}
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
const model = resolveModelFromInput(presetArg);
|
|
182
|
-
if (!model) {
|
|
183
|
-
console.log(
|
|
184
|
-
`Usage: ${configureCommandName} <${CONFIGURE_PROVIDERS.join("|")}|provider/model-id>
|
|
185
|
-
${configureCommandName}
|
|
186
|
-
${configureCommandName} --clear`
|
|
187
|
-
);
|
|
188
|
-
throw new Error(
|
|
189
|
-
`Invalid provider or model. Use one of: ${formatConfigureProviders()}, or a full model string like "openai/gpt-4o".`
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
const config = writeAiConfig(model, configPath);
|
|
193
|
-
console.log("AI config saved.");
|
|
194
|
-
printAiConfig(config, configPath);
|
|
195
|
-
}
|
|
196
132
|
export {
|
|
197
133
|
AiConfigSchema,
|
|
198
134
|
CURRENT_CONFIG_VERSION,
|
|
@@ -202,7 +138,6 @@ export {
|
|
|
202
138
|
clearAiConfig,
|
|
203
139
|
readAiConfig,
|
|
204
140
|
readLibrettoConfig,
|
|
205
|
-
runAiConfigure,
|
|
206
141
|
writeAiConfig,
|
|
207
142
|
writeLibrettoConfig
|
|
208
143
|
};
|
package/dist/cli/core/context.js
CHANGED
|
@@ -54,34 +54,14 @@ function createLoggerForSession(session) {
|
|
|
54
54
|
[createFileLogSink({ filePath: logFilePath })]
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
|
-
async function closeLogger(logger) {
|
|
58
|
-
if (!logger) return;
|
|
59
|
-
await logger.close();
|
|
60
|
-
}
|
|
61
57
|
async function withSessionLogger(session, run) {
|
|
62
58
|
const logger = createLoggerForSession(session);
|
|
63
59
|
try {
|
|
64
60
|
return await run(logger);
|
|
65
61
|
} finally {
|
|
66
|
-
await
|
|
62
|
+
await logger.close();
|
|
67
63
|
}
|
|
68
64
|
}
|
|
69
|
-
let llmClientFactory = null;
|
|
70
|
-
function setLLMClientFactory(factory) {
|
|
71
|
-
llmClientFactory = factory;
|
|
72
|
-
}
|
|
73
|
-
function getLLMClientFactory() {
|
|
74
|
-
return llmClientFactory;
|
|
75
|
-
}
|
|
76
|
-
function maybeConfigureLLMClientFactoryFromEnv() {
|
|
77
|
-
if (llmClientFactory) return;
|
|
78
|
-
const hasAnyCreds = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
79
|
-
if (!hasAnyCreds) return;
|
|
80
|
-
setLLMClientFactory(async (_logger, model) => {
|
|
81
|
-
const { createLLMClient } = await import("../../shared/llm/index.js");
|
|
82
|
-
return createLLMClient(model);
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
65
|
export {
|
|
86
66
|
LIBRETTO_CONFIG_DIR,
|
|
87
67
|
LIBRETTO_CONFIG_PATH,
|
|
@@ -89,10 +69,8 @@ export {
|
|
|
89
69
|
LIBRETTO_SESSIONS_DIR,
|
|
90
70
|
PROFILES_DIR,
|
|
91
71
|
REPO_ROOT,
|
|
92
|
-
closeLogger,
|
|
93
72
|
createLoggerForSession,
|
|
94
73
|
ensureLibrettoSetup,
|
|
95
|
-
getLLMClientFactory,
|
|
96
74
|
getSessionActionsLogPath,
|
|
97
75
|
getSessionDir,
|
|
98
76
|
getSessionLogsPath,
|
|
@@ -100,7 +78,5 @@ export {
|
|
|
100
78
|
getSessionSnapshotRunDir,
|
|
101
79
|
getSessionSnapshotsDir,
|
|
102
80
|
getSessionStatePath,
|
|
103
|
-
maybeConfigureLLMClientFactoryFromEnv,
|
|
104
|
-
setLLMClientFactory,
|
|
105
81
|
withSessionLogger
|
|
106
82
|
};
|