opensteer 0.6.2 → 0.6.4
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/bin/opensteer.mjs +94 -8
- package/dist/{browser-profile-client-DK9qa_Dj.d.cts → browser-profile-client-D6PuRefA.d.cts} +1 -1
- package/dist/{browser-profile-client-CaL-mwqs.d.ts → browser-profile-client-OUHaODro.d.ts} +1 -1
- package/dist/{chunk-7RMY26CM.js → chunk-54KNQTOL.js} +172 -55
- package/dist/{chunk-WJI7TGBQ.js → chunk-6B6LOYU3.js} +1 -1
- package/dist/{chunk-F2VDVOJO.js → chunk-G6V2DJRN.js} +451 -609
- package/dist/chunk-K5CL76MG.js +81 -0
- package/dist/{chunk-WDRMHPWL.js → chunk-KPPOTU3D.js} +159 -180
- package/dist/cli/auth.cjs +186 -95
- package/dist/cli/auth.d.cts +1 -1
- package/dist/cli/auth.d.ts +1 -1
- package/dist/cli/auth.js +2 -2
- package/dist/cli/local-profile.cjs +197 -0
- package/dist/cli/local-profile.d.cts +18 -0
- package/dist/cli/local-profile.d.ts +18 -0
- package/dist/cli/local-profile.js +97 -0
- package/dist/cli/profile.cjs +1747 -1279
- package/dist/cli/profile.d.cts +2 -2
- package/dist/cli/profile.d.ts +2 -2
- package/dist/cli/profile.js +469 -7
- package/dist/cli/server.cjs +759 -257
- package/dist/cli/server.js +69 -16
- package/dist/index.cjs +688 -238
- package/dist/index.d.cts +7 -5
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4 -3
- package/dist/{types-BxiRblC7.d.cts → types-BWItZPl_.d.cts} +31 -13
- package/dist/{types-BxiRblC7.d.ts → types-BWItZPl_.d.ts} +31 -13
- package/package.json +2 -2
- package/skills/opensteer/SKILL.md +34 -14
- package/skills/opensteer/references/cli-reference.md +1 -1
- package/skills/opensteer/references/examples.md +5 -3
- package/skills/opensteer/references/sdk-reference.md +16 -14
package/dist/cli/profile.cjs
CHANGED
|
@@ -424,8 +424,8 @@ __export(profile_exports, {
|
|
|
424
424
|
runOpensteerProfileCli: () => runOpensteerProfileCli
|
|
425
425
|
});
|
|
426
426
|
module.exports = __toCommonJS(profile_exports);
|
|
427
|
-
var
|
|
428
|
-
var
|
|
427
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
428
|
+
var import_promises4 = require("readline/promises");
|
|
429
429
|
|
|
430
430
|
// src/config.ts
|
|
431
431
|
var import_fs = __toESM(require("fs"), 1);
|
|
@@ -659,6 +659,65 @@ function toJsonSafeValue(value, seen) {
|
|
|
659
659
|
return void 0;
|
|
660
660
|
}
|
|
661
661
|
|
|
662
|
+
// src/cloud/credential-selection.ts
|
|
663
|
+
function selectCloudCredential(options) {
|
|
664
|
+
const apiKey = normalizeNonEmptyString(options.apiKey);
|
|
665
|
+
const accessToken = normalizeNonEmptyString(options.accessToken);
|
|
666
|
+
if (apiKey) {
|
|
667
|
+
if (options.authScheme === "bearer") {
|
|
668
|
+
return {
|
|
669
|
+
apiKey,
|
|
670
|
+
authScheme: "bearer",
|
|
671
|
+
kind: "access-token",
|
|
672
|
+
token: apiKey,
|
|
673
|
+
compatibilityBearerApiKey: true
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
return {
|
|
677
|
+
apiKey,
|
|
678
|
+
authScheme: "api-key",
|
|
679
|
+
kind: "api-key",
|
|
680
|
+
token: apiKey
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
if (accessToken) {
|
|
684
|
+
return {
|
|
685
|
+
accessToken,
|
|
686
|
+
authScheme: "bearer",
|
|
687
|
+
kind: "access-token",
|
|
688
|
+
token: accessToken
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
function selectCloudCredentialByPrecedence(layers, authScheme) {
|
|
694
|
+
for (const layer of layers) {
|
|
695
|
+
const hasApiKey = layer.hasApiKey ?? Object.prototype.hasOwnProperty.call(layer, "apiKey");
|
|
696
|
+
const hasAccessToken = layer.hasAccessToken ?? Object.prototype.hasOwnProperty.call(layer, "accessToken");
|
|
697
|
+
if (!hasApiKey && !hasAccessToken) {
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
source: layer.source,
|
|
702
|
+
apiKey: layer.apiKey,
|
|
703
|
+
accessToken: layer.accessToken,
|
|
704
|
+
hasApiKey,
|
|
705
|
+
hasAccessToken,
|
|
706
|
+
credential: selectCloudCredential({
|
|
707
|
+
apiKey: layer.apiKey,
|
|
708
|
+
accessToken: layer.accessToken,
|
|
709
|
+
authScheme: layer.authScheme ?? authScheme
|
|
710
|
+
})
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
function normalizeNonEmptyString(value) {
|
|
716
|
+
if (typeof value !== "string") return void 0;
|
|
717
|
+
const normalized = value.trim();
|
|
718
|
+
return normalized.length ? normalized : void 0;
|
|
719
|
+
}
|
|
720
|
+
|
|
662
721
|
// src/storage/namespace.ts
|
|
663
722
|
var import_path = __toESM(require("path"), 1);
|
|
664
723
|
var DEFAULT_NAMESPACE = "default";
|
|
@@ -697,9 +756,10 @@ var DEFAULT_CONFIG = {
|
|
|
697
756
|
headless: false,
|
|
698
757
|
executablePath: void 0,
|
|
699
758
|
slowMo: 0,
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
759
|
+
mode: void 0,
|
|
760
|
+
cdpUrl: void 0,
|
|
761
|
+
userDataDir: void 0,
|
|
762
|
+
profileDirectory: void 0
|
|
703
763
|
},
|
|
704
764
|
storage: {
|
|
705
765
|
rootDir: process.cwd()
|
|
@@ -983,11 +1043,6 @@ function normalizeCloudOptions(value) {
|
|
|
983
1043
|
}
|
|
984
1044
|
return value;
|
|
985
1045
|
}
|
|
986
|
-
function normalizeNonEmptyString(value) {
|
|
987
|
-
if (typeof value !== "string") return void 0;
|
|
988
|
-
const normalized = value.trim();
|
|
989
|
-
return normalized.length ? normalized : void 0;
|
|
990
|
-
}
|
|
991
1046
|
function parseCloudEnabled(value, source) {
|
|
992
1047
|
if (value == null) return void 0;
|
|
993
1048
|
if (typeof value === "boolean") return value;
|
|
@@ -996,6 +1051,18 @@ function parseCloudEnabled(value, source) {
|
|
|
996
1051
|
`Invalid ${source} value "${String(value)}". Use true, false, or a cloud options object.`
|
|
997
1052
|
);
|
|
998
1053
|
}
|
|
1054
|
+
function resolveCloudCredentialFields(selectedLayer) {
|
|
1055
|
+
const credential = selectedLayer?.credential;
|
|
1056
|
+
if (!credential) return {};
|
|
1057
|
+
if (credential.kind === "api-key" || credential.compatibilityBearerApiKey === true && selectedLayer?.source !== "env") {
|
|
1058
|
+
return {
|
|
1059
|
+
apiKey: credential.token
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
accessToken: credential.token
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
999
1066
|
function resolveCloudSelection(config, env = process.env) {
|
|
1000
1067
|
const configCloud = parseCloudEnabled(config.cloud, "cloud");
|
|
1001
1068
|
if (configCloud !== void 0) {
|
|
@@ -1032,7 +1099,12 @@ function resolveConfigWithEnv(input = {}, options = {}) {
|
|
|
1032
1099
|
});
|
|
1033
1100
|
assertNoLegacyAiConfig(".opensteer/config.json", fileConfig);
|
|
1034
1101
|
assertNoLegacyRuntimeConfig(".opensteer/config.json", fileConfig);
|
|
1102
|
+
const fileCloudOptions = normalizeCloudOptions(fileConfig.cloud);
|
|
1103
|
+
const fileHasCloudApiKey = hasOwn(fileCloudOptions, "apiKey");
|
|
1104
|
+
const fileHasCloudAccessToken = hasOwn(fileCloudOptions, "accessToken");
|
|
1035
1105
|
const fileRootDir = typeof fileConfig.storage?.rootDir === "string" ? fileConfig.storage.rootDir : void 0;
|
|
1106
|
+
assertNoRemovedBrowserConfig(input.browser, "Opensteer constructor config");
|
|
1107
|
+
assertNoRemovedBrowserConfig(fileConfig.browser, ".opensteer/config.json");
|
|
1036
1108
|
const envRootDir = input.storage?.rootDir ?? fileRootDir ?? initialRootDir;
|
|
1037
1109
|
const env = resolveEnv(envRootDir, {
|
|
1038
1110
|
debug: debugHint,
|
|
@@ -1048,14 +1120,30 @@ function resolveConfigWithEnv(input = {}, options = {}) {
|
|
|
1048
1120
|
"OPENSTEER_RUNTIME is no longer supported. Use OPENSTEER_MODE instead."
|
|
1049
1121
|
);
|
|
1050
1122
|
}
|
|
1123
|
+
if (env.OPENSTEER_CONNECT_URL != null) {
|
|
1124
|
+
throw new Error(
|
|
1125
|
+
"OPENSTEER_CONNECT_URL is no longer supported. Use OPENSTEER_CDP_URL instead."
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
if (env.OPENSTEER_CHANNEL != null) {
|
|
1129
|
+
throw new Error(
|
|
1130
|
+
"OPENSTEER_CHANNEL is no longer supported. Use OPENSTEER_BROWSER plus OPENSTEER_BROWSER_PATH when needed."
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
if (env.OPENSTEER_PROFILE_DIR != null) {
|
|
1134
|
+
throw new Error(
|
|
1135
|
+
"OPENSTEER_PROFILE_DIR is no longer supported. Use OPENSTEER_USER_DATA_DIR and OPENSTEER_PROFILE_DIRECTORY instead."
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1051
1138
|
const envConfig = {
|
|
1052
1139
|
browser: {
|
|
1053
1140
|
headless: parseBool(env.OPENSTEER_HEADLESS),
|
|
1054
1141
|
executablePath: env.OPENSTEER_BROWSER_PATH || void 0,
|
|
1055
1142
|
slowMo: parseNumber(env.OPENSTEER_SLOW_MO),
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1143
|
+
mode: env.OPENSTEER_BROWSER === "real" || env.OPENSTEER_BROWSER === "chromium" ? env.OPENSTEER_BROWSER : void 0,
|
|
1144
|
+
cdpUrl: env.OPENSTEER_CDP_URL || void 0,
|
|
1145
|
+
userDataDir: env.OPENSTEER_USER_DATA_DIR || void 0,
|
|
1146
|
+
profileDirectory: env.OPENSTEER_PROFILE_DIRECTORY || void 0
|
|
1059
1147
|
},
|
|
1060
1148
|
cursor: {
|
|
1061
1149
|
enabled: parseBool(env.OPENSTEER_CURSOR)
|
|
@@ -1066,17 +1154,38 @@ function resolveConfigWithEnv(input = {}, options = {}) {
|
|
|
1066
1154
|
const mergedWithFile = mergeDeep(runtimeDefaults, fileConfig);
|
|
1067
1155
|
const mergedWithEnv = mergeDeep(mergedWithFile, envConfig);
|
|
1068
1156
|
const resolved = mergeDeep(mergedWithEnv, input);
|
|
1157
|
+
const browserHeadlessExplicit = input.browser?.headless !== void 0 || fileConfig.browser?.headless !== void 0 || envConfig.browser?.headless !== void 0;
|
|
1158
|
+
if (!browserHeadlessExplicit && resolved.browser?.mode === "real") {
|
|
1159
|
+
resolved.browser = {
|
|
1160
|
+
...resolved.browser,
|
|
1161
|
+
headless: true
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
function assertNoRemovedBrowserConfig(value, source) {
|
|
1165
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
const record = value;
|
|
1169
|
+
if (record.connectUrl !== void 0) {
|
|
1170
|
+
throw new Error(
|
|
1171
|
+
`${source}.browser.connectUrl is no longer supported. Use browser.cdpUrl instead.`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (record.channel !== void 0) {
|
|
1175
|
+
throw new Error(
|
|
1176
|
+
`${source}.browser.channel is no longer supported. Use browser.mode plus browser.executablePath instead.`
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
if (record.profileDir !== void 0) {
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
`${source}.browser.profileDir is no longer supported. Use browser.userDataDir and browser.profileDirectory instead.`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1069
1185
|
const envApiKey = resolveOpensteerApiKey(env);
|
|
1070
1186
|
const envAccessTokenRaw = resolveOpensteerAccessToken(env);
|
|
1071
1187
|
const envBaseUrl = resolveOpensteerBaseUrl(env);
|
|
1072
1188
|
const envAuthScheme = resolveOpensteerAuthScheme(env);
|
|
1073
|
-
if (envApiKey && envAccessTokenRaw) {
|
|
1074
|
-
throw new Error(
|
|
1075
|
-
"OPENSTEER_API_KEY and OPENSTEER_ACCESS_TOKEN are mutually exclusive. Set only one."
|
|
1076
|
-
);
|
|
1077
|
-
}
|
|
1078
|
-
const envAccessToken = envAccessTokenRaw || (envAuthScheme === "bearer" ? envApiKey : void 0);
|
|
1079
|
-
const envApiCredential = envAuthScheme === "bearer" && !envAccessTokenRaw ? void 0 : envApiKey;
|
|
1080
1189
|
const envCloudProfileId = resolveOpensteerCloudProfileId(env);
|
|
1081
1190
|
const envCloudProfileReuseIfActive = resolveOpensteerCloudProfileReuseIfActive(env);
|
|
1082
1191
|
const envCloudAnnounce = parseCloudAnnounce(
|
|
@@ -1105,11 +1214,6 @@ function resolveConfigWithEnv(input = {}, options = {}) {
|
|
|
1105
1214
|
const inputHasCloudBaseUrl = Boolean(
|
|
1106
1215
|
inputCloudOptions && Object.prototype.hasOwnProperty.call(inputCloudOptions, "baseUrl")
|
|
1107
1216
|
);
|
|
1108
|
-
if (normalizeNonEmptyString(inputCloudOptions?.apiKey) && normalizeNonEmptyString(inputCloudOptions?.accessToken)) {
|
|
1109
|
-
throw new Error(
|
|
1110
|
-
"cloud.apiKey and cloud.accessToken are mutually exclusive. Set only one."
|
|
1111
|
-
);
|
|
1112
|
-
}
|
|
1113
1217
|
const cloudSelection = resolveCloudSelection({
|
|
1114
1218
|
cloud: resolved.cloud
|
|
1115
1219
|
}, env);
|
|
@@ -1120,11 +1224,6 @@ function resolveConfigWithEnv(input = {}, options = {}) {
|
|
|
1120
1224
|
accessToken: resolvedCloudAccessTokenRaw,
|
|
1121
1225
|
...resolvedCloudRest
|
|
1122
1226
|
} = resolvedCloud;
|
|
1123
|
-
if (normalizeNonEmptyString(resolvedCloudApiKeyRaw) && normalizeNonEmptyString(resolvedCloudAccessTokenRaw)) {
|
|
1124
|
-
throw new Error(
|
|
1125
|
-
"Cloud config cannot include both apiKey and accessToken at the same time."
|
|
1126
|
-
);
|
|
1127
|
-
}
|
|
1128
1227
|
const resolvedCloudBrowserProfile = normalizeCloudBrowserProfileOptions(
|
|
1129
1228
|
resolvedCloud.browserProfile,
|
|
1130
1229
|
"resolved.cloud.browserProfile"
|
|
@@ -1136,25 +1235,40 @@ function resolveConfigWithEnv(input = {}, options = {}) {
|
|
|
1136
1235
|
const browserProfile = inputCloudBrowserProfile ?? envCloudBrowserProfile ?? resolvedCloudBrowserProfile;
|
|
1137
1236
|
let authScheme = inputAuthScheme ?? envAuthScheme ?? parseAuthScheme(resolvedCloud.authScheme, "cloud.authScheme") ?? "api-key";
|
|
1138
1237
|
const announce = inputCloudAnnounce ?? envCloudAnnounce ?? parseCloudAnnounce(resolvedCloud.announce, "cloud.announce") ?? "always";
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1238
|
+
const selectedCredentialLayer = selectCloudCredentialByPrecedence(
|
|
1239
|
+
[
|
|
1240
|
+
{
|
|
1241
|
+
source: "input",
|
|
1242
|
+
apiKey: inputCloudOptions?.apiKey,
|
|
1243
|
+
accessToken: inputCloudOptions?.accessToken,
|
|
1244
|
+
hasApiKey: inputHasCloudApiKey,
|
|
1245
|
+
hasAccessToken: inputHasCloudAccessToken
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
source: "env",
|
|
1249
|
+
apiKey: envApiKey,
|
|
1250
|
+
accessToken: envAccessTokenRaw,
|
|
1251
|
+
hasApiKey: envApiKey !== void 0,
|
|
1252
|
+
hasAccessToken: envAccessTokenRaw !== void 0
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
source: "file",
|
|
1256
|
+
apiKey: fileCloudOptions?.apiKey,
|
|
1257
|
+
accessToken: fileCloudOptions?.accessToken,
|
|
1258
|
+
hasApiKey: fileHasCloudApiKey,
|
|
1259
|
+
hasAccessToken: fileHasCloudAccessToken
|
|
1260
|
+
}
|
|
1261
|
+
],
|
|
1262
|
+
authScheme
|
|
1263
|
+
);
|
|
1264
|
+
const { apiKey, accessToken } = resolveCloudCredentialFields(selectedCredentialLayer);
|
|
1151
1265
|
if (accessToken) {
|
|
1152
1266
|
authScheme = "bearer";
|
|
1153
1267
|
}
|
|
1154
1268
|
resolved.cloud = {
|
|
1155
1269
|
...resolvedCloudRest,
|
|
1156
|
-
...
|
|
1157
|
-
...
|
|
1270
|
+
...apiKey ? { apiKey } : selectedCredentialLayer?.hasApiKey && !accessToken ? { apiKey: selectedCredentialLayer.apiKey } : {},
|
|
1271
|
+
...accessToken ? { accessToken } : selectedCredentialLayer?.hasAccessToken && !apiKey ? { accessToken: selectedCredentialLayer.accessToken } : {},
|
|
1158
1272
|
authScheme,
|
|
1159
1273
|
announce,
|
|
1160
1274
|
...browserProfile ? { browserProfile } : {}
|
|
@@ -1209,7 +1323,13 @@ function getCallerFilePath() {
|
|
|
1209
1323
|
var import_crypto = require("crypto");
|
|
1210
1324
|
|
|
1211
1325
|
// src/browser/pool.ts
|
|
1212
|
-
var
|
|
1326
|
+
var import_node_child_process = require("child_process");
|
|
1327
|
+
var import_node_fs = require("fs");
|
|
1328
|
+
var import_promises = require("fs/promises");
|
|
1329
|
+
var import_node_net = require("net");
|
|
1330
|
+
var import_node_os = require("os");
|
|
1331
|
+
var import_node_path = require("path");
|
|
1332
|
+
var import_playwright = require("playwright");
|
|
1213
1333
|
|
|
1214
1334
|
// src/browser/cdp-proxy.ts
|
|
1215
1335
|
var import_ws = __toESM(require("ws"), 1);
|
|
@@ -1548,839 +1668,598 @@ function errorMessage(error) {
|
|
|
1548
1668
|
return error instanceof Error ? error.message : String(error);
|
|
1549
1669
|
}
|
|
1550
1670
|
|
|
1551
|
-
// src/browser/
|
|
1552
|
-
var
|
|
1553
|
-
var
|
|
1554
|
-
var
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1671
|
+
// src/browser/chrome.ts
|
|
1672
|
+
var import_os = require("os");
|
|
1673
|
+
var import_path3 = require("path");
|
|
1674
|
+
var import_fs2 = require("fs");
|
|
1675
|
+
function detectChromePaths() {
|
|
1676
|
+
const os2 = (0, import_os.platform)();
|
|
1677
|
+
if (os2 === "darwin") {
|
|
1678
|
+
const executable2 = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
|
|
1679
|
+
return {
|
|
1680
|
+
executable: (0, import_fs2.existsSync)(executable2) ? executable2 : null,
|
|
1681
|
+
defaultUserDataDir: (0, import_path3.join)(
|
|
1682
|
+
(0, import_os.homedir)(),
|
|
1683
|
+
"Library",
|
|
1684
|
+
"Application Support",
|
|
1685
|
+
"Google",
|
|
1686
|
+
"Chrome"
|
|
1687
|
+
)
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
if (os2 === "win32") {
|
|
1691
|
+
const executable2 = (0, import_path3.join)(
|
|
1692
|
+
process.env.PROGRAMFILES || "C:\\Program Files",
|
|
1693
|
+
"Google",
|
|
1694
|
+
"Chrome",
|
|
1695
|
+
"Application",
|
|
1696
|
+
"chrome.exe"
|
|
1697
|
+
);
|
|
1698
|
+
return {
|
|
1699
|
+
executable: (0, import_fs2.existsSync)(executable2) ? executable2 : null,
|
|
1700
|
+
defaultUserDataDir: (0, import_path3.join)(
|
|
1701
|
+
process.env.LOCALAPPDATA || (0, import_path3.join)((0, import_os.homedir)(), "AppData", "Local"),
|
|
1702
|
+
"Google",
|
|
1703
|
+
"Chrome",
|
|
1704
|
+
"User Data"
|
|
1705
|
+
)
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
const executable = "/usr/bin/google-chrome";
|
|
1709
|
+
return {
|
|
1710
|
+
executable: (0, import_fs2.existsSync)(executable) ? executable : null,
|
|
1711
|
+
defaultUserDataDir: (0, import_path3.join)((0, import_os.homedir)(), ".config", "google-chrome")
|
|
1712
|
+
};
|
|
1569
1713
|
}
|
|
1570
|
-
function
|
|
1571
|
-
|
|
1714
|
+
function expandHome(p) {
|
|
1715
|
+
if (p.startsWith("~/") || p === "~") {
|
|
1716
|
+
return (0, import_path3.join)((0, import_os.homedir)(), p.slice(1));
|
|
1717
|
+
}
|
|
1718
|
+
return p;
|
|
1572
1719
|
}
|
|
1573
|
-
function
|
|
1574
|
-
|
|
1575
|
-
|
|
1720
|
+
function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDataDir) {
|
|
1721
|
+
const resolvedUserDataDir = expandHome(userDataDir);
|
|
1722
|
+
const localStatePath = (0, import_path3.join)(resolvedUserDataDir, "Local State");
|
|
1723
|
+
if (!(0, import_fs2.existsSync)(localStatePath)) {
|
|
1724
|
+
return [];
|
|
1576
1725
|
}
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
sanitized.push("[REDACTED]");
|
|
1583
|
-
index += 1;
|
|
1726
|
+
try {
|
|
1727
|
+
const raw = JSON.parse((0, import_fs2.readFileSync)(localStatePath, "utf-8"));
|
|
1728
|
+
const infoCache = raw && typeof raw === "object" && !Array.isArray(raw) && raw.profile && typeof raw.profile === "object" && !Array.isArray(raw.profile) ? raw.profile.info_cache : void 0;
|
|
1729
|
+
if (!infoCache || typeof infoCache !== "object") {
|
|
1730
|
+
return [];
|
|
1584
1731
|
}
|
|
1732
|
+
return Object.entries(infoCache).map(([directory, info]) => {
|
|
1733
|
+
const record = info && typeof info === "object" && !Array.isArray(info) ? info : {};
|
|
1734
|
+
const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : directory;
|
|
1735
|
+
return {
|
|
1736
|
+
directory,
|
|
1737
|
+
name
|
|
1738
|
+
};
|
|
1739
|
+
}).filter((profile) => profile.directory.trim().length > 0).sort(
|
|
1740
|
+
(left, right) => left.directory.localeCompare(right.directory)
|
|
1741
|
+
);
|
|
1742
|
+
} catch {
|
|
1743
|
+
return [];
|
|
1585
1744
|
}
|
|
1586
|
-
return sanitized;
|
|
1587
|
-
}
|
|
1588
|
-
function buildCommandError(command, args, result) {
|
|
1589
|
-
const stderr = typeof result.stderr === "string" && result.stderr.trim() ? result.stderr.trim() : `Command "${command}" failed with status ${String(result.status)}.`;
|
|
1590
|
-
const sanitizedArgs = sanitizeCommandArgs(command, args);
|
|
1591
|
-
return new Error(
|
|
1592
|
-
[
|
|
1593
|
-
`Unable to persist credential via ${command}.`,
|
|
1594
|
-
`${command} ${sanitizedArgs.join(" ")}`,
|
|
1595
|
-
stderr
|
|
1596
|
-
].join(" ")
|
|
1597
|
-
);
|
|
1598
1745
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1746
|
+
|
|
1747
|
+
// src/browser/pool.ts
|
|
1748
|
+
var BrowserPool = class {
|
|
1749
|
+
browser = null;
|
|
1750
|
+
cdpProxy = null;
|
|
1751
|
+
launchedProcess = null;
|
|
1752
|
+
tempUserDataDir = null;
|
|
1753
|
+
defaults;
|
|
1754
|
+
constructor(defaults = {}) {
|
|
1755
|
+
this.defaults = defaults;
|
|
1756
|
+
}
|
|
1757
|
+
async launch(options = {}) {
|
|
1758
|
+
if (this.browser || this.cdpProxy || this.launchedProcess || this.tempUserDataDir) {
|
|
1759
|
+
await this.close();
|
|
1760
|
+
}
|
|
1761
|
+
const mode = options.mode ?? this.defaults.mode ?? "chromium";
|
|
1762
|
+
const cdpUrl = options.cdpUrl ?? this.defaults.cdpUrl;
|
|
1763
|
+
const userDataDir = options.userDataDir ?? this.defaults.userDataDir;
|
|
1764
|
+
const profileDirectory = options.profileDirectory ?? this.defaults.profileDirectory;
|
|
1765
|
+
const executablePath = options.executablePath ?? this.defaults.executablePath;
|
|
1766
|
+
if (cdpUrl) {
|
|
1767
|
+
if (mode === "real") {
|
|
1768
|
+
throw new Error(
|
|
1769
|
+
'cdpUrl cannot be combined with mode "real". Use one browser launch path at a time.'
|
|
1770
|
+
);
|
|
1610
1771
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
const args = [
|
|
1616
|
-
"add-generic-password",
|
|
1617
|
-
"-U",
|
|
1618
|
-
"-s",
|
|
1619
|
-
service,
|
|
1620
|
-
"-a",
|
|
1621
|
-
account,
|
|
1622
|
-
"-w",
|
|
1623
|
-
secret
|
|
1624
|
-
];
|
|
1625
|
-
const result = (0, import_node_child_process.spawnSync)("security", args, { encoding: "utf8" });
|
|
1626
|
-
if (commandFailed(result)) {
|
|
1627
|
-
throw buildCommandError("security", args, result);
|
|
1772
|
+
if (userDataDir || profileDirectory) {
|
|
1773
|
+
throw new Error(
|
|
1774
|
+
"userDataDir/profileDirectory cannot be combined with cdpUrl."
|
|
1775
|
+
);
|
|
1628
1776
|
}
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
if (commandFailed(result)) {
|
|
1634
|
-
return;
|
|
1777
|
+
if (options.context && Object.keys(options.context).length > 0) {
|
|
1778
|
+
throw new Error(
|
|
1779
|
+
"context launch options are not supported when attaching over CDP."
|
|
1780
|
+
);
|
|
1635
1781
|
}
|
|
1782
|
+
return this.connectToRunning(cdpUrl, options.timeout);
|
|
1636
1783
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
return {
|
|
1641
|
-
backend: "linux-secret-tool",
|
|
1642
|
-
get(service, account) {
|
|
1643
|
-
const result = (0, import_node_child_process.spawnSync)(
|
|
1644
|
-
"secret-tool",
|
|
1645
|
-
["lookup", "service", service, "account", account],
|
|
1646
|
-
{
|
|
1647
|
-
encoding: "utf8"
|
|
1648
|
-
}
|
|
1784
|
+
if (mode !== "real" && (userDataDir || profileDirectory)) {
|
|
1785
|
+
throw new Error(
|
|
1786
|
+
'userDataDir/profileDirectory require mode "real".'
|
|
1649
1787
|
);
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
set(service, account, secret) {
|
|
1657
|
-
const args = [
|
|
1658
|
-
"store",
|
|
1659
|
-
"--label",
|
|
1660
|
-
"Opensteer CLI",
|
|
1661
|
-
"service",
|
|
1662
|
-
service,
|
|
1663
|
-
"account",
|
|
1664
|
-
account
|
|
1665
|
-
];
|
|
1666
|
-
const result = (0, import_node_child_process.spawnSync)("secret-tool", args, {
|
|
1667
|
-
encoding: "utf8",
|
|
1668
|
-
input: secret
|
|
1669
|
-
});
|
|
1670
|
-
if (commandFailed(result)) {
|
|
1671
|
-
throw buildCommandError("secret-tool", args, result);
|
|
1788
|
+
}
|
|
1789
|
+
if (mode === "real") {
|
|
1790
|
+
if (options.context && Object.keys(options.context).length > 0) {
|
|
1791
|
+
throw new Error(
|
|
1792
|
+
"context launch options are not supported for real-browser mode."
|
|
1793
|
+
);
|
|
1672
1794
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1795
|
+
return this.launchOwnedRealBrowser({
|
|
1796
|
+
...options,
|
|
1797
|
+
executablePath,
|
|
1798
|
+
userDataDir,
|
|
1799
|
+
profileDirectory
|
|
1678
1800
|
});
|
|
1679
1801
|
}
|
|
1680
|
-
|
|
1681
|
-
}
|
|
1682
|
-
function createKeychainStore() {
|
|
1683
|
-
if (process.platform === "darwin") {
|
|
1684
|
-
if (!commandExists("security")) {
|
|
1685
|
-
return null;
|
|
1686
|
-
}
|
|
1687
|
-
return createMacosSecurityStore();
|
|
1802
|
+
return this.launchSandbox(options);
|
|
1688
1803
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1804
|
+
async close() {
|
|
1805
|
+
const browser = this.browser;
|
|
1806
|
+
const cdpProxy = this.cdpProxy;
|
|
1807
|
+
const launchedProcess = this.launchedProcess;
|
|
1808
|
+
const tempUserDataDir = this.tempUserDataDir;
|
|
1809
|
+
this.browser = null;
|
|
1810
|
+
this.cdpProxy = null;
|
|
1811
|
+
this.launchedProcess = null;
|
|
1812
|
+
this.tempUserDataDir = null;
|
|
1813
|
+
try {
|
|
1814
|
+
if (browser) {
|
|
1815
|
+
await browser.close().catch(() => void 0);
|
|
1816
|
+
}
|
|
1817
|
+
} finally {
|
|
1818
|
+
cdpProxy?.close();
|
|
1819
|
+
await killProcessTree(launchedProcess);
|
|
1820
|
+
if (tempUserDataDir) {
|
|
1821
|
+
await (0, import_promises.rm)(tempUserDataDir, {
|
|
1822
|
+
recursive: true,
|
|
1823
|
+
force: true
|
|
1824
|
+
}).catch(() => void 0);
|
|
1825
|
+
}
|
|
1692
1826
|
}
|
|
1693
|
-
return createLinuxSecretToolStore();
|
|
1694
|
-
}
|
|
1695
|
-
return null;
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
// src/browser/chrome.ts
|
|
1699
|
-
var import_os = require("os");
|
|
1700
|
-
var import_path3 = require("path");
|
|
1701
|
-
function expandHome(p) {
|
|
1702
|
-
if (p.startsWith("~/") || p === "~") {
|
|
1703
|
-
return (0, import_path3.join)((0, import_os.homedir)(), p.slice(1));
|
|
1704
1827
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
brand: {
|
|
1733
|
-
macService: "Microsoft Edge Safe Storage",
|
|
1734
|
-
macAccount: "Microsoft Edge",
|
|
1735
|
-
linuxApplications: ["microsoft-edge"],
|
|
1736
|
-
playwrightChannel: "msedge"
|
|
1737
|
-
}
|
|
1738
|
-
},
|
|
1739
|
-
{
|
|
1740
|
-
match: ["google", "chrome beta"],
|
|
1741
|
-
brand: {
|
|
1742
|
-
macService: "Chrome Beta Safe Storage",
|
|
1743
|
-
macAccount: "Chrome Beta",
|
|
1744
|
-
linuxApplications: ["chrome-beta"],
|
|
1745
|
-
playwrightChannel: "chrome-beta"
|
|
1828
|
+
async connectToRunning(cdpUrl, timeout) {
|
|
1829
|
+
let browser = null;
|
|
1830
|
+
let cdpProxy = null;
|
|
1831
|
+
try {
|
|
1832
|
+
const { browserWsUrl, targets } = await discoverTargets(cdpUrl);
|
|
1833
|
+
if (targets.length === 0) {
|
|
1834
|
+
throw new Error(
|
|
1835
|
+
"No page targets found. Is the browser running with an open window?"
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
cdpProxy = new CDPProxy(browserWsUrl, targets[0].id);
|
|
1839
|
+
const proxyWsUrl = await cdpProxy.start();
|
|
1840
|
+
browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
|
|
1841
|
+
timeout: timeout ?? 3e4
|
|
1842
|
+
});
|
|
1843
|
+
this.browser = browser;
|
|
1844
|
+
this.cdpProxy = cdpProxy;
|
|
1845
|
+
const { context, page } = await pickBrowserContextAndPage(browser);
|
|
1846
|
+
return { browser, context, page, isExternal: true };
|
|
1847
|
+
} catch (error) {
|
|
1848
|
+
if (browser) {
|
|
1849
|
+
await browser.close().catch(() => void 0);
|
|
1850
|
+
}
|
|
1851
|
+
cdpProxy?.close();
|
|
1852
|
+
this.browser = null;
|
|
1853
|
+
this.cdpProxy = null;
|
|
1854
|
+
throw error;
|
|
1746
1855
|
}
|
|
1747
|
-
}
|
|
1748
|
-
{
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1856
|
+
}
|
|
1857
|
+
async launchOwnedRealBrowser(options) {
|
|
1858
|
+
const chromePaths = detectChromePaths();
|
|
1859
|
+
const executablePath = options.executablePath ?? chromePaths.executable ?? void 0;
|
|
1860
|
+
if (!executablePath) {
|
|
1861
|
+
throw new Error(
|
|
1862
|
+
"Chrome was not found. Set browser.executablePath or install Chrome in a supported location."
|
|
1863
|
+
);
|
|
1755
1864
|
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1865
|
+
const sourceUserDataDir = expandHome(
|
|
1866
|
+
options.userDataDir ?? chromePaths.defaultUserDataDir
|
|
1867
|
+
);
|
|
1868
|
+
const profileDirectory = options.profileDirectory ?? "Default";
|
|
1869
|
+
const tempUserDataDir = await cloneProfileToTempDir(
|
|
1870
|
+
sourceUserDataDir,
|
|
1871
|
+
profileDirectory
|
|
1872
|
+
);
|
|
1873
|
+
const debugPort = await reserveDebugPort();
|
|
1874
|
+
const headless = resolveLaunchHeadless(
|
|
1875
|
+
"real",
|
|
1876
|
+
options.headless,
|
|
1877
|
+
this.defaults.headless
|
|
1878
|
+
);
|
|
1879
|
+
const launchArgs = buildRealBrowserLaunchArgs({
|
|
1880
|
+
userDataDir: tempUserDataDir,
|
|
1881
|
+
profileDirectory,
|
|
1882
|
+
debugPort,
|
|
1883
|
+
headless
|
|
1884
|
+
});
|
|
1885
|
+
const processHandle = (0, import_node_child_process.spawn)(executablePath, launchArgs, {
|
|
1886
|
+
detached: process.platform !== "win32",
|
|
1887
|
+
stdio: "ignore"
|
|
1888
|
+
});
|
|
1889
|
+
processHandle.unref();
|
|
1890
|
+
let browser = null;
|
|
1891
|
+
try {
|
|
1892
|
+
const wsUrl = await resolveCdpWebSocketUrl(
|
|
1893
|
+
`http://127.0.0.1:${debugPort}`,
|
|
1894
|
+
options.timeout ?? 3e4
|
|
1895
|
+
);
|
|
1896
|
+
browser = await import_playwright.chromium.connectOverCDP(wsUrl, {
|
|
1897
|
+
timeout: options.timeout ?? 3e4
|
|
1898
|
+
});
|
|
1899
|
+
const { context, page } = await createOwnedBrowserContextAndPage(
|
|
1900
|
+
browser,
|
|
1901
|
+
{
|
|
1902
|
+
headless,
|
|
1903
|
+
initialUrl: options.initialUrl,
|
|
1904
|
+
timeoutMs: options.timeout ?? 3e4
|
|
1905
|
+
}
|
|
1906
|
+
);
|
|
1907
|
+
this.browser = browser;
|
|
1908
|
+
this.launchedProcess = processHandle;
|
|
1909
|
+
this.tempUserDataDir = tempUserDataDir;
|
|
1910
|
+
return { browser, context, page, isExternal: false };
|
|
1911
|
+
} catch (error) {
|
|
1912
|
+
await browser?.close().catch(() => void 0);
|
|
1913
|
+
await killProcessTree(processHandle);
|
|
1914
|
+
await (0, import_promises.rm)(tempUserDataDir, {
|
|
1915
|
+
recursive: true,
|
|
1916
|
+
force: true
|
|
1917
|
+
}).catch(() => void 0);
|
|
1918
|
+
throw error;
|
|
1763
1919
|
}
|
|
1764
1920
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1921
|
+
async launchSandbox(options) {
|
|
1922
|
+
const browser = await import_playwright.chromium.launch({
|
|
1923
|
+
headless: resolveLaunchHeadless(
|
|
1924
|
+
"chromium",
|
|
1925
|
+
options.headless,
|
|
1926
|
+
this.defaults.headless
|
|
1927
|
+
),
|
|
1928
|
+
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
1929
|
+
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
1930
|
+
timeout: options.timeout
|
|
1931
|
+
});
|
|
1932
|
+
const context = await browser.newContext(options.context || {});
|
|
1933
|
+
const page = await context.newPage();
|
|
1934
|
+
this.browser = browser;
|
|
1935
|
+
return { browser, context, page, isExternal: false };
|
|
1771
1936
|
}
|
|
1937
|
+
};
|
|
1938
|
+
async function pickBrowserContextAndPage(browser) {
|
|
1939
|
+
const context = getPrimaryBrowserContext(browser);
|
|
1940
|
+
const pages = context.pages();
|
|
1941
|
+
const page = pages.find((candidate) => isInspectablePageUrl2(candidate.url())) || pages[0] || await context.newPage();
|
|
1942
|
+
return { context, page };
|
|
1772
1943
|
}
|
|
1773
|
-
function
|
|
1774
|
-
|
|
1775
|
-
return
|
|
1776
|
-
} catch {
|
|
1777
|
-
return false;
|
|
1944
|
+
function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
|
|
1945
|
+
if (requestedHeadless !== void 0) {
|
|
1946
|
+
return requestedHeadless;
|
|
1778
1947
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
const candidates = [(0, import_node_path.join)(profileDir, "Network", "Cookies"), (0, import_node_path.join)(profileDir, "Cookies")];
|
|
1782
|
-
for (const candidate of candidates) {
|
|
1783
|
-
if (fileExists(candidate)) {
|
|
1784
|
-
return candidate;
|
|
1785
|
-
}
|
|
1948
|
+
if (defaultHeadless !== void 0) {
|
|
1949
|
+
return defaultHeadless;
|
|
1786
1950
|
}
|
|
1787
|
-
return
|
|
1951
|
+
return mode === "real";
|
|
1788
1952
|
}
|
|
1789
|
-
async function
|
|
1790
|
-
const
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path.join)(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
|
|
1794
|
-
return candidates;
|
|
1953
|
+
async function createOwnedBrowserContextAndPage(browser, options) {
|
|
1954
|
+
const context = getPrimaryBrowserContext(browser);
|
|
1955
|
+
const page = await createOwnedBrowserPage(browser, context, options);
|
|
1956
|
+
return { context, page };
|
|
1795
1957
|
}
|
|
1796
|
-
async function
|
|
1797
|
-
const
|
|
1798
|
-
|
|
1799
|
-
|
|
1958
|
+
async function createOwnedBrowserPage(browser, context, options) {
|
|
1959
|
+
const targetUrl = options.initialUrl ?? "about:blank";
|
|
1960
|
+
const existingPages = new Set(context.pages());
|
|
1961
|
+
const browserSession = await browser.newBrowserCDPSession();
|
|
1962
|
+
try {
|
|
1963
|
+
const { targetId } = await browserSession.send("Target.createTarget", {
|
|
1964
|
+
url: targetUrl,
|
|
1965
|
+
newWindow: !options.headless
|
|
1966
|
+
});
|
|
1967
|
+
await browserSession.send("Target.activateTarget", { targetId }).catch(() => void 0);
|
|
1968
|
+
const page = await waitForOwnedBrowserPage(context, {
|
|
1969
|
+
existingPages,
|
|
1970
|
+
targetUrl,
|
|
1971
|
+
timeoutMs: options.timeoutMs
|
|
1972
|
+
});
|
|
1973
|
+
if (targetUrl !== "about:blank") {
|
|
1974
|
+
await page.waitForLoadState("domcontentloaded", {
|
|
1975
|
+
timeout: options.timeoutMs
|
|
1976
|
+
});
|
|
1977
|
+
}
|
|
1978
|
+
await closeDisposableStartupTargets(browserSession, targetId);
|
|
1979
|
+
return page;
|
|
1980
|
+
} finally {
|
|
1981
|
+
await browserSession.detach().catch(() => void 0);
|
|
1800
1982
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
return
|
|
1806
|
-
userDataDir,
|
|
1807
|
-
profileDir,
|
|
1808
|
-
profileDirectory: (0, import_node_path.basename)(profileDir),
|
|
1809
|
-
cookieDbPath: expandedPath,
|
|
1810
|
-
localStatePath: fileExists((0, import_node_path.join)(userDataDir, "Local State")) ? (0, import_node_path.join)(userDataDir, "Local State") : null
|
|
1811
|
-
};
|
|
1983
|
+
}
|
|
1984
|
+
async function closeDisposableStartupTargets(browserSession, preservedTargetId) {
|
|
1985
|
+
const response = await browserSession.send("Target.getTargets").catch(() => null);
|
|
1986
|
+
if (!response) {
|
|
1987
|
+
return;
|
|
1812
1988
|
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1989
|
+
for (const targetInfo of response.targetInfos) {
|
|
1990
|
+
if (targetInfo.targetId === preservedTargetId || targetInfo.type !== "page" || !isDisposableStartupPageUrl(targetInfo.url)) {
|
|
1991
|
+
continue;
|
|
1992
|
+
}
|
|
1993
|
+
await browserSession.send("Target.closeTarget", { targetId: targetInfo.targetId }).catch(() => void 0);
|
|
1817
1994
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1995
|
+
}
|
|
1996
|
+
async function waitForOwnedBrowserPage(context, options) {
|
|
1997
|
+
const deadline = Date.now() + options.timeoutMs;
|
|
1998
|
+
let fallbackPage = null;
|
|
1999
|
+
while (Date.now() < deadline) {
|
|
2000
|
+
for (const candidate of context.pages()) {
|
|
2001
|
+
if (options.existingPages.has(candidate)) {
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
2004
|
+
const url = candidate.url();
|
|
2005
|
+
if (!isInspectablePageUrl2(url)) {
|
|
2006
|
+
continue;
|
|
2007
|
+
}
|
|
2008
|
+
fallbackPage ??= candidate;
|
|
2009
|
+
if (options.targetUrl === "about:blank") {
|
|
2010
|
+
return candidate;
|
|
2011
|
+
}
|
|
2012
|
+
if (pageLooselyMatchesUrl(url, options.targetUrl)) {
|
|
2013
|
+
return candidate;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
await sleep(100);
|
|
1822
2017
|
}
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
const userDataDir = (0, import_node_path.dirname)(expandedPath);
|
|
1826
|
-
return {
|
|
1827
|
-
userDataDir,
|
|
1828
|
-
profileDir: expandedPath,
|
|
1829
|
-
profileDirectory: (0, import_node_path.basename)(expandedPath),
|
|
1830
|
-
cookieDbPath: directCookieDb,
|
|
1831
|
-
localStatePath: fileExists((0, import_node_path.join)(userDataDir, "Local State")) ? (0, import_node_path.join)(userDataDir, "Local State") : null
|
|
1832
|
-
};
|
|
2018
|
+
if (fallbackPage) {
|
|
2019
|
+
return fallbackPage;
|
|
1833
2020
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
2021
|
+
throw new Error(
|
|
2022
|
+
`Chrome created a target for ${options.targetUrl}, but Playwright did not expose the page in time.`
|
|
2023
|
+
);
|
|
2024
|
+
}
|
|
2025
|
+
function getPrimaryBrowserContext(browser) {
|
|
2026
|
+
const contexts = browser.contexts();
|
|
2027
|
+
if (contexts.length === 0) {
|
|
1836
2028
|
throw new Error(
|
|
1837
|
-
|
|
2029
|
+
"Connection succeeded but no browser contexts were exposed."
|
|
1838
2030
|
);
|
|
1839
2031
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2032
|
+
return contexts[0];
|
|
2033
|
+
}
|
|
2034
|
+
function isInspectablePageUrl2(url) {
|
|
2035
|
+
return url === "about:blank" || url.startsWith("http://") || url.startsWith("https://");
|
|
2036
|
+
}
|
|
2037
|
+
function isDisposableStartupPageUrl(url) {
|
|
2038
|
+
return url === "about:blank" || url === "chrome://newtab/" || url === "chrome://new-tab-page/";
|
|
2039
|
+
}
|
|
2040
|
+
function pageLooselyMatchesUrl(currentUrl, initialUrl) {
|
|
2041
|
+
try {
|
|
2042
|
+
const current = new URL(currentUrl);
|
|
2043
|
+
const requested = new URL(initialUrl);
|
|
2044
|
+
if (current.href === requested.href) {
|
|
2045
|
+
return true;
|
|
2046
|
+
}
|
|
2047
|
+
return current.hostname === requested.hostname && current.pathname === requested.pathname;
|
|
2048
|
+
} catch {
|
|
2049
|
+
return currentUrl === initialUrl;
|
|
1845
2050
|
}
|
|
1846
|
-
|
|
1847
|
-
|
|
2051
|
+
}
|
|
2052
|
+
function normalizeDiscoveryUrl(cdpUrl) {
|
|
2053
|
+
let parsed;
|
|
2054
|
+
try {
|
|
2055
|
+
parsed = new URL(cdpUrl);
|
|
2056
|
+
} catch {
|
|
1848
2057
|
throw new Error(
|
|
1849
|
-
`"${
|
|
2058
|
+
`Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
|
|
1850
2059
|
);
|
|
1851
2060
|
}
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
2061
|
+
if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
|
|
2062
|
+
return parsed;
|
|
2063
|
+
}
|
|
2064
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1855
2065
|
throw new Error(
|
|
1856
|
-
`
|
|
2066
|
+
`Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
|
|
1857
2067
|
);
|
|
1858
2068
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
localStatePath
|
|
1865
|
-
};
|
|
2069
|
+
const normalized = new URL(parsed.toString());
|
|
2070
|
+
normalized.pathname = "/json/version";
|
|
2071
|
+
normalized.search = "";
|
|
2072
|
+
normalized.hash = "";
|
|
2073
|
+
return normalized;
|
|
1866
2074
|
}
|
|
1867
|
-
function
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
return {
|
|
1871
|
-
userDataDir: inputPath
|
|
1872
|
-
};
|
|
1873
|
-
}
|
|
1874
|
-
if (fileExists(expandedPath) && (0, import_node_path.basename)(expandedPath) === "Cookies") {
|
|
1875
|
-
const directParent = (0, import_node_path.dirname)(expandedPath);
|
|
1876
|
-
const profileDir = (0, import_node_path.basename)(directParent) === "Network" ? (0, import_node_path.dirname)(directParent) : directParent;
|
|
1877
|
-
return {
|
|
1878
|
-
userDataDir: (0, import_node_path.dirname)(profileDir),
|
|
1879
|
-
profileDirectory: (0, import_node_path.basename)(profileDir)
|
|
1880
|
-
};
|
|
2075
|
+
async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
2076
|
+
if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
|
|
2077
|
+
return cdpUrl;
|
|
1881
2078
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
2079
|
+
const versionUrl = normalizeDiscoveryUrl(cdpUrl);
|
|
2080
|
+
const deadline = Date.now() + timeoutMs;
|
|
2081
|
+
let lastError = "CDP discovery did not respond.";
|
|
2082
|
+
while (Date.now() < deadline) {
|
|
2083
|
+
const remaining = Math.max(deadline - Date.now(), 1e3);
|
|
2084
|
+
try {
|
|
2085
|
+
const response = await fetch(versionUrl, {
|
|
2086
|
+
signal: AbortSignal.timeout(Math.min(remaining, 5e3))
|
|
2087
|
+
});
|
|
2088
|
+
if (!response.ok) {
|
|
2089
|
+
lastError = `${response.status} ${response.statusText}`;
|
|
2090
|
+
} else {
|
|
2091
|
+
const payload = await response.json();
|
|
2092
|
+
const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
|
|
2093
|
+
if (wsUrl && wsUrl.trim()) {
|
|
2094
|
+
return wsUrl;
|
|
2095
|
+
}
|
|
2096
|
+
lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
|
|
2097
|
+
}
|
|
2098
|
+
} catch (error) {
|
|
2099
|
+
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
2100
|
+
}
|
|
2101
|
+
await sleep(100);
|
|
1887
2102
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2103
|
+
throw new Error(
|
|
2104
|
+
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
2105
|
+
);
|
|
1891
2106
|
}
|
|
1892
|
-
function
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
return {
|
|
1913
|
-
snapshotPath,
|
|
1914
|
-
cleanup: async () => {
|
|
1915
|
-
await (0, import_promises.rm)(snapshotDir, { recursive: true, force: true });
|
|
1916
|
-
}
|
|
1917
|
-
};
|
|
1918
|
-
}
|
|
1919
|
-
async function querySqliteJson(dbPath, query) {
|
|
1920
|
-
const result = await execFileAsync("sqlite3", ["-json", dbPath, query], {
|
|
1921
|
-
encoding: "utf8",
|
|
1922
|
-
maxBuffer: 64 * 1024 * 1024
|
|
2107
|
+
async function reserveDebugPort() {
|
|
2108
|
+
return await new Promise((resolve, reject) => {
|
|
2109
|
+
const server = (0, import_node_net.createServer)();
|
|
2110
|
+
server.unref();
|
|
2111
|
+
server.on("error", reject);
|
|
2112
|
+
server.listen(0, "127.0.0.1", () => {
|
|
2113
|
+
const address = server.address();
|
|
2114
|
+
if (!address || typeof address === "string") {
|
|
2115
|
+
server.close();
|
|
2116
|
+
reject(new Error("Failed to reserve a local debug port."));
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
server.close((error) => {
|
|
2120
|
+
if (error) {
|
|
2121
|
+
reject(error);
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
resolve(address.port);
|
|
2125
|
+
});
|
|
2126
|
+
});
|
|
1923
2127
|
});
|
|
1924
|
-
const stdout = result.stdout;
|
|
1925
|
-
const trimmed = stdout.trim();
|
|
1926
|
-
if (!trimmed) {
|
|
1927
|
-
return [];
|
|
1928
|
-
}
|
|
1929
|
-
return JSON.parse(trimmed);
|
|
1930
|
-
}
|
|
1931
|
-
function convertChromiumTimestampToUnixSeconds(value) {
|
|
1932
|
-
if (!value || value === "0") {
|
|
1933
|
-
return -1;
|
|
1934
|
-
}
|
|
1935
|
-
const micros = BigInt(value);
|
|
1936
|
-
if (micros <= CHROMIUM_EPOCH_MICROS) {
|
|
1937
|
-
return -1;
|
|
1938
|
-
}
|
|
1939
|
-
return Number((micros - CHROMIUM_EPOCH_MICROS) / 1000000n);
|
|
1940
2128
|
}
|
|
1941
|
-
function
|
|
1942
|
-
|
|
1943
|
-
|
|
2129
|
+
async function cloneProfileToTempDir(userDataDir, profileDirectory) {
|
|
2130
|
+
const resolvedUserDataDir = expandHome(userDataDir);
|
|
2131
|
+
const tempUserDataDir = await (0, import_promises.mkdtemp)(
|
|
2132
|
+
(0, import_node_path.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-")
|
|
2133
|
+
);
|
|
2134
|
+
const sourceProfileDir = (0, import_node_path.join)(resolvedUserDataDir, profileDirectory);
|
|
2135
|
+
const targetProfileDir = (0, import_node_path.join)(tempUserDataDir, profileDirectory);
|
|
2136
|
+
if ((0, import_node_fs.existsSync)(sourceProfileDir)) {
|
|
2137
|
+
await (0, import_promises.cp)(sourceProfileDir, targetProfileDir, {
|
|
2138
|
+
recursive: true
|
|
2139
|
+
});
|
|
2140
|
+
} else {
|
|
2141
|
+
await (0, import_promises.mkdir)(targetProfileDir, {
|
|
2142
|
+
recursive: true
|
|
2143
|
+
});
|
|
1944
2144
|
}
|
|
1945
|
-
|
|
1946
|
-
|
|
2145
|
+
const localStatePath = (0, import_node_path.join)(resolvedUserDataDir, "Local State");
|
|
2146
|
+
if ((0, import_node_fs.existsSync)(localStatePath)) {
|
|
2147
|
+
await (0, import_promises.copyFile)(localStatePath, (0, import_node_path.join)(tempUserDataDir, "Local State"));
|
|
2148
|
+
}
|
|
2149
|
+
return tempUserDataDir;
|
|
2150
|
+
}
|
|
2151
|
+
function buildRealBrowserLaunchArgs(options) {
|
|
2152
|
+
const args = [
|
|
2153
|
+
`--user-data-dir=${options.userDataDir}`,
|
|
2154
|
+
`--profile-directory=${options.profileDirectory}`,
|
|
2155
|
+
`--remote-debugging-port=${options.debugPort}`,
|
|
2156
|
+
"--no-first-run",
|
|
2157
|
+
"--no-default-browser-check",
|
|
2158
|
+
"--disable-background-networking",
|
|
2159
|
+
"--disable-sync",
|
|
2160
|
+
"--disable-popup-blocking"
|
|
2161
|
+
];
|
|
2162
|
+
if (options.headless) {
|
|
2163
|
+
args.push("--headless=new");
|
|
1947
2164
|
}
|
|
1948
|
-
return
|
|
2165
|
+
return args;
|
|
1949
2166
|
}
|
|
1950
|
-
function
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
return buffer;
|
|
2167
|
+
async function killProcessTree(processHandle) {
|
|
2168
|
+
if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
|
|
2169
|
+
return;
|
|
1954
2170
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
2171
|
+
if (process.platform === "win32") {
|
|
2172
|
+
await new Promise((resolve) => {
|
|
2173
|
+
const killer = (0, import_node_child_process.spawn)(
|
|
2174
|
+
"taskkill",
|
|
2175
|
+
["/pid", String(processHandle.pid), "/t", "/f"],
|
|
2176
|
+
{
|
|
2177
|
+
stdio: "ignore"
|
|
2178
|
+
}
|
|
2179
|
+
);
|
|
2180
|
+
killer.on("error", () => resolve());
|
|
2181
|
+
killer.on("exit", () => resolve());
|
|
2182
|
+
});
|
|
2183
|
+
return;
|
|
1960
2184
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2185
|
+
try {
|
|
2186
|
+
process.kill(-processHandle.pid, "SIGKILL");
|
|
2187
|
+
} catch {
|
|
2188
|
+
try {
|
|
2189
|
+
processHandle.kill("SIGKILL");
|
|
2190
|
+
} catch {
|
|
2191
|
+
}
|
|
1964
2192
|
}
|
|
1965
|
-
return buffer;
|
|
1966
2193
|
}
|
|
1967
|
-
function
|
|
1968
|
-
|
|
1969
|
-
const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
|
|
1970
|
-
const decipher = (0, import_node_crypto.createDecipheriv)("aes-128-cbc", key, iv);
|
|
1971
|
-
const plaintext = Buffer.concat([
|
|
1972
|
-
decipher.update(ciphertext),
|
|
1973
|
-
decipher.final()
|
|
1974
|
-
]);
|
|
1975
|
-
return stripDomainHashPrefix(stripChromiumPadding(plaintext), hostKey).toString(
|
|
1976
|
-
"utf8"
|
|
1977
|
-
);
|
|
2194
|
+
async function sleep(ms) {
|
|
2195
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1978
2196
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2197
|
+
|
|
2198
|
+
// src/navigation.ts
|
|
2199
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
2200
|
+
var DEFAULT_SETTLE_MS = 750;
|
|
2201
|
+
var FRAME_EVALUATE_GRACE_MS = 200;
|
|
2202
|
+
var TRANSIENT_CONTEXT_RETRY_DELAY_MS = 25;
|
|
2203
|
+
var STEALTH_WORLD_NAME = "__opensteer_wait__";
|
|
2204
|
+
var StealthWaitUnavailableError = class extends Error {
|
|
2205
|
+
constructor(cause) {
|
|
2206
|
+
super("Stealth visual wait requires Chromium CDP support.", { cause });
|
|
2207
|
+
this.name = "StealthWaitUnavailableError";
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
function isStealthWaitUnavailableError(error) {
|
|
2211
|
+
return error instanceof StealthWaitUnavailableError;
|
|
1989
2212
|
}
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
2003
|
-
{
|
|
2004
|
-
encoding: "utf8",
|
|
2005
|
-
maxBuffer: 8 * 1024 * 1024
|
|
2213
|
+
var FRAME_OWNER_VISIBILITY_FUNCTION = `function() {
|
|
2214
|
+
if (!(this instanceof HTMLElement)) return false;
|
|
2215
|
+
|
|
2216
|
+
var rect = this.getBoundingClientRect();
|
|
2217
|
+
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
2218
|
+
if (
|
|
2219
|
+
rect.bottom <= 0 ||
|
|
2220
|
+
rect.right <= 0 ||
|
|
2221
|
+
rect.top >= window.innerHeight ||
|
|
2222
|
+
rect.left >= window.innerWidth
|
|
2223
|
+
) {
|
|
2224
|
+
return false;
|
|
2006
2225
|
}
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
if (!password) {
|
|
2016
|
-
throw new Error(
|
|
2017
|
-
`Unable to read ${brand.macService} from macOS Keychain.`
|
|
2018
|
-
);
|
|
2226
|
+
|
|
2227
|
+
var style = window.getComputedStyle(this);
|
|
2228
|
+
if (
|
|
2229
|
+
style.display === 'none' ||
|
|
2230
|
+
style.visibility === 'hidden' ||
|
|
2231
|
+
Number(style.opacity) === 0
|
|
2232
|
+
) {
|
|
2233
|
+
return false;
|
|
2019
2234
|
}
|
|
2020
|
-
|
|
2021
|
-
return
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
);
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
Buffer.from(row.encrypted_value || "", "hex"),
|
|
2040
|
-
key,
|
|
2041
|
-
row.host_key
|
|
2042
|
-
);
|
|
2043
|
-
}
|
|
2044
|
-
if (process.platform === "win32") {
|
|
2045
|
-
if (!location.localStatePath) {
|
|
2046
|
-
throw new Error(
|
|
2047
|
-
`Unable to locate Chromium Local State for profile: ${location.profileDir}`
|
|
2048
|
-
);
|
|
2235
|
+
|
|
2236
|
+
return true;
|
|
2237
|
+
}`;
|
|
2238
|
+
function buildStabilityScript(timeout, settleMs) {
|
|
2239
|
+
return `new Promise(function(resolve) {
|
|
2240
|
+
var deadline = Date.now() + ${timeout};
|
|
2241
|
+
var resolved = false;
|
|
2242
|
+
var timer = null;
|
|
2243
|
+
var observers = [];
|
|
2244
|
+
var observedShadowRoots = [];
|
|
2245
|
+
var fonts = document.fonts;
|
|
2246
|
+
var fontsReady = !fonts || fonts.status === 'loaded';
|
|
2247
|
+
var lastRelevantMutationAt = Date.now();
|
|
2248
|
+
|
|
2249
|
+
function clearObservers() {
|
|
2250
|
+
for (var i = 0; i < observers.length; i++) {
|
|
2251
|
+
observers[i].disconnect();
|
|
2252
|
+
}
|
|
2253
|
+
observers = [];
|
|
2049
2254
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
}
|
|
2059
|
-
const encryptedKey = Buffer.from(encryptedKeyBase64, "base64");
|
|
2060
|
-
const masterKey = await dpapiUnprotect(encryptedKey.subarray(5));
|
|
2061
|
-
return async (row) => {
|
|
2062
|
-
const encryptedValue = Buffer.from(row.encrypted_value || "", "hex");
|
|
2063
|
-
if (encryptedValue.length > 4 && encryptedValue[0] === 1 && encryptedValue[1] === 0 && encryptedValue[2] === 0 && encryptedValue[3] === 0) {
|
|
2064
|
-
const decrypted = await dpapiUnprotect(encryptedValue);
|
|
2065
|
-
return decrypted.toString("utf8");
|
|
2066
|
-
}
|
|
2067
|
-
return decryptChromiumAes256GcmValue(encryptedValue, masterKey);
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
|
-
throw new Error(
|
|
2071
|
-
`Local Chromium cookie sync is not supported on ${process.platform}.`
|
|
2072
|
-
);
|
|
2073
|
-
}
|
|
2074
|
-
function buildPlaywrightCookie(row, value) {
|
|
2075
|
-
if (!row.name.trim()) {
|
|
2076
|
-
return null;
|
|
2077
|
-
}
|
|
2078
|
-
if (!row.host_key.trim()) {
|
|
2079
|
-
return null;
|
|
2080
|
-
}
|
|
2081
|
-
const expires = row.has_expires === 1 ? convertChromiumTimestampToUnixSeconds(row.expires_utc) : -1;
|
|
2082
|
-
if (expires !== -1 && expires <= Math.floor(Date.now() / 1e3)) {
|
|
2083
|
-
return null;
|
|
2084
|
-
}
|
|
2085
|
-
return {
|
|
2086
|
-
name: row.name,
|
|
2087
|
-
value,
|
|
2088
|
-
domain: row.host_key,
|
|
2089
|
-
path: row.path || "/",
|
|
2090
|
-
expires,
|
|
2091
|
-
httpOnly: row.is_httponly === 1,
|
|
2092
|
-
secure: row.is_secure === 1,
|
|
2093
|
-
sameSite: mapChromiumSameSite(row.samesite)
|
|
2094
|
-
};
|
|
2095
|
-
}
|
|
2096
|
-
async function loadCookiesFromLocalProfileDir(inputPath, options = {}) {
|
|
2097
|
-
const location = await resolveChromiumProfileLocation(inputPath);
|
|
2098
|
-
try {
|
|
2099
|
-
return await loadCookiesFromSqlite(location);
|
|
2100
|
-
} catch (error) {
|
|
2101
|
-
if (!isMissingSqliteBinary(error)) {
|
|
2102
|
-
throw error;
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
return await loadCookiesFromBrowserSnapshot(location, options);
|
|
2106
|
-
}
|
|
2107
|
-
async function loadCookiesFromSqlite(location) {
|
|
2108
|
-
const snapshot = await createSqliteSnapshot(location.cookieDbPath);
|
|
2109
|
-
try {
|
|
2110
|
-
const rows = await querySqliteJson(
|
|
2111
|
-
snapshot.snapshotPath,
|
|
2112
|
-
[
|
|
2113
|
-
"SELECT",
|
|
2114
|
-
" host_key,",
|
|
2115
|
-
" name,",
|
|
2116
|
-
" value,",
|
|
2117
|
-
" hex(encrypted_value) AS encrypted_value,",
|
|
2118
|
-
" path,",
|
|
2119
|
-
" CAST(expires_utc AS TEXT) AS expires_utc,",
|
|
2120
|
-
" is_secure,",
|
|
2121
|
-
" is_httponly,",
|
|
2122
|
-
" has_expires,",
|
|
2123
|
-
" samesite",
|
|
2124
|
-
"FROM cookies"
|
|
2125
|
-
].join(" ")
|
|
2126
|
-
);
|
|
2127
|
-
const decryptValue = await buildChromiumDecryptor(location);
|
|
2128
|
-
const cookies = [];
|
|
2129
|
-
for (const row of rows) {
|
|
2130
|
-
let value = row.value || "";
|
|
2131
|
-
if (!value && row.encrypted_value) {
|
|
2132
|
-
value = await decryptValue(row);
|
|
2133
|
-
}
|
|
2134
|
-
const cookie = buildPlaywrightCookie(row, value);
|
|
2135
|
-
if (cookie) {
|
|
2136
|
-
cookies.push(cookie);
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
return cookies;
|
|
2140
|
-
} finally {
|
|
2141
|
-
await snapshot.cleanup();
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
async function loadCookiesFromBrowserSnapshot(location, options) {
|
|
2145
|
-
const snapshotRootDir = await (0, import_promises.mkdtemp)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "opensteer-profile-"));
|
|
2146
|
-
const snapshotProfileDir = (0, import_node_path.join)(
|
|
2147
|
-
snapshotRootDir,
|
|
2148
|
-
(0, import_node_path.basename)(location.profileDir)
|
|
2149
|
-
);
|
|
2150
|
-
let context = null;
|
|
2151
|
-
try {
|
|
2152
|
-
await (0, import_promises.cp)(location.profileDir, snapshotProfileDir, {
|
|
2153
|
-
recursive: true
|
|
2154
|
-
});
|
|
2155
|
-
if (location.localStatePath) {
|
|
2156
|
-
await (0, import_promises.copyFile)(location.localStatePath, (0, import_node_path.join)(snapshotRootDir, "Local State"));
|
|
2157
|
-
}
|
|
2158
|
-
const brand = detectChromiumBrand(location);
|
|
2159
|
-
const args = [`--profile-directory=${(0, import_node_path.basename)(snapshotProfileDir)}`];
|
|
2160
|
-
context = await import_playwright.chromium.launchPersistentContext(snapshotRootDir, {
|
|
2161
|
-
channel: brand.playwrightChannel,
|
|
2162
|
-
headless: options.headless ?? true,
|
|
2163
|
-
timeout: options.timeout ?? 12e4,
|
|
2164
|
-
args
|
|
2165
|
-
});
|
|
2166
|
-
return await context.cookies();
|
|
2167
|
-
} finally {
|
|
2168
|
-
await context?.close().catch(() => void 0);
|
|
2169
|
-
await (0, import_promises.rm)(snapshotRootDir, { recursive: true, force: true });
|
|
2170
|
-
}
|
|
2171
|
-
}
|
|
2172
|
-
function isMissingSqliteBinary(error) {
|
|
2173
|
-
return Boolean(
|
|
2174
|
-
error && typeof error === "object" && "code" in error && error.code === "ENOENT"
|
|
2175
|
-
);
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
// src/browser/pool.ts
|
|
2179
|
-
var BrowserPool = class {
|
|
2180
|
-
browser = null;
|
|
2181
|
-
persistentContext = null;
|
|
2182
|
-
cdpProxy = null;
|
|
2183
|
-
defaults;
|
|
2184
|
-
constructor(defaults = {}) {
|
|
2185
|
-
this.defaults = defaults;
|
|
2186
|
-
}
|
|
2187
|
-
async launch(options = {}) {
|
|
2188
|
-
if (this.browser || this.cdpProxy) {
|
|
2189
|
-
await this.close();
|
|
2190
|
-
}
|
|
2191
|
-
const connectUrl = options.connectUrl ?? this.defaults.connectUrl;
|
|
2192
|
-
const channel = options.channel ?? this.defaults.channel;
|
|
2193
|
-
const profileDir = options.profileDir ?? this.defaults.profileDir;
|
|
2194
|
-
if (connectUrl) {
|
|
2195
|
-
return this.connectToRunning(connectUrl, options.timeout);
|
|
2196
|
-
}
|
|
2197
|
-
if (profileDir) {
|
|
2198
|
-
return this.launchPersistentProfile(options, channel, profileDir);
|
|
2199
|
-
}
|
|
2200
|
-
if (channel) {
|
|
2201
|
-
return this.launchWithChannel(options, channel);
|
|
2202
|
-
}
|
|
2203
|
-
return this.launchSandbox(options);
|
|
2204
|
-
}
|
|
2205
|
-
async close() {
|
|
2206
|
-
const browser = this.browser;
|
|
2207
|
-
const persistentContext = this.persistentContext;
|
|
2208
|
-
this.browser = null;
|
|
2209
|
-
this.persistentContext = null;
|
|
2210
|
-
try {
|
|
2211
|
-
if (persistentContext) {
|
|
2212
|
-
await persistentContext.close();
|
|
2213
|
-
} else if (browser) {
|
|
2214
|
-
await browser.close();
|
|
2215
|
-
}
|
|
2216
|
-
} finally {
|
|
2217
|
-
this.cdpProxy?.close();
|
|
2218
|
-
this.cdpProxy = null;
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
async connectToRunning(connectUrl, timeout) {
|
|
2222
|
-
this.cdpProxy?.close();
|
|
2223
|
-
this.cdpProxy = null;
|
|
2224
|
-
let browser = null;
|
|
2225
|
-
try {
|
|
2226
|
-
const { browserWsUrl, targets } = await discoverTargets(connectUrl);
|
|
2227
|
-
if (targets.length === 0) {
|
|
2228
|
-
throw new Error(
|
|
2229
|
-
"No page targets found. Is the browser running with an open window?"
|
|
2230
|
-
);
|
|
2231
|
-
}
|
|
2232
|
-
const target = targets[0];
|
|
2233
|
-
this.cdpProxy = new CDPProxy(browserWsUrl, target.id);
|
|
2234
|
-
const proxyWsUrl = await this.cdpProxy.start();
|
|
2235
|
-
browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
|
|
2236
|
-
timeout: timeout ?? 3e4
|
|
2237
|
-
});
|
|
2238
|
-
this.browser = browser;
|
|
2239
|
-
this.persistentContext = null;
|
|
2240
|
-
const contexts = browser.contexts();
|
|
2241
|
-
if (contexts.length === 0) {
|
|
2242
|
-
throw new Error(
|
|
2243
|
-
"Connection succeeded but no browser contexts found. Is the browser running with an open window?"
|
|
2244
|
-
);
|
|
2245
|
-
}
|
|
2246
|
-
const context = contexts[0];
|
|
2247
|
-
const pages = context.pages();
|
|
2248
|
-
const page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
2249
|
-
return { browser, context, page, isExternal: true };
|
|
2250
|
-
} catch (error) {
|
|
2251
|
-
if (browser) {
|
|
2252
|
-
await browser.close().catch(() => void 0);
|
|
2253
|
-
}
|
|
2254
|
-
this.browser = null;
|
|
2255
|
-
this.persistentContext = null;
|
|
2256
|
-
this.cdpProxy?.close();
|
|
2257
|
-
this.cdpProxy = null;
|
|
2258
|
-
throw error;
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
async launchPersistentProfile(options, channel, profileDir) {
|
|
2262
|
-
const args = [];
|
|
2263
|
-
const launchProfile = resolvePersistentChromiumLaunchProfile(profileDir);
|
|
2264
|
-
if (launchProfile.profileDirectory) {
|
|
2265
|
-
args.push(`--profile-directory=${launchProfile.profileDirectory}`);
|
|
2266
|
-
}
|
|
2267
|
-
const context = await import_playwright2.chromium.launchPersistentContext(
|
|
2268
|
-
launchProfile.userDataDir,
|
|
2269
|
-
{
|
|
2270
|
-
channel,
|
|
2271
|
-
headless: options.headless ?? this.defaults.headless,
|
|
2272
|
-
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
2273
|
-
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
2274
|
-
timeout: options.timeout,
|
|
2275
|
-
...options.context || {},
|
|
2276
|
-
args
|
|
2277
|
-
}
|
|
2278
|
-
);
|
|
2279
|
-
const browser = context.browser();
|
|
2280
|
-
if (!browser) {
|
|
2281
|
-
await context.close().catch(() => void 0);
|
|
2282
|
-
throw new Error("Persistent browser launch did not expose a browser instance.");
|
|
2283
|
-
}
|
|
2284
|
-
this.browser = browser;
|
|
2285
|
-
this.persistentContext = context;
|
|
2286
|
-
const pages = context.pages();
|
|
2287
|
-
const page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
2288
|
-
return { browser, context, page, isExternal: false };
|
|
2289
|
-
}
|
|
2290
|
-
async launchWithChannel(options, channel) {
|
|
2291
|
-
const browser = await import_playwright2.chromium.launch({
|
|
2292
|
-
channel,
|
|
2293
|
-
headless: options.headless ?? this.defaults.headless,
|
|
2294
|
-
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
2295
|
-
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
2296
|
-
timeout: options.timeout
|
|
2297
|
-
});
|
|
2298
|
-
this.browser = browser;
|
|
2299
|
-
this.persistentContext = null;
|
|
2300
|
-
const context = await browser.newContext(options.context || {});
|
|
2301
|
-
const page = await context.newPage();
|
|
2302
|
-
return { browser, context, page, isExternal: false };
|
|
2303
|
-
}
|
|
2304
|
-
async launchSandbox(options) {
|
|
2305
|
-
const browser = await import_playwright2.chromium.launch({
|
|
2306
|
-
headless: options.headless ?? this.defaults.headless,
|
|
2307
|
-
executablePath: options.executablePath ?? this.defaults.executablePath ?? void 0,
|
|
2308
|
-
slowMo: options.slowMo ?? this.defaults.slowMo ?? 0,
|
|
2309
|
-
timeout: options.timeout
|
|
2310
|
-
});
|
|
2311
|
-
const context = await browser.newContext(options.context || {});
|
|
2312
|
-
const page = await context.newPage();
|
|
2313
|
-
this.browser = browser;
|
|
2314
|
-
this.persistentContext = null;
|
|
2315
|
-
return { browser, context, page, isExternal: false };
|
|
2316
|
-
}
|
|
2317
|
-
};
|
|
2318
|
-
|
|
2319
|
-
// src/navigation.ts
|
|
2320
|
-
var DEFAULT_TIMEOUT = 3e4;
|
|
2321
|
-
var DEFAULT_SETTLE_MS = 750;
|
|
2322
|
-
var FRAME_EVALUATE_GRACE_MS = 200;
|
|
2323
|
-
var TRANSIENT_CONTEXT_RETRY_DELAY_MS = 25;
|
|
2324
|
-
var STEALTH_WORLD_NAME = "__opensteer_wait__";
|
|
2325
|
-
var StealthWaitUnavailableError = class extends Error {
|
|
2326
|
-
constructor(cause) {
|
|
2327
|
-
super("Stealth visual wait requires Chromium CDP support.", { cause });
|
|
2328
|
-
this.name = "StealthWaitUnavailableError";
|
|
2329
|
-
}
|
|
2330
|
-
};
|
|
2331
|
-
function isStealthWaitUnavailableError(error) {
|
|
2332
|
-
return error instanceof StealthWaitUnavailableError;
|
|
2333
|
-
}
|
|
2334
|
-
var FRAME_OWNER_VISIBILITY_FUNCTION = `function() {
|
|
2335
|
-
if (!(this instanceof HTMLElement)) return false;
|
|
2336
|
-
|
|
2337
|
-
var rect = this.getBoundingClientRect();
|
|
2338
|
-
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
2339
|
-
if (
|
|
2340
|
-
rect.bottom <= 0 ||
|
|
2341
|
-
rect.right <= 0 ||
|
|
2342
|
-
rect.top >= window.innerHeight ||
|
|
2343
|
-
rect.left >= window.innerWidth
|
|
2344
|
-
) {
|
|
2345
|
-
return false;
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
var style = window.getComputedStyle(this);
|
|
2349
|
-
if (
|
|
2350
|
-
style.display === 'none' ||
|
|
2351
|
-
style.visibility === 'hidden' ||
|
|
2352
|
-
Number(style.opacity) === 0
|
|
2353
|
-
) {
|
|
2354
|
-
return false;
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
return true;
|
|
2358
|
-
}`;
|
|
2359
|
-
function buildStabilityScript(timeout, settleMs) {
|
|
2360
|
-
return `new Promise(function(resolve) {
|
|
2361
|
-
var deadline = Date.now() + ${timeout};
|
|
2362
|
-
var resolved = false;
|
|
2363
|
-
var timer = null;
|
|
2364
|
-
var observers = [];
|
|
2365
|
-
var observedShadowRoots = [];
|
|
2366
|
-
var fonts = document.fonts;
|
|
2367
|
-
var fontsReady = !fonts || fonts.status === 'loaded';
|
|
2368
|
-
var lastRelevantMutationAt = Date.now();
|
|
2369
|
-
|
|
2370
|
-
function clearObservers() {
|
|
2371
|
-
for (var i = 0; i < observers.length; i++) {
|
|
2372
|
-
observers[i].disconnect();
|
|
2373
|
-
}
|
|
2374
|
-
observers = [];
|
|
2375
|
-
}
|
|
2376
|
-
|
|
2377
|
-
function done() {
|
|
2378
|
-
if (resolved) return;
|
|
2379
|
-
resolved = true;
|
|
2380
|
-
if (timer) clearTimeout(timer);
|
|
2381
|
-
if (safetyTimer) clearTimeout(safetyTimer);
|
|
2382
|
-
clearObservers();
|
|
2383
|
-
resolve();
|
|
2255
|
+
|
|
2256
|
+
function done() {
|
|
2257
|
+
if (resolved) return;
|
|
2258
|
+
resolved = true;
|
|
2259
|
+
if (timer) clearTimeout(timer);
|
|
2260
|
+
if (safetyTimer) clearTimeout(safetyTimer);
|
|
2261
|
+
clearObservers();
|
|
2262
|
+
resolve();
|
|
2384
2263
|
}
|
|
2385
2264
|
|
|
2386
2265
|
function isElementVisiblyIntersectingViewport(element) {
|
|
@@ -2694,7 +2573,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
2694
2573
|
TRANSIENT_CONTEXT_RETRY_DELAY_MS,
|
|
2695
2574
|
Math.max(0, deadline - Date.now())
|
|
2696
2575
|
);
|
|
2697
|
-
await
|
|
2576
|
+
await sleep2(retryDelay);
|
|
2698
2577
|
}
|
|
2699
2578
|
}
|
|
2700
2579
|
}
|
|
@@ -2727,7 +2606,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
2727
2606
|
() => ({ kind: "resolved" }),
|
|
2728
2607
|
(error) => ({ kind: "rejected", error })
|
|
2729
2608
|
);
|
|
2730
|
-
const timeoutPromise =
|
|
2609
|
+
const timeoutPromise = sleep2(
|
|
2731
2610
|
timeout + FRAME_EVALUATE_GRACE_MS
|
|
2732
2611
|
).then(() => ({ kind: "timeout" }));
|
|
2733
2612
|
const result = await Promise.race([
|
|
@@ -2869,14 +2748,14 @@ function isIgnorableFrameError(error) {
|
|
|
2869
2748
|
const message = error.message;
|
|
2870
2749
|
return message.includes("Frame was detached") || message.includes("Target page, context or browser has been closed") || isTransientExecutionContextError(error) || message.includes("No frame for given id found");
|
|
2871
2750
|
}
|
|
2872
|
-
function
|
|
2751
|
+
function sleep2(ms) {
|
|
2873
2752
|
return new Promise((resolve) => {
|
|
2874
2753
|
setTimeout(resolve, ms);
|
|
2875
2754
|
});
|
|
2876
2755
|
}
|
|
2877
2756
|
|
|
2878
2757
|
// src/storage/local.ts
|
|
2879
|
-
var
|
|
2758
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
2880
2759
|
var import_path4 = __toESM(require("path"), 1);
|
|
2881
2760
|
|
|
2882
2761
|
// src/storage/registry.ts
|
|
@@ -2920,14 +2799,14 @@ var LocalSelectorStorage = class {
|
|
|
2920
2799
|
return import_path4.default.join(this.getNamespaceDir(), this.getSelectorFileName(id));
|
|
2921
2800
|
}
|
|
2922
2801
|
ensureDirs() {
|
|
2923
|
-
|
|
2802
|
+
import_fs3.default.mkdirSync(this.getNamespaceDir(), { recursive: true });
|
|
2924
2803
|
}
|
|
2925
2804
|
loadRegistry() {
|
|
2926
2805
|
this.ensureDirs();
|
|
2927
2806
|
const file = this.getRegistryPath();
|
|
2928
|
-
if (!
|
|
2807
|
+
if (!import_fs3.default.existsSync(file)) return createEmptyRegistry(this.namespace);
|
|
2929
2808
|
try {
|
|
2930
|
-
const raw =
|
|
2809
|
+
const raw = import_fs3.default.readFileSync(file, "utf8");
|
|
2931
2810
|
return JSON.parse(raw);
|
|
2932
2811
|
} catch (error) {
|
|
2933
2812
|
const message = extractErrorMessage(
|
|
@@ -2944,16 +2823,16 @@ var LocalSelectorStorage = class {
|
|
|
2944
2823
|
}
|
|
2945
2824
|
saveRegistry(registry) {
|
|
2946
2825
|
this.ensureDirs();
|
|
2947
|
-
|
|
2826
|
+
import_fs3.default.writeFileSync(
|
|
2948
2827
|
this.getRegistryPath(),
|
|
2949
2828
|
JSON.stringify(registry, null, 2)
|
|
2950
2829
|
);
|
|
2951
2830
|
}
|
|
2952
2831
|
readSelector(id) {
|
|
2953
2832
|
const file = this.getSelectorPath(id);
|
|
2954
|
-
if (!
|
|
2833
|
+
if (!import_fs3.default.existsSync(file)) return null;
|
|
2955
2834
|
try {
|
|
2956
|
-
const raw =
|
|
2835
|
+
const raw = import_fs3.default.readFileSync(file, "utf8");
|
|
2957
2836
|
return JSON.parse(raw);
|
|
2958
2837
|
} catch (error) {
|
|
2959
2838
|
const message = extractErrorMessage(
|
|
@@ -2970,15 +2849,15 @@ var LocalSelectorStorage = class {
|
|
|
2970
2849
|
}
|
|
2971
2850
|
writeSelector(payload) {
|
|
2972
2851
|
this.ensureDirs();
|
|
2973
|
-
|
|
2852
|
+
import_fs3.default.writeFileSync(
|
|
2974
2853
|
this.getSelectorPath(payload.id),
|
|
2975
2854
|
JSON.stringify(payload, null, 2)
|
|
2976
2855
|
);
|
|
2977
2856
|
}
|
|
2978
2857
|
clearNamespace() {
|
|
2979
2858
|
const dir = this.getNamespaceDir();
|
|
2980
|
-
if (!
|
|
2981
|
-
|
|
2859
|
+
if (!import_fs3.default.existsSync(dir)) return;
|
|
2860
|
+
import_fs3.default.rmSync(dir, { recursive: true, force: true });
|
|
2982
2861
|
}
|
|
2983
2862
|
};
|
|
2984
2863
|
|
|
@@ -7422,7 +7301,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
7422
7301
|
this.idleSince = 0;
|
|
7423
7302
|
}
|
|
7424
7303
|
const remaining = Math.max(1, options.deadline - now);
|
|
7425
|
-
await
|
|
7304
|
+
await sleep3(Math.min(NETWORK_POLL_MS, remaining));
|
|
7426
7305
|
}
|
|
7427
7306
|
}
|
|
7428
7307
|
handleRequestStarted = (request) => {
|
|
@@ -7467,7 +7346,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
7467
7346
|
return false;
|
|
7468
7347
|
}
|
|
7469
7348
|
};
|
|
7470
|
-
async function
|
|
7349
|
+
async function sleep3(ms) {
|
|
7471
7350
|
await new Promise((resolve) => {
|
|
7472
7351
|
setTimeout(resolve, ms);
|
|
7473
7352
|
});
|
|
@@ -8944,15 +8823,15 @@ function withTokenQuery(wsUrl, token) {
|
|
|
8944
8823
|
}
|
|
8945
8824
|
|
|
8946
8825
|
// src/cloud/local-cache-sync.ts
|
|
8947
|
-
var
|
|
8826
|
+
var import_fs4 = __toESM(require("fs"), 1);
|
|
8948
8827
|
var import_path5 = __toESM(require("path"), 1);
|
|
8949
8828
|
function collectLocalSelectorCacheEntries(storage, options = {}) {
|
|
8950
8829
|
const debug = options.debug === true;
|
|
8951
8830
|
const namespace = storage.getNamespace();
|
|
8952
8831
|
const namespaceDir = storage.getNamespaceDir();
|
|
8953
|
-
if (!
|
|
8832
|
+
if (!import_fs4.default.existsSync(namespaceDir)) return [];
|
|
8954
8833
|
const entries = [];
|
|
8955
|
-
const fileNames =
|
|
8834
|
+
const fileNames = import_fs4.default.readdirSync(namespaceDir);
|
|
8956
8835
|
for (const fileName of fileNames) {
|
|
8957
8836
|
if (fileName === "index.json" || !fileName.endsWith(".json")) continue;
|
|
8958
8837
|
const filePath = import_path5.default.join(namespaceDir, fileName);
|
|
@@ -8983,7 +8862,7 @@ function collectLocalSelectorCacheEntries(storage, options = {}) {
|
|
|
8983
8862
|
}
|
|
8984
8863
|
function readSelectorFile(filePath, debug) {
|
|
8985
8864
|
try {
|
|
8986
|
-
const raw =
|
|
8865
|
+
const raw = import_fs4.default.readFileSync(filePath, "utf8");
|
|
8987
8866
|
return JSON.parse(raw);
|
|
8988
8867
|
} catch (error) {
|
|
8989
8868
|
const message = extractErrorMessage(
|
|
@@ -9078,13 +8957,13 @@ function dedupeNewest(entries) {
|
|
|
9078
8957
|
}
|
|
9079
8958
|
|
|
9080
8959
|
// src/cloud/cdp-client.ts
|
|
9081
|
-
var
|
|
8960
|
+
var import_playwright2 = require("playwright");
|
|
9082
8961
|
var CloudCdpClient = class {
|
|
9083
8962
|
async connect(args) {
|
|
9084
8963
|
const endpoint = withTokenQuery2(args.wsUrl, args.token);
|
|
9085
8964
|
let browser;
|
|
9086
8965
|
try {
|
|
9087
|
-
browser = await
|
|
8966
|
+
browser = await import_playwright2.chromium.connectOverCDP(endpoint);
|
|
9088
8967
|
} catch (error) {
|
|
9089
8968
|
const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
|
|
9090
8969
|
throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
|
|
@@ -10877,7 +10756,7 @@ async function executeAgentAction(page, action) {
|
|
|
10877
10756
|
}
|
|
10878
10757
|
case "wait": {
|
|
10879
10758
|
const ms = numberOr(action.timeMs, action.time_ms, 1e3);
|
|
10880
|
-
await
|
|
10759
|
+
await sleep4(ms);
|
|
10881
10760
|
return;
|
|
10882
10761
|
}
|
|
10883
10762
|
case "goto": {
|
|
@@ -11042,7 +10921,7 @@ async function pressKeyCombo(page, combo) {
|
|
|
11042
10921
|
}
|
|
11043
10922
|
}
|
|
11044
10923
|
}
|
|
11045
|
-
function
|
|
10924
|
+
function sleep4(ms) {
|
|
11046
10925
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11047
10926
|
}
|
|
11048
10927
|
|
|
@@ -11073,7 +10952,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
11073
10952
|
if (isMutatingAgentAction(action)) {
|
|
11074
10953
|
this.onMutatingAction?.(action);
|
|
11075
10954
|
}
|
|
11076
|
-
await
|
|
10955
|
+
await sleep5(this.config.waitBetweenActionsMs);
|
|
11077
10956
|
});
|
|
11078
10957
|
try {
|
|
11079
10958
|
const result = await this.client.execute({
|
|
@@ -11135,7 +11014,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
11135
11014
|
await this.cursorController.preview({ x, y }, "agent");
|
|
11136
11015
|
}
|
|
11137
11016
|
};
|
|
11138
|
-
function
|
|
11017
|
+
function sleep5(ms) {
|
|
11139
11018
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
11140
11019
|
}
|
|
11141
11020
|
|
|
@@ -11571,7 +11450,7 @@ var CursorController = class {
|
|
|
11571
11450
|
for (const step of motion.points) {
|
|
11572
11451
|
await this.renderer.move(step, this.style);
|
|
11573
11452
|
if (motion.stepDelayMs > 0) {
|
|
11574
|
-
await
|
|
11453
|
+
await sleep6(motion.stepDelayMs);
|
|
11575
11454
|
}
|
|
11576
11455
|
}
|
|
11577
11456
|
if (shouldPulse(intent)) {
|
|
@@ -11729,7 +11608,7 @@ function clamp2(value, min, max) {
|
|
|
11729
11608
|
function shouldPulse(intent) {
|
|
11730
11609
|
return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
|
|
11731
11610
|
}
|
|
11732
|
-
function
|
|
11611
|
+
function sleep6(ms) {
|
|
11733
11612
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11734
11613
|
}
|
|
11735
11614
|
|
|
@@ -11780,30 +11659,20 @@ var Opensteer = class _Opensteer {
|
|
|
11780
11659
|
this.pool = new BrowserPool(resolved.browser || {});
|
|
11781
11660
|
if (cloudSelection.cloud) {
|
|
11782
11661
|
const cloudConfig = resolved.cloud && typeof resolved.cloud === "object" ? resolved.cloud : void 0;
|
|
11783
|
-
const
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11788
|
-
);
|
|
11789
|
-
}
|
|
11790
|
-
let credential = "";
|
|
11791
|
-
let authScheme = cloudConfig?.authScheme ?? "api-key";
|
|
11792
|
-
if (accessToken) {
|
|
11793
|
-
credential = accessToken;
|
|
11794
|
-
authScheme = "bearer";
|
|
11795
|
-
} else if (apiKey) {
|
|
11796
|
-
credential = apiKey;
|
|
11797
|
-
}
|
|
11662
|
+
const credential = selectCloudCredential({
|
|
11663
|
+
apiKey: cloudConfig?.apiKey,
|
|
11664
|
+
accessToken: cloudConfig?.accessToken,
|
|
11665
|
+
authScheme: cloudConfig?.authScheme
|
|
11666
|
+
});
|
|
11798
11667
|
if (!credential) {
|
|
11799
11668
|
throw new Error(
|
|
11800
11669
|
"Cloud mode requires credentials via cloud.apiKey/cloud.accessToken or OPENSTEER_API_KEY/OPENSTEER_ACCESS_TOKEN."
|
|
11801
11670
|
);
|
|
11802
11671
|
}
|
|
11803
11672
|
this.cloud = createCloudRuntimeState(
|
|
11804
|
-
credential,
|
|
11673
|
+
credential.token,
|
|
11805
11674
|
cloudConfig?.baseUrl,
|
|
11806
|
-
authScheme
|
|
11675
|
+
credential.authScheme
|
|
11807
11676
|
);
|
|
11808
11677
|
} else {
|
|
11809
11678
|
this.cloud = null;
|
|
@@ -12124,9 +11993,11 @@ var Opensteer = class _Opensteer {
|
|
|
12124
11993
|
}
|
|
12125
11994
|
const session = await this.pool.launch({
|
|
12126
11995
|
...options,
|
|
12127
|
-
|
|
12128
|
-
|
|
12129
|
-
|
|
11996
|
+
mode: options.mode ?? this.config.browser?.mode,
|
|
11997
|
+
cdpUrl: options.cdpUrl ?? this.config.browser?.cdpUrl,
|
|
11998
|
+
userDataDir: options.userDataDir ?? this.config.browser?.userDataDir,
|
|
11999
|
+
profileDirectory: options.profileDirectory ?? this.config.browser?.profileDirectory,
|
|
12000
|
+
executablePath: options.executablePath ?? this.config.browser?.executablePath
|
|
12130
12001
|
});
|
|
12131
12002
|
this.browser = session.browser;
|
|
12132
12003
|
this.contextRef = session.context;
|
|
@@ -12157,6 +12028,32 @@ var Opensteer = class _Opensteer {
|
|
|
12157
12028
|
instance.snapshotCache = null;
|
|
12158
12029
|
return instance;
|
|
12159
12030
|
}
|
|
12031
|
+
static listLocalProfiles(userDataDir) {
|
|
12032
|
+
return listLocalChromeProfiles(userDataDir);
|
|
12033
|
+
}
|
|
12034
|
+
static fromSystemChrome(browser = {}, config = {}) {
|
|
12035
|
+
const chromePaths = detectChromePaths();
|
|
12036
|
+
const executablePath = browser.executablePath ?? config.browser?.executablePath ?? chromePaths.executable ?? void 0;
|
|
12037
|
+
if (!executablePath) {
|
|
12038
|
+
throw new Error(
|
|
12039
|
+
"Chrome was not found. Pass executablePath explicitly or install Chrome in a supported location."
|
|
12040
|
+
);
|
|
12041
|
+
}
|
|
12042
|
+
const userDataDir = browser.userDataDir ?? config.browser?.userDataDir ?? chromePaths.defaultUserDataDir;
|
|
12043
|
+
const autoDetectedProfiles = listLocalChromeProfiles(userDataDir);
|
|
12044
|
+
const profileDirectory = browser.profileDirectory ?? config.browser?.profileDirectory ?? autoDetectedProfiles[0]?.directory ?? "Default";
|
|
12045
|
+
return new _Opensteer({
|
|
12046
|
+
...config,
|
|
12047
|
+
browser: {
|
|
12048
|
+
...config.browser || {},
|
|
12049
|
+
mode: "real",
|
|
12050
|
+
headless: browser.headless ?? config.browser?.headless ?? true,
|
|
12051
|
+
executablePath,
|
|
12052
|
+
userDataDir,
|
|
12053
|
+
profileDirectory
|
|
12054
|
+
}
|
|
12055
|
+
});
|
|
12056
|
+
}
|
|
12160
12057
|
async close() {
|
|
12161
12058
|
this.snapshotCache = null;
|
|
12162
12059
|
if (this.cloud) {
|
|
@@ -14124,400 +14021,992 @@ var Opensteer = class _Opensteer {
|
|
|
14124
14021
|
});
|
|
14125
14022
|
continue;
|
|
14126
14023
|
}
|
|
14127
|
-
pathFields.push({
|
|
14128
|
-
key: field.key,
|
|
14129
|
-
path: this.normalizePath(field.path),
|
|
14130
|
-
attribute: field.attribute
|
|
14024
|
+
pathFields.push({
|
|
14025
|
+
key: field.key,
|
|
14026
|
+
path: this.normalizePath(field.path),
|
|
14027
|
+
attribute: field.attribute
|
|
14028
|
+
});
|
|
14029
|
+
}
|
|
14030
|
+
if (currentUrlKeys.length) {
|
|
14031
|
+
const pageUrl = this.page.url();
|
|
14032
|
+
for (const key of currentUrlKeys) {
|
|
14033
|
+
result[key] = pageUrl;
|
|
14034
|
+
}
|
|
14035
|
+
}
|
|
14036
|
+
if (counterRequests.length) {
|
|
14037
|
+
const counterValues = await resolveCountersBatch(this.page, counterRequests);
|
|
14038
|
+
Object.assign(result, counterValues);
|
|
14039
|
+
}
|
|
14040
|
+
if (pathFields.length) {
|
|
14041
|
+
const pathValues = await extractWithPaths(this.page, pathFields);
|
|
14042
|
+
Object.assign(result, pathValues);
|
|
14043
|
+
}
|
|
14044
|
+
return result;
|
|
14045
|
+
}
|
|
14046
|
+
async resolveFieldTargetsToPersistableFields(fields) {
|
|
14047
|
+
const resolved = [];
|
|
14048
|
+
for (const field of fields) {
|
|
14049
|
+
if ("source" in field) {
|
|
14050
|
+
resolved.push({
|
|
14051
|
+
key: field.key,
|
|
14052
|
+
source: "current_url"
|
|
14053
|
+
});
|
|
14054
|
+
continue;
|
|
14055
|
+
}
|
|
14056
|
+
if ("path" in field) {
|
|
14057
|
+
resolved.push({
|
|
14058
|
+
key: field.key,
|
|
14059
|
+
path: this.normalizePath(field.path),
|
|
14060
|
+
attribute: field.attribute
|
|
14061
|
+
});
|
|
14062
|
+
continue;
|
|
14063
|
+
}
|
|
14064
|
+
const path7 = await this.buildPathFromElement(field.counter);
|
|
14065
|
+
if (!path7) {
|
|
14066
|
+
throw new Error(
|
|
14067
|
+
`Unable to persist extraction schema field "${field.key}": counter ${field.counter} could not be converted into a stable element path.`
|
|
14068
|
+
);
|
|
14069
|
+
}
|
|
14070
|
+
resolved.push({
|
|
14071
|
+
key: field.key,
|
|
14072
|
+
path: path7,
|
|
14073
|
+
attribute: field.attribute
|
|
14074
|
+
});
|
|
14075
|
+
}
|
|
14076
|
+
return resolved;
|
|
14077
|
+
}
|
|
14078
|
+
buildActionResult(storageKey, method, persisted, selectorUsed) {
|
|
14079
|
+
return {
|
|
14080
|
+
method,
|
|
14081
|
+
namespace: this.storage.getNamespace(),
|
|
14082
|
+
persisted,
|
|
14083
|
+
pathFile: storageKey && persisted ? this.storage.getSelectorFileName(storageKey) : null,
|
|
14084
|
+
selectorUsed: selectorUsed || null
|
|
14085
|
+
};
|
|
14086
|
+
}
|
|
14087
|
+
resolveStorageKey(description) {
|
|
14088
|
+
if (!description) return null;
|
|
14089
|
+
return (0, import_crypto.createHash)("sha256").update(description).digest("hex").slice(0, 16);
|
|
14090
|
+
}
|
|
14091
|
+
normalizePath(path7) {
|
|
14092
|
+
return sanitizeElementPath(path7);
|
|
14093
|
+
}
|
|
14094
|
+
};
|
|
14095
|
+
function formatActionFailureMessage(action, description, cause) {
|
|
14096
|
+
const label = description ? `"${description}"` : "unnamed target";
|
|
14097
|
+
return `${action} action failed for ${label}: ${cause}`;
|
|
14098
|
+
}
|
|
14099
|
+
function cloneContextHops(context) {
|
|
14100
|
+
return JSON.parse(JSON.stringify(context || []));
|
|
14101
|
+
}
|
|
14102
|
+
function collectIframeContextPrefix(path7) {
|
|
14103
|
+
const context = path7.context || [];
|
|
14104
|
+
let lastIframeIndex = -1;
|
|
14105
|
+
for (let index = 0; index < context.length; index += 1) {
|
|
14106
|
+
if (context[index]?.kind === "iframe") {
|
|
14107
|
+
lastIframeIndex = index;
|
|
14108
|
+
}
|
|
14109
|
+
}
|
|
14110
|
+
if (lastIframeIndex < 0) return [];
|
|
14111
|
+
return cloneContextHops(context.slice(0, lastIframeIndex + 1));
|
|
14112
|
+
}
|
|
14113
|
+
function measureContextOverlap(indexedPrefix, builtContext) {
|
|
14114
|
+
const maxOverlap = Math.min(indexedPrefix.length, builtContext.length);
|
|
14115
|
+
for (let size = maxOverlap; size > 0; size -= 1) {
|
|
14116
|
+
if (matchesContextPrefix(indexedPrefix, builtContext, size, true)) {
|
|
14117
|
+
return size;
|
|
14118
|
+
}
|
|
14119
|
+
}
|
|
14120
|
+
for (let size = maxOverlap; size > 0; size -= 1) {
|
|
14121
|
+
if (matchesContextPrefix(indexedPrefix, builtContext, size, false)) {
|
|
14122
|
+
return size;
|
|
14123
|
+
}
|
|
14124
|
+
}
|
|
14125
|
+
return 0;
|
|
14126
|
+
}
|
|
14127
|
+
function matchesContextPrefix(indexedPrefix, builtContext, size, strictHost) {
|
|
14128
|
+
for (let idx = 0; idx < size; idx += 1) {
|
|
14129
|
+
const left = indexedPrefix[indexedPrefix.length - size + idx];
|
|
14130
|
+
const right = builtContext[idx];
|
|
14131
|
+
if (left.kind !== right.kind) {
|
|
14132
|
+
return false;
|
|
14133
|
+
}
|
|
14134
|
+
if (strictHost && JSON.stringify(left.host) !== JSON.stringify(right.host)) {
|
|
14135
|
+
return false;
|
|
14136
|
+
}
|
|
14137
|
+
}
|
|
14138
|
+
return true;
|
|
14139
|
+
}
|
|
14140
|
+
function normalizeSchemaValue(value) {
|
|
14141
|
+
if (!value) return null;
|
|
14142
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
14143
|
+
return null;
|
|
14144
|
+
}
|
|
14145
|
+
const field = value;
|
|
14146
|
+
return {
|
|
14147
|
+
element: field.element,
|
|
14148
|
+
selector: field.selector,
|
|
14149
|
+
attribute: field.attribute,
|
|
14150
|
+
source: normalizeExtractSource(field.source)
|
|
14151
|
+
};
|
|
14152
|
+
}
|
|
14153
|
+
function normalizeExtractSource(source) {
|
|
14154
|
+
if (typeof source !== "string") return void 0;
|
|
14155
|
+
const normalized = source.trim().toLowerCase();
|
|
14156
|
+
if (normalized === "current_url") return "current_url";
|
|
14157
|
+
return void 0;
|
|
14158
|
+
}
|
|
14159
|
+
function computeSchemaHash(schema) {
|
|
14160
|
+
const stable = stableStringify(schema);
|
|
14161
|
+
return (0, import_crypto.createHash)("sha256").update(stable).digest("hex");
|
|
14162
|
+
}
|
|
14163
|
+
function buildPathMap(fields) {
|
|
14164
|
+
const out = {};
|
|
14165
|
+
for (const field of fields) {
|
|
14166
|
+
out[field.key] = cloneElementPath(field.path);
|
|
14167
|
+
}
|
|
14168
|
+
return out;
|
|
14169
|
+
}
|
|
14170
|
+
function toPathFields(fields) {
|
|
14171
|
+
return fields.filter(isPersistablePathField).map((field) => ({
|
|
14172
|
+
key: field.key,
|
|
14173
|
+
path: field.path,
|
|
14174
|
+
attribute: field.attribute
|
|
14175
|
+
}));
|
|
14176
|
+
}
|
|
14177
|
+
function normalizePersistedExtractPayload(raw) {
|
|
14178
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
14179
|
+
throw new Error(
|
|
14180
|
+
"Invalid persisted extraction payload: expected an object payload."
|
|
14181
|
+
);
|
|
14182
|
+
}
|
|
14183
|
+
const root = {};
|
|
14184
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
14185
|
+
const normalizedKey = String(key || "").trim();
|
|
14186
|
+
if (!normalizedKey) continue;
|
|
14187
|
+
if (normalizedKey.startsWith("$")) {
|
|
14188
|
+
throw new Error(
|
|
14189
|
+
`Invalid persisted extraction payload key "${normalizedKey}": root keys must not start with "$".`
|
|
14190
|
+
);
|
|
14191
|
+
}
|
|
14192
|
+
root[normalizedKey] = normalizePersistedExtractNode(
|
|
14193
|
+
value,
|
|
14194
|
+
normalizedKey
|
|
14195
|
+
);
|
|
14196
|
+
}
|
|
14197
|
+
return root;
|
|
14198
|
+
}
|
|
14199
|
+
function normalizePersistedExtractNode(raw, label) {
|
|
14200
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
14201
|
+
throw new Error(
|
|
14202
|
+
`Invalid persisted extraction node at "${label}": expected an object.`
|
|
14203
|
+
);
|
|
14204
|
+
}
|
|
14205
|
+
const record = raw;
|
|
14206
|
+
if (record.$path) {
|
|
14207
|
+
if (typeof record.$path !== "object") {
|
|
14208
|
+
throw new Error(
|
|
14209
|
+
`Invalid persisted extraction value node at "${label}": "$path" must be an element path object.`
|
|
14210
|
+
);
|
|
14211
|
+
}
|
|
14212
|
+
return {
|
|
14213
|
+
$path: sanitizeElementPath(record.$path),
|
|
14214
|
+
attribute: typeof record.attribute === "string" ? record.attribute : void 0
|
|
14215
|
+
};
|
|
14216
|
+
}
|
|
14217
|
+
if (record.$source != null) {
|
|
14218
|
+
const source = normalizeExtractSource(record.$source);
|
|
14219
|
+
if (!source) {
|
|
14220
|
+
throw new Error(
|
|
14221
|
+
`Invalid persisted extraction source node at "${label}": unsupported "$source" value.`
|
|
14222
|
+
);
|
|
14223
|
+
}
|
|
14224
|
+
return {
|
|
14225
|
+
$source: source
|
|
14226
|
+
};
|
|
14227
|
+
}
|
|
14228
|
+
if (record.$array) {
|
|
14229
|
+
if (!record.$array || typeof record.$array !== "object" || Array.isArray(record.$array)) {
|
|
14230
|
+
throw new Error(
|
|
14231
|
+
`Invalid persisted extraction array node at "${label}": "$array" must be an object.`
|
|
14232
|
+
);
|
|
14233
|
+
}
|
|
14234
|
+
const arrayRecord = record.$array;
|
|
14235
|
+
if (arrayRecord.itemParentPath !== void 0 || arrayRecord.item !== void 0) {
|
|
14236
|
+
throw new Error(
|
|
14237
|
+
`Legacy persisted extraction array format detected at "${label}". Clear cached selectors in .opensteer/selectors/<namespace> and rerun extraction.`
|
|
14238
|
+
);
|
|
14239
|
+
}
|
|
14240
|
+
if (!Array.isArray(arrayRecord.variants) || !arrayRecord.variants.length) {
|
|
14241
|
+
throw new Error(
|
|
14242
|
+
`Invalid persisted extraction array node at "${label}": variants must be a non-empty array.`
|
|
14243
|
+
);
|
|
14244
|
+
}
|
|
14245
|
+
const variants = arrayRecord.variants.map((variantRaw, index) => {
|
|
14246
|
+
if (!variantRaw || typeof variantRaw !== "object" || Array.isArray(variantRaw)) {
|
|
14247
|
+
throw new Error(
|
|
14248
|
+
`Invalid persisted extraction array variant at "${label}"[${index}]: expected an object.`
|
|
14249
|
+
);
|
|
14250
|
+
}
|
|
14251
|
+
const variant = variantRaw;
|
|
14252
|
+
if (!variant.itemParentPath || typeof variant.itemParentPath !== "object") {
|
|
14253
|
+
throw new Error(
|
|
14254
|
+
`Invalid persisted extraction array variant at "${label}"[${index}]: itemParentPath is required.`
|
|
14255
|
+
);
|
|
14256
|
+
}
|
|
14257
|
+
if (!variant.item || typeof variant.item !== "object" || Array.isArray(variant.item)) {
|
|
14258
|
+
throw new Error(
|
|
14259
|
+
`Invalid persisted extraction array variant at "${label}"[${index}]: item is required.`
|
|
14260
|
+
);
|
|
14261
|
+
}
|
|
14262
|
+
return {
|
|
14263
|
+
itemParentPath: sanitizeElementPath(
|
|
14264
|
+
variant.itemParentPath
|
|
14265
|
+
),
|
|
14266
|
+
item: normalizePersistedExtractNode(
|
|
14267
|
+
variant.item,
|
|
14268
|
+
`${label}[${index}]`
|
|
14269
|
+
)
|
|
14270
|
+
};
|
|
14271
|
+
});
|
|
14272
|
+
return {
|
|
14273
|
+
$array: {
|
|
14274
|
+
variants
|
|
14275
|
+
}
|
|
14276
|
+
};
|
|
14277
|
+
}
|
|
14278
|
+
const objectNode = {};
|
|
14279
|
+
for (const [key, value] of Object.entries(record)) {
|
|
14280
|
+
const normalizedKey = String(key || "").trim();
|
|
14281
|
+
if (!normalizedKey) continue;
|
|
14282
|
+
if (normalizedKey.startsWith("$")) {
|
|
14283
|
+
throw new Error(
|
|
14284
|
+
`Invalid persisted extraction node at "${label}": unexpected reserved key "${normalizedKey}".`
|
|
14285
|
+
);
|
|
14286
|
+
}
|
|
14287
|
+
objectNode[normalizedKey] = normalizePersistedExtractNode(
|
|
14288
|
+
value,
|
|
14289
|
+
`${label}.${normalizedKey}`
|
|
14290
|
+
);
|
|
14291
|
+
}
|
|
14292
|
+
return objectNode;
|
|
14293
|
+
}
|
|
14294
|
+
function computeArrayRowCoverage(value, flat) {
|
|
14295
|
+
if (isPrimitiveLike(value)) {
|
|
14296
|
+
return value == null ? 0 : 1;
|
|
14297
|
+
}
|
|
14298
|
+
const flatCoverage = Object.values(flat).reduce((sum, current) => {
|
|
14299
|
+
return current == null ? sum : sum + 1;
|
|
14300
|
+
}, 0);
|
|
14301
|
+
if (flatCoverage > 0) return flatCoverage;
|
|
14302
|
+
return countNonNullLeaves(value);
|
|
14303
|
+
}
|
|
14304
|
+
function countNonNullLeaves(value) {
|
|
14305
|
+
if (value == null) return 0;
|
|
14306
|
+
if (Array.isArray(value)) {
|
|
14307
|
+
return value.reduce(
|
|
14308
|
+
(sum, current) => sum + countNonNullLeaves(current),
|
|
14309
|
+
0
|
|
14310
|
+
);
|
|
14311
|
+
}
|
|
14312
|
+
if (typeof value === "object") {
|
|
14313
|
+
return Object.values(value).reduce(
|
|
14314
|
+
(sum, current) => sum + countNonNullLeaves(current),
|
|
14315
|
+
0
|
|
14316
|
+
);
|
|
14317
|
+
}
|
|
14318
|
+
return 1;
|
|
14319
|
+
}
|
|
14320
|
+
function isPrimitiveLike(value) {
|
|
14321
|
+
return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
14322
|
+
}
|
|
14323
|
+
function assertValidExtractSchemaRoot(schema) {
|
|
14324
|
+
if (!schema || typeof schema !== "object") {
|
|
14325
|
+
throw new Error(
|
|
14326
|
+
"Invalid extraction schema: expected a JSON object at the top level."
|
|
14327
|
+
);
|
|
14328
|
+
}
|
|
14329
|
+
if (Array.isArray(schema)) {
|
|
14330
|
+
throw new Error(
|
|
14331
|
+
'Invalid extraction schema: top-level arrays are not supported. Wrap array fields in an object (for example {"items":[...]}).'
|
|
14332
|
+
);
|
|
14333
|
+
}
|
|
14334
|
+
}
|
|
14335
|
+
function parseAiExtractResponse(response) {
|
|
14336
|
+
if (typeof response === "string") {
|
|
14337
|
+
const trimmed = stripCodeFence2(response);
|
|
14338
|
+
try {
|
|
14339
|
+
return JSON.parse(trimmed);
|
|
14340
|
+
} catch {
|
|
14341
|
+
const preview = summarizeForError(trimmed);
|
|
14342
|
+
throw new Error(
|
|
14343
|
+
`LLM extraction returned a non-JSON response.${preview ? ` Preview: "${preview}"` : ""}`
|
|
14344
|
+
);
|
|
14345
|
+
}
|
|
14346
|
+
}
|
|
14347
|
+
if (response && typeof response === "object") {
|
|
14348
|
+
const candidate = response;
|
|
14349
|
+
if (candidate.fields || candidate.paths || candidate.data !== void 0) {
|
|
14350
|
+
return candidate;
|
|
14351
|
+
}
|
|
14352
|
+
}
|
|
14353
|
+
return {
|
|
14354
|
+
data: response
|
|
14355
|
+
};
|
|
14356
|
+
}
|
|
14357
|
+
function stripCodeFence2(input) {
|
|
14358
|
+
const trimmed = input.trim();
|
|
14359
|
+
if (!trimmed.startsWith("```")) return trimmed;
|
|
14360
|
+
const firstBreak = trimmed.indexOf("\n");
|
|
14361
|
+
if (firstBreak === -1) {
|
|
14362
|
+
return trimmed.replace(/```/g, "").trim();
|
|
14363
|
+
}
|
|
14364
|
+
const withoutHeader = trimmed.slice(firstBreak + 1);
|
|
14365
|
+
const lastFence = withoutHeader.lastIndexOf("```");
|
|
14366
|
+
if (lastFence === -1) return withoutHeader.trim();
|
|
14367
|
+
return withoutHeader.slice(0, lastFence).trim();
|
|
14368
|
+
}
|
|
14369
|
+
function summarizeForError(input, maxLength = 180) {
|
|
14370
|
+
const compact = input.replace(/\s+/g, " ").trim();
|
|
14371
|
+
if (!compact) return "";
|
|
14372
|
+
if (compact.length <= maxLength) return compact;
|
|
14373
|
+
return `${compact.slice(0, maxLength)}...`;
|
|
14374
|
+
}
|
|
14375
|
+
function getScrollDelta2(options) {
|
|
14376
|
+
const amount = typeof options.amount === "number" ? options.amount : 600;
|
|
14377
|
+
const absoluteAmount = Math.abs(amount);
|
|
14378
|
+
switch (options.direction) {
|
|
14379
|
+
case "up":
|
|
14380
|
+
return { x: 0, y: -absoluteAmount };
|
|
14381
|
+
case "left":
|
|
14382
|
+
return { x: -absoluteAmount, y: 0 };
|
|
14383
|
+
case "right":
|
|
14384
|
+
return { x: absoluteAmount, y: 0 };
|
|
14385
|
+
case "down":
|
|
14386
|
+
default:
|
|
14387
|
+
return { x: 0, y: absoluteAmount };
|
|
14388
|
+
}
|
|
14389
|
+
}
|
|
14390
|
+
function isInternalOrBlankPageUrl(url) {
|
|
14391
|
+
if (!url) return true;
|
|
14392
|
+
if (url === "about:blank") return true;
|
|
14393
|
+
return url.startsWith("chrome://") || url.startsWith("devtools://") || url.startsWith("edge://");
|
|
14394
|
+
}
|
|
14395
|
+
function normalizeCloudBrowserProfilePreference(value, source) {
|
|
14396
|
+
if (!value) {
|
|
14397
|
+
return void 0;
|
|
14398
|
+
}
|
|
14399
|
+
const profileId = typeof value.profileId === "string" ? value.profileId.trim() : "";
|
|
14400
|
+
if (!profileId) {
|
|
14401
|
+
throw new Error(
|
|
14402
|
+
`Invalid cloud browser profile in ${source}: profileId must be a non-empty string.`
|
|
14403
|
+
);
|
|
14404
|
+
}
|
|
14405
|
+
if (value.reuseIfActive !== void 0 && typeof value.reuseIfActive !== "boolean") {
|
|
14406
|
+
throw new Error(
|
|
14407
|
+
`Invalid cloud browser profile in ${source}: reuseIfActive must be a boolean.`
|
|
14408
|
+
);
|
|
14409
|
+
}
|
|
14410
|
+
return {
|
|
14411
|
+
profileId,
|
|
14412
|
+
reuseIfActive: value.reuseIfActive
|
|
14413
|
+
};
|
|
14414
|
+
}
|
|
14415
|
+
function buildLocalRunId(namespace) {
|
|
14416
|
+
const normalized = namespace.trim() || "default";
|
|
14417
|
+
return `${normalized}-${Date.now().toString(36)}-${(0, import_crypto.randomUUID)().slice(0, 8)}`;
|
|
14418
|
+
}
|
|
14419
|
+
|
|
14420
|
+
// src/browser/chromium-profile.ts
|
|
14421
|
+
var import_node_util = require("util");
|
|
14422
|
+
var import_node_child_process3 = require("child_process");
|
|
14423
|
+
var import_node_crypto = require("crypto");
|
|
14424
|
+
var import_promises3 = require("fs/promises");
|
|
14425
|
+
var import_node_fs2 = require("fs");
|
|
14426
|
+
var import_node_path2 = require("path");
|
|
14427
|
+
var import_node_os2 = require("os");
|
|
14428
|
+
var import_playwright3 = require("playwright");
|
|
14429
|
+
|
|
14430
|
+
// src/auth/keychain-store.ts
|
|
14431
|
+
var import_node_child_process2 = require("child_process");
|
|
14432
|
+
function commandExists(command) {
|
|
14433
|
+
const result = (0, import_node_child_process2.spawnSync)(command, ["--help"], {
|
|
14434
|
+
encoding: "utf8",
|
|
14435
|
+
stdio: "ignore"
|
|
14436
|
+
});
|
|
14437
|
+
return result.error == null;
|
|
14438
|
+
}
|
|
14439
|
+
function commandFailed(result) {
|
|
14440
|
+
return typeof result.status === "number" && result.status !== 0;
|
|
14441
|
+
}
|
|
14442
|
+
function sanitizeCommandArgs(command, args) {
|
|
14443
|
+
if (command !== "security") {
|
|
14444
|
+
return args;
|
|
14445
|
+
}
|
|
14446
|
+
const sanitized = [];
|
|
14447
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
14448
|
+
const value = args[index];
|
|
14449
|
+
sanitized.push(value);
|
|
14450
|
+
if (value === "-w" && index + 1 < args.length) {
|
|
14451
|
+
sanitized.push("[REDACTED]");
|
|
14452
|
+
index += 1;
|
|
14453
|
+
}
|
|
14454
|
+
}
|
|
14455
|
+
return sanitized;
|
|
14456
|
+
}
|
|
14457
|
+
function buildCommandError(command, args, result) {
|
|
14458
|
+
const stderr = typeof result.stderr === "string" && result.stderr.trim() ? result.stderr.trim() : `Command "${command}" failed with status ${String(result.status)}.`;
|
|
14459
|
+
const sanitizedArgs = sanitizeCommandArgs(command, args);
|
|
14460
|
+
return new Error(
|
|
14461
|
+
[
|
|
14462
|
+
`Unable to persist credential via ${command}.`,
|
|
14463
|
+
`${command} ${sanitizedArgs.join(" ")}`,
|
|
14464
|
+
stderr
|
|
14465
|
+
].join(" ")
|
|
14466
|
+
);
|
|
14467
|
+
}
|
|
14468
|
+
function createMacosSecurityStore() {
|
|
14469
|
+
return {
|
|
14470
|
+
backend: "macos-security",
|
|
14471
|
+
get(service, account) {
|
|
14472
|
+
const result = (0, import_node_child_process2.spawnSync)(
|
|
14473
|
+
"security",
|
|
14474
|
+
["find-generic-password", "-s", service, "-a", account, "-w"],
|
|
14475
|
+
{ encoding: "utf8" }
|
|
14476
|
+
);
|
|
14477
|
+
if (commandFailed(result)) {
|
|
14478
|
+
return null;
|
|
14479
|
+
}
|
|
14480
|
+
const secret = result.stdout.trim();
|
|
14481
|
+
return secret.length ? secret : null;
|
|
14482
|
+
},
|
|
14483
|
+
set(service, account, secret) {
|
|
14484
|
+
const args = [
|
|
14485
|
+
"add-generic-password",
|
|
14486
|
+
"-U",
|
|
14487
|
+
"-s",
|
|
14488
|
+
service,
|
|
14489
|
+
"-a",
|
|
14490
|
+
account,
|
|
14491
|
+
"-w",
|
|
14492
|
+
secret
|
|
14493
|
+
];
|
|
14494
|
+
const result = (0, import_node_child_process2.spawnSync)("security", args, { encoding: "utf8" });
|
|
14495
|
+
if (commandFailed(result)) {
|
|
14496
|
+
throw buildCommandError("security", args, result);
|
|
14497
|
+
}
|
|
14498
|
+
},
|
|
14499
|
+
delete(service, account) {
|
|
14500
|
+
const args = ["delete-generic-password", "-s", service, "-a", account];
|
|
14501
|
+
const result = (0, import_node_child_process2.spawnSync)("security", args, { encoding: "utf8" });
|
|
14502
|
+
if (commandFailed(result)) {
|
|
14503
|
+
return;
|
|
14504
|
+
}
|
|
14505
|
+
}
|
|
14506
|
+
};
|
|
14507
|
+
}
|
|
14508
|
+
function createLinuxSecretToolStore() {
|
|
14509
|
+
return {
|
|
14510
|
+
backend: "linux-secret-tool",
|
|
14511
|
+
get(service, account) {
|
|
14512
|
+
const result = (0, import_node_child_process2.spawnSync)(
|
|
14513
|
+
"secret-tool",
|
|
14514
|
+
["lookup", "service", service, "account", account],
|
|
14515
|
+
{
|
|
14516
|
+
encoding: "utf8"
|
|
14517
|
+
}
|
|
14518
|
+
);
|
|
14519
|
+
if (commandFailed(result)) {
|
|
14520
|
+
return null;
|
|
14521
|
+
}
|
|
14522
|
+
const secret = result.stdout.trim();
|
|
14523
|
+
return secret.length ? secret : null;
|
|
14524
|
+
},
|
|
14525
|
+
set(service, account, secret) {
|
|
14526
|
+
const args = [
|
|
14527
|
+
"store",
|
|
14528
|
+
"--label",
|
|
14529
|
+
"Opensteer CLI",
|
|
14530
|
+
"service",
|
|
14531
|
+
service,
|
|
14532
|
+
"account",
|
|
14533
|
+
account
|
|
14534
|
+
];
|
|
14535
|
+
const result = (0, import_node_child_process2.spawnSync)("secret-tool", args, {
|
|
14536
|
+
encoding: "utf8",
|
|
14537
|
+
input: secret
|
|
14131
14538
|
});
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
const pageUrl = this.page.url();
|
|
14135
|
-
for (const key of currentUrlKeys) {
|
|
14136
|
-
result[key] = pageUrl;
|
|
14539
|
+
if (commandFailed(result)) {
|
|
14540
|
+
throw buildCommandError("secret-tool", args, result);
|
|
14137
14541
|
}
|
|
14542
|
+
},
|
|
14543
|
+
delete(service, account) {
|
|
14544
|
+
const args = ["clear", "service", service, "account", account];
|
|
14545
|
+
(0, import_node_child_process2.spawnSync)("secret-tool", args, {
|
|
14546
|
+
encoding: "utf8"
|
|
14547
|
+
});
|
|
14138
14548
|
}
|
|
14139
|
-
|
|
14140
|
-
|
|
14141
|
-
|
|
14142
|
-
|
|
14143
|
-
if (
|
|
14144
|
-
|
|
14145
|
-
Object.assign(result, pathValues);
|
|
14549
|
+
};
|
|
14550
|
+
}
|
|
14551
|
+
function createKeychainStore() {
|
|
14552
|
+
if (process.platform === "darwin") {
|
|
14553
|
+
if (!commandExists("security")) {
|
|
14554
|
+
return null;
|
|
14146
14555
|
}
|
|
14147
|
-
return
|
|
14556
|
+
return createMacosSecurityStore();
|
|
14148
14557
|
}
|
|
14149
|
-
|
|
14150
|
-
|
|
14151
|
-
|
|
14152
|
-
if ("source" in field) {
|
|
14153
|
-
resolved.push({
|
|
14154
|
-
key: field.key,
|
|
14155
|
-
source: "current_url"
|
|
14156
|
-
});
|
|
14157
|
-
continue;
|
|
14158
|
-
}
|
|
14159
|
-
if ("path" in field) {
|
|
14160
|
-
resolved.push({
|
|
14161
|
-
key: field.key,
|
|
14162
|
-
path: this.normalizePath(field.path),
|
|
14163
|
-
attribute: field.attribute
|
|
14164
|
-
});
|
|
14165
|
-
continue;
|
|
14166
|
-
}
|
|
14167
|
-
const path7 = await this.buildPathFromElement(field.counter);
|
|
14168
|
-
if (!path7) {
|
|
14169
|
-
throw new Error(
|
|
14170
|
-
`Unable to persist extraction schema field "${field.key}": counter ${field.counter} could not be converted into a stable element path.`
|
|
14171
|
-
);
|
|
14172
|
-
}
|
|
14173
|
-
resolved.push({
|
|
14174
|
-
key: field.key,
|
|
14175
|
-
path: path7,
|
|
14176
|
-
attribute: field.attribute
|
|
14177
|
-
});
|
|
14558
|
+
if (process.platform === "linux") {
|
|
14559
|
+
if (!commandExists("secret-tool")) {
|
|
14560
|
+
return null;
|
|
14178
14561
|
}
|
|
14179
|
-
return
|
|
14180
|
-
}
|
|
14181
|
-
buildActionResult(storageKey, method, persisted, selectorUsed) {
|
|
14182
|
-
return {
|
|
14183
|
-
method,
|
|
14184
|
-
namespace: this.storage.getNamespace(),
|
|
14185
|
-
persisted,
|
|
14186
|
-
pathFile: storageKey && persisted ? this.storage.getSelectorFileName(storageKey) : null,
|
|
14187
|
-
selectorUsed: selectorUsed || null
|
|
14188
|
-
};
|
|
14189
|
-
}
|
|
14190
|
-
resolveStorageKey(description) {
|
|
14191
|
-
if (!description) return null;
|
|
14192
|
-
return (0, import_crypto.createHash)("sha256").update(description).digest("hex").slice(0, 16);
|
|
14193
|
-
}
|
|
14194
|
-
normalizePath(path7) {
|
|
14195
|
-
return sanitizeElementPath(path7);
|
|
14562
|
+
return createLinuxSecretToolStore();
|
|
14196
14563
|
}
|
|
14197
|
-
|
|
14198
|
-
function formatActionFailureMessage(action, description, cause) {
|
|
14199
|
-
const label = description ? `"${description}"` : "unnamed target";
|
|
14200
|
-
return `${action} action failed for ${label}: ${cause}`;
|
|
14201
|
-
}
|
|
14202
|
-
function cloneContextHops(context) {
|
|
14203
|
-
return JSON.parse(JSON.stringify(context || []));
|
|
14564
|
+
return null;
|
|
14204
14565
|
}
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14566
|
+
|
|
14567
|
+
// src/browser/chromium-profile.ts
|
|
14568
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process3.execFile);
|
|
14569
|
+
var CHROMIUM_EPOCH_MICROS = 11644473600000000n;
|
|
14570
|
+
var AES_BLOCK_BYTES = 16;
|
|
14571
|
+
var MAC_KEY_ITERATIONS = 1003;
|
|
14572
|
+
var LINUX_KEY_ITERATIONS = 1;
|
|
14573
|
+
var KEY_LENGTH = 16;
|
|
14574
|
+
var KEY_SALT = "saltysalt";
|
|
14575
|
+
var DEFAULT_CHROMIUM_BRAND = {
|
|
14576
|
+
macService: "Chrome Safe Storage",
|
|
14577
|
+
macAccount: "Chrome",
|
|
14578
|
+
linuxApplications: ["chrome", "google-chrome"]
|
|
14579
|
+
};
|
|
14580
|
+
var CHROMIUM_BRANDS = [
|
|
14581
|
+
{
|
|
14582
|
+
match: ["bravesoftware", "brave-browser"],
|
|
14583
|
+
brand: {
|
|
14584
|
+
macService: "Brave Safe Storage",
|
|
14585
|
+
macAccount: "Brave",
|
|
14586
|
+
linuxApplications: ["brave-browser", "brave"]
|
|
14211
14587
|
}
|
|
14212
|
-
}
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
return size;
|
|
14588
|
+
},
|
|
14589
|
+
{
|
|
14590
|
+
match: ["microsoft", "edge"],
|
|
14591
|
+
brand: {
|
|
14592
|
+
macService: "Microsoft Edge Safe Storage",
|
|
14593
|
+
macAccount: "Microsoft Edge",
|
|
14594
|
+
linuxApplications: ["microsoft-edge"],
|
|
14595
|
+
playwrightChannel: "msedge"
|
|
14221
14596
|
}
|
|
14222
|
-
}
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14597
|
+
},
|
|
14598
|
+
{
|
|
14599
|
+
match: ["google", "chrome beta"],
|
|
14600
|
+
brand: {
|
|
14601
|
+
macService: "Chrome Beta Safe Storage",
|
|
14602
|
+
macAccount: "Chrome Beta",
|
|
14603
|
+
linuxApplications: ["chrome-beta"],
|
|
14604
|
+
playwrightChannel: "chrome-beta"
|
|
14226
14605
|
}
|
|
14227
|
-
}
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
|
|
14234
|
-
|
|
14235
|
-
return false;
|
|
14606
|
+
},
|
|
14607
|
+
{
|
|
14608
|
+
match: ["google", "chrome"],
|
|
14609
|
+
brand: {
|
|
14610
|
+
macService: "Chrome Safe Storage",
|
|
14611
|
+
macAccount: "Chrome",
|
|
14612
|
+
linuxApplications: ["chrome", "google-chrome"],
|
|
14613
|
+
playwrightChannel: "chrome"
|
|
14236
14614
|
}
|
|
14237
|
-
|
|
14238
|
-
|
|
14615
|
+
},
|
|
14616
|
+
{
|
|
14617
|
+
match: ["chromium"],
|
|
14618
|
+
brand: {
|
|
14619
|
+
macService: "Chromium Safe Storage",
|
|
14620
|
+
macAccount: "Chromium",
|
|
14621
|
+
linuxApplications: ["chromium"]
|
|
14239
14622
|
}
|
|
14240
14623
|
}
|
|
14241
|
-
|
|
14624
|
+
];
|
|
14625
|
+
function directoryExists(filePath) {
|
|
14626
|
+
try {
|
|
14627
|
+
return (0, import_node_fs2.statSync)(filePath).isDirectory();
|
|
14628
|
+
} catch {
|
|
14629
|
+
return false;
|
|
14630
|
+
}
|
|
14242
14631
|
}
|
|
14243
|
-
function
|
|
14244
|
-
|
|
14245
|
-
|
|
14246
|
-
|
|
14632
|
+
function fileExists(filePath) {
|
|
14633
|
+
try {
|
|
14634
|
+
return (0, import_node_fs2.statSync)(filePath).isFile();
|
|
14635
|
+
} catch {
|
|
14636
|
+
return false;
|
|
14247
14637
|
}
|
|
14248
|
-
const field = value;
|
|
14249
|
-
return {
|
|
14250
|
-
element: field.element,
|
|
14251
|
-
selector: field.selector,
|
|
14252
|
-
attribute: field.attribute,
|
|
14253
|
-
source: normalizeExtractSource(field.source)
|
|
14254
|
-
};
|
|
14255
14638
|
}
|
|
14256
|
-
function
|
|
14257
|
-
|
|
14258
|
-
const
|
|
14259
|
-
|
|
14260
|
-
|
|
14639
|
+
function resolveCookieDbPath(profileDir) {
|
|
14640
|
+
const candidates = [(0, import_node_path2.join)(profileDir, "Network", "Cookies"), (0, import_node_path2.join)(profileDir, "Cookies")];
|
|
14641
|
+
for (const candidate of candidates) {
|
|
14642
|
+
if (fileExists(candidate)) {
|
|
14643
|
+
return candidate;
|
|
14644
|
+
}
|
|
14645
|
+
}
|
|
14646
|
+
return null;
|
|
14261
14647
|
}
|
|
14262
|
-
function
|
|
14263
|
-
const
|
|
14264
|
-
|
|
14648
|
+
async function selectProfileDirFromUserDataDir(userDataDir) {
|
|
14649
|
+
const entries = await (0, import_promises3.readdir)(userDataDir, {
|
|
14650
|
+
withFileTypes: true
|
|
14651
|
+
}).catch(() => []);
|
|
14652
|
+
const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path2.join)(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
|
|
14653
|
+
return candidates;
|
|
14265
14654
|
}
|
|
14266
|
-
function
|
|
14267
|
-
const
|
|
14268
|
-
|
|
14269
|
-
|
|
14655
|
+
async function resolveChromiumProfileLocation(inputPath) {
|
|
14656
|
+
const expandedPath = expandHome(inputPath.trim());
|
|
14657
|
+
if (!expandedPath) {
|
|
14658
|
+
throw new Error("Profile path cannot be empty.");
|
|
14659
|
+
}
|
|
14660
|
+
if (fileExists(expandedPath) && (0, import_node_path2.basename)(expandedPath) === "Cookies") {
|
|
14661
|
+
const directParent = (0, import_node_path2.dirname)(expandedPath);
|
|
14662
|
+
const profileDir = (0, import_node_path2.basename)(directParent) === "Network" ? (0, import_node_path2.dirname)(directParent) : directParent;
|
|
14663
|
+
const userDataDir = (0, import_node_path2.dirname)(profileDir);
|
|
14664
|
+
return {
|
|
14665
|
+
userDataDir,
|
|
14666
|
+
profileDir,
|
|
14667
|
+
profileDirectory: (0, import_node_path2.basename)(profileDir),
|
|
14668
|
+
cookieDbPath: expandedPath,
|
|
14669
|
+
localStatePath: fileExists((0, import_node_path2.join)(userDataDir, "Local State")) ? (0, import_node_path2.join)(userDataDir, "Local State") : null
|
|
14670
|
+
};
|
|
14671
|
+
}
|
|
14672
|
+
if (fileExists(expandedPath)) {
|
|
14673
|
+
throw new Error(
|
|
14674
|
+
`Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
|
|
14675
|
+
);
|
|
14676
|
+
}
|
|
14677
|
+
if (!directoryExists(expandedPath)) {
|
|
14678
|
+
throw new Error(
|
|
14679
|
+
`Could not find a Chromium profile at "${inputPath}".`
|
|
14680
|
+
);
|
|
14681
|
+
}
|
|
14682
|
+
const directCookieDb = resolveCookieDbPath(expandedPath);
|
|
14683
|
+
if (directCookieDb) {
|
|
14684
|
+
const userDataDir = (0, import_node_path2.dirname)(expandedPath);
|
|
14685
|
+
return {
|
|
14686
|
+
userDataDir,
|
|
14687
|
+
profileDir: expandedPath,
|
|
14688
|
+
profileDirectory: (0, import_node_path2.basename)(expandedPath),
|
|
14689
|
+
cookieDbPath: directCookieDb,
|
|
14690
|
+
localStatePath: fileExists((0, import_node_path2.join)(userDataDir, "Local State")) ? (0, import_node_path2.join)(userDataDir, "Local State") : null
|
|
14691
|
+
};
|
|
14270
14692
|
}
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
function toPathFields(fields) {
|
|
14274
|
-
return fields.filter(isPersistablePathField).map((field) => ({
|
|
14275
|
-
key: field.key,
|
|
14276
|
-
path: field.path,
|
|
14277
|
-
attribute: field.attribute
|
|
14278
|
-
}));
|
|
14279
|
-
}
|
|
14280
|
-
function normalizePersistedExtractPayload(raw) {
|
|
14281
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
14693
|
+
const localStatePath = (0, import_node_path2.join)(expandedPath, "Local State");
|
|
14694
|
+
if (!fileExists(localStatePath)) {
|
|
14282
14695
|
throw new Error(
|
|
14283
|
-
"
|
|
14696
|
+
`Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
|
|
14284
14697
|
);
|
|
14285
14698
|
}
|
|
14286
|
-
const
|
|
14287
|
-
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
if (normalizedKey.startsWith("$")) {
|
|
14291
|
-
throw new Error(
|
|
14292
|
-
`Invalid persisted extraction payload key "${normalizedKey}": root keys must not start with "$".`
|
|
14293
|
-
);
|
|
14294
|
-
}
|
|
14295
|
-
root[normalizedKey] = normalizePersistedExtractNode(
|
|
14296
|
-
value,
|
|
14297
|
-
normalizedKey
|
|
14699
|
+
const profileDirs = await selectProfileDirFromUserDataDir(expandedPath);
|
|
14700
|
+
if (profileDirs.length === 0) {
|
|
14701
|
+
throw new Error(
|
|
14702
|
+
`No Chromium profile with a Cookies database was found under "${inputPath}".`
|
|
14298
14703
|
);
|
|
14299
14704
|
}
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
function normalizePersistedExtractNode(raw, label) {
|
|
14303
|
-
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
14705
|
+
if (profileDirs.length > 1) {
|
|
14706
|
+
const candidates = profileDirs.map((entry) => (0, import_node_path2.basename)(entry)).join(", ");
|
|
14304
14707
|
throw new Error(
|
|
14305
|
-
`
|
|
14708
|
+
`"${inputPath}" contains multiple Chromium profiles (${candidates}). Pass a specific profile directory such as "${profileDirs[0]}".`
|
|
14306
14709
|
);
|
|
14307
14710
|
}
|
|
14308
|
-
const
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
}
|
|
14315
|
-
return {
|
|
14316
|
-
$path: sanitizeElementPath(record.$path),
|
|
14317
|
-
attribute: typeof record.attribute === "string" ? record.attribute : void 0
|
|
14318
|
-
};
|
|
14711
|
+
const selectedProfileDir = profileDirs[0];
|
|
14712
|
+
const cookieDbPath = resolveCookieDbPath(selectedProfileDir);
|
|
14713
|
+
if (!cookieDbPath) {
|
|
14714
|
+
throw new Error(
|
|
14715
|
+
`No Chromium Cookies database was found for "${inputPath}".`
|
|
14716
|
+
);
|
|
14319
14717
|
}
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
|
|
14718
|
+
return {
|
|
14719
|
+
userDataDir: expandedPath,
|
|
14720
|
+
profileDir: selectedProfileDir,
|
|
14721
|
+
profileDirectory: (0, import_node_path2.basename)(selectedProfileDir),
|
|
14722
|
+
cookieDbPath,
|
|
14723
|
+
localStatePath
|
|
14724
|
+
};
|
|
14725
|
+
}
|
|
14726
|
+
function detectChromiumBrand(location) {
|
|
14727
|
+
const normalizedPath = location.userDataDir.toLowerCase();
|
|
14728
|
+
for (const candidate of CHROMIUM_BRANDS) {
|
|
14729
|
+
if (candidate.match.every((fragment) => normalizedPath.includes(fragment))) {
|
|
14730
|
+
return candidate.brand;
|
|
14326
14731
|
}
|
|
14327
|
-
return {
|
|
14328
|
-
$source: source
|
|
14329
|
-
};
|
|
14330
14732
|
}
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
);
|
|
14342
|
-
}
|
|
14343
|
-
if (!Array.isArray(arrayRecord.variants) || !arrayRecord.variants.length) {
|
|
14344
|
-
throw new Error(
|
|
14345
|
-
`Invalid persisted extraction array node at "${label}": variants must be a non-empty array.`
|
|
14346
|
-
);
|
|
14733
|
+
return DEFAULT_CHROMIUM_BRAND;
|
|
14734
|
+
}
|
|
14735
|
+
async function createSqliteSnapshot(dbPath) {
|
|
14736
|
+
const snapshotDir = await (0, import_promises3.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-cookie-db-"));
|
|
14737
|
+
const snapshotPath = (0, import_node_path2.join)(snapshotDir, "Cookies");
|
|
14738
|
+
await (0, import_promises3.copyFile)(dbPath, snapshotPath);
|
|
14739
|
+
for (const suffix of ["-wal", "-shm", "-journal"]) {
|
|
14740
|
+
const source = `${dbPath}${suffix}`;
|
|
14741
|
+
if (!(0, import_node_fs2.existsSync)(source)) {
|
|
14742
|
+
continue;
|
|
14347
14743
|
}
|
|
14348
|
-
|
|
14349
|
-
if (!variantRaw || typeof variantRaw !== "object" || Array.isArray(variantRaw)) {
|
|
14350
|
-
throw new Error(
|
|
14351
|
-
`Invalid persisted extraction array variant at "${label}"[${index}]: expected an object.`
|
|
14352
|
-
);
|
|
14353
|
-
}
|
|
14354
|
-
const variant = variantRaw;
|
|
14355
|
-
if (!variant.itemParentPath || typeof variant.itemParentPath !== "object") {
|
|
14356
|
-
throw new Error(
|
|
14357
|
-
`Invalid persisted extraction array variant at "${label}"[${index}]: itemParentPath is required.`
|
|
14358
|
-
);
|
|
14359
|
-
}
|
|
14360
|
-
if (!variant.item || typeof variant.item !== "object" || Array.isArray(variant.item)) {
|
|
14361
|
-
throw new Error(
|
|
14362
|
-
`Invalid persisted extraction array variant at "${label}"[${index}]: item is required.`
|
|
14363
|
-
);
|
|
14364
|
-
}
|
|
14365
|
-
return {
|
|
14366
|
-
itemParentPath: sanitizeElementPath(
|
|
14367
|
-
variant.itemParentPath
|
|
14368
|
-
),
|
|
14369
|
-
item: normalizePersistedExtractNode(
|
|
14370
|
-
variant.item,
|
|
14371
|
-
`${label}[${index}]`
|
|
14372
|
-
)
|
|
14373
|
-
};
|
|
14374
|
-
});
|
|
14375
|
-
return {
|
|
14376
|
-
$array: {
|
|
14377
|
-
variants
|
|
14378
|
-
}
|
|
14379
|
-
};
|
|
14744
|
+
await (0, import_promises3.copyFile)(source, `${snapshotPath}${suffix}`);
|
|
14380
14745
|
}
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
if (normalizedKey.startsWith("$")) {
|
|
14386
|
-
throw new Error(
|
|
14387
|
-
`Invalid persisted extraction node at "${label}": unexpected reserved key "${normalizedKey}".`
|
|
14388
|
-
);
|
|
14746
|
+
return {
|
|
14747
|
+
snapshotPath,
|
|
14748
|
+
cleanup: async () => {
|
|
14749
|
+
await (0, import_promises3.rm)(snapshotDir, { recursive: true, force: true });
|
|
14389
14750
|
}
|
|
14390
|
-
|
|
14391
|
-
|
|
14392
|
-
|
|
14393
|
-
|
|
14751
|
+
};
|
|
14752
|
+
}
|
|
14753
|
+
async function querySqliteJson(dbPath, query) {
|
|
14754
|
+
const result = await execFileAsync("sqlite3", ["-json", dbPath, query], {
|
|
14755
|
+
encoding: "utf8",
|
|
14756
|
+
maxBuffer: 64 * 1024 * 1024
|
|
14757
|
+
});
|
|
14758
|
+
const stdout = result.stdout;
|
|
14759
|
+
const trimmed = stdout.trim();
|
|
14760
|
+
if (!trimmed) {
|
|
14761
|
+
return [];
|
|
14394
14762
|
}
|
|
14395
|
-
return
|
|
14763
|
+
return JSON.parse(trimmed);
|
|
14396
14764
|
}
|
|
14397
|
-
function
|
|
14398
|
-
if (
|
|
14399
|
-
return
|
|
14765
|
+
function convertChromiumTimestampToUnixSeconds(value) {
|
|
14766
|
+
if (!value || value === "0") {
|
|
14767
|
+
return -1;
|
|
14400
14768
|
}
|
|
14401
|
-
const
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
|
|
14405
|
-
return
|
|
14769
|
+
const micros = BigInt(value);
|
|
14770
|
+
if (micros <= CHROMIUM_EPOCH_MICROS) {
|
|
14771
|
+
return -1;
|
|
14772
|
+
}
|
|
14773
|
+
return Number((micros - CHROMIUM_EPOCH_MICROS) / 1000000n);
|
|
14406
14774
|
}
|
|
14407
|
-
function
|
|
14408
|
-
if (value
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14775
|
+
function mapChromiumSameSite(value) {
|
|
14776
|
+
if (value === 2) {
|
|
14777
|
+
return "Strict";
|
|
14778
|
+
}
|
|
14779
|
+
if (value === 0) {
|
|
14780
|
+
return "None";
|
|
14781
|
+
}
|
|
14782
|
+
return "Lax";
|
|
14783
|
+
}
|
|
14784
|
+
function stripChromiumPadding(buffer) {
|
|
14785
|
+
const paddingLength = buffer[buffer.length - 1];
|
|
14786
|
+
if (paddingLength <= 0 || paddingLength > AES_BLOCK_BYTES) {
|
|
14787
|
+
return buffer;
|
|
14788
|
+
}
|
|
14789
|
+
return buffer.subarray(0, buffer.length - paddingLength);
|
|
14790
|
+
}
|
|
14791
|
+
function stripDomainHashPrefix(buffer, hostKey) {
|
|
14792
|
+
if (buffer.length < 32) {
|
|
14793
|
+
return buffer;
|
|
14794
|
+
}
|
|
14795
|
+
const domainHash = (0, import_node_crypto.createHash)("sha256").update(hostKey, "utf8").digest();
|
|
14796
|
+
if (buffer.subarray(0, 32).equals(domainHash)) {
|
|
14797
|
+
return buffer.subarray(32);
|
|
14798
|
+
}
|
|
14799
|
+
return buffer;
|
|
14800
|
+
}
|
|
14801
|
+
function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
|
|
14802
|
+
const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
|
|
14803
|
+
const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
|
|
14804
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-128-cbc", key, iv);
|
|
14805
|
+
const plaintext = Buffer.concat([
|
|
14806
|
+
decipher.update(ciphertext),
|
|
14807
|
+
decipher.final()
|
|
14808
|
+
]);
|
|
14809
|
+
return stripDomainHashPrefix(stripChromiumPadding(plaintext), hostKey).toString(
|
|
14810
|
+
"utf8"
|
|
14811
|
+
);
|
|
14812
|
+
}
|
|
14813
|
+
function decryptChromiumAes256GcmValue(encryptedValue, key) {
|
|
14814
|
+
const nonce = encryptedValue.subarray(3, 15);
|
|
14815
|
+
const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
|
|
14816
|
+
const authTag = encryptedValue.subarray(encryptedValue.length - 16);
|
|
14817
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", key, nonce);
|
|
14818
|
+
decipher.setAuthTag(authTag);
|
|
14819
|
+
return Buffer.concat([
|
|
14820
|
+
decipher.update(ciphertext),
|
|
14821
|
+
decipher.final()
|
|
14822
|
+
]).toString("utf8");
|
|
14823
|
+
}
|
|
14824
|
+
async function dpapiUnprotect(buffer) {
|
|
14825
|
+
const script = [
|
|
14826
|
+
`$inputBytes = [Convert]::FromBase64String('${buffer.toString("base64")}')`,
|
|
14827
|
+
"$plainBytes = [System.Security.Cryptography.ProtectedData]::Unprotect(",
|
|
14828
|
+
" $inputBytes,",
|
|
14829
|
+
" $null,",
|
|
14830
|
+
" [System.Security.Cryptography.DataProtectionScope]::CurrentUser",
|
|
14831
|
+
")",
|
|
14832
|
+
"[Convert]::ToBase64String($plainBytes)"
|
|
14833
|
+
].join("\n");
|
|
14834
|
+
const { stdout } = await execFileAsync(
|
|
14835
|
+
"powershell.exe",
|
|
14836
|
+
["-NoProfile", "-NonInteractive", "-Command", script],
|
|
14837
|
+
{
|
|
14838
|
+
encoding: "utf8",
|
|
14839
|
+
maxBuffer: 8 * 1024 * 1024
|
|
14840
|
+
}
|
|
14841
|
+
);
|
|
14842
|
+
return Buffer.from(stdout.trim(), "base64");
|
|
14843
|
+
}
|
|
14844
|
+
async function buildChromiumDecryptor(location) {
|
|
14845
|
+
if (process.platform === "darwin") {
|
|
14846
|
+
const brand = detectChromiumBrand(location);
|
|
14847
|
+
const keychainStore = createKeychainStore();
|
|
14848
|
+
const password = keychainStore?.get(brand.macService, brand.macAccount) ?? null;
|
|
14849
|
+
if (!password) {
|
|
14850
|
+
throw new Error(
|
|
14851
|
+
`Unable to read ${brand.macService} from macOS Keychain.`
|
|
14852
|
+
);
|
|
14853
|
+
}
|
|
14854
|
+
const key = (0, import_node_crypto.pbkdf2Sync)(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
|
|
14855
|
+
return async (row) => decryptChromiumAes128CbcValue(
|
|
14856
|
+
Buffer.from(row.encrypted_value || "", "hex"),
|
|
14857
|
+
key,
|
|
14858
|
+
row.host_key
|
|
14413
14859
|
);
|
|
14414
14860
|
}
|
|
14415
|
-
if (
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14861
|
+
if (process.platform === "linux") {
|
|
14862
|
+
const brand = detectChromiumBrand(location);
|
|
14863
|
+
const keychainStore = createKeychainStore();
|
|
14864
|
+
const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
|
|
14865
|
+
const key = (0, import_node_crypto.pbkdf2Sync)(
|
|
14866
|
+
password || "peanuts",
|
|
14867
|
+
KEY_SALT,
|
|
14868
|
+
LINUX_KEY_ITERATIONS,
|
|
14869
|
+
KEY_LENGTH,
|
|
14870
|
+
"sha1"
|
|
14419
14871
|
);
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
14425
|
-
}
|
|
14426
|
-
function assertValidExtractSchemaRoot(schema) {
|
|
14427
|
-
if (!schema || typeof schema !== "object") {
|
|
14428
|
-
throw new Error(
|
|
14429
|
-
"Invalid extraction schema: expected a JSON object at the top level."
|
|
14872
|
+
return async (row) => decryptChromiumAes128CbcValue(
|
|
14873
|
+
Buffer.from(row.encrypted_value || "", "hex"),
|
|
14874
|
+
key,
|
|
14875
|
+
row.host_key
|
|
14430
14876
|
);
|
|
14431
14877
|
}
|
|
14432
|
-
if (
|
|
14433
|
-
|
|
14434
|
-
|
|
14878
|
+
if (process.platform === "win32") {
|
|
14879
|
+
if (!location.localStatePath) {
|
|
14880
|
+
throw new Error(
|
|
14881
|
+
`Unable to locate Chromium Local State for profile: ${location.profileDir}`
|
|
14882
|
+
);
|
|
14883
|
+
}
|
|
14884
|
+
const localState = JSON.parse(
|
|
14885
|
+
await (0, import_promises3.readFile)(location.localStatePath, "utf8")
|
|
14435
14886
|
);
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
function parseAiExtractResponse(response) {
|
|
14439
|
-
if (typeof response === "string") {
|
|
14440
|
-
const trimmed = stripCodeFence2(response);
|
|
14441
|
-
try {
|
|
14442
|
-
return JSON.parse(trimmed);
|
|
14443
|
-
} catch {
|
|
14444
|
-
const preview = summarizeForError(trimmed);
|
|
14887
|
+
const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
|
|
14888
|
+
if (!encryptedKeyBase64) {
|
|
14445
14889
|
throw new Error(
|
|
14446
|
-
`
|
|
14890
|
+
`Local State did not include os_crypt.encrypted_key for ${location.userDataDir}`
|
|
14447
14891
|
);
|
|
14448
14892
|
}
|
|
14893
|
+
const encryptedKey = Buffer.from(encryptedKeyBase64, "base64");
|
|
14894
|
+
const masterKey = await dpapiUnprotect(encryptedKey.subarray(5));
|
|
14895
|
+
return async (row) => {
|
|
14896
|
+
const encryptedValue = Buffer.from(row.encrypted_value || "", "hex");
|
|
14897
|
+
if (encryptedValue.length > 4 && encryptedValue[0] === 1 && encryptedValue[1] === 0 && encryptedValue[2] === 0 && encryptedValue[3] === 0) {
|
|
14898
|
+
const decrypted = await dpapiUnprotect(encryptedValue);
|
|
14899
|
+
return decrypted.toString("utf8");
|
|
14900
|
+
}
|
|
14901
|
+
return decryptChromiumAes256GcmValue(encryptedValue, masterKey);
|
|
14902
|
+
};
|
|
14449
14903
|
}
|
|
14450
|
-
|
|
14451
|
-
|
|
14452
|
-
|
|
14453
|
-
|
|
14454
|
-
|
|
14904
|
+
throw new Error(
|
|
14905
|
+
`Local Chromium cookie sync is not supported on ${process.platform}.`
|
|
14906
|
+
);
|
|
14907
|
+
}
|
|
14908
|
+
function buildPlaywrightCookie(row, value) {
|
|
14909
|
+
if (!row.name.trim()) {
|
|
14910
|
+
return null;
|
|
14911
|
+
}
|
|
14912
|
+
if (!row.host_key.trim()) {
|
|
14913
|
+
return null;
|
|
14914
|
+
}
|
|
14915
|
+
const expires = row.has_expires === 1 ? convertChromiumTimestampToUnixSeconds(row.expires_utc) : -1;
|
|
14916
|
+
if (expires !== -1 && expires <= Math.floor(Date.now() / 1e3)) {
|
|
14917
|
+
return null;
|
|
14455
14918
|
}
|
|
14456
14919
|
return {
|
|
14457
|
-
|
|
14920
|
+
name: row.name,
|
|
14921
|
+
value,
|
|
14922
|
+
domain: row.host_key,
|
|
14923
|
+
path: row.path || "/",
|
|
14924
|
+
expires,
|
|
14925
|
+
httpOnly: row.is_httponly === 1,
|
|
14926
|
+
secure: row.is_secure === 1,
|
|
14927
|
+
sameSite: mapChromiumSameSite(row.samesite)
|
|
14458
14928
|
};
|
|
14459
14929
|
}
|
|
14460
|
-
function
|
|
14461
|
-
const
|
|
14462
|
-
|
|
14463
|
-
|
|
14464
|
-
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14468
|
-
const lastFence = withoutHeader.lastIndexOf("```");
|
|
14469
|
-
if (lastFence === -1) return withoutHeader.trim();
|
|
14470
|
-
return withoutHeader.slice(0, lastFence).trim();
|
|
14471
|
-
}
|
|
14472
|
-
function summarizeForError(input, maxLength = 180) {
|
|
14473
|
-
const compact = input.replace(/\s+/g, " ").trim();
|
|
14474
|
-
if (!compact) return "";
|
|
14475
|
-
if (compact.length <= maxLength) return compact;
|
|
14476
|
-
return `${compact.slice(0, maxLength)}...`;
|
|
14477
|
-
}
|
|
14478
|
-
function getScrollDelta2(options) {
|
|
14479
|
-
const amount = typeof options.amount === "number" ? options.amount : 600;
|
|
14480
|
-
const absoluteAmount = Math.abs(amount);
|
|
14481
|
-
switch (options.direction) {
|
|
14482
|
-
case "up":
|
|
14483
|
-
return { x: 0, y: -absoluteAmount };
|
|
14484
|
-
case "left":
|
|
14485
|
-
return { x: -absoluteAmount, y: 0 };
|
|
14486
|
-
case "right":
|
|
14487
|
-
return { x: absoluteAmount, y: 0 };
|
|
14488
|
-
case "down":
|
|
14489
|
-
default:
|
|
14490
|
-
return { x: 0, y: absoluteAmount };
|
|
14930
|
+
async function loadCookiesFromLocalProfileDir(inputPath, options = {}) {
|
|
14931
|
+
const location = await resolveChromiumProfileLocation(inputPath);
|
|
14932
|
+
try {
|
|
14933
|
+
return await loadCookiesFromSqlite(location);
|
|
14934
|
+
} catch (error) {
|
|
14935
|
+
if (!isMissingSqliteBinary(error)) {
|
|
14936
|
+
throw error;
|
|
14937
|
+
}
|
|
14491
14938
|
}
|
|
14939
|
+
return await loadCookiesFromBrowserSnapshot(location, options);
|
|
14492
14940
|
}
|
|
14493
|
-
function
|
|
14494
|
-
|
|
14495
|
-
|
|
14496
|
-
|
|
14497
|
-
|
|
14498
|
-
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
|
|
14505
|
-
|
|
14941
|
+
async function loadCookiesFromSqlite(location) {
|
|
14942
|
+
const snapshot = await createSqliteSnapshot(location.cookieDbPath);
|
|
14943
|
+
try {
|
|
14944
|
+
const rows = await querySqliteJson(
|
|
14945
|
+
snapshot.snapshotPath,
|
|
14946
|
+
[
|
|
14947
|
+
"SELECT",
|
|
14948
|
+
" host_key,",
|
|
14949
|
+
" name,",
|
|
14950
|
+
" value,",
|
|
14951
|
+
" hex(encrypted_value) AS encrypted_value,",
|
|
14952
|
+
" path,",
|
|
14953
|
+
" CAST(expires_utc AS TEXT) AS expires_utc,",
|
|
14954
|
+
" is_secure,",
|
|
14955
|
+
" is_httponly,",
|
|
14956
|
+
" has_expires,",
|
|
14957
|
+
" samesite",
|
|
14958
|
+
"FROM cookies"
|
|
14959
|
+
].join(" ")
|
|
14506
14960
|
);
|
|
14961
|
+
const decryptValue = await buildChromiumDecryptor(location);
|
|
14962
|
+
const cookies = [];
|
|
14963
|
+
for (const row of rows) {
|
|
14964
|
+
let value = row.value || "";
|
|
14965
|
+
if (!value && row.encrypted_value) {
|
|
14966
|
+
value = await decryptValue(row);
|
|
14967
|
+
}
|
|
14968
|
+
const cookie = buildPlaywrightCookie(row, value);
|
|
14969
|
+
if (cookie) {
|
|
14970
|
+
cookies.push(cookie);
|
|
14971
|
+
}
|
|
14972
|
+
}
|
|
14973
|
+
return cookies;
|
|
14974
|
+
} finally {
|
|
14975
|
+
await snapshot.cleanup();
|
|
14507
14976
|
}
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
|
|
14511
|
-
|
|
14977
|
+
}
|
|
14978
|
+
async function loadCookiesFromBrowserSnapshot(location, options) {
|
|
14979
|
+
const snapshotRootDir = await (0, import_promises3.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-profile-"));
|
|
14980
|
+
const snapshotProfileDir = (0, import_node_path2.join)(
|
|
14981
|
+
snapshotRootDir,
|
|
14982
|
+
(0, import_node_path2.basename)(location.profileDir)
|
|
14983
|
+
);
|
|
14984
|
+
let context = null;
|
|
14985
|
+
try {
|
|
14986
|
+
await (0, import_promises3.cp)(location.profileDir, snapshotProfileDir, {
|
|
14987
|
+
recursive: true
|
|
14988
|
+
});
|
|
14989
|
+
if (location.localStatePath) {
|
|
14990
|
+
await (0, import_promises3.copyFile)(location.localStatePath, (0, import_node_path2.join)(snapshotRootDir, "Local State"));
|
|
14991
|
+
}
|
|
14992
|
+
const brand = detectChromiumBrand(location);
|
|
14993
|
+
const args = [`--profile-directory=${(0, import_node_path2.basename)(snapshotProfileDir)}`];
|
|
14994
|
+
context = await import_playwright3.chromium.launchPersistentContext(snapshotRootDir, {
|
|
14995
|
+
channel: brand.playwrightChannel,
|
|
14996
|
+
headless: options.headless ?? true,
|
|
14997
|
+
timeout: options.timeout ?? 12e4,
|
|
14998
|
+
args
|
|
14999
|
+
});
|
|
15000
|
+
return await context.cookies();
|
|
15001
|
+
} finally {
|
|
15002
|
+
await context?.close().catch(() => void 0);
|
|
15003
|
+
await (0, import_promises3.rm)(snapshotRootDir, { recursive: true, force: true });
|
|
14512
15004
|
}
|
|
14513
|
-
return {
|
|
14514
|
-
profileId,
|
|
14515
|
-
reuseIfActive: value.reuseIfActive
|
|
14516
|
-
};
|
|
14517
15005
|
}
|
|
14518
|
-
function
|
|
14519
|
-
|
|
14520
|
-
|
|
15006
|
+
function isMissingSqliteBinary(error) {
|
|
15007
|
+
return Boolean(
|
|
15008
|
+
error && typeof error === "object" && "code" in error && error.code === "ENOENT"
|
|
15009
|
+
);
|
|
14521
15010
|
}
|
|
14522
15011
|
|
|
14523
15012
|
// src/cloud/browser-profile-client.ts
|
|
@@ -14691,59 +15180,21 @@ var import_open = __toESM(require("open"), 1);
|
|
|
14691
15180
|
|
|
14692
15181
|
// src/auth/credential-resolver.ts
|
|
14693
15182
|
function resolveCloudCredential(options) {
|
|
14694
|
-
const
|
|
14695
|
-
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
return {
|
|
14701
|
-
kind: "access-token",
|
|
14702
|
-
source: "flag",
|
|
14703
|
-
token: flagAccessToken,
|
|
14704
|
-
authScheme: "bearer"
|
|
14705
|
-
};
|
|
14706
|
-
}
|
|
14707
|
-
if (flagApiKey) {
|
|
14708
|
-
return {
|
|
14709
|
-
kind: "api-key",
|
|
14710
|
-
source: "flag",
|
|
14711
|
-
token: flagApiKey,
|
|
14712
|
-
authScheme: "api-key"
|
|
14713
|
-
};
|
|
15183
|
+
const flagCredential = selectCloudCredential({
|
|
15184
|
+
apiKey: options.apiKeyFlag,
|
|
15185
|
+
accessToken: options.accessTokenFlag
|
|
15186
|
+
});
|
|
15187
|
+
if (flagCredential) {
|
|
15188
|
+
return toResolvedCloudCredential("flag", flagCredential);
|
|
14714
15189
|
}
|
|
14715
15190
|
const envAuthScheme = parseEnvAuthScheme(options.env.OPENSTEER_AUTH_SCHEME);
|
|
14716
|
-
const
|
|
14717
|
-
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
if (envAccessToken) {
|
|
14724
|
-
return {
|
|
14725
|
-
kind: "access-token",
|
|
14726
|
-
source: "env",
|
|
14727
|
-
token: envAccessToken,
|
|
14728
|
-
authScheme: "bearer"
|
|
14729
|
-
};
|
|
14730
|
-
}
|
|
14731
|
-
if (envApiKey) {
|
|
14732
|
-
if (envAuthScheme === "bearer") {
|
|
14733
|
-
return {
|
|
14734
|
-
kind: "access-token",
|
|
14735
|
-
source: "env",
|
|
14736
|
-
token: envApiKey,
|
|
14737
|
-
authScheme: "bearer",
|
|
14738
|
-
compatibilityBearerApiKey: true
|
|
14739
|
-
};
|
|
14740
|
-
}
|
|
14741
|
-
return {
|
|
14742
|
-
kind: "api-key",
|
|
14743
|
-
source: "env",
|
|
14744
|
-
token: envApiKey,
|
|
14745
|
-
authScheme: envAuthScheme ?? "api-key"
|
|
14746
|
-
};
|
|
15191
|
+
const envCredential = selectCloudCredential({
|
|
15192
|
+
apiKey: options.env.OPENSTEER_API_KEY,
|
|
15193
|
+
accessToken: options.env.OPENSTEER_ACCESS_TOKEN,
|
|
15194
|
+
authScheme: envAuthScheme
|
|
15195
|
+
});
|
|
15196
|
+
if (envCredential) {
|
|
15197
|
+
return toResolvedCloudCredential("env", envCredential);
|
|
14747
15198
|
}
|
|
14748
15199
|
return null;
|
|
14749
15200
|
}
|
|
@@ -14773,12 +15224,29 @@ function normalizeToken(value) {
|
|
|
14773
15224
|
const normalized = value.trim();
|
|
14774
15225
|
return normalized.length ? normalized : void 0;
|
|
14775
15226
|
}
|
|
15227
|
+
function toResolvedCloudCredential(source, credential) {
|
|
15228
|
+
if (credential.compatibilityBearerApiKey) {
|
|
15229
|
+
return {
|
|
15230
|
+
kind: credential.kind,
|
|
15231
|
+
source,
|
|
15232
|
+
token: credential.token,
|
|
15233
|
+
authScheme: credential.authScheme,
|
|
15234
|
+
compatibilityBearerApiKey: true
|
|
15235
|
+
};
|
|
15236
|
+
}
|
|
15237
|
+
return {
|
|
15238
|
+
kind: credential.kind,
|
|
15239
|
+
source,
|
|
15240
|
+
token: credential.token,
|
|
15241
|
+
authScheme: credential.authScheme
|
|
15242
|
+
};
|
|
15243
|
+
}
|
|
14776
15244
|
|
|
14777
15245
|
// src/auth/machine-credential-store.ts
|
|
14778
15246
|
var import_node_crypto2 = require("crypto");
|
|
14779
|
-
var
|
|
14780
|
-
var
|
|
14781
|
-
var
|
|
15247
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
15248
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
15249
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
14782
15250
|
var METADATA_VERSION = 2;
|
|
14783
15251
|
var ACTIVE_TARGET_VERSION = 2;
|
|
14784
15252
|
var KEYCHAIN_SERVICE = "com.opensteer.cli.cloud";
|
|
@@ -14793,7 +15261,7 @@ var MachineCredentialStore = class {
|
|
|
14793
15261
|
const appName = options.appName || "opensteer";
|
|
14794
15262
|
const env = options.env ?? process.env;
|
|
14795
15263
|
const configDir = resolveConfigDir(appName, env);
|
|
14796
|
-
this.authDir =
|
|
15264
|
+
this.authDir = import_node_path3.default.join(configDir, "auth");
|
|
14797
15265
|
this.warn = options.warn ?? (() => void 0);
|
|
14798
15266
|
}
|
|
14799
15267
|
readCloudCredential(target) {
|
|
@@ -14926,15 +15394,15 @@ function resolveCredentialSlot(authDir, target) {
|
|
|
14926
15394
|
const storageKey = (0, import_node_crypto2.createHash)("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
|
|
14927
15395
|
return {
|
|
14928
15396
|
keychainAccount: `${KEYCHAIN_ACCOUNT_PREFIX}${storageKey}`,
|
|
14929
|
-
metadataPath:
|
|
14930
|
-
fallbackSecretPath:
|
|
15397
|
+
metadataPath: import_node_path3.default.join(authDir, `cli-login.${storageKey}.json`),
|
|
15398
|
+
fallbackSecretPath: import_node_path3.default.join(
|
|
14931
15399
|
authDir,
|
|
14932
15400
|
`cli-login.${storageKey}.secret.json`
|
|
14933
15401
|
)
|
|
14934
15402
|
};
|
|
14935
15403
|
}
|
|
14936
15404
|
function resolveActiveTargetPath(authDir) {
|
|
14937
|
-
return
|
|
15405
|
+
return import_node_path3.default.join(authDir, ACTIVE_TARGET_FILE_NAME);
|
|
14938
15406
|
}
|
|
14939
15407
|
function matchesCredentialTarget(value, target) {
|
|
14940
15408
|
return normalizeCredentialUrl(value.baseUrl) === normalizeCredentialUrl(target.baseUrl);
|
|
@@ -14953,36 +15421,36 @@ function normalizeCloudCredentialTarget(target) {
|
|
|
14953
15421
|
}
|
|
14954
15422
|
function resolveConfigDir(appName, env) {
|
|
14955
15423
|
if (process.platform === "win32") {
|
|
14956
|
-
const appData = env.APPDATA?.trim() ||
|
|
14957
|
-
return
|
|
15424
|
+
const appData = env.APPDATA?.trim() || import_node_path3.default.join(import_node_os3.default.homedir(), "AppData", "Roaming");
|
|
15425
|
+
return import_node_path3.default.join(appData, appName);
|
|
14958
15426
|
}
|
|
14959
15427
|
if (process.platform === "darwin") {
|
|
14960
|
-
return
|
|
14961
|
-
|
|
15428
|
+
return import_node_path3.default.join(
|
|
15429
|
+
import_node_os3.default.homedir(),
|
|
14962
15430
|
"Library",
|
|
14963
15431
|
"Application Support",
|
|
14964
15432
|
appName
|
|
14965
15433
|
);
|
|
14966
15434
|
}
|
|
14967
|
-
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() ||
|
|
14968
|
-
return
|
|
15435
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || import_node_path3.default.join(import_node_os3.default.homedir(), ".config");
|
|
15436
|
+
return import_node_path3.default.join(xdgConfigHome, appName);
|
|
14969
15437
|
}
|
|
14970
15438
|
function ensureDirectory(directoryPath) {
|
|
14971
|
-
|
|
15439
|
+
import_node_fs3.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
|
|
14972
15440
|
}
|
|
14973
15441
|
function removeFileIfExists(filePath) {
|
|
14974
15442
|
try {
|
|
14975
|
-
|
|
15443
|
+
import_node_fs3.default.rmSync(filePath, { force: true });
|
|
14976
15444
|
} catch {
|
|
14977
15445
|
return;
|
|
14978
15446
|
}
|
|
14979
15447
|
}
|
|
14980
15448
|
function readMetadata(filePath) {
|
|
14981
|
-
if (!
|
|
15449
|
+
if (!import_node_fs3.default.existsSync(filePath)) {
|
|
14982
15450
|
return null;
|
|
14983
15451
|
}
|
|
14984
15452
|
try {
|
|
14985
|
-
const raw =
|
|
15453
|
+
const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
|
|
14986
15454
|
const parsed = JSON.parse(raw);
|
|
14987
15455
|
if (parsed.version !== METADATA_VERSION) return null;
|
|
14988
15456
|
if (parsed.secretBackend !== "keychain" && parsed.secretBackend !== "file") {
|
|
@@ -15009,11 +15477,11 @@ function readMetadata(filePath) {
|
|
|
15009
15477
|
}
|
|
15010
15478
|
}
|
|
15011
15479
|
function readActiveCloudTargetMetadata(filePath) {
|
|
15012
|
-
if (!
|
|
15480
|
+
if (!import_node_fs3.default.existsSync(filePath)) {
|
|
15013
15481
|
return null;
|
|
15014
15482
|
}
|
|
15015
15483
|
try {
|
|
15016
|
-
const raw =
|
|
15484
|
+
const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
|
|
15017
15485
|
const parsed = JSON.parse(raw);
|
|
15018
15486
|
if (parsed.version !== ACTIVE_TARGET_VERSION) {
|
|
15019
15487
|
return null;
|
|
@@ -15043,22 +15511,22 @@ function parseSecretPayload(raw) {
|
|
|
15043
15511
|
}
|
|
15044
15512
|
}
|
|
15045
15513
|
function readSecretFile(filePath) {
|
|
15046
|
-
if (!
|
|
15514
|
+
if (!import_node_fs3.default.existsSync(filePath)) {
|
|
15047
15515
|
return null;
|
|
15048
15516
|
}
|
|
15049
15517
|
try {
|
|
15050
|
-
return parseSecretPayload(
|
|
15518
|
+
return parseSecretPayload(import_node_fs3.default.readFileSync(filePath, "utf8"));
|
|
15051
15519
|
} catch {
|
|
15052
15520
|
return null;
|
|
15053
15521
|
}
|
|
15054
15522
|
}
|
|
15055
15523
|
function writeJsonFile(filePath, value, options = {}) {
|
|
15056
|
-
|
|
15524
|
+
import_node_fs3.default.writeFileSync(filePath, JSON.stringify(value, null, 2), {
|
|
15057
15525
|
encoding: "utf8",
|
|
15058
15526
|
mode: options.mode ?? 384
|
|
15059
15527
|
});
|
|
15060
15528
|
if (typeof options.mode === "number") {
|
|
15061
|
-
|
|
15529
|
+
import_node_fs3.default.chmodSync(filePath, options.mode);
|
|
15062
15530
|
}
|
|
15063
15531
|
}
|
|
15064
15532
|
|
|
@@ -15392,7 +15860,7 @@ async function ensureCloudCredentialsForCommand(options) {
|
|
|
15392
15860
|
const writeProgress = options.writeProgress ?? options.writeStdout ?? ((message) => process.stdout.write(message));
|
|
15393
15861
|
const writeStderr = options.writeStderr ?? ((message) => process.stderr.write(message));
|
|
15394
15862
|
const fetchFn = options.fetchFn ?? fetch;
|
|
15395
|
-
const
|
|
15863
|
+
const sleep7 = options.sleep ?? (async (ms) => {
|
|
15396
15864
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
15397
15865
|
});
|
|
15398
15866
|
const now = options.now ?? Date.now;
|
|
@@ -15436,7 +15904,7 @@ async function ensureCloudCredentialsForCommand(options) {
|
|
|
15436
15904
|
fetchFn,
|
|
15437
15905
|
writeProgress,
|
|
15438
15906
|
openExternalUrl,
|
|
15439
|
-
sleep:
|
|
15907
|
+
sleep: sleep7,
|
|
15440
15908
|
now,
|
|
15441
15909
|
openBrowser: true
|
|
15442
15910
|
});
|
|
@@ -15848,7 +16316,7 @@ function createDefaultDeps() {
|
|
|
15848
16316
|
loadLocalProfileCookies: (profileDir, options) => loadCookiesFromLocalProfileDir(profileDir, options),
|
|
15849
16317
|
isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
15850
16318
|
confirm: async (message) => {
|
|
15851
|
-
const rl = (0,
|
|
16319
|
+
const rl = (0, import_promises4.createInterface)({
|
|
15852
16320
|
input: process.stdin,
|
|
15853
16321
|
output: process.stderr
|
|
15854
16322
|
});
|
|
@@ -16012,7 +16480,7 @@ async function resolveTargetProfileId(args, deps, client) {
|
|
|
16012
16480
|
"Sync target is required in non-interactive mode. Use --to-profile-id <id> or --name <name>."
|
|
16013
16481
|
);
|
|
16014
16482
|
}
|
|
16015
|
-
const defaultName = `Synced ${
|
|
16483
|
+
const defaultName = `Synced ${import_node_path4.default.basename(args.fromProfileDir)}`;
|
|
16016
16484
|
const shouldCreate = await deps.confirm(
|
|
16017
16485
|
`No destination profile provided. Create a new cloud profile named "${defaultName}"?`
|
|
16018
16486
|
);
|