libretto 0.6.24 → 0.6.25
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 +9 -1
- package/README.template.md +9 -1
- package/dist/cli/commands/browser.js +17 -10
- package/dist/cli/commands/cloud-credentials.js +70 -0
- package/dist/cli/commands/deploy.js +24 -2
- package/dist/cli/commands/execution.js +9 -30
- package/dist/cli/commands/import-chrome-profiles.js +46 -0
- package/dist/cli/commands/profiles.js +71 -0
- package/dist/cli/commands/shared.js +1 -3
- package/dist/cli/core/browser.js +89 -75
- package/dist/cli/core/daemon/daemon.js +47 -35
- package/dist/cli/core/daemon/ipc.js +3 -0
- package/dist/cli/core/deploy-artifact.js +85 -22
- package/dist/cli/core/profiles.js +47 -0
- package/dist/cli/core/prompt.js +9 -0
- package/dist/cli/core/providers/libretto-cloud.js +6 -2
- package/dist/cli/core/session-logs.js +325 -0
- package/dist/cli/core/telemetry.js +83 -313
- package/dist/cli/core/workflow-runner/runner.js +65 -0
- package/dist/cli/router.js +9 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +12 -0
- package/dist/shared/workflow/auth-profile-name.d.ts +3 -0
- package/dist/shared/workflow/auth-profile-name.js +29 -0
- package/dist/shared/workflow/auth-profile-state.d.ts +20 -0
- package/dist/shared/workflow/auth-profile-state.js +105 -0
- package/dist/shared/workflow/authenticate.d.ts +17 -0
- package/dist/shared/workflow/authenticate.js +37 -0
- package/dist/shared/workflow/credentials.d.ts +5 -0
- package/dist/shared/workflow/credentials.js +68 -0
- package/dist/shared/workflow/workflow.d.ts +16 -1
- package/dist/shared/workflow/workflow.js +56 -4
- package/package.json +1 -1
- package/skills/libretto/SKILL.md +3 -4
- package/skills/libretto/references/auth-profiles.md +61 -11
- package/skills/libretto/references/code-generation-rules.md +31 -1
- package/skills/libretto-readonly/SKILL.md +1 -1
- package/src/cli/commands/browser.ts +19 -11
- package/src/cli/commands/cloud-credentials.ts +82 -0
- package/src/cli/commands/deploy.ts +41 -2
- package/src/cli/commands/execution.ts +10 -31
- package/src/cli/commands/import-chrome-profiles.ts +46 -0
- package/src/cli/commands/profiles.ts +90 -0
- package/src/cli/commands/shared.ts +4 -8
- package/src/cli/core/browser.ts +102 -91
- package/src/cli/core/daemon/config.ts +4 -1
- package/src/cli/core/daemon/daemon.ts +52 -44
- package/src/cli/core/daemon/ipc.ts +15 -0
- package/src/cli/core/deploy-artifact.ts +131 -32
- package/src/cli/core/profiles.ts +53 -0
- package/src/cli/core/prompt.ts +15 -0
- package/src/cli/core/providers/libretto-cloud.ts +6 -2
- package/src/cli/core/providers/types.ts +4 -1
- package/src/cli/core/session-logs.ts +445 -0
- package/src/cli/core/telemetry.ts +105 -422
- package/src/cli/core/workflow-runner/runner.ts +86 -1
- package/src/cli/router.ts +8 -0
- package/src/index.ts +10 -0
- package/src/shared/workflow/auth-profile-name.ts +27 -0
- package/src/shared/workflow/auth-profile-state.ts +144 -0
- package/src/shared/workflow/authenticate.ts +63 -0
- package/src/shared/workflow/credentials.ts +91 -0
- package/src/shared/workflow/workflow.ts +89 -4
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
getWorkflowsFromModuleExports,
|
|
22
22
|
LIBRETTO_WORKFLOW_BRAND,
|
|
23
23
|
} from "../../shared/workflow/workflow.js";
|
|
24
|
+
import { normalizeCredentialNames } from "../../shared/workflow/credentials.js";
|
|
24
25
|
|
|
25
26
|
type PackageManifest = {
|
|
26
27
|
name?: string;
|
|
@@ -48,6 +49,14 @@ type HostedDeployPackage = {
|
|
|
48
49
|
cleanup: () => void;
|
|
49
50
|
entryPoint: string;
|
|
50
51
|
outputDir: string;
|
|
52
|
+
workflows: WorkflowDeployMetadata[];
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type WorkflowDeployMetadata = {
|
|
56
|
+
name: string;
|
|
57
|
+
credentialNames: string[];
|
|
58
|
+
authProfileName?: string;
|
|
59
|
+
authProfileRefresh?: boolean;
|
|
51
60
|
};
|
|
52
61
|
|
|
53
62
|
type BuildHostedDeployTarballArgs = {
|
|
@@ -66,6 +75,7 @@ const DEFAULT_RUNTIME_EXTERNALS = [
|
|
|
66
75
|
"chromium-bidi",
|
|
67
76
|
] as const;
|
|
68
77
|
const BUILT_IN_MANIFEST_DEPENDENCIES = ["libretto"] as const;
|
|
78
|
+
const DEPLOY_METADATA_FILENAME = ".libretto-workflows.json";
|
|
69
79
|
const SOURCE_FILE_EXTENSIONS = [
|
|
70
80
|
"",
|
|
71
81
|
".ts",
|
|
@@ -598,6 +608,16 @@ function writeDeployManifest(args: {
|
|
|
598
608
|
);
|
|
599
609
|
}
|
|
600
610
|
|
|
611
|
+
function writeDeployMetadata(args: {
|
|
612
|
+
outputDir: string;
|
|
613
|
+
workflows: readonly WorkflowDeployMetadata[];
|
|
614
|
+
}): void {
|
|
615
|
+
writeFileSync(
|
|
616
|
+
join(args.outputDir, DEPLOY_METADATA_FILENAME),
|
|
617
|
+
JSON.stringify({ workflows: args.workflows }, null, 2) + "\n",
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
601
621
|
function shouldVendorCurrentLibretto(versionSpec: string): boolean {
|
|
602
622
|
return (
|
|
603
623
|
versionSpec.startsWith("file:") ||
|
|
@@ -696,11 +716,17 @@ function createExternalDiscoveryStub(): object {
|
|
|
696
716
|
});
|
|
697
717
|
}
|
|
698
718
|
|
|
699
|
-
function createDiscoveryLibrettoModule(
|
|
719
|
+
function createDiscoveryLibrettoModule(
|
|
720
|
+
workflowsByName: Map<string, WorkflowDeployMetadata>,
|
|
721
|
+
): object {
|
|
700
722
|
const moduleShape: Record<PropertyKey, unknown> = {
|
|
701
723
|
LIBRETTO_WORKFLOW_BRAND,
|
|
702
|
-
workflow: (name: string) => {
|
|
703
|
-
|
|
724
|
+
workflow: (name: string, definitionOrHandler?: unknown) => {
|
|
725
|
+
workflowsByName.set(name, {
|
|
726
|
+
name,
|
|
727
|
+
...extractDiscoveryCredentialMetadata(definitionOrHandler),
|
|
728
|
+
...extractDiscoveryAuthProfileMetadata(definitionOrHandler),
|
|
729
|
+
});
|
|
704
730
|
return {
|
|
705
731
|
[LIBRETTO_WORKFLOW_BRAND]: true,
|
|
706
732
|
name,
|
|
@@ -724,19 +750,64 @@ function createDiscoveryLibrettoModule(workflowNames: Set<string>): object {
|
|
|
724
750
|
});
|
|
725
751
|
}
|
|
726
752
|
|
|
727
|
-
function
|
|
753
|
+
function extractDiscoveryCredentialMetadata(
|
|
754
|
+
definitionOrHandler: unknown,
|
|
755
|
+
): Pick<WorkflowDeployMetadata, "credentialNames"> {
|
|
756
|
+
if (
|
|
757
|
+
!definitionOrHandler ||
|
|
758
|
+
typeof definitionOrHandler !== "object" ||
|
|
759
|
+
!("credentials" in definitionOrHandler)
|
|
760
|
+
) {
|
|
761
|
+
return { credentialNames: [] };
|
|
762
|
+
}
|
|
763
|
+
const rawCredentials = (definitionOrHandler as { credentials?: unknown })
|
|
764
|
+
.credentials;
|
|
765
|
+
return {
|
|
766
|
+
credentialNames: Array.isArray(rawCredentials)
|
|
767
|
+
? normalizeCredentialNames(rawCredentials)
|
|
768
|
+
: [],
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function extractDiscoveryAuthProfileMetadata(
|
|
773
|
+
definitionOrHandler: unknown,
|
|
774
|
+
): Omit<WorkflowDeployMetadata, "name" | "credentialNames"> {
|
|
775
|
+
if (
|
|
776
|
+
!definitionOrHandler ||
|
|
777
|
+
typeof definitionOrHandler !== "object" ||
|
|
778
|
+
!("authProfile" in definitionOrHandler)
|
|
779
|
+
) {
|
|
780
|
+
return {};
|
|
781
|
+
}
|
|
782
|
+
const authProfile = (definitionOrHandler as { authProfile?: unknown }).authProfile;
|
|
783
|
+
if (typeof authProfile === "string") return { authProfileName: authProfile };
|
|
784
|
+
if (!authProfile || typeof authProfile !== "object") return {};
|
|
785
|
+
const record = authProfile as {
|
|
786
|
+
name?: unknown;
|
|
787
|
+
refresh?: unknown;
|
|
788
|
+
};
|
|
789
|
+
if (typeof record.name !== "string") return {};
|
|
790
|
+
return {
|
|
791
|
+
authProfileName: record.name,
|
|
792
|
+
...(typeof record.refresh === "boolean"
|
|
793
|
+
? { authProfileRefresh: record.refresh }
|
|
794
|
+
: {}),
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function discoverBundledWorkflows(args: {
|
|
728
799
|
absEntryPoint: string;
|
|
729
800
|
absSourceDir: string;
|
|
730
801
|
bundleBuffer: Buffer;
|
|
731
802
|
externalPackages: ReadonlySet<string>;
|
|
732
|
-
}):
|
|
803
|
+
}): WorkflowDeployMetadata[] {
|
|
733
804
|
const discoveryPath = join(
|
|
734
805
|
args.absSourceDir,
|
|
735
806
|
`.libretto-deploy-discovery-${process.pid}-${Date.now()}.cjs`,
|
|
736
807
|
);
|
|
737
808
|
const originalRequire = Module.prototype.require;
|
|
738
|
-
const
|
|
739
|
-
const discoveryLibrettoModule = createDiscoveryLibrettoModule(
|
|
809
|
+
const workflowsByName = new Map<string, WorkflowDeployMetadata>();
|
|
810
|
+
const discoveryLibrettoModule = createDiscoveryLibrettoModule(workflowsByName);
|
|
740
811
|
let loadedModuleExports: Record<string, unknown> | null = null;
|
|
741
812
|
|
|
742
813
|
try {
|
|
@@ -764,11 +835,11 @@ function discoverBundledWorkflowNames(args: {
|
|
|
764
835
|
rmSync(discoveryPath, { force: true });
|
|
765
836
|
}
|
|
766
837
|
|
|
767
|
-
const
|
|
768
|
-
left.localeCompare(right),
|
|
838
|
+
const discoveredWorkflows = [...workflowsByName.values()].sort((left, right) =>
|
|
839
|
+
left.name.localeCompare(right.name),
|
|
769
840
|
);
|
|
770
841
|
|
|
771
|
-
if (
|
|
842
|
+
if (discoveredWorkflows.length === 0) {
|
|
772
843
|
throw new Error(
|
|
773
844
|
`No workflows were found in ${args.absEntryPoint}. Import the workflow files you want to deploy from the entry point, or export a workflow directly from it.`,
|
|
774
845
|
);
|
|
@@ -779,23 +850,23 @@ function discoverBundledWorkflowNames(args: {
|
|
|
779
850
|
(workflow) => workflow.name,
|
|
780
851
|
),
|
|
781
852
|
);
|
|
782
|
-
const nonExportedWorkflowNames =
|
|
783
|
-
(
|
|
853
|
+
const nonExportedWorkflowNames = discoveredWorkflows.filter(
|
|
854
|
+
(workflow) => !exportedWorkflowNames.has(workflow.name),
|
|
784
855
|
);
|
|
785
856
|
|
|
786
857
|
if (nonExportedWorkflowNames.length > 0) {
|
|
787
858
|
throw new Error(
|
|
788
|
-
`Workflows discovered in ${args.absEntryPoint} must be exported from the deploy entry point. Re-export them from the entry point or export them through a \`workflows\` object. Non-exported workflows: ${nonExportedWorkflowNames.join(", ")}`,
|
|
859
|
+
`Workflows discovered in ${args.absEntryPoint} must be exported from the deploy entry point. Re-export them from the entry point or export them through a \`workflows\` object. Non-exported workflows: ${nonExportedWorkflowNames.map((workflow) => workflow.name).join(", ")}`,
|
|
789
860
|
);
|
|
790
861
|
}
|
|
791
862
|
|
|
792
|
-
return
|
|
863
|
+
return discoveredWorkflows;
|
|
793
864
|
}
|
|
794
865
|
|
|
795
866
|
function createBootstrapSource(args: {
|
|
796
867
|
bundleBuffer: Buffer;
|
|
797
868
|
deploymentName: string;
|
|
798
|
-
|
|
869
|
+
workflows: readonly WorkflowDeployMetadata[];
|
|
799
870
|
}): string {
|
|
800
871
|
const bundleHash = createHash("sha256")
|
|
801
872
|
.update(args.bundleBuffer)
|
|
@@ -805,10 +876,14 @@ function createBootstrapSource(args: {
|
|
|
805
876
|
"base64",
|
|
806
877
|
);
|
|
807
878
|
const outputPrefix = `${normalizePackageName(args.deploymentName)}-`;
|
|
808
|
-
const exportLines = args.
|
|
879
|
+
const exportLines = args.workflows
|
|
809
880
|
.map(
|
|
810
|
-
(
|
|
811
|
-
`export const ${getGeneratedWorkflowExportName(index)} = createWorkflowProxy(${JSON.stringify(name)}
|
|
881
|
+
(workflow, index) =>
|
|
882
|
+
`export const ${getGeneratedWorkflowExportName(index)} = createWorkflowProxy(${JSON.stringify(workflow.name)}, ${JSON.stringify({
|
|
883
|
+
credentialNames: workflow.credentialNames,
|
|
884
|
+
authProfileName: workflow.authProfileName,
|
|
885
|
+
authProfileRefresh: workflow.authProfileRefresh,
|
|
886
|
+
})});`,
|
|
812
887
|
)
|
|
813
888
|
.join("\n");
|
|
814
889
|
|
|
@@ -844,8 +919,8 @@ function ensureBundleFile() {
|
|
|
844
919
|
return BUNDLE_FILENAME;
|
|
845
920
|
}
|
|
846
921
|
|
|
847
|
-
function createWorkflowProxy(workflowName) {
|
|
848
|
-
|
|
922
|
+
function createWorkflowProxy(workflowName, metadata) {
|
|
923
|
+
const handler = async (ctx, input) => {
|
|
849
924
|
const impl = nativeRequire(ensureBundleFile());
|
|
850
925
|
const target = getWorkflowFromModuleExports(impl, workflowName);
|
|
851
926
|
if (!target || typeof target.run !== "function") {
|
|
@@ -854,6 +929,26 @@ function createWorkflowProxy(workflowName) {
|
|
|
854
929
|
);
|
|
855
930
|
}
|
|
856
931
|
return await target.run(ctx, input);
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
if (!metadata?.authProfileName) {
|
|
935
|
+
return workflow(workflowName, {
|
|
936
|
+
credentials: Array.isArray(metadata?.credentialNames)
|
|
937
|
+
? metadata.credentialNames
|
|
938
|
+
: [],
|
|
939
|
+
handler,
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
return workflow(workflowName, {
|
|
944
|
+
credentials: Array.isArray(metadata.credentialNames)
|
|
945
|
+
? metadata.credentialNames
|
|
946
|
+
: [],
|
|
947
|
+
authProfile: {
|
|
948
|
+
name: metadata.authProfileName,
|
|
949
|
+
...(typeof metadata.authProfileRefresh === "boolean" ? { refresh: metadata.authProfileRefresh } : {}),
|
|
950
|
+
},
|
|
951
|
+
handler,
|
|
857
952
|
});
|
|
858
953
|
}
|
|
859
954
|
|
|
@@ -868,7 +963,7 @@ async function writeBundledDeployEntrypoint(args: {
|
|
|
868
963
|
externalPackages: ReadonlySet<string>;
|
|
869
964
|
outputDir: string;
|
|
870
965
|
workspacePackages: Map<string, WorkspacePackage>;
|
|
871
|
-
}): Promise<
|
|
966
|
+
}): Promise<WorkflowDeployMetadata[]> {
|
|
872
967
|
try {
|
|
873
968
|
// The implementation bundle is CommonJS so the bootstrap can load it lazily
|
|
874
969
|
// with createRequire() after workflow discovery, while external packages
|
|
@@ -898,7 +993,7 @@ async function writeBundledDeployEntrypoint(args: {
|
|
|
898
993
|
);
|
|
899
994
|
}
|
|
900
995
|
|
|
901
|
-
const
|
|
996
|
+
const workflows = discoverBundledWorkflows({
|
|
902
997
|
absEntryPoint: args.absEntryPoint,
|
|
903
998
|
absSourceDir: args.absSourceDir,
|
|
904
999
|
bundleBuffer: Buffer.from(bundledImplementation.contents),
|
|
@@ -910,9 +1005,10 @@ async function writeBundledDeployEntrypoint(args: {
|
|
|
910
1005
|
createBootstrapSource({
|
|
911
1006
|
bundleBuffer: Buffer.from(bundledImplementation.contents),
|
|
912
1007
|
deploymentName: args.deploymentName,
|
|
913
|
-
|
|
1008
|
+
workflows,
|
|
914
1009
|
}),
|
|
915
1010
|
);
|
|
1011
|
+
return workflows;
|
|
916
1012
|
} catch (error) {
|
|
917
1013
|
throw new Error(
|
|
918
1014
|
`Failed to bundle deploy entry point ${args.absEntryPoint}.\n${formatBuildError(error)}`,
|
|
@@ -944,7 +1040,7 @@ export async function createHostedDeployPackage(
|
|
|
944
1040
|
let callerOwnsTempRoot = false;
|
|
945
1041
|
|
|
946
1042
|
try {
|
|
947
|
-
await writeBundledDeployEntrypoint({
|
|
1043
|
+
const workflows = await writeBundledDeployEntrypoint({
|
|
948
1044
|
absEntryPoint,
|
|
949
1045
|
absSourceDir,
|
|
950
1046
|
deploymentName: args.deploymentName,
|
|
@@ -967,17 +1063,19 @@ export async function createHostedDeployPackage(
|
|
|
967
1063
|
outputDir,
|
|
968
1064
|
sourceDir: absSourceDir,
|
|
969
1065
|
});
|
|
1066
|
+
writeDeployMetadata({ outputDir, workflows });
|
|
970
1067
|
|
|
971
1068
|
// Success transfers ownership of the temp directory to the caller, who is
|
|
972
1069
|
// responsible for invoking cleanup() after the tarball/upload step.
|
|
973
1070
|
callerOwnsTempRoot = true;
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1071
|
+
return {
|
|
1072
|
+
cleanup: () => {
|
|
1073
|
+
rmSync(tempRoot, { force: true, recursive: true });
|
|
1074
|
+
},
|
|
1075
|
+
entryPoint: "index.js",
|
|
1076
|
+
outputDir,
|
|
1077
|
+
workflows,
|
|
1078
|
+
};
|
|
981
1079
|
} finally {
|
|
982
1080
|
// On any failure before we return, this function still owns the temp dir
|
|
983
1081
|
// and must remove it to avoid leaking deploy workspaces in /tmp.
|
|
@@ -989,7 +1087,7 @@ export async function createHostedDeployPackage(
|
|
|
989
1087
|
|
|
990
1088
|
export async function buildHostedDeployTarball(
|
|
991
1089
|
args: BuildHostedDeployTarballArgs,
|
|
992
|
-
): Promise<{ entryPoint: string; source: string }> {
|
|
1090
|
+
): Promise<{ entryPoint: string; source: string; workflows: WorkflowDeployMetadata[] }> {
|
|
993
1091
|
const deployPackage = await createHostedDeployPackage(args);
|
|
994
1092
|
|
|
995
1093
|
try {
|
|
@@ -1001,6 +1099,7 @@ export async function buildHostedDeployTarball(
|
|
|
1001
1099
|
return {
|
|
1002
1100
|
entryPoint: deployPackage.entryPoint,
|
|
1003
1101
|
source: readFileSync(tarPath).toString("base64"),
|
|
1102
|
+
workflows: deployPackage.workflows,
|
|
1004
1103
|
};
|
|
1005
1104
|
} finally {
|
|
1006
1105
|
deployPackage.cleanup();
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { PROFILES_DIR } from "./context.js";
|
|
5
|
+
import type { AuthProfileStorageState } from "../../shared/workflow/auth-profile-state.js";
|
|
6
|
+
import { normalizeProfileName } from "../../shared/workflow/auth-profile-name.js";
|
|
7
|
+
|
|
8
|
+
export { normalizeProfileName } from "../../shared/workflow/auth-profile-name.js";
|
|
9
|
+
|
|
10
|
+
export function getProfilePath(profileName: string): string {
|
|
11
|
+
return join(PROFILES_DIR, `${normalizeProfileName(profileName)}.json`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function hasProfile(profileName: string): boolean {
|
|
15
|
+
return existsSync(getProfilePath(profileName));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function readProfile(profileName: string): AuthProfileStorageState {
|
|
19
|
+
const profilePath = getProfilePath(profileName);
|
|
20
|
+
const parsed = JSON.parse(readFileSync(profilePath, "utf8")) as unknown;
|
|
21
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
22
|
+
throw new Error(`Saved auth profile "${profileName}" is not a JSON object.`);
|
|
23
|
+
}
|
|
24
|
+
return parsed as AuthProfileStorageState;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function writeProfile(
|
|
28
|
+
profileName: string,
|
|
29
|
+
profile: AuthProfileStorageState,
|
|
30
|
+
): Promise<string> {
|
|
31
|
+
const profilePath = getProfilePath(profileName);
|
|
32
|
+
await mkdir(dirname(profilePath), { recursive: true });
|
|
33
|
+
await writeFile(profilePath, JSON.stringify(profile, null, 2), "utf8");
|
|
34
|
+
return profilePath;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function formatMissingLocalAuthProfileMessage(args: {
|
|
38
|
+
profileName: string;
|
|
39
|
+
profilePath: string;
|
|
40
|
+
session: string;
|
|
41
|
+
}): string {
|
|
42
|
+
return [
|
|
43
|
+
`Local auth profile not found: "${args.profileName}".`,
|
|
44
|
+
`Expected profile file: ${args.profilePath}`,
|
|
45
|
+
"To create it locally:",
|
|
46
|
+
` 1. libretto open <site-url> --headed --session ${args.session}`,
|
|
47
|
+
" 2. Log in manually in the browser window.",
|
|
48
|
+
` 3. libretto save ${args.profileName} --session ${args.session} --sites <site>`,
|
|
49
|
+
"Or import site-scoped state from Chrome with:",
|
|
50
|
+
` libretto import-chrome-profiles ${args.profileName} --cdp-url <url> --sites <site>`,
|
|
51
|
+
"Local profile files are not uploaded to cloud profiles.",
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
package/src/cli/core/prompt.ts
CHANGED
|
@@ -26,6 +26,21 @@ export async function prompt(
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export async function promptConfirm(
|
|
30
|
+
question: string,
|
|
31
|
+
opts: { defaultValue?: boolean } = {},
|
|
32
|
+
): Promise<boolean> {
|
|
33
|
+
const defaultValue = opts.defaultValue ?? false;
|
|
34
|
+
if (!stdin.isTTY) return defaultValue;
|
|
35
|
+
|
|
36
|
+
const suffix = defaultValue ? "[Y/n]" : "[y/N]";
|
|
37
|
+
const answer = (await prompt(`${question} ${suffix}`)).trim().toLowerCase();
|
|
38
|
+
|
|
39
|
+
if (answer.length === 0) return defaultValue;
|
|
40
|
+
|
|
41
|
+
return answer === "y" || answer === "yes";
|
|
42
|
+
}
|
|
43
|
+
|
|
29
44
|
const CTRL_C = "";
|
|
30
45
|
const CR = "\r";
|
|
31
46
|
const LF = "\n";
|
|
@@ -23,7 +23,7 @@ export function createLibrettoCloudProvider(): ProviderApi {
|
|
|
23
23
|
// The Libretto Cloud API is an oRPC RPCHandler, not plain REST, so inputs
|
|
24
24
|
// must be wrapped as { json: ... } and outputs arrive the same way.
|
|
25
25
|
return {
|
|
26
|
-
async createSession() {
|
|
26
|
+
async createSession(options) {
|
|
27
27
|
const browserSessionTimeoutSeconds = readPositiveNumberEnv(
|
|
28
28
|
"LIBRETTO_TIMEOUT_SECONDS",
|
|
29
29
|
DEFAULT_BROWSER_SESSION_TIMEOUT_SECONDS,
|
|
@@ -35,7 +35,11 @@ export function createLibrettoCloudProvider(): ProviderApi {
|
|
|
35
35
|
"Content-Type": "application/json",
|
|
36
36
|
},
|
|
37
37
|
body: JSON.stringify({
|
|
38
|
-
json: {
|
|
38
|
+
json: {
|
|
39
|
+
timeout_seconds: browserSessionTimeoutSeconds,
|
|
40
|
+
profile_name: options?.authProfileName,
|
|
41
|
+
profile_persist: options?.authProfilePersist,
|
|
42
|
+
},
|
|
39
43
|
}),
|
|
40
44
|
});
|
|
41
45
|
if (!resp.ok) {
|
|
@@ -18,6 +18,9 @@ export type ProviderCloseResult = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export type ProviderApi = {
|
|
21
|
-
createSession(
|
|
21
|
+
createSession(options?: {
|
|
22
|
+
authProfileName?: string;
|
|
23
|
+
authProfilePersist?: boolean;
|
|
24
|
+
}): Promise<ProviderSession>;
|
|
22
25
|
closeSession(sessionId: string): Promise<ProviderCloseResult>;
|
|
23
26
|
};
|