@hraness/peopleblade 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -18
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/dist/cli.ts +19 -9
- package/dist/peopleblade.js +832 -432
- package/package.json +1 -1
package/dist/peopleblade.js
CHANGED
|
@@ -4312,20 +4312,21 @@ var peoplebladeCapabilities = Object.freeze({
|
|
|
4312
4312
|
sources: [
|
|
4313
4313
|
{ id: "apple-contacts", command: "contacts sync", status: "supported", transport: "local-database", requirement: "macOS Contacts access" },
|
|
4314
4314
|
{ id: "imessage", command: "imessage sync", status: "supported", transport: "local-metadata", requirement: "macOS Messages access; no message bodies" },
|
|
4315
|
-
{ id: "google-contacts", command: "google sync", status: "supported", transport: "
|
|
4316
|
-
{ id: "beeper", command: "beeper sync", status: "supported", transport: "
|
|
4317
|
-
{ id: "whatsapp", command: "whatsapp sync", status: "supported", transport: "
|
|
4318
|
-
{ id: "linkedin", command: "linkedin import", status: "supported", transport: "official-archive", requirement: "Owner-selected official ZIP;
|
|
4315
|
+
{ id: "google-contacts", command: "google sync", status: "supported", transport: "ghostget", requirement: "Reviewed Gmail/People contacts.list and contact scopes" },
|
|
4316
|
+
{ id: "beeper", command: "beeper sync", status: "supported", transport: "ghostget", requirement: "Ghostget 0.17.3, Beeper adapter 2.4.0, CLI 0.6.2; body-free relationships require macOS arm64" },
|
|
4317
|
+
{ id: "whatsapp", command: "whatsapp sync", status: "supported", transport: "ghostget", requirement: "Reviewed linked-device contacts.list v2" },
|
|
4318
|
+
{ id: "linkedin", command: "linkedin import", status: "supported", transport: "official-archive", requirement: "Owner-selected official ZIP; connection lists are not collected from a signed-in browser" },
|
|
4319
4319
|
{ id: "instagram", command: "instagram import", status: "supported", transport: "official-archive", requirement: "Owner-selected JSON ZIP and exact owner username" },
|
|
4320
4320
|
{ id: "x-archive", command: "x import", status: "bounded", transport: "official-archive", requirement: "Owner-selected ZIP; unresolved IDs remain inert" },
|
|
4321
|
-
{ id: "x-live", command: "x mutuals sync", status: "blocked", transport: "unavailable", requirement: "A reviewed bounded
|
|
4321
|
+
{ id: "x-live", command: "x mutuals sync", status: "blocked", transport: "unavailable", requirement: "A reviewed bounded Ghostget mutuals capability is not yet available" },
|
|
4322
4322
|
{ id: "telegram", command: "telegram status", status: "planned", transport: "unavailable", requirement: "First real export and importer validation remain pending" }
|
|
4323
4323
|
],
|
|
4324
4324
|
enrichment: [
|
|
4325
4325
|
{ id: "local-anchors", priority: 1, status: "supported", commands: ["identity audit", "identity suggest", "identity decide"], paid: false, boundary: "Exact source evidence first; joins require reversible evidence-bound decisions" },
|
|
4326
|
-
{ id: "
|
|
4327
|
-
{ id: "
|
|
4328
|
-
{ id: "cloud-
|
|
4326
|
+
{ id: "linkedin-contact-info", priority: 2, status: "supported", commands: ["linkedin contact-info --person-id ID"], paid: false, boundary: "Reviewed Ghostget linkedin-web contacts.read for one exact first-degree connection already imported from the official export; signed-in browser profile, one bounded read, no connect, message, or feed access" },
|
|
4327
|
+
{ id: "manual-public-research", priority: 3, status: "supported", commands: ["research prepare", "research apply"], paid: false, boundary: "Agent or human supplies cited public evidence; PeopleBlade performs no provider calls" },
|
|
4328
|
+
{ id: "cloud-professional-research", priority: 4, status: "optional", commands: ["cloud enrich --person-id ID", "cloud enrich --confirm TOKEN"], paid: true, boundary: "Exa exact profiles or bounded search, then cited model extraction; device-bound preview approval required" },
|
|
4329
|
+
{ id: "cloud-email-fallback", priority: 5, status: "optional", commands: ["cloud enrich --person-id ID"], paid: true, boundary: "Hunter is a separately configured narrow fallback, not a substitute for identity evidence" }
|
|
4329
4330
|
],
|
|
4330
4331
|
interfaces: {
|
|
4331
4332
|
workspace: { command: "ui [--port N]", availability: "included-in-this-build", binding: "127.0.0.1", defaultPort: "random", authentication: "per-process capability file", remoteAssets: false, noteUpload: false },
|
|
@@ -12341,7 +12342,7 @@ function importContactSourceSnapshot(database, value) {
|
|
|
12341
12342
|
}
|
|
12342
12343
|
}
|
|
12343
12344
|
|
|
12344
|
-
// node_modules/@hraness/
|
|
12345
|
+
// node_modules/@hraness/ghostget/dist/index-26yq8q16.js
|
|
12345
12346
|
var PROVIDER_PLUGIN_ID_MAX_LENGTH = 63;
|
|
12346
12347
|
var PROVIDER_PLUGIN_OPERATION_NAME_MAX_LENGTH = 163;
|
|
12347
12348
|
var strictKebabPattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u;
|
|
@@ -12360,7 +12361,7 @@ function isProviderPluginOperationName(value) {
|
|
|
12360
12361
|
return segments.length >= 2 && segments.length <= 4 && segments.every((segment) => segment.length <= 40 && strictKebabSegmentPattern.test(segment));
|
|
12361
12362
|
}
|
|
12362
12363
|
|
|
12363
|
-
// node_modules/@hraness/
|
|
12364
|
+
// node_modules/@hraness/ghostget/dist/index-gwk7rbyj.js
|
|
12364
12365
|
import { createHash as createHash4 } from "crypto";
|
|
12365
12366
|
function isRecord(value) {
|
|
12366
12367
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -12388,7 +12389,7 @@ function sha2563(value) {
|
|
|
12388
12389
|
return createHash4("sha256").update(value).digest("hex");
|
|
12389
12390
|
}
|
|
12390
12391
|
|
|
12391
|
-
// node_modules/@hraness/
|
|
12392
|
+
// node_modules/@hraness/ghostget/dist/client.js
|
|
12392
12393
|
import { spawn, spawnSync } from "child_process";
|
|
12393
12394
|
import { existsSync as existsSync3 } from "fs";
|
|
12394
12395
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -12485,7 +12486,7 @@ function publicWebSessionAuthority(request) {
|
|
|
12485
12486
|
function expectedRequestAuthId(request, authKind) {
|
|
12486
12487
|
if (authKind === PUBLIC_WEB_SESSION_AUTHORITY_KIND) {
|
|
12487
12488
|
if (request.authId !== undefined) {
|
|
12488
|
-
throw new Error("
|
|
12489
|
+
throw new Error("Ghostget public invocation unexpectedly accepted an auth locator");
|
|
12489
12490
|
}
|
|
12490
12491
|
return publicWebSessionAuthority(request).id;
|
|
12491
12492
|
}
|
|
@@ -12535,14 +12536,14 @@ var clientReadFailureRetryDisposition = Object.freeze({
|
|
|
12535
12536
|
"cleanup-required": "do-not-retry"
|
|
12536
12537
|
});
|
|
12537
12538
|
function parseClientReadFailure(value) {
|
|
12538
|
-
const failure = record(value, "
|
|
12539
|
-
assertExactKeys(failure, ["category", "retryDisposition"], [], "
|
|
12539
|
+
const failure = record(value, "Ghostget read failure");
|
|
12540
|
+
assertExactKeys(failure, ["category", "retryDisposition"], [], "Ghostget read failure");
|
|
12540
12541
|
const category = failure.category;
|
|
12541
12542
|
if (typeof category !== "string" || !Object.hasOwn(clientReadFailureRetryDisposition, category))
|
|
12542
|
-
throw new Error("
|
|
12543
|
+
throw new Error("Ghostget read failure category is malformed");
|
|
12543
12544
|
const retryDisposition = clientReadFailureRetryDisposition[category];
|
|
12544
12545
|
if (failure.retryDisposition !== retryDisposition) {
|
|
12545
|
-
throw new Error("
|
|
12546
|
+
throw new Error("Ghostget read failure retry disposition is inconsistent");
|
|
12546
12547
|
}
|
|
12547
12548
|
return Object.freeze({ category, retryDisposition });
|
|
12548
12549
|
}
|
|
@@ -12693,16 +12694,16 @@ function cliSourcePath() {
|
|
|
12693
12694
|
const packagedSource = fileURLToPath2(new URL("../src/cli.ts", import.meta.url));
|
|
12694
12695
|
if (existsSync3(packagedSource))
|
|
12695
12696
|
return packagedSource;
|
|
12696
|
-
throw new Error("the installed
|
|
12697
|
+
throw new Error("the installed Ghostget CLI source is unavailable");
|
|
12697
12698
|
}
|
|
12698
12699
|
function requireBunRuntime() {
|
|
12699
12700
|
if (typeof process.versions.bun !== "string") {
|
|
12700
|
-
throw new Error("@hraness/
|
|
12701
|
+
throw new Error("@hraness/ghostget/client requires Bun to run the installed Ghostget CLI");
|
|
12701
12702
|
}
|
|
12702
12703
|
}
|
|
12703
12704
|
function environmentName(value) {
|
|
12704
12705
|
if (value.length < 1 || value.includes("=") || value.includes("\x00")) {
|
|
12705
|
-
throw new Error("
|
|
12706
|
+
throw new Error("Ghostget client environment name is malformed");
|
|
12706
12707
|
}
|
|
12707
12708
|
return value;
|
|
12708
12709
|
}
|
|
@@ -12723,26 +12724,26 @@ function snapshotChildEnvironment(overrides) {
|
|
|
12723
12724
|
if (overrides === undefined)
|
|
12724
12725
|
return Object.freeze(environment);
|
|
12725
12726
|
if (!isRecord2(overrides) || nodeTypes.isProxy(overrides) || Object.getPrototypeOf(overrides) !== Object.prototype && Object.getPrototypeOf(overrides) !== null) {
|
|
12726
|
-
throw new Error("
|
|
12727
|
+
throw new Error("Ghostget client environment must use a plain, non-proxy object");
|
|
12727
12728
|
}
|
|
12728
12729
|
const descriptors = Object.getOwnPropertyDescriptors(overrides);
|
|
12729
12730
|
const keys = Reflect.ownKeys(descriptors);
|
|
12730
12731
|
if (keys.some((key) => typeof key !== "string")) {
|
|
12731
|
-
throw new Error("
|
|
12732
|
+
throw new Error("Ghostget client environment has unsupported symbol fields");
|
|
12732
12733
|
}
|
|
12733
12734
|
for (const key of keys.sort((left, right) => left.localeCompare(right))) {
|
|
12734
12735
|
const descriptor = descriptors[key];
|
|
12735
12736
|
if (descriptor === undefined || !("value" in descriptor)) {
|
|
12736
|
-
throw new Error("
|
|
12737
|
+
throw new Error("Ghostget client environment must contain only data properties");
|
|
12737
12738
|
}
|
|
12738
12739
|
if (!descriptor.enumerable) {
|
|
12739
|
-
throw new Error("
|
|
12740
|
+
throw new Error("Ghostget client environment properties must be enumerable");
|
|
12740
12741
|
}
|
|
12741
12742
|
const name = environmentName(key);
|
|
12742
12743
|
if (descriptor.value === undefined) {
|
|
12743
12744
|
delete environment[name];
|
|
12744
12745
|
} else if (typeof descriptor.value !== "string" || descriptor.value.includes("\x00")) {
|
|
12745
|
-
throw new Error("
|
|
12746
|
+
throw new Error("Ghostget client environment value is malformed");
|
|
12746
12747
|
} else {
|
|
12747
12748
|
defineEnvironmentValue(environment, name, descriptor.value);
|
|
12748
12749
|
}
|
|
@@ -12762,20 +12763,20 @@ function isBrandedAbortSignal(value) {
|
|
|
12762
12763
|
}
|
|
12763
12764
|
function snapshotClientOptions(optionsValue, mode) {
|
|
12764
12765
|
if (!isRecord2(optionsValue) || nodeTypes.isProxy(optionsValue) || Object.getPrototypeOf(optionsValue) !== Object.prototype && Object.getPrototypeOf(optionsValue) !== null)
|
|
12765
|
-
throw new Error("
|
|
12766
|
+
throw new Error("Ghostget client options must use a plain, non-proxy object");
|
|
12766
12767
|
const descriptors = Object.getOwnPropertyDescriptors(optionsValue);
|
|
12767
12768
|
const keys = Reflect.ownKeys(descriptors);
|
|
12768
12769
|
if (keys.some((key) => typeof key !== "string")) {
|
|
12769
|
-
throw new Error("
|
|
12770
|
+
throw new Error("Ghostget client options have unsupported symbol fields");
|
|
12770
12771
|
}
|
|
12771
12772
|
const allowed = new Set(mode === "read" ? ["environment", "freshForMs", "now"] : mode === "revalidation" ? ["environment", "freshForMs", "now", "headed", "signal"] : mode === "invoke-async" ? ["environment", "headed", "signal"] : ["environment", "headed"]);
|
|
12772
12773
|
for (const key of keys) {
|
|
12773
12774
|
const descriptor = descriptors[key];
|
|
12774
12775
|
if (descriptor === undefined || !allowed.has(key)) {
|
|
12775
|
-
throw new Error("
|
|
12776
|
+
throw new Error("Ghostget client options contain an unsupported field");
|
|
12776
12777
|
}
|
|
12777
12778
|
if (!("value" in descriptor)) {
|
|
12778
|
-
throw new Error("
|
|
12779
|
+
throw new Error("Ghostget client options must contain only data properties");
|
|
12779
12780
|
}
|
|
12780
12781
|
}
|
|
12781
12782
|
const option = (key) => descriptors[key]?.value;
|
|
@@ -12783,27 +12784,27 @@ function snapshotClientOptions(optionsValue, mode) {
|
|
|
12783
12784
|
const nowValue = option("now");
|
|
12784
12785
|
const headedValue = option("headed");
|
|
12785
12786
|
const signalValue = option("signal");
|
|
12786
|
-
const freshForMs = freshForMsValue === undefined ? undefined : safeInteger(freshForMsValue, "
|
|
12787
|
+
const freshForMs = freshForMsValue === undefined ? undefined : safeInteger(freshForMsValue, "Ghostget client freshness window", 0, MAX_FRESH_FOR_MS);
|
|
12787
12788
|
let now;
|
|
12788
12789
|
if (nowValue !== undefined) {
|
|
12789
12790
|
if (nodeTypes.isProxy(nowValue) || !(nowValue instanceof Date) || Object.getPrototypeOf(nowValue) !== Date.prototype)
|
|
12790
|
-
throw new Error("
|
|
12791
|
+
throw new Error("Ghostget client observation time is invalid");
|
|
12791
12792
|
let time;
|
|
12792
12793
|
try {
|
|
12793
12794
|
time = Date.prototype.getTime.call(nowValue);
|
|
12794
12795
|
} catch {
|
|
12795
|
-
throw new Error("
|
|
12796
|
+
throw new Error("Ghostget client observation time is invalid");
|
|
12796
12797
|
}
|
|
12797
12798
|
if (!Number.isFinite(time)) {
|
|
12798
|
-
throw new Error("
|
|
12799
|
+
throw new Error("Ghostget client observation time is invalid");
|
|
12799
12800
|
}
|
|
12800
12801
|
now = new Date(time);
|
|
12801
12802
|
}
|
|
12802
12803
|
if (headedValue !== undefined && typeof headedValue !== "boolean") {
|
|
12803
|
-
throw new Error("
|
|
12804
|
+
throw new Error("Ghostget client headed option is malformed");
|
|
12804
12805
|
}
|
|
12805
12806
|
if (signalValue !== undefined && !isBrandedAbortSignal(signalValue))
|
|
12806
|
-
throw new Error("
|
|
12807
|
+
throw new Error("Ghostget client abort signal is malformed");
|
|
12807
12808
|
return Object.freeze({
|
|
12808
12809
|
cwd: process.cwd(),
|
|
12809
12810
|
environment: snapshotChildEnvironment(option("environment")),
|
|
@@ -12814,18 +12815,18 @@ function snapshotClientOptions(optionsValue, mode) {
|
|
|
12814
12815
|
});
|
|
12815
12816
|
}
|
|
12816
12817
|
function prepareRequest(requestValue) {
|
|
12817
|
-
const snapshot = snapshotJson(requestValue, "
|
|
12818
|
-
const request = record(snapshot, "
|
|
12819
|
-
assertExactKeys(request, ["adapterId", "operationId"], ["authId", "input"], "
|
|
12820
|
-
const adapterId = safeString(request.adapterId, "
|
|
12821
|
-
const operationId = providerOperationName(request.operationId, "
|
|
12822
|
-
const authId = Object.hasOwn(request, "authId") ? safeString(request.authId, "
|
|
12818
|
+
const snapshot = snapshotJson(requestValue, "Ghostget client request");
|
|
12819
|
+
const request = record(snapshot, "Ghostget client request");
|
|
12820
|
+
assertExactKeys(request, ["adapterId", "operationId"], ["authId", "input"], "Ghostget client request");
|
|
12821
|
+
const adapterId = safeString(request.adapterId, "Ghostget client adapter ID", 64);
|
|
12822
|
+
const operationId = providerOperationName(request.operationId, "Ghostget client operation ID");
|
|
12823
|
+
const authId = Object.hasOwn(request, "authId") ? safeString(request.authId, "Ghostget client auth ID", 64) : undefined;
|
|
12823
12824
|
const rawInput = Object.hasOwn(request, "input") ? request.input : {};
|
|
12824
12825
|
if (!isRecord2(rawInput))
|
|
12825
12826
|
throw new Error("input must be a JSON object");
|
|
12826
12827
|
const input = canonicalJson2(rawInput);
|
|
12827
12828
|
if (Buffer.byteLength(input, "utf8") > MAX_INPUT_BYTES) {
|
|
12828
|
-
throw new Error("
|
|
12829
|
+
throw new Error("Ghostget client input exceeds its byte bound");
|
|
12829
12830
|
}
|
|
12830
12831
|
return Object.freeze({
|
|
12831
12832
|
adapterId,
|
|
@@ -12896,7 +12897,7 @@ function runIdentityCommand(command, options, label, responseLabel = `${label} r
|
|
|
12896
12897
|
return parseOutput(stdout, responseLabel);
|
|
12897
12898
|
}
|
|
12898
12899
|
function runProjectionIdentityCommand(command, options) {
|
|
12899
|
-
return runIdentityCommand(command, options, "
|
|
12900
|
+
return runIdentityCommand(command, options, "Ghostget projection identity preflight", "Ghostget projection identity response");
|
|
12900
12901
|
}
|
|
12901
12902
|
function observeProjectionIdentity(request, options) {
|
|
12902
12903
|
const value = runProjectionIdentityCommand(preparedCommand(request, {
|
|
@@ -12905,7 +12906,7 @@ function observeProjectionIdentity(request, options) {
|
|
|
12905
12906
|
headed: false
|
|
12906
12907
|
}), options);
|
|
12907
12908
|
if (value.ok !== true || value.source !== "projection-identity")
|
|
12908
|
-
throw new Error("
|
|
12909
|
+
throw new Error("Ghostget projection identity response is malformed");
|
|
12909
12910
|
if (value.status === "ready") {
|
|
12910
12911
|
assertExactKeys(value, [
|
|
12911
12912
|
"ok",
|
|
@@ -12915,15 +12916,15 @@ function observeProjectionIdentity(request, options) {
|
|
|
12915
12916
|
"authHash",
|
|
12916
12917
|
"inputHash",
|
|
12917
12918
|
"projection"
|
|
12918
|
-
], [], "
|
|
12919
|
-
const projection = record(value.projection, "
|
|
12920
|
-
assertExactKeys(projection, ["key"], [], "
|
|
12919
|
+
], [], "Ghostget projection identity response");
|
|
12920
|
+
const projection = record(value.projection, "Ghostget projection identity");
|
|
12921
|
+
assertExactKeys(projection, ["key"], [], "Ghostget projection identity");
|
|
12921
12922
|
return Object.freeze({
|
|
12922
12923
|
status: "ready",
|
|
12923
|
-
key: digest(projection.key, "
|
|
12924
|
-
authIdentity: digest(value.authIdentity, "
|
|
12925
|
-
authHash: digest(value.authHash, "
|
|
12926
|
-
inputHash: digest(value.inputHash, "
|
|
12924
|
+
key: digest(projection.key, "Ghostget projection identity key"),
|
|
12925
|
+
authIdentity: digest(value.authIdentity, "Ghostget projection auth identity"),
|
|
12926
|
+
authHash: digest(value.authHash, "Ghostget projection auth hash"),
|
|
12927
|
+
inputHash: digest(value.inputHash, "Ghostget projection validated input hash")
|
|
12927
12928
|
});
|
|
12928
12929
|
}
|
|
12929
12930
|
if (value.status === "unbound") {
|
|
@@ -12934,15 +12935,15 @@ function observeProjectionIdentity(request, options) {
|
|
|
12934
12935
|
"authIdentity",
|
|
12935
12936
|
"authHash",
|
|
12936
12937
|
"inputHash"
|
|
12937
|
-
], [], "
|
|
12938
|
+
], [], "Ghostget projection identity response");
|
|
12938
12939
|
return Object.freeze({
|
|
12939
12940
|
status: "unbound",
|
|
12940
|
-
authIdentity: digest(value.authIdentity, "
|
|
12941
|
-
authHash: digest(value.authHash, "
|
|
12942
|
-
inputHash: digest(value.inputHash, "
|
|
12941
|
+
authIdentity: digest(value.authIdentity, "Ghostget projection auth identity"),
|
|
12942
|
+
authHash: digest(value.authHash, "Ghostget projection auth hash"),
|
|
12943
|
+
inputHash: digest(value.inputHash, "Ghostget projection validated input hash")
|
|
12943
12944
|
});
|
|
12944
12945
|
}
|
|
12945
|
-
throw new Error("
|
|
12946
|
+
throw new Error("Ghostget projection identity response is malformed");
|
|
12946
12947
|
}
|
|
12947
12948
|
function projectionIdentitiesMatch(before, after) {
|
|
12948
12949
|
return before.status === "ready" ? after.status === "ready" && after.key === before.key && after.authIdentity === before.authIdentity && after.authHash === before.authHash && after.inputHash === before.inputHash : after.status === "unbound" && after.authIdentity === before.authIdentity && after.authHash === before.authHash && after.inputHash === before.inputHash;
|
|
@@ -12962,43 +12963,43 @@ function parseExecutionPreview(value, request) {
|
|
|
12962
12963
|
"identityBinding",
|
|
12963
12964
|
"transport",
|
|
12964
12965
|
...portable ? ["portablePluginContract"] : []
|
|
12965
|
-
], [], "
|
|
12966
|
+
], [], "Ghostget execution identity preview");
|
|
12966
12967
|
if (value.ok !== true || value.status !== "preview" || value.requiresConfirmation !== false || value.risk !== "R1")
|
|
12967
|
-
throw new Error("
|
|
12968
|
-
const adapterValue = record(value.adapter, "
|
|
12969
|
-
const auth = record(value.auth, "
|
|
12970
|
-
const binding2 = record(value.identityBinding, "
|
|
12971
|
-
assertExactKeys(adapterValue, ["id", "version", "hash"], [], "
|
|
12972
|
-
assertExactKeys(auth, ["id", "kind", "realmFingerprint"], [], "
|
|
12973
|
-
assertExactKeys(binding2, ["status", "subject", "accountActor", "requestedActor"], [], "
|
|
12968
|
+
throw new Error("Ghostget execution identity preview is malformed");
|
|
12969
|
+
const adapterValue = record(value.adapter, "Ghostget execution identity preview adapter");
|
|
12970
|
+
const auth = record(value.auth, "Ghostget execution identity preview auth");
|
|
12971
|
+
const binding2 = record(value.identityBinding, "Ghostget execution identity preview binding");
|
|
12972
|
+
assertExactKeys(adapterValue, ["id", "version", "hash"], [], "Ghostget execution identity preview adapter");
|
|
12973
|
+
assertExactKeys(auth, ["id", "kind", "realmFingerprint"], [], "Ghostget execution identity preview auth");
|
|
12974
|
+
assertExactKeys(binding2, ["status", "subject", "accountActor", "requestedActor"], [], "Ghostget execution identity preview binding");
|
|
12974
12975
|
const adapter = Object.freeze({
|
|
12975
|
-
id: safeString(adapterValue.id, "
|
|
12976
|
-
version: safeString(adapterValue.version, "
|
|
12977
|
-
hash: digest(adapterValue.hash, "
|
|
12976
|
+
id: safeString(adapterValue.id, "Ghostget execution identity preview adapter ID", 64),
|
|
12977
|
+
version: safeString(adapterValue.version, "Ghostget execution identity preview adapter version", 64),
|
|
12978
|
+
hash: digest(adapterValue.hash, "Ghostget execution identity preview adapter hash")
|
|
12978
12979
|
});
|
|
12979
|
-
const operation = providerOperationName(value.operation, "
|
|
12980
|
+
const operation = providerOperationName(value.operation, "Ghostget execution identity preview operation");
|
|
12980
12981
|
if (adapter.id !== request.adapterId || operation !== request.operationId) {
|
|
12981
|
-
throw new Error("
|
|
12982
|
+
throw new Error("Ghostget execution identity preview route is malformed");
|
|
12982
12983
|
}
|
|
12983
|
-
const authKind = safeString(auth.kind, "
|
|
12984
|
-
if (safeString(auth.id, "
|
|
12985
|
-
throw new Error("
|
|
12986
|
-
const realmFingerprint = safeString(auth.realmFingerprint, "
|
|
12984
|
+
const authKind = safeString(auth.kind, "Ghostget execution identity preview auth kind", 64);
|
|
12985
|
+
if (safeString(auth.id, "Ghostget execution identity preview auth ID", 64) !== expectedRequestAuthId(request, authKind))
|
|
12986
|
+
throw new Error("Ghostget execution identity preview auth is malformed");
|
|
12987
|
+
const realmFingerprint = safeString(auth.realmFingerprint, "Ghostget execution identity preview auth fingerprint", 16);
|
|
12987
12988
|
if (!/^[a-f0-9]{16}$/u.test(realmFingerprint)) {
|
|
12988
|
-
throw new Error("
|
|
12989
|
+
throw new Error("Ghostget execution identity preview auth fingerprint is malformed");
|
|
12989
12990
|
}
|
|
12990
12991
|
if (authKind === PUBLIC_WEB_SESSION_AUTHORITY_KIND) {
|
|
12991
12992
|
const authority = publicWebSessionAuthority(request);
|
|
12992
12993
|
if (value.transport !== "web-session-api" || realmFingerprint !== sha2563(canonicalJson2(authority)).slice(0, 16) || binding2.status !== "public" || binding2.subject !== authority.subject || binding2.accountActor !== null || binding2.requestedActor !== null) {
|
|
12993
|
-
throw new Error("
|
|
12994
|
+
throw new Error("Ghostget execution identity preview public authority is malformed");
|
|
12994
12995
|
}
|
|
12995
12996
|
}
|
|
12996
|
-
safeString(value.sideEffect, "
|
|
12997
|
+
safeString(value.sideEffect, "Ghostget execution identity preview side effect", 64);
|
|
12997
12998
|
const transport = value.transport;
|
|
12998
12999
|
if (transport === "portable-provider-plugin") {
|
|
12999
13000
|
const portablePluginContract = parsePortableOperationIdentity(value.portablePluginContract);
|
|
13000
13001
|
if (portablePluginContract.adapterId !== adapter.id || portablePluginContract.operation !== operation)
|
|
13001
|
-
throw new Error("
|
|
13002
|
+
throw new Error("Ghostget execution identity preview route is malformed");
|
|
13002
13003
|
return Object.freeze({
|
|
13003
13004
|
adapter,
|
|
13004
13005
|
operation,
|
|
@@ -13007,18 +13008,18 @@ function parseExecutionPreview(value, request) {
|
|
|
13007
13008
|
});
|
|
13008
13009
|
}
|
|
13009
13010
|
if (transport !== "browser" && transport !== "provider-api" && transport !== "web-session-api" && transport !== "reviewed-template-api" && transport !== "local-cli")
|
|
13010
|
-
throw new Error("
|
|
13011
|
+
throw new Error("Ghostget execution identity preview transport is malformed");
|
|
13011
13012
|
return Object.freeze({ adapter, operation, transport });
|
|
13012
13013
|
}
|
|
13013
13014
|
function parseCatalogExecutionIdentity(value, request, preview) {
|
|
13014
|
-
assertExactKeys(value, ["ok", "adapters"], [], "
|
|
13015
|
+
assertExactKeys(value, ["ok", "adapters"], [], "Ghostget execution identity catalog");
|
|
13015
13016
|
if (value.ok !== true || !isUnknownArray(value.adapters)) {
|
|
13016
|
-
throw new Error("
|
|
13017
|
+
throw new Error("Ghostget execution identity catalog is malformed");
|
|
13017
13018
|
}
|
|
13018
13019
|
if (value.adapters.length !== 1) {
|
|
13019
|
-
throw new Error("
|
|
13020
|
+
throw new Error("Ghostget execution identity catalog is ambiguous");
|
|
13020
13021
|
}
|
|
13021
|
-
const adapterValue = record(value.adapters[0], "
|
|
13022
|
+
const adapterValue = record(value.adapters[0], "Ghostget execution identity catalog adapter");
|
|
13022
13023
|
assertExactKeys(adapterValue, [
|
|
13023
13024
|
"id",
|
|
13024
13025
|
"version",
|
|
@@ -13027,17 +13028,17 @@ function parseCatalogExecutionIdentity(value, request, preview) {
|
|
|
13027
13028
|
"origins",
|
|
13028
13029
|
"manifestHash",
|
|
13029
13030
|
"operations"
|
|
13030
|
-
], [], "
|
|
13031
|
+
], [], "Ghostget execution identity catalog adapter");
|
|
13031
13032
|
const adapter = Object.freeze({
|
|
13032
|
-
id: safeString(adapterValue.id, "
|
|
13033
|
-
version: safeString(adapterValue.version, "
|
|
13034
|
-
hash: digest(adapterValue.manifestHash, "
|
|
13033
|
+
id: safeString(adapterValue.id, "Ghostget execution identity catalog adapter ID", 64),
|
|
13034
|
+
version: safeString(adapterValue.version, "Ghostget execution identity catalog adapter version", 64),
|
|
13035
|
+
hash: digest(adapterValue.manifestHash, "Ghostget execution identity catalog adapter hash")
|
|
13035
13036
|
});
|
|
13036
13037
|
if (adapter.id !== request.adapterId || adapter.id !== preview.adapter.id || adapter.version !== preview.adapter.version || adapter.hash !== preview.adapter.hash || !isUnknownArray(adapterValue.operations))
|
|
13037
|
-
throw new Error("
|
|
13038
|
+
throw new Error("Ghostget execution identity preflights disagreed");
|
|
13038
13039
|
const operations = adapterValue.operations.filter((candidate) => isRecord2(candidate) && candidate.id === request.operationId);
|
|
13039
13040
|
if (operations.length !== 1) {
|
|
13040
|
-
throw new Error("
|
|
13041
|
+
throw new Error("Ghostget execution identity catalog operation is ambiguous");
|
|
13041
13042
|
}
|
|
13042
13043
|
const operation = operations[0];
|
|
13043
13044
|
const commonKeys = [
|
|
@@ -13051,7 +13052,7 @@ function parseCatalogExecutionIdentity(value, request, preview) {
|
|
|
13051
13052
|
"input"
|
|
13052
13053
|
];
|
|
13053
13054
|
if (operation.risk !== "R1" || operation.transport !== preview.transport)
|
|
13054
|
-
throw new Error("
|
|
13055
|
+
throw new Error("Ghostget execution identity preflights disagreed");
|
|
13055
13056
|
const common = Object.freeze({
|
|
13056
13057
|
adapter,
|
|
13057
13058
|
operation: preview.operation
|
|
@@ -13066,18 +13067,18 @@ function parseCatalogExecutionIdentity(value, request, preview) {
|
|
|
13066
13067
|
"requiredScopeSets",
|
|
13067
13068
|
"coverage",
|
|
13068
13069
|
"implementation"
|
|
13069
|
-
], [], "
|
|
13070
|
-
const catalogSurface = providerSurfaceId(adapterValue.surfaceId, "
|
|
13071
|
-
const provider = providerSurfaceId(operation.provider, "
|
|
13072
|
-
const providerAction = providerOperationName(operation.providerAction, "
|
|
13073
|
-
safeInteger(operation.providerContractVersion, "
|
|
13070
|
+
], [], "Ghostget execution identity provider capability");
|
|
13071
|
+
const catalogSurface = providerSurfaceId(adapterValue.surfaceId, "Ghostget execution identity catalog surface");
|
|
13072
|
+
const provider = providerSurfaceId(operation.provider, "Ghostget execution identity provider surface");
|
|
13073
|
+
const providerAction = providerOperationName(operation.providerAction, "Ghostget execution identity provider action");
|
|
13074
|
+
safeInteger(operation.providerContractVersion, "Ghostget execution identity provider contract version", 1, 1e6);
|
|
13074
13075
|
if (provider !== catalogSurface || providerAction !== preview.operation)
|
|
13075
|
-
throw new Error("
|
|
13076
|
+
throw new Error("Ghostget execution identity provider route is malformed");
|
|
13076
13077
|
return Object.freeze({
|
|
13077
13078
|
...common,
|
|
13078
13079
|
schemaVersion: 3,
|
|
13079
13080
|
transport: "provider-api",
|
|
13080
|
-
providerContractHash: digest(operation.providerContractHash, "
|
|
13081
|
+
providerContractHash: digest(operation.providerContractHash, "Ghostget execution identity provider contract hash")
|
|
13081
13082
|
});
|
|
13082
13083
|
}
|
|
13083
13084
|
if (preview.transport === "web-session-api") {
|
|
@@ -13089,18 +13090,18 @@ function parseCatalogExecutionIdentity(value, request, preview) {
|
|
|
13089
13090
|
"webSessionContractHash",
|
|
13090
13091
|
"state",
|
|
13091
13092
|
"implementation"
|
|
13092
|
-
], [], "
|
|
13093
|
-
const catalogSurface = providerSurfaceId(adapterValue.surfaceId, "
|
|
13094
|
-
const site = providerSurfaceId(operation.site, "
|
|
13095
|
-
const webSessionAction = providerOperationName(operation.webSessionAction, "
|
|
13096
|
-
safeInteger(operation.webSessionContractVersion, "
|
|
13093
|
+
], [], "Ghostget execution identity web-session capability");
|
|
13094
|
+
const catalogSurface = providerSurfaceId(adapterValue.surfaceId, "Ghostget execution identity catalog surface");
|
|
13095
|
+
const site = providerSurfaceId(operation.site, "Ghostget execution identity web-session surface");
|
|
13096
|
+
const webSessionAction = providerOperationName(operation.webSessionAction, "Ghostget execution identity web-session action");
|
|
13097
|
+
safeInteger(operation.webSessionContractVersion, "Ghostget execution identity web-session contract version", 1, 1e6);
|
|
13097
13098
|
if (site !== catalogSurface || webSessionAction !== preview.operation)
|
|
13098
|
-
throw new Error("
|
|
13099
|
+
throw new Error("Ghostget execution identity web-session route is malformed");
|
|
13099
13100
|
return Object.freeze({
|
|
13100
13101
|
...common,
|
|
13101
13102
|
schemaVersion: 4,
|
|
13102
13103
|
transport: "web-session-api",
|
|
13103
|
-
webSessionContractHash: digest(operation.webSessionContractHash, "
|
|
13104
|
+
webSessionContractHash: digest(operation.webSessionContractHash, "Ghostget execution identity web-session contract hash")
|
|
13104
13105
|
});
|
|
13105
13106
|
}
|
|
13106
13107
|
if (preview.transport === "local-cli") {
|
|
@@ -13113,8 +13114,8 @@ function parseCatalogExecutionIdentity(value, request, preview) {
|
|
|
13113
13114
|
"localCliTool",
|
|
13114
13115
|
"state",
|
|
13115
13116
|
"implementation"
|
|
13116
|
-
], [], "
|
|
13117
|
-
const catalogSurface = providerSurfaceId(adapterValue.surfaceId, "
|
|
13117
|
+
], [], "Ghostget execution identity local CLI capability");
|
|
13118
|
+
const catalogSurface = providerSurfaceId(adapterValue.surfaceId, "Ghostget execution identity catalog surface");
|
|
13118
13119
|
const localCliContract = parseLocalCliContractIdentity({
|
|
13119
13120
|
surface: operation.surface,
|
|
13120
13121
|
action: operation.localCliAction,
|
|
@@ -13123,7 +13124,7 @@ function parseCatalogExecutionIdentity(value, request, preview) {
|
|
|
13123
13124
|
tool: operation.localCliTool
|
|
13124
13125
|
});
|
|
13125
13126
|
if (localCliContract.surface !== catalogSurface || localCliContract.action !== preview.operation) {
|
|
13126
|
-
throw new Error("
|
|
13127
|
+
throw new Error("Ghostget execution identity local CLI route is malformed");
|
|
13127
13128
|
}
|
|
13128
13129
|
return Object.freeze({
|
|
13129
13130
|
...common,
|
|
@@ -13137,13 +13138,13 @@ function parseCatalogExecutionIdentity(value, request, preview) {
|
|
|
13137
13138
|
"state",
|
|
13138
13139
|
"reviewedTemplateContractVersion",
|
|
13139
13140
|
"reviewedTemplateContractHash"
|
|
13140
|
-
], ["instructions", "reviewedAt", "evidenceSha256", "origin"], "
|
|
13141
|
-
safeInteger(operation.reviewedTemplateContractVersion, "
|
|
13141
|
+
], ["instructions", "reviewedAt", "evidenceSha256", "origin"], "Ghostget execution identity reviewed-template capability");
|
|
13142
|
+
safeInteger(operation.reviewedTemplateContractVersion, "Ghostget execution identity reviewed-template contract version", 1, Number.MAX_SAFE_INTEGER);
|
|
13142
13143
|
return Object.freeze({
|
|
13143
13144
|
...common,
|
|
13144
13145
|
schemaVersion: 5,
|
|
13145
13146
|
transport: "reviewed-template-api",
|
|
13146
|
-
reviewedTemplateContractHash: digest(operation.reviewedTemplateContractHash, "
|
|
13147
|
+
reviewedTemplateContractHash: digest(operation.reviewedTemplateContractHash, "Ghostget execution identity reviewed-template contract hash")
|
|
13147
13148
|
});
|
|
13148
13149
|
}
|
|
13149
13150
|
function observeExecutionIdentity(request, options) {
|
|
@@ -13152,7 +13153,7 @@ function observeExecutionIdentity(request, options) {
|
|
|
13152
13153
|
projectionIdentityOnly: false,
|
|
13153
13154
|
preview: true,
|
|
13154
13155
|
headed: false
|
|
13155
|
-
}), options, "
|
|
13156
|
+
}), options, "Ghostget execution identity preview"), request);
|
|
13156
13157
|
if (preview.transport === "portable-provider-plugin") {
|
|
13157
13158
|
return Object.freeze({
|
|
13158
13159
|
adapter: preview.adapter,
|
|
@@ -13170,7 +13171,7 @@ function observeExecutionIdentity(request, options) {
|
|
|
13170
13171
|
transport: preview.transport
|
|
13171
13172
|
});
|
|
13172
13173
|
}
|
|
13173
|
-
return parseCatalogExecutionIdentity(runIdentityCommand(preparedCapabilitiesCommand(request), options, "
|
|
13174
|
+
return parseCatalogExecutionIdentity(runIdentityCommand(preparedCapabilitiesCommand(request), options, "Ghostget execution identity catalog preflight"), request, preview);
|
|
13174
13175
|
}
|
|
13175
13176
|
function receiptExecutionIdentity(receipt) {
|
|
13176
13177
|
const common = Object.freeze({
|
|
@@ -13240,59 +13241,59 @@ function runLiveCommandSync(command, options) {
|
|
|
13240
13241
|
const stdout = String(result.stdout ?? "");
|
|
13241
13242
|
const stderr = String(result.stderr ?? "");
|
|
13242
13243
|
if (stdout.trim().length === 0) {
|
|
13243
|
-
throw new Error(boundedMessage(stderr) || `
|
|
13244
|
+
throw new Error(boundedMessage(stderr) || `Ghostget live invocation exited ${String(code)}`);
|
|
13244
13245
|
}
|
|
13245
13246
|
if (code !== 0 && code !== 3 && code !== 5) {
|
|
13246
|
-
throw new Error(boundedMessage(stderr) || `
|
|
13247
|
+
throw new Error(boundedMessage(stderr) || `Ghostget live invocation exited ${String(code)}`);
|
|
13247
13248
|
}
|
|
13248
|
-
return parseOutput(stdout, "
|
|
13249
|
+
return parseOutput(stdout, "Ghostget live response");
|
|
13249
13250
|
}
|
|
13250
13251
|
function parsePublication(value) {
|
|
13251
|
-
const publication = record(value, "
|
|
13252
|
-
assertExactKeys(publication, ["key", "dataRevision", "validatedAt", "dataChangedAt", "disposition"], ["currentDataRevision"], "
|
|
13252
|
+
const publication = record(value, "Ghostget cache publication");
|
|
13253
|
+
assertExactKeys(publication, ["key", "dataRevision", "validatedAt", "dataChangedAt", "disposition"], ["currentDataRevision"], "Ghostget cache publication");
|
|
13253
13254
|
const disposition = publication.disposition;
|
|
13254
13255
|
if (disposition !== "created" && disposition !== "changed" && disposition !== "unchanged" && disposition !== "superseded")
|
|
13255
|
-
throw new Error("
|
|
13256
|
+
throw new Error("Ghostget cache publication disposition is malformed");
|
|
13256
13257
|
if (disposition === "superseded" !== Object.hasOwn(publication, "currentDataRevision"))
|
|
13257
|
-
throw new Error("
|
|
13258
|
+
throw new Error("Ghostget cache publication current revision is malformed");
|
|
13258
13259
|
return Object.freeze({
|
|
13259
|
-
key: digest(publication.key, "
|
|
13260
|
-
dataRevision: digest(publication.dataRevision, "
|
|
13261
|
-
validatedAt: timestamp2(publication.validatedAt, "
|
|
13262
|
-
dataChangedAt: timestamp2(publication.dataChangedAt, "
|
|
13260
|
+
key: digest(publication.key, "Ghostget cache publication key"),
|
|
13261
|
+
dataRevision: digest(publication.dataRevision, "Ghostget cache publication data revision"),
|
|
13262
|
+
validatedAt: timestamp2(publication.validatedAt, "Ghostget cache publication validation time"),
|
|
13263
|
+
dataChangedAt: timestamp2(publication.dataChangedAt, "Ghostget cache publication data-change time"),
|
|
13263
13264
|
disposition,
|
|
13264
13265
|
...publication.currentDataRevision === undefined ? {} : {
|
|
13265
|
-
currentDataRevision: digest(publication.currentDataRevision, "
|
|
13266
|
+
currentDataRevision: digest(publication.currentDataRevision, "Ghostget current cache data revision")
|
|
13266
13267
|
}
|
|
13267
13268
|
});
|
|
13268
13269
|
}
|
|
13269
13270
|
function parseCacheOutcome(value) {
|
|
13270
|
-
const outcome = record(value, "
|
|
13271
|
+
const outcome = record(value, "Ghostget cache outcome");
|
|
13271
13272
|
if (outcome.status === "stored") {
|
|
13272
|
-
assertExactKeys(outcome, ["status", "publication"], [], "
|
|
13273
|
+
assertExactKeys(outcome, ["status", "publication"], [], "Ghostget cache outcome");
|
|
13273
13274
|
return Object.freeze({ status: "stored", publication: parsePublication(outcome.publication) });
|
|
13274
13275
|
}
|
|
13275
13276
|
if (outcome.status === "retained" && outcome.reason === "live-read-failed") {
|
|
13276
|
-
assertExactKeys(outcome, ["status", "reason"], [], "
|
|
13277
|
+
assertExactKeys(outcome, ["status", "reason"], [], "Ghostget cache outcome");
|
|
13277
13278
|
return Object.freeze({ status: "retained", reason: "live-read-failed" });
|
|
13278
13279
|
}
|
|
13279
13280
|
if (outcome.status === "miss" && outcome.reason === "no-cached-snapshot") {
|
|
13280
|
-
assertExactKeys(outcome, ["status", "reason"], [], "
|
|
13281
|
+
assertExactKeys(outcome, ["status", "reason"], [], "Ghostget cache outcome");
|
|
13281
13282
|
return Object.freeze({ status: "miss", reason: "no-cached-snapshot" });
|
|
13282
13283
|
}
|
|
13283
13284
|
if (outcome.status === "skipped" && outcome.reason === "auth-subject-unbound") {
|
|
13284
|
-
assertExactKeys(outcome, ["status", "reason"], [], "
|
|
13285
|
+
assertExactKeys(outcome, ["status", "reason"], [], "Ghostget cache outcome");
|
|
13285
13286
|
return Object.freeze({ status: "skipped", reason: outcome.reason });
|
|
13286
13287
|
}
|
|
13287
13288
|
if (outcome.status === "error") {
|
|
13288
|
-
assertExactKeys(outcome, ["status", "message"], [], "
|
|
13289
|
-
return Object.freeze({ status: "error", message: safeString(outcome.message, "
|
|
13289
|
+
assertExactKeys(outcome, ["status", "message"], [], "Ghostget cache outcome");
|
|
13290
|
+
return Object.freeze({ status: "error", message: safeString(outcome.message, "Ghostget cache error", MAX_ERROR_BYTES) });
|
|
13290
13291
|
}
|
|
13291
|
-
throw new Error("
|
|
13292
|
+
throw new Error("Ghostget cache outcome is malformed");
|
|
13292
13293
|
}
|
|
13293
13294
|
function parseReceiptStatus(value) {
|
|
13294
13295
|
if (value !== "succeeded" && value !== "failed") {
|
|
13295
|
-
throw new Error("
|
|
13296
|
+
throw new Error("Ghostget live receipt status is malformed");
|
|
13296
13297
|
}
|
|
13297
13298
|
return value;
|
|
13298
13299
|
}
|
|
@@ -13367,7 +13368,7 @@ function parseLocalCliToolIdentity(value) {
|
|
|
13367
13368
|
}
|
|
13368
13369
|
return parsed;
|
|
13369
13370
|
};
|
|
13370
|
-
const tool = strictRecord(value, "
|
|
13371
|
+
const tool = strictRecord(value, "Ghostget local CLI tool identity");
|
|
13371
13372
|
assertExactKeys(tool, [
|
|
13372
13373
|
"schemaVersion",
|
|
13373
13374
|
"id",
|
|
@@ -13380,58 +13381,58 @@ function parseLocalCliToolIdentity(value) {
|
|
|
13380
13381
|
"releaseCommit",
|
|
13381
13382
|
"releaseManifestSha256",
|
|
13382
13383
|
"releaseManifestUrl"
|
|
13383
|
-
], "
|
|
13384
|
-
const artifactValues = strictArray(tool.artifacts, "
|
|
13384
|
+
], "Ghostget local CLI tool identity");
|
|
13385
|
+
const artifactValues = strictArray(tool.artifacts, "Ghostget local CLI tool artifact table");
|
|
13385
13386
|
if (tool.schemaVersion !== 1) {
|
|
13386
|
-
throw new Error("
|
|
13387
|
+
throw new Error("Ghostget local CLI tool identity is malformed");
|
|
13387
13388
|
}
|
|
13388
13389
|
if (artifactValues.length < 1) {
|
|
13389
|
-
throw new Error("
|
|
13390
|
+
throw new Error("Ghostget local CLI tool artifact table is malformed");
|
|
13390
13391
|
}
|
|
13391
13392
|
const artifacts = artifactValues.map((artifactValue, index) => {
|
|
13392
|
-
const artifact = strictRecord(artifactValue, `
|
|
13393
|
-
assertExactKeys(artifact, ["platform", "arch", "executableSha256"], ["archiveSha256", "downloadUrl"], `
|
|
13394
|
-
const archiveSha256 = artifact.archiveSha256 === undefined ? undefined : digest(artifact.archiveSha256, `
|
|
13395
|
-
const downloadUrl = artifact.downloadUrl === undefined ? undefined : exactHttpsUrl(artifact.downloadUrl, `
|
|
13393
|
+
const artifact = strictRecord(artifactValue, `Ghostget local CLI tool artifact ${index}`);
|
|
13394
|
+
assertExactKeys(artifact, ["platform", "arch", "executableSha256"], ["archiveSha256", "downloadUrl"], `Ghostget local CLI tool artifact ${index}`);
|
|
13395
|
+
const archiveSha256 = artifact.archiveSha256 === undefined ? undefined : digest(artifact.archiveSha256, `Ghostget local CLI tool artifact ${index} archive hash`);
|
|
13396
|
+
const downloadUrl = artifact.downloadUrl === undefined ? undefined : exactHttpsUrl(artifact.downloadUrl, `Ghostget local CLI tool artifact ${index} download URL`);
|
|
13396
13397
|
if (archiveSha256 === undefined !== (downloadUrl === undefined)) {
|
|
13397
|
-
throw new Error("
|
|
13398
|
+
throw new Error("Ghostget local CLI tool artifact archive provenance is malformed");
|
|
13398
13399
|
}
|
|
13399
13400
|
return Object.freeze({
|
|
13400
|
-
platform: token(artifact.platform, `
|
|
13401
|
-
arch: token(artifact.arch, `
|
|
13402
|
-
executableSha256: digest(artifact.executableSha256, `
|
|
13401
|
+
platform: token(artifact.platform, `Ghostget local CLI tool artifact ${index} platform`),
|
|
13402
|
+
arch: token(artifact.arch, `Ghostget local CLI tool artifact ${index} architecture`),
|
|
13403
|
+
executableSha256: digest(artifact.executableSha256, `Ghostget local CLI tool artifact ${index} executable hash`),
|
|
13403
13404
|
...archiveSha256 === undefined ? {} : { archiveSha256 },
|
|
13404
13405
|
...downloadUrl === undefined ? {} : { downloadUrl }
|
|
13405
13406
|
});
|
|
13406
13407
|
});
|
|
13407
13408
|
const coordinates = artifacts.map((artifact) => `${artifact.platform}\x00${artifact.arch}`);
|
|
13408
13409
|
if (new Set(coordinates).size !== coordinates.length || coordinates.some((coordinate, index) => index > 0 && coordinates[index - 1] >= coordinate))
|
|
13409
|
-
throw new Error("
|
|
13410
|
-
const sourceUrl = tool.sourceUrl === undefined ? undefined : exactHttpsUrl(tool.sourceUrl, "
|
|
13411
|
-
const releaseManifestSha256 = tool.releaseManifestSha256 === undefined ? undefined : digest(tool.releaseManifestSha256, "
|
|
13412
|
-
const releaseManifestUrl = tool.releaseManifestUrl === undefined ? undefined : exactHttpsUrl(tool.releaseManifestUrl, "
|
|
13410
|
+
throw new Error("Ghostget local CLI tool artifact table is not canonical");
|
|
13411
|
+
const sourceUrl = tool.sourceUrl === undefined ? undefined : exactHttpsUrl(tool.sourceUrl, "Ghostget local CLI tool source URL");
|
|
13412
|
+
const releaseManifestSha256 = tool.releaseManifestSha256 === undefined ? undefined : digest(tool.releaseManifestSha256, "Ghostget local CLI tool release manifest hash");
|
|
13413
|
+
const releaseManifestUrl = tool.releaseManifestUrl === undefined ? undefined : exactHttpsUrl(tool.releaseManifestUrl, "Ghostget local CLI tool release manifest URL");
|
|
13413
13414
|
if (releaseManifestSha256 === undefined !== (releaseManifestUrl === undefined))
|
|
13414
|
-
throw new Error("
|
|
13415
|
+
throw new Error("Ghostget local CLI tool release manifest provenance is malformed");
|
|
13415
13416
|
if (tool.versionScheme !== "semver" && tool.versionScheme !== "opaque") {
|
|
13416
|
-
throw new Error("
|
|
13417
|
+
throw new Error("Ghostget local CLI tool version scheme is malformed");
|
|
13417
13418
|
}
|
|
13418
13419
|
if (typeof tool.version !== "string" || tool.version.length < 1 || tool.version.length > 128)
|
|
13419
|
-
throw new Error("
|
|
13420
|
+
throw new Error("Ghostget local CLI tool version is malformed");
|
|
13420
13421
|
const version = tool.version;
|
|
13421
13422
|
if (!wellFormedVisible(version) || tool.versionScheme === "semver" && !/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/u.test(version)) {
|
|
13422
|
-
throw new Error("
|
|
13423
|
+
throw new Error("Ghostget local CLI tool version is malformed");
|
|
13423
13424
|
}
|
|
13424
13425
|
const hasReleaseCommit = tool.releaseCommit !== undefined;
|
|
13425
|
-
const releaseCommit = hasReleaseCommit ? safeString(tool.releaseCommit, "
|
|
13426
|
+
const releaseCommit = hasReleaseCommit ? safeString(tool.releaseCommit, "Ghostget local CLI tool release commit", 40) : undefined;
|
|
13426
13427
|
if (releaseCommit !== undefined && !/^[a-f0-9]{40}$/u.test(releaseCommit))
|
|
13427
|
-
throw new Error("
|
|
13428
|
-
const implementation = safeString(tool.implementation, "
|
|
13428
|
+
throw new Error("Ghostget local CLI tool release commit is malformed");
|
|
13429
|
+
const implementation = safeString(tool.implementation, "Ghostget local CLI tool implementation", 256);
|
|
13429
13430
|
if (!/^[A-Za-z0-9](?:[A-Za-z0-9._/+:-]{0,254}[A-Za-z0-9])?$/u.test(implementation)) {
|
|
13430
|
-
throw new Error("
|
|
13431
|
+
throw new Error("Ghostget local CLI tool implementation is malformed");
|
|
13431
13432
|
}
|
|
13432
13433
|
return Object.freeze({
|
|
13433
13434
|
schemaVersion: 1,
|
|
13434
|
-
id: token(tool.id, "
|
|
13435
|
+
id: token(tool.id, "Ghostget local CLI tool ID"),
|
|
13435
13436
|
implementation,
|
|
13436
13437
|
versionScheme: tool.versionScheme,
|
|
13437
13438
|
version,
|
|
@@ -13442,20 +13443,20 @@ function parseLocalCliToolIdentity(value) {
|
|
|
13442
13443
|
});
|
|
13443
13444
|
}
|
|
13444
13445
|
function parseLocalCliContractIdentity(value) {
|
|
13445
|
-
const identity = record(value, "
|
|
13446
|
-
assertExactKeys(identity, ["surface", "action", "version", "hash", "tool"], [], "
|
|
13447
|
-
const surface = providerSurfaceId(identity.surface, "
|
|
13448
|
-
const action = providerOperationName(identity.action, "
|
|
13446
|
+
const identity = record(value, "Ghostget local CLI contract identity");
|
|
13447
|
+
assertExactKeys(identity, ["surface", "action", "version", "hash", "tool"], [], "Ghostget local CLI contract identity");
|
|
13448
|
+
const surface = providerSurfaceId(identity.surface, "Ghostget local CLI surface");
|
|
13449
|
+
const action = providerOperationName(identity.action, "Ghostget local CLI action");
|
|
13449
13450
|
return Object.freeze({
|
|
13450
13451
|
surface,
|
|
13451
13452
|
action,
|
|
13452
|
-
version: safeInteger(identity.version, "
|
|
13453
|
-
hash: digest(identity.hash, "
|
|
13453
|
+
version: safeInteger(identity.version, "Ghostget local CLI contract version", 1, 1e6),
|
|
13454
|
+
hash: digest(identity.hash, "Ghostget local CLI contract hash"),
|
|
13454
13455
|
tool: parseLocalCliToolIdentity(identity.tool)
|
|
13455
13456
|
});
|
|
13456
13457
|
}
|
|
13457
13458
|
function parseLiveReceipt(value, request, expectedInputHash) {
|
|
13458
|
-
const receipt = record(value, "
|
|
13459
|
+
const receipt = record(value, "Ghostget live receipt");
|
|
13459
13460
|
const commonKeys = [
|
|
13460
13461
|
"schemaVersion",
|
|
13461
13462
|
"runId",
|
|
@@ -13475,66 +13476,66 @@ function parseLiveReceipt(value, request, expectedInputHash) {
|
|
|
13475
13476
|
"error"
|
|
13476
13477
|
];
|
|
13477
13478
|
if (receipt.schemaVersion === 2 && receipt.transport === "browser") {
|
|
13478
|
-
assertExactKeys(receipt, commonKeys, [], "
|
|
13479
|
+
assertExactKeys(receipt, commonKeys, [], "Ghostget live receipt");
|
|
13479
13480
|
} else if (receipt.schemaVersion === 3 && receipt.transport === "provider-api") {
|
|
13480
|
-
assertExactKeys(receipt, [...commonKeys, "providerContractHash"], [], "
|
|
13481
|
+
assertExactKeys(receipt, [...commonKeys, "providerContractHash"], [], "Ghostget live receipt");
|
|
13481
13482
|
} else if (receipt.schemaVersion === 4 && receipt.transport === "web-session-api") {
|
|
13482
|
-
assertExactKeys(receipt, [...commonKeys, "webSessionContractHash"], [], "
|
|
13483
|
+
assertExactKeys(receipt, [...commonKeys, "webSessionContractHash"], [], "Ghostget live receipt");
|
|
13483
13484
|
} else if (receipt.schemaVersion === 5 && receipt.transport === "reviewed-template-api") {
|
|
13484
|
-
assertExactKeys(receipt, [...commonKeys, "reviewedTemplateContractHash"], [], "
|
|
13485
|
+
assertExactKeys(receipt, [...commonKeys, "reviewedTemplateContractHash"], [], "Ghostget live receipt");
|
|
13485
13486
|
} else if (receipt.schemaVersion === 6 && receipt.transport === "portable-provider-plugin") {
|
|
13486
|
-
assertExactKeys(receipt, [...commonKeys, "portablePluginContract"], [], "
|
|
13487
|
+
assertExactKeys(receipt, [...commonKeys, "portablePluginContract"], [], "Ghostget live receipt");
|
|
13487
13488
|
} else if (receipt.schemaVersion === 7 && receipt.transport === "local-cli") {
|
|
13488
|
-
assertExactKeys(receipt, [...commonKeys, "localCliContract"], [], "
|
|
13489
|
+
assertExactKeys(receipt, [...commonKeys, "localCliContract"], [], "Ghostget live receipt");
|
|
13489
13490
|
} else {
|
|
13490
|
-
throw new Error("
|
|
13491
|
-
}
|
|
13492
|
-
const adapter = record(receipt.adapter, "
|
|
13493
|
-
const auth = record(receipt.auth, "
|
|
13494
|
-
const dispatch = record(receipt.dispatch, "
|
|
13495
|
-
assertExactKeys(adapter, ["id", "version", "hash"], [], "
|
|
13496
|
-
assertExactKeys(auth, ["id", "hash", "kind"], [], "
|
|
13497
|
-
assertExactKeys(dispatch, ["planned", "started", "verified"], [], "
|
|
13491
|
+
throw new Error("Ghostget live receipt schema and transport are malformed");
|
|
13492
|
+
}
|
|
13493
|
+
const adapter = record(receipt.adapter, "Ghostget live receipt adapter");
|
|
13494
|
+
const auth = record(receipt.auth, "Ghostget live receipt auth");
|
|
13495
|
+
const dispatch = record(receipt.dispatch, "Ghostget live receipt dispatch");
|
|
13496
|
+
assertExactKeys(adapter, ["id", "version", "hash"], [], "Ghostget live receipt adapter");
|
|
13497
|
+
assertExactKeys(auth, ["id", "hash", "kind"], [], "Ghostget live receipt auth");
|
|
13498
|
+
assertExactKeys(dispatch, ["planned", "started", "verified"], [], "Ghostget live receipt dispatch");
|
|
13498
13499
|
if (receipt.risk !== "R1") {
|
|
13499
|
-
throw new Error("
|
|
13500
|
+
throw new Error("Ghostget live receipt risk is malformed");
|
|
13500
13501
|
}
|
|
13501
13502
|
const authKind = auth.kind;
|
|
13502
13503
|
if (authKind !== "browser-profile" && authKind !== "cookie-source" && authKind !== "cookies-file" && authKind !== "linked-device-store" && authKind !== "oauth-token-file" && authKind !== PUBLIC_WEB_SESSION_AUTHORITY_KIND)
|
|
13503
|
-
throw new Error("
|
|
13504
|
+
throw new Error("Ghostget live receipt auth kind is malformed");
|
|
13504
13505
|
if (receipt.dispatchStarted !== false) {
|
|
13505
|
-
throw new Error("
|
|
13506
|
+
throw new Error("Ghostget live receipt dispatch state is malformed");
|
|
13506
13507
|
}
|
|
13507
|
-
const planned = safeInteger(dispatch.planned, "
|
|
13508
|
-
const started = safeInteger(dispatch.started, "
|
|
13509
|
-
const verified = safeInteger(dispatch.verified, "
|
|
13508
|
+
const planned = safeInteger(dispatch.planned, "Ghostget live receipt planned dispatch count", 0, Number.MAX_SAFE_INTEGER);
|
|
13509
|
+
const started = safeInteger(dispatch.started, "Ghostget live receipt started dispatch count", 0, Number.MAX_SAFE_INTEGER);
|
|
13510
|
+
const verified = safeInteger(dispatch.verified, "Ghostget live receipt verified dispatch count", 0, Number.MAX_SAFE_INTEGER);
|
|
13510
13511
|
if (planned !== 0 || started !== 0 || verified !== 0) {
|
|
13511
|
-
throw new Error("
|
|
13512
|
+
throw new Error("Ghostget live receipt dispatch counts are inconsistent");
|
|
13512
13513
|
}
|
|
13513
13514
|
const status = parseReceiptStatus(receipt.status);
|
|
13514
|
-
const finalOrigin = receipt.finalOrigin === null ? null : safeString(receipt.finalOrigin, "
|
|
13515
|
-
const error = receipt.error === null ? null : boundedUtf8String(receipt.error, "
|
|
13515
|
+
const finalOrigin = receipt.finalOrigin === null ? null : safeString(receipt.finalOrigin, "Ghostget live receipt final origin", 4096);
|
|
13516
|
+
const error = receipt.error === null ? null : boundedUtf8String(receipt.error, "Ghostget live receipt error", MAX_RUN_RECEIPT_ERROR_BYTES);
|
|
13516
13517
|
if (receipt.planDigest !== null) {
|
|
13517
|
-
throw new Error("
|
|
13518
|
+
throw new Error("Ghostget live receipt plan digest is malformed");
|
|
13518
13519
|
}
|
|
13519
|
-
const startedAt = timestamp2(receipt.startedAt, "
|
|
13520
|
-
const finishedAt = timestamp2(receipt.finishedAt, "
|
|
13520
|
+
const startedAt = timestamp2(receipt.startedAt, "Ghostget live receipt start time");
|
|
13521
|
+
const finishedAt = timestamp2(receipt.finishedAt, "Ghostget live receipt finish time");
|
|
13521
13522
|
if (startedAt > finishedAt) {
|
|
13522
|
-
throw new Error("
|
|
13523
|
+
throw new Error("Ghostget live receipt finished before it started");
|
|
13523
13524
|
}
|
|
13524
13525
|
const common = Object.freeze({
|
|
13525
|
-
runId: safeString(receipt.runId, "
|
|
13526
|
+
runId: safeString(receipt.runId, "Ghostget live receipt run ID", 64),
|
|
13526
13527
|
planDigest: null,
|
|
13527
13528
|
adapter: Object.freeze({
|
|
13528
|
-
id: safeString(adapter.id, "
|
|
13529
|
-
version: safeString(adapter.version, "
|
|
13530
|
-
hash: digest(adapter.hash, "
|
|
13529
|
+
id: safeString(adapter.id, "Ghostget live receipt adapter ID", 64),
|
|
13530
|
+
version: safeString(adapter.version, "Ghostget live receipt adapter version", 64),
|
|
13531
|
+
hash: digest(adapter.hash, "Ghostget live receipt adapter hash")
|
|
13531
13532
|
}),
|
|
13532
|
-
operation: providerOperationName(receipt.operation, "
|
|
13533
|
+
operation: providerOperationName(receipt.operation, "Ghostget live receipt operation"),
|
|
13533
13534
|
risk: "R1",
|
|
13534
|
-
inputHash: digest(receipt.inputHash, "
|
|
13535
|
+
inputHash: digest(receipt.inputHash, "Ghostget live receipt input hash"),
|
|
13535
13536
|
auth: Object.freeze({
|
|
13536
|
-
id: safeString(auth.id, "
|
|
13537
|
-
hash: digest(auth.hash, "
|
|
13537
|
+
id: safeString(auth.id, "Ghostget live receipt auth ID", 64),
|
|
13538
|
+
hash: digest(auth.hash, "Ghostget live receipt auth hash"),
|
|
13538
13539
|
kind: authKind
|
|
13539
13540
|
}),
|
|
13540
13541
|
status,
|
|
@@ -13550,18 +13551,18 @@ function parseLiveReceipt(value, request, expectedInputHash) {
|
|
|
13550
13551
|
error
|
|
13551
13552
|
});
|
|
13552
13553
|
if (common.adapter.id !== request.adapterId || common.operation !== request.operationId)
|
|
13553
|
-
throw new Error("
|
|
13554
|
+
throw new Error("Ghostget live receipt route does not match its request");
|
|
13554
13555
|
if (common.auth.id !== expectedRequestAuthId(request, common.auth.kind)) {
|
|
13555
|
-
throw new Error("
|
|
13556
|
+
throw new Error("Ghostget live receipt auth does not match its request");
|
|
13556
13557
|
}
|
|
13557
13558
|
if (common.auth.kind === PUBLIC_WEB_SESSION_AUTHORITY_KIND) {
|
|
13558
13559
|
const authority = publicWebSessionAuthority(request);
|
|
13559
13560
|
if (receipt.schemaVersion !== 4 || receipt.transport !== "web-session-api" || common.auth.hash !== sha2563(canonicalJson2(authority))) {
|
|
13560
|
-
throw new Error("
|
|
13561
|
+
throw new Error("Ghostget live receipt public authority is malformed");
|
|
13561
13562
|
}
|
|
13562
13563
|
}
|
|
13563
13564
|
if (common.inputHash !== expectedInputHash) {
|
|
13564
|
-
throw new Error("
|
|
13565
|
+
throw new Error("Ghostget live receipt input does not match its request");
|
|
13565
13566
|
}
|
|
13566
13567
|
if (receipt.schemaVersion === 2 && receipt.transport === "browser") {
|
|
13567
13568
|
return Object.freeze({ ...common, schemaVersion: 2, transport: "browser" });
|
|
@@ -13571,7 +13572,7 @@ function parseLiveReceipt(value, request, expectedInputHash) {
|
|
|
13571
13572
|
...common,
|
|
13572
13573
|
schemaVersion: 3,
|
|
13573
13574
|
transport: "provider-api",
|
|
13574
|
-
providerContractHash: digest(receipt.providerContractHash, "
|
|
13575
|
+
providerContractHash: digest(receipt.providerContractHash, "Ghostget live receipt provider contract hash")
|
|
13575
13576
|
});
|
|
13576
13577
|
}
|
|
13577
13578
|
if (receipt.schemaVersion === 4 && receipt.transport === "web-session-api") {
|
|
@@ -13579,7 +13580,7 @@ function parseLiveReceipt(value, request, expectedInputHash) {
|
|
|
13579
13580
|
...common,
|
|
13580
13581
|
schemaVersion: 4,
|
|
13581
13582
|
transport: "web-session-api",
|
|
13582
|
-
webSessionContractHash: digest(receipt.webSessionContractHash, "
|
|
13583
|
+
webSessionContractHash: digest(receipt.webSessionContractHash, "Ghostget live receipt web-session contract hash")
|
|
13583
13584
|
});
|
|
13584
13585
|
}
|
|
13585
13586
|
if (receipt.schemaVersion === 5 && receipt.transport === "reviewed-template-api") {
|
|
@@ -13587,13 +13588,13 @@ function parseLiveReceipt(value, request, expectedInputHash) {
|
|
|
13587
13588
|
...common,
|
|
13588
13589
|
schemaVersion: 5,
|
|
13589
13590
|
transport: "reviewed-template-api",
|
|
13590
|
-
reviewedTemplateContractHash: digest(receipt.reviewedTemplateContractHash, "
|
|
13591
|
+
reviewedTemplateContractHash: digest(receipt.reviewedTemplateContractHash, "Ghostget live receipt reviewed-template contract hash")
|
|
13591
13592
|
});
|
|
13592
13593
|
}
|
|
13593
13594
|
if (receipt.schemaVersion === 6 && receipt.transport === "portable-provider-plugin") {
|
|
13594
13595
|
const portablePluginContract = parsePortableOperationIdentity(receipt.portablePluginContract);
|
|
13595
13596
|
if (portablePluginContract.adapterId !== common.adapter.id || portablePluginContract.operation !== common.operation) {
|
|
13596
|
-
throw new Error("
|
|
13597
|
+
throw new Error("Ghostget live portable contract route is malformed");
|
|
13597
13598
|
}
|
|
13598
13599
|
return Object.freeze({
|
|
13599
13600
|
...common,
|
|
@@ -13605,7 +13606,7 @@ function parseLiveReceipt(value, request, expectedInputHash) {
|
|
|
13605
13606
|
if (receipt.schemaVersion === 7 && receipt.transport === "local-cli") {
|
|
13606
13607
|
const localCliContract = parseLocalCliContractIdentity(receipt.localCliContract);
|
|
13607
13608
|
if (localCliContract.action !== common.operation) {
|
|
13608
|
-
throw new Error("
|
|
13609
|
+
throw new Error("Ghostget live local CLI contract route is malformed");
|
|
13609
13610
|
}
|
|
13610
13611
|
return Object.freeze({
|
|
13611
13612
|
...common,
|
|
@@ -13614,30 +13615,30 @@ function parseLiveReceipt(value, request, expectedInputHash) {
|
|
|
13614
13615
|
localCliContract
|
|
13615
13616
|
});
|
|
13616
13617
|
}
|
|
13617
|
-
throw new Error("
|
|
13618
|
+
throw new Error("Ghostget live receipt schema and transport are malformed");
|
|
13618
13619
|
}
|
|
13619
13620
|
function parseLiveResult(value, request, expectedInputHash) {
|
|
13620
|
-
assertExactKeys(value, ["ok", "source", "status", "runId", "replayed", "receipt", "output", "cache"], ["readFailure"], "
|
|
13621
|
+
assertExactKeys(value, ["ok", "source", "status", "runId", "replayed", "receipt", "output", "cache"], ["readFailure"], "Ghostget live response");
|
|
13621
13622
|
if (value.source !== "live")
|
|
13622
|
-
throw new Error("
|
|
13623
|
+
throw new Error("Ghostget live response has the wrong source");
|
|
13623
13624
|
const receipt = parseLiveReceipt(value.receipt, request, expectedInputHash);
|
|
13624
|
-
const status = safeString(value.status, "
|
|
13625
|
-
const responseRunId = safeString(value.runId, "
|
|
13625
|
+
const status = safeString(value.status, "Ghostget live status", 32);
|
|
13626
|
+
const responseRunId = safeString(value.runId, "Ghostget live run ID", 64);
|
|
13626
13627
|
const expectedOk = receipt.status === "succeeded";
|
|
13627
13628
|
if (status !== receipt.status || receipt.runId !== responseRunId || value.ok !== expectedOk) {
|
|
13628
|
-
throw new Error("
|
|
13629
|
+
throw new Error("Ghostget live response is not bound to its receipt");
|
|
13629
13630
|
}
|
|
13630
13631
|
if (typeof value.replayed !== "boolean" || !("output" in value)) {
|
|
13631
|
-
throw new Error("
|
|
13632
|
+
throw new Error("Ghostget live response is incomplete");
|
|
13632
13633
|
}
|
|
13633
13634
|
const cache = parseCacheOutcome(value.cache);
|
|
13634
13635
|
const cacheMatchesReceipt = receipt.status === "succeeded" ? cache.status === "stored" || cache.status === "error" || cache.status === "skipped" : cache.status === "retained" || cache.status === "miss" || cache.status === "error" || cache.status === "skipped";
|
|
13635
13636
|
if (!cacheMatchesReceipt) {
|
|
13636
|
-
throw new Error("
|
|
13637
|
+
throw new Error("Ghostget live cache outcome is inconsistent with its receipt");
|
|
13637
13638
|
}
|
|
13638
13639
|
const live = receipt.status === "failed" ? (() => {
|
|
13639
13640
|
if (value.output !== null) {
|
|
13640
|
-
throw new Error("
|
|
13641
|
+
throw new Error("Ghostget failed live response retained an output");
|
|
13641
13642
|
}
|
|
13642
13643
|
return Object.freeze({
|
|
13643
13644
|
status: "failed",
|
|
@@ -13648,7 +13649,7 @@ function parseLiveResult(value, request, expectedInputHash) {
|
|
|
13648
13649
|
});
|
|
13649
13650
|
})() : (() => {
|
|
13650
13651
|
if (value.readFailure !== undefined) {
|
|
13651
|
-
throw new Error("
|
|
13652
|
+
throw new Error("Ghostget successful live response included a read failure");
|
|
13652
13653
|
}
|
|
13653
13654
|
return Object.freeze({
|
|
13654
13655
|
status: "succeeded",
|
|
@@ -13665,22 +13666,22 @@ function parseLiveResult(value, request, expectedInputHash) {
|
|
|
13665
13666
|
function assertLiveInvocationFences(request, options, executionIdentityBefore, identityBefore, parsed, activity) {
|
|
13666
13667
|
const identityAfter = observeProjectionIdentity(request, options);
|
|
13667
13668
|
if (!projectionIdentitiesMatch(identityBefore, identityAfter)) {
|
|
13668
|
-
throw new Error(`
|
|
13669
|
+
throw new Error(`Ghostget projection identity changed while ${activity} was running; the live result was discarded`);
|
|
13669
13670
|
}
|
|
13670
13671
|
if (!executionIdentitiesMatch(executionIdentityBefore, receiptExecutionIdentity(parsed.live.receipt))) {
|
|
13671
|
-
throw new Error(`
|
|
13672
|
+
throw new Error(`Ghostget execution identity changed while ${activity} was running; the live result was discarded`);
|
|
13672
13673
|
}
|
|
13673
13674
|
if (parsed.live.receipt.auth.hash !== identityBefore.authHash) {
|
|
13674
|
-
throw new Error(`
|
|
13675
|
+
throw new Error(`Ghostget projection identity changed while ${activity} was running; the live result was discarded`);
|
|
13675
13676
|
}
|
|
13676
13677
|
if (identityBefore.status === "unbound") {
|
|
13677
13678
|
if (parsed.cache.status !== "skipped") {
|
|
13678
|
-
throw new Error(`
|
|
13679
|
+
throw new Error(`Ghostget projection identity changed while ${activity} was running; the live result was discarded`);
|
|
13679
13680
|
}
|
|
13680
13681
|
return;
|
|
13681
13682
|
}
|
|
13682
13683
|
if (parsed.cache.status === "skipped" || parsed.cache.status === "stored" && parsed.cache.publication.key !== identityBefore.key) {
|
|
13683
|
-
throw new Error(`
|
|
13684
|
+
throw new Error(`Ghostget projection identity changed while ${activity} was running; the live result was discarded`);
|
|
13684
13685
|
}
|
|
13685
13686
|
}
|
|
13686
13687
|
function invokeCapabilitySync(request, options = {}) {
|
|
@@ -13697,10 +13698,10 @@ function invokeCapabilitySync(request, options = {}) {
|
|
|
13697
13698
|
return parsed.live;
|
|
13698
13699
|
}
|
|
13699
13700
|
|
|
13700
|
-
// node_modules/@hraness/
|
|
13701
|
-
var
|
|
13701
|
+
// node_modules/@hraness/ghostget/dist/index-rxvsqb3f.js
|
|
13702
|
+
var GHOSTGET_VERSION = "0.17.3";
|
|
13702
13703
|
|
|
13703
|
-
// node_modules/@hraness/
|
|
13704
|
+
// node_modules/@hraness/ghostget/dist/beeper-client.js
|
|
13704
13705
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
13705
13706
|
import { existsSync as existsSync4 } from "fs";
|
|
13706
13707
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -13735,7 +13736,7 @@ var LOCAL_MESSAGE_BUNDLE_V1_ARTIFACTS = Object.freeze([
|
|
|
13735
13736
|
Object.freeze({ path: "tombstones.ndjson", kind: "tombstone" })
|
|
13736
13737
|
]);
|
|
13737
13738
|
|
|
13738
|
-
// node_modules/@hraness/
|
|
13739
|
+
// node_modules/@hraness/ghostget/dist/beeper-client.js
|
|
13739
13740
|
import { types as nodeTypes4 } from "util";
|
|
13740
13741
|
var wrench_web_adapter_default = {
|
|
13741
13742
|
schemaVersion: 6,
|
|
@@ -17622,8 +17623,8 @@ var BEEPER_CONTACT_INTERACTION_TRANSFORM = Object.freeze({
|
|
|
17622
17623
|
});
|
|
17623
17624
|
var BEEPER_CONTACT_INTERACTION_IMPLEMENTATION = Object.freeze({
|
|
17624
17625
|
producer: Object.freeze({
|
|
17625
|
-
package: "@hraness/
|
|
17626
|
-
version:
|
|
17626
|
+
package: "@hraness/ghostget",
|
|
17627
|
+
version: GHOSTGET_VERSION
|
|
17627
17628
|
}),
|
|
17628
17629
|
officialCli: Object.freeze({
|
|
17629
17630
|
implementation: BEEPER_CLI_PIN.implementation,
|
|
@@ -18144,7 +18145,7 @@ function parseBeeperContactInteractionExportResult(value) {
|
|
|
18144
18145
|
var MAX_STDERR_BYTES = 8 * 1024;
|
|
18145
18146
|
var PROCESS_TIMEOUT_MS = 6 * 60 * 60 * 1000 + 60000;
|
|
18146
18147
|
function fail22(message) {
|
|
18147
|
-
throw new Error(`
|
|
18148
|
+
throw new Error(`Ghostget Beeper client: ${message}`);
|
|
18148
18149
|
}
|
|
18149
18150
|
function cliSourcePath2() {
|
|
18150
18151
|
const besideSource = fileURLToPath3(new URL("./cli.ts", import.meta.url));
|
|
@@ -18153,11 +18154,11 @@ function cliSourcePath2() {
|
|
|
18153
18154
|
const packagedSource = fileURLToPath3(new URL("../src/cli.ts", import.meta.url));
|
|
18154
18155
|
if (existsSync4(packagedSource))
|
|
18155
18156
|
return packagedSource;
|
|
18156
|
-
return fail22("the installed
|
|
18157
|
+
return fail22("the installed Ghostget CLI source is unavailable");
|
|
18157
18158
|
}
|
|
18158
18159
|
function requireBunRuntime2() {
|
|
18159
18160
|
if (typeof process.versions.bun !== "string") {
|
|
18160
|
-
fail22("@hraness/
|
|
18161
|
+
fail22("@hraness/ghostget/beeper requires Bun to run the installed Ghostget CLI");
|
|
18161
18162
|
}
|
|
18162
18163
|
}
|
|
18163
18164
|
function plainDataObject(value, label) {
|
|
@@ -18391,7 +18392,7 @@ function recordSourceExecutionReceipt(database, input) {
|
|
|
18391
18392
|
return { id: Number(inserted.lastInsertRowid), replayed: false };
|
|
18392
18393
|
}
|
|
18393
18394
|
|
|
18394
|
-
// src/local/
|
|
18395
|
+
// src/local/ghostget-adapter.ts
|
|
18395
18396
|
var DIGEST3 = /^[a-f0-9]{64}$/u;
|
|
18396
18397
|
var RUN_ID = /^(?:[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}|[a-z][a-z0-9._+-]{0,127})$/u;
|
|
18397
18398
|
function contractSha256(receipt) {
|
|
@@ -18419,41 +18420,41 @@ function canonicalTimestamp(value, label) {
|
|
|
18419
18420
|
}
|
|
18420
18421
|
function boundedRunId(value) {
|
|
18421
18422
|
if (!RUN_ID.test(value))
|
|
18422
|
-
throw new Error("
|
|
18423
|
+
throw new Error("Ghostget run ID is invalid");
|
|
18423
18424
|
return value;
|
|
18424
18425
|
}
|
|
18425
|
-
function
|
|
18426
|
+
function validatePeopleBladeGhostgetInvocation(result, request, policy) {
|
|
18426
18427
|
const receipt = result.receipt;
|
|
18427
18428
|
if (result.replayed || receipt.status !== "succeeded" || receipt.error !== null || receipt.planDigest !== null || receipt.risk !== "R1" || receipt.dispatchStarted || receipt.dispatch.planned !== 0 || receipt.dispatch.started !== 0 || receipt.dispatch.verified !== 0)
|
|
18428
|
-
throw new Error("
|
|
18429
|
+
throw new Error("Ghostget invocation crossed PeopleBlade's read-only execution boundary");
|
|
18429
18430
|
if (receipt.adapter.id !== request.adapterId || receipt.adapter.version !== policy.adapterVersion || receipt.operation !== request.operationId || receipt.auth.id !== request.authId || receipt.auth.kind !== policy.authKind || receipt.transport !== policy.transport || receipt.finalOrigin !== policy.finalOrigin)
|
|
18430
|
-
throw new Error("
|
|
18431
|
+
throw new Error("Ghostget invocation route identity drifted");
|
|
18431
18432
|
if (receipt.transport === "local-cli") {
|
|
18432
18433
|
const expected = policy.localCli;
|
|
18433
18434
|
const actual = receipt.localCliContract;
|
|
18434
18435
|
if (expected === undefined || actual.surface !== expected.surface || actual.action !== expected.action || actual.version !== expected.version || actual.tool.id !== expected.toolId || actual.tool.implementation !== expected.toolImplementation || actual.tool.versionScheme !== "semver" || actual.tool.version !== expected.toolVersion || actual.tool.releaseCommit !== expected.toolCommit || actual.tool.releaseManifestSha256 !== expected.toolReleaseManifestSha256)
|
|
18435
|
-
throw new Error("
|
|
18436
|
+
throw new Error("Ghostget local CLI contract identity drifted");
|
|
18436
18437
|
} else if (policy.localCli !== undefined) {
|
|
18437
|
-
throw new Error("
|
|
18438
|
+
throw new Error("Ghostget invocation did not use the required local CLI route");
|
|
18438
18439
|
}
|
|
18439
18440
|
boundedRunId(receipt.runId);
|
|
18440
18441
|
const expectedInput = sha256(canonicalJson(request.input));
|
|
18441
18442
|
if (receipt.inputHash !== expectedInput || !DIGEST3.test(receipt.inputHash)) {
|
|
18442
|
-
throw new Error("
|
|
18443
|
+
throw new Error("Ghostget receipt does not bind the exact input");
|
|
18443
18444
|
}
|
|
18444
18445
|
if (!DIGEST3.test(receipt.adapter.hash))
|
|
18445
|
-
throw new Error("
|
|
18446
|
+
throw new Error("Ghostget adapter identity is invalid");
|
|
18446
18447
|
if (policy.adapterSha256 !== undefined && receipt.adapter.hash !== policy.adapterSha256) {
|
|
18447
|
-
throw new Error("
|
|
18448
|
+
throw new Error("Ghostget adapter implementation drifted");
|
|
18448
18449
|
}
|
|
18449
18450
|
const contract = contractSha256(receipt);
|
|
18450
18451
|
if (contract !== policy.contractSha256 || !DIGEST3.test(policy.contractSha256)) {
|
|
18451
|
-
throw new Error("
|
|
18452
|
+
throw new Error("Ghostget operation contract identity drifted");
|
|
18452
18453
|
}
|
|
18453
|
-
const startedAt = canonicalTimestamp(receipt.startedAt, "
|
|
18454
|
-
const finishedAt = canonicalTimestamp(receipt.finishedAt, "
|
|
18454
|
+
const startedAt = canonicalTimestamp(receipt.startedAt, "Ghostget start");
|
|
18455
|
+
const finishedAt = canonicalTimestamp(receipt.finishedAt, "Ghostget finish");
|
|
18455
18456
|
if (finishedAt < startedAt)
|
|
18456
|
-
throw new Error("
|
|
18457
|
+
throw new Error("Ghostget receipt timing is invalid");
|
|
18457
18458
|
return {
|
|
18458
18459
|
output: result.output,
|
|
18459
18460
|
execution: {
|
|
@@ -18470,13 +18471,13 @@ function validatePeopleBladeWrenchInvocation(result, request, policy) {
|
|
|
18470
18471
|
}
|
|
18471
18472
|
};
|
|
18472
18473
|
}
|
|
18473
|
-
function
|
|
18474
|
+
function callPeopleBladeGhostget(request, invoke = invokeCapabilitySync) {
|
|
18474
18475
|
return invoke(request);
|
|
18475
18476
|
}
|
|
18476
18477
|
function callPeopleBladeBeeperContactInteractions(request, exportInteractions = exportBeeperContactInteractionsSync) {
|
|
18477
18478
|
return parseBeeperContactInteractionExportResult(exportInteractions(request));
|
|
18478
18479
|
}
|
|
18479
|
-
function
|
|
18480
|
+
function ledgerPeopleBladeGhostgetInvocation(database, input) {
|
|
18480
18481
|
const { receipt } = input.execution;
|
|
18481
18482
|
return recordSourceExecutionReceipt(database, {
|
|
18482
18483
|
provider: input.provider,
|
|
@@ -18542,21 +18543,21 @@ function canonicalTimestamp2(value, label) {
|
|
|
18542
18543
|
function readBeeperContactInteractions(request, exportInteractions) {
|
|
18543
18544
|
const result = callPeopleBladeBeeperContactInteractions(request, exportInteractions);
|
|
18544
18545
|
if (result.receipt.auth.id !== request.authId || result.receipt.auth.kind !== "linked-device-store" || result.receipt.auth.provider !== "beeper" || result.receipt.operation !== "beeper.export-contact-interactions" || result.receipt.status !== "succeeded" || result.receipt.transport !== "linked-device")
|
|
18545
|
-
throw new Error("
|
|
18546
|
+
throw new Error("Ghostget Beeper interaction export route identity drifted");
|
|
18546
18547
|
const expectedBounds = {
|
|
18547
18548
|
limitChats: request.limitChats ?? null,
|
|
18548
18549
|
limitMessages: request.limitMessages ?? null,
|
|
18549
18550
|
maxParticipants: request.maxParticipants ?? null
|
|
18550
18551
|
};
|
|
18551
18552
|
if (canonicalJson(result.receipt.bounds) !== canonicalJson(expectedBounds)) {
|
|
18552
|
-
throw new Error("
|
|
18553
|
+
throw new Error("Ghostget Beeper interaction export did not bind the requested bounds");
|
|
18553
18554
|
}
|
|
18554
18555
|
if (result.receipt.output.summarySha256 !== result.output.integrity.summarySha256 || !DIGEST4.test(result.receipt.integrity.receiptSha256) || !DIGEST4.test(result.receipt.auth.identitySha256))
|
|
18555
|
-
throw new Error("
|
|
18556
|
+
throw new Error("Ghostget Beeper interaction export integrity drifted");
|
|
18556
18557
|
canonicalTimestamp2(result.receipt.startedAt, "Beeper interaction export start");
|
|
18557
18558
|
canonicalTimestamp2(result.receipt.finishedAt, "Beeper interaction export finish");
|
|
18558
18559
|
if (result.receipt.finishedAt < result.receipt.startedAt) {
|
|
18559
|
-
throw new Error("
|
|
18560
|
+
throw new Error("Ghostget Beeper interaction export timing is invalid");
|
|
18560
18561
|
}
|
|
18561
18562
|
return result;
|
|
18562
18563
|
}
|
|
@@ -18681,10 +18682,10 @@ function projectBeeperContactInteractions(summary2, coordinates) {
|
|
|
18681
18682
|
}
|
|
18682
18683
|
|
|
18683
18684
|
// src/local/providers/beeper-compatibility.ts
|
|
18684
|
-
var
|
|
18685
|
-
|
|
18686
|
-
version: "0.
|
|
18687
|
-
commit: "
|
|
18685
|
+
var BEEPER_GHOSTGET_COMPATIBILITY = Object.freeze({
|
|
18686
|
+
ghostget: Object.freeze({
|
|
18687
|
+
version: "0.17.3",
|
|
18688
|
+
commit: "67b53cc1917bb09a48b22042cc988c3afbc0aaf8"
|
|
18688
18689
|
}),
|
|
18689
18690
|
adapter: Object.freeze({
|
|
18690
18691
|
id: "beeper-local",
|
|
@@ -18721,6 +18722,38 @@ var BEEPER_WRENCH_COMPATIBILITY = Object.freeze({
|
|
|
18721
18722
|
commit: "b9c1714410139c2139b597338cd002d785653e85"
|
|
18722
18723
|
})
|
|
18723
18724
|
});
|
|
18725
|
+
var PREVIOUS_GHOSTGET_0171_BEEPER_SOURCE_BINDING_METADATA = Object.freeze({
|
|
18726
|
+
transport: "local-cli",
|
|
18727
|
+
wrenchVersion: "0.17.1",
|
|
18728
|
+
wrenchCommit: "b74ae2ac594bcf07c3def71dc4bb2e5a98c826dd",
|
|
18729
|
+
adapterId: "beeper-local",
|
|
18730
|
+
adapterVersion: "2.4.0",
|
|
18731
|
+
adapterSha256: "2714af7e821b799c1d0b57106e8e193cb9787e5a6da37e1269f4aae0710a4ddc",
|
|
18732
|
+
cliId: "beeper-cli",
|
|
18733
|
+
cliVersion: "0.6.2",
|
|
18734
|
+
cliCommit: "a416af06023449a87312dc11e54643fd9dc94b8c",
|
|
18735
|
+
contracts: Object.freeze({
|
|
18736
|
+
contactsList: "8048565547c2a09078528d0a35f7a0c3a2e8850b17b075dc572e8b59115fcb57",
|
|
18737
|
+
contactsSearch: "e57cdcc9012ed5fb8f956e16a2368e95adf0a6b8bb404f903e64df85061b1ca1",
|
|
18738
|
+
messagingSearch: "2fc54d139a54d19c31a82237e4b417994e12679db3da5fd71ec9265e3622b0bf"
|
|
18739
|
+
})
|
|
18740
|
+
});
|
|
18741
|
+
var PREVIOUS_WRENCH_0168_BEEPER_SOURCE_BINDING_METADATA = Object.freeze({
|
|
18742
|
+
transport: "local-cli",
|
|
18743
|
+
wrenchVersion: "0.16.8",
|
|
18744
|
+
wrenchCommit: "b0cf2ecbc1113fcc8ef673a824be475c3d7bbb46",
|
|
18745
|
+
adapterId: "beeper-local",
|
|
18746
|
+
adapterVersion: "2.4.0",
|
|
18747
|
+
adapterSha256: "2714af7e821b799c1d0b57106e8e193cb9787e5a6da37e1269f4aae0710a4ddc",
|
|
18748
|
+
cliId: "beeper-cli",
|
|
18749
|
+
cliVersion: "0.6.2",
|
|
18750
|
+
cliCommit: "a416af06023449a87312dc11e54643fd9dc94b8c",
|
|
18751
|
+
contracts: Object.freeze({
|
|
18752
|
+
contactsList: "8048565547c2a09078528d0a35f7a0c3a2e8850b17b075dc572e8b59115fcb57",
|
|
18753
|
+
contactsSearch: "e57cdcc9012ed5fb8f956e16a2368e95adf0a6b8bb404f903e64df85061b1ca1",
|
|
18754
|
+
messagingSearch: "2fc54d139a54d19c31a82237e4b417994e12679db3da5fd71ec9265e3622b0bf"
|
|
18755
|
+
})
|
|
18756
|
+
});
|
|
18724
18757
|
var PEOPLEBLADE_010_WRENCH_0150_BEEPER_SOURCE_BINDING_METADATA = Object.freeze({
|
|
18725
18758
|
transport: "local-cli",
|
|
18726
18759
|
wrenchVersion: "0.15.0",
|
|
@@ -18818,15 +18851,15 @@ var BEEPER_INTERACTION_EXPORT_COMPATIBILITY = Object.freeze({
|
|
|
18818
18851
|
sourceVersion: "1.1.0",
|
|
18819
18852
|
transformId: "beeper-direct-contact-interactions",
|
|
18820
18853
|
transformVersion: 1,
|
|
18821
|
-
wrenchVersion:
|
|
18854
|
+
wrenchVersion: BEEPER_GHOSTGET_COMPATIBILITY.ghostget.version,
|
|
18822
18855
|
platform: "darwin-arm64"
|
|
18823
18856
|
});
|
|
18824
18857
|
function beeperSourceBindingMetadata() {
|
|
18825
|
-
const compatibility =
|
|
18858
|
+
const compatibility = BEEPER_GHOSTGET_COMPATIBILITY;
|
|
18826
18859
|
return Object.freeze({
|
|
18827
18860
|
transport: compatibility.adapter.transport,
|
|
18828
|
-
wrenchVersion: compatibility.
|
|
18829
|
-
wrenchCommit: compatibility.
|
|
18861
|
+
wrenchVersion: compatibility.ghostget.version,
|
|
18862
|
+
wrenchCommit: compatibility.ghostget.commit,
|
|
18830
18863
|
adapterId: compatibility.adapter.id,
|
|
18831
18864
|
adapterVersion: compatibility.adapter.version,
|
|
18832
18865
|
adapterSha256: compatibility.adapter.sha256,
|
|
@@ -18844,11 +18877,11 @@ function beeperSourceBindingMetadata() {
|
|
|
18844
18877
|
// src/local/providers/beeper.ts
|
|
18845
18878
|
var PROVIDER4 = "beeper";
|
|
18846
18879
|
var MODE3 = "wrench-contacts-v1";
|
|
18847
|
-
var ADAPTER_ID =
|
|
18848
|
-
var ADAPTER_VERSION =
|
|
18849
|
-
var ADAPTER_SHA256 =
|
|
18850
|
-
var CONTRACT_SHA256 =
|
|
18851
|
-
var FINAL_ORIGIN =
|
|
18880
|
+
var ADAPTER_ID = BEEPER_GHOSTGET_COMPATIBILITY.adapter.id;
|
|
18881
|
+
var ADAPTER_VERSION = BEEPER_GHOSTGET_COMPATIBILITY.adapter.version;
|
|
18882
|
+
var ADAPTER_SHA256 = BEEPER_GHOSTGET_COMPATIBILITY.adapter.sha256;
|
|
18883
|
+
var CONTRACT_SHA256 = BEEPER_GHOSTGET_COMPATIBILITY.operations["contacts.list"].sha256;
|
|
18884
|
+
var FINAL_ORIGIN = BEEPER_GHOSTGET_COMPATIBILITY.adapter.origin;
|
|
18852
18885
|
var MAX_ACCOUNTS = 128;
|
|
18853
18886
|
var MAX_CONTACTS2 = 200;
|
|
18854
18887
|
var SUBJECT = /^beeper:local:[a-f0-9]{64}$/u;
|
|
@@ -19011,22 +19044,22 @@ function parseContact(value, label) {
|
|
|
19011
19044
|
}
|
|
19012
19045
|
function parseBeeperContactPage(value, input, authId) {
|
|
19013
19046
|
const request = { adapterId: ADAPTER_ID, operationId: "contacts.list", authId, input };
|
|
19014
|
-
const validated =
|
|
19047
|
+
const validated = validatePeopleBladeGhostgetInvocation(value, request, {
|
|
19015
19048
|
adapterVersion: ADAPTER_VERSION,
|
|
19016
19049
|
adapterSha256: ADAPTER_SHA256,
|
|
19017
19050
|
authKind: "linked-device-store",
|
|
19018
|
-
transport:
|
|
19051
|
+
transport: BEEPER_GHOSTGET_COMPATIBILITY.adapter.transport,
|
|
19019
19052
|
contractSha256: CONTRACT_SHA256,
|
|
19020
19053
|
finalOrigin: FINAL_ORIGIN,
|
|
19021
19054
|
localCli: {
|
|
19022
|
-
surface:
|
|
19055
|
+
surface: BEEPER_GHOSTGET_COMPATIBILITY.adapter.surface,
|
|
19023
19056
|
action: "contacts.list",
|
|
19024
|
-
version:
|
|
19025
|
-
toolId:
|
|
19026
|
-
toolImplementation:
|
|
19027
|
-
toolVersion:
|
|
19028
|
-
toolCommit:
|
|
19029
|
-
toolReleaseManifestSha256:
|
|
19057
|
+
version: BEEPER_GHOSTGET_COMPATIBILITY.operations["contacts.list"].version,
|
|
19058
|
+
toolId: BEEPER_GHOSTGET_COMPATIBILITY.cli.id,
|
|
19059
|
+
toolImplementation: BEEPER_GHOSTGET_COMPATIBILITY.cli.implementation,
|
|
19060
|
+
toolVersion: BEEPER_GHOSTGET_COMPATIBILITY.cli.version,
|
|
19061
|
+
toolCommit: BEEPER_GHOSTGET_COMPATIBILITY.cli.commit,
|
|
19062
|
+
toolReleaseManifestSha256: BEEPER_GHOSTGET_COMPATIBILITY.cli.releaseManifestSha256
|
|
19030
19063
|
}
|
|
19031
19064
|
});
|
|
19032
19065
|
const execution = validated.execution;
|
|
@@ -19044,7 +19077,7 @@ function parseBeeperContactPage(value, input, authId) {
|
|
|
19044
19077
|
"continuation"
|
|
19045
19078
|
], "Beeper contact output");
|
|
19046
19079
|
if (output.provider !== "beeper" || output.operation !== "contacts.list" || output.projection !== "bounded-local-desktop-api" || output.query !== null) {
|
|
19047
|
-
throw new Error("
|
|
19080
|
+
throw new Error("Ghostget Beeper output changed projection semantics");
|
|
19048
19081
|
}
|
|
19049
19082
|
const accountSubject = text2(output.accountSubject, "Beeper account subject", 128);
|
|
19050
19083
|
if (!SUBJECT.test(accountSubject))
|
|
@@ -19198,7 +19231,7 @@ function sameExecution(left, right) {
|
|
|
19198
19231
|
return left.authId === right.authId && left.authSha256 === right.authSha256 && left.adapterSha256 === right.adapterSha256 && left.contractSha256 === right.contractSha256;
|
|
19199
19232
|
}
|
|
19200
19233
|
function ledgerBeeperContactRead(database, account, page) {
|
|
19201
|
-
|
|
19234
|
+
ledgerPeopleBladeGhostgetInvocation(database, {
|
|
19202
19235
|
provider: PROVIDER4,
|
|
19203
19236
|
accountKey: account,
|
|
19204
19237
|
capability: "contacts",
|
|
@@ -19273,8 +19306,14 @@ function classifyReviewedBeeperPredecessor(value, currentMetadata) {
|
|
|
19273
19306
|
if (encoded === canonicalJson(PREVIOUS_WRENCH_0167_BEEPER_SOURCE_BINDING_METADATA)) {
|
|
19274
19307
|
return "wrench-0.16.7-beeper-2.4";
|
|
19275
19308
|
}
|
|
19276
|
-
if (encoded ===
|
|
19309
|
+
if (encoded === canonicalJson(PREVIOUS_WRENCH_0168_BEEPER_SOURCE_BINDING_METADATA)) {
|
|
19277
19310
|
return "wrench-0.16.8-beeper-2.4";
|
|
19311
|
+
}
|
|
19312
|
+
if (encoded === canonicalJson(PREVIOUS_GHOSTGET_0171_BEEPER_SOURCE_BINDING_METADATA)) {
|
|
19313
|
+
return "ghostget-0.17.1-beeper-2.4";
|
|
19314
|
+
}
|
|
19315
|
+
if (encoded === currentMetadata)
|
|
19316
|
+
return "ghostget-0.17.3-beeper-2.4";
|
|
19278
19317
|
return null;
|
|
19279
19318
|
}
|
|
19280
19319
|
function assertStoredRealmInventory(database, sourceBindingId, accounts, storedSha256) {
|
|
@@ -19322,7 +19361,7 @@ function rebindBeeperSource(database, options = {}) {
|
|
|
19322
19361
|
if (!AUTH_ID.test(authId))
|
|
19323
19362
|
throw new Error("Beeper auth ID must be lowercase kebab text.");
|
|
19324
19363
|
const input = { limit: 1 };
|
|
19325
|
-
const invocation =
|
|
19364
|
+
const invocation = callPeopleBladeGhostget({
|
|
19326
19365
|
adapterId: ADAPTER_ID,
|
|
19327
19366
|
operationId: "contacts.list",
|
|
19328
19367
|
authId,
|
|
@@ -19353,7 +19392,7 @@ function rebindBeeperSource(database, options = {}) {
|
|
|
19353
19392
|
if (predecessor === null) {
|
|
19354
19393
|
throw new Error("Beeper rebind source coordinates are not a reviewed predecessor.");
|
|
19355
19394
|
}
|
|
19356
|
-
const metadataOnly = sameIdentity && (predecessor === "peopleblade-0.1.0-wrench-0.15-beeper-2.0" || predecessor === "wrench-0.16.7-beeper-2.4");
|
|
19395
|
+
const metadataOnly = sameIdentity && (predecessor === "peopleblade-0.1.0-wrench-0.15-beeper-2.0" || predecessor === "wrench-0.16.7-beeper-2.4" || predecessor === "wrench-0.16.8-beeper-2.4" || predecessor === "ghostget-0.17.1-beeper-2.4");
|
|
19357
19396
|
if (sameIdentity && !metadataOnly) {
|
|
19358
19397
|
throw new Error("Beeper metadata-only rebind requires an exact reviewed same-identity predecessor.");
|
|
19359
19398
|
}
|
|
@@ -19381,11 +19420,11 @@ function rebindBeeperSource(database, options = {}) {
|
|
|
19381
19420
|
subjectSha256: next.subjectSha256,
|
|
19382
19421
|
authSha256: next.authSha256,
|
|
19383
19422
|
metadataSha256: sha256(currentMetadata),
|
|
19384
|
-
wrenchVersion:
|
|
19385
|
-
adapterVersion:
|
|
19386
|
-
adapterSha256:
|
|
19387
|
-
transport:
|
|
19388
|
-
contractSha256:
|
|
19423
|
+
wrenchVersion: BEEPER_GHOSTGET_COMPATIBILITY.ghostget.version,
|
|
19424
|
+
adapterVersion: BEEPER_GHOSTGET_COMPATIBILITY.adapter.version,
|
|
19425
|
+
adapterSha256: BEEPER_GHOSTGET_COMPATIBILITY.adapter.sha256,
|
|
19426
|
+
transport: BEEPER_GHOSTGET_COMPATIBILITY.adapter.transport,
|
|
19427
|
+
contractSha256: BEEPER_GHOSTGET_COMPATIBILITY.operations["contacts.list"].sha256
|
|
19389
19428
|
},
|
|
19390
19429
|
inventory: {
|
|
19391
19430
|
accounts: inventory.accounts.length,
|
|
@@ -19441,7 +19480,7 @@ function syncBeeperContacts(database, options = {}) {
|
|
|
19441
19480
|
const limit = options.contactLimit ?? DEFAULT_BEEPER_CONTACT_LIMIT;
|
|
19442
19481
|
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_CONTACTS2)
|
|
19443
19482
|
throw new Error("Beeper contact limit must be from 1 through 200.");
|
|
19444
|
-
const invoke = (input) =>
|
|
19483
|
+
const invoke = (input) => callPeopleBladeGhostget({
|
|
19445
19484
|
adapterId: ADAPTER_ID,
|
|
19446
19485
|
operationId: "contacts.list",
|
|
19447
19486
|
authId,
|
|
@@ -19705,12 +19744,12 @@ function beeperStats(database) {
|
|
|
19705
19744
|
var PROVIDER5 = "beeper";
|
|
19706
19745
|
var MODE4 = "wrench-targeted-search-v1";
|
|
19707
19746
|
var ACCEPTANCE_MODE = "search-candidate-acceptance-v1";
|
|
19708
|
-
var ADAPTER_ID2 =
|
|
19709
|
-
var ADAPTER_VERSION2 =
|
|
19710
|
-
var ADAPTER_SHA2562 =
|
|
19711
|
-
var CONTACTS_SEARCH_CONTRACT_SHA256 =
|
|
19712
|
-
var MESSAGING_SEARCH_CONTRACT_SHA256 =
|
|
19713
|
-
var FINAL_ORIGIN2 =
|
|
19747
|
+
var ADAPTER_ID2 = BEEPER_GHOSTGET_COMPATIBILITY.adapter.id;
|
|
19748
|
+
var ADAPTER_VERSION2 = BEEPER_GHOSTGET_COMPATIBILITY.adapter.version;
|
|
19749
|
+
var ADAPTER_SHA2562 = BEEPER_GHOSTGET_COMPATIBILITY.adapter.sha256;
|
|
19750
|
+
var CONTACTS_SEARCH_CONTRACT_SHA256 = BEEPER_GHOSTGET_COMPATIBILITY.operations["contacts.search"].sha256;
|
|
19751
|
+
var MESSAGING_SEARCH_CONTRACT_SHA256 = BEEPER_GHOSTGET_COMPATIBILITY.operations["messaging.search"].sha256;
|
|
19752
|
+
var FINAL_ORIGIN2 = BEEPER_GHOSTGET_COMPATIBILITY.adapter.origin;
|
|
19714
19753
|
var MAX_QUERIES = 50;
|
|
19715
19754
|
var MAX_RESULTS = 20;
|
|
19716
19755
|
var SHA2562 = /^[a-f0-9]{64}$/u;
|
|
@@ -19854,7 +19893,7 @@ function parseConversation(value, label) {
|
|
|
19854
19893
|
}
|
|
19855
19894
|
function parseSearchPage(value, operation, input, authId) {
|
|
19856
19895
|
const expectedContract = operation === "contacts.search" ? CONTACTS_SEARCH_CONTRACT_SHA256 : MESSAGING_SEARCH_CONTRACT_SHA256;
|
|
19857
|
-
const validated =
|
|
19896
|
+
const validated = validatePeopleBladeGhostgetInvocation(value, {
|
|
19858
19897
|
adapterId: ADAPTER_ID2,
|
|
19859
19898
|
operationId: operation,
|
|
19860
19899
|
authId,
|
|
@@ -19863,18 +19902,18 @@ function parseSearchPage(value, operation, input, authId) {
|
|
|
19863
19902
|
adapterVersion: ADAPTER_VERSION2,
|
|
19864
19903
|
adapterSha256: ADAPTER_SHA2562,
|
|
19865
19904
|
authKind: "linked-device-store",
|
|
19866
|
-
transport:
|
|
19905
|
+
transport: BEEPER_GHOSTGET_COMPATIBILITY.adapter.transport,
|
|
19867
19906
|
contractSha256: expectedContract,
|
|
19868
19907
|
finalOrigin: FINAL_ORIGIN2,
|
|
19869
19908
|
localCli: {
|
|
19870
|
-
surface:
|
|
19909
|
+
surface: BEEPER_GHOSTGET_COMPATIBILITY.adapter.surface,
|
|
19871
19910
|
action: operation,
|
|
19872
|
-
version:
|
|
19873
|
-
toolId:
|
|
19874
|
-
toolImplementation:
|
|
19875
|
-
toolVersion:
|
|
19876
|
-
toolCommit:
|
|
19877
|
-
toolReleaseManifestSha256:
|
|
19911
|
+
version: BEEPER_GHOSTGET_COMPATIBILITY.operations[operation].version,
|
|
19912
|
+
toolId: BEEPER_GHOSTGET_COMPATIBILITY.cli.id,
|
|
19913
|
+
toolImplementation: BEEPER_GHOSTGET_COMPATIBILITY.cli.implementation,
|
|
19914
|
+
toolVersion: BEEPER_GHOSTGET_COMPATIBILITY.cli.version,
|
|
19915
|
+
toolCommit: BEEPER_GHOSTGET_COMPATIBILITY.cli.commit,
|
|
19916
|
+
toolReleaseManifestSha256: BEEPER_GHOSTGET_COMPATIBILITY.cli.releaseManifestSha256
|
|
19878
19917
|
}
|
|
19879
19918
|
});
|
|
19880
19919
|
const execution = validated.execution;
|
|
@@ -19892,7 +19931,7 @@ function parseSearchPage(value, operation, input, authId) {
|
|
|
19892
19931
|
"completeness"
|
|
19893
19932
|
], "Beeper search output");
|
|
19894
19933
|
if (output.provider !== "beeper" || output.operation !== operation || output.projection !== "bounded-local-desktop-search" || output.searchSemantics !== "provider-fuzzy-candidates")
|
|
19895
|
-
throw new Error("
|
|
19934
|
+
throw new Error("Ghostget Beeper search changed projection semantics");
|
|
19896
19935
|
const accountSubject = text3(output.accountSubject, "Beeper account subject", 128);
|
|
19897
19936
|
if (!/^beeper:local:[a-f0-9]{64}$/u.test(accountSubject))
|
|
19898
19937
|
throw new Error("Beeper account subject is invalid");
|
|
@@ -20084,7 +20123,7 @@ function stageCandidates(database, snapshot, candidates, query, service) {
|
|
|
20084
20123
|
function ledgerSearchExecution(database, account, operation, execution) {
|
|
20085
20124
|
if (execution.receipt.operation !== operation)
|
|
20086
20125
|
throw new Error("Beeper search execution operation drifted");
|
|
20087
|
-
return !
|
|
20126
|
+
return !ledgerPeopleBladeGhostgetInvocation(database, {
|
|
20088
20127
|
provider: PROVIDER5,
|
|
20089
20128
|
accountKey: account,
|
|
20090
20129
|
capability: "search_people",
|
|
@@ -20114,7 +20153,7 @@ function backfillBeeperSearch(database, options) {
|
|
|
20114
20153
|
}
|
|
20115
20154
|
return normalized;
|
|
20116
20155
|
}));
|
|
20117
|
-
const invoke = (operation, input) =>
|
|
20156
|
+
const invoke = (operation, input) => callPeopleBladeGhostget({ adapterId: ADAPTER_ID2, operationId: operation, authId, input }, options.invoke);
|
|
20118
20157
|
const inventoryInput = { limit: 1 };
|
|
20119
20158
|
const inventory = parseBeeperContactPage(invoke("contacts.list", inventoryInput), inventoryInput, authId);
|
|
20120
20159
|
assertSourceBindingCurrent(database, PROVIDER5, {
|
|
@@ -20124,7 +20163,7 @@ function backfillBeeperSearch(database, options) {
|
|
|
20124
20163
|
metadata: beeperSourceBindingMetadata()
|
|
20125
20164
|
});
|
|
20126
20165
|
let executionReceiptsRecorded = 0;
|
|
20127
|
-
if (!
|
|
20166
|
+
if (!ledgerPeopleBladeGhostgetInvocation(database, {
|
|
20128
20167
|
provider: PROVIDER5,
|
|
20129
20168
|
accountKey: `beeper:binding:${sha256(inventory.accountSubject)}`,
|
|
20130
20169
|
capability: "contacts",
|
|
@@ -20753,7 +20792,7 @@ function parseContact3(value, label, spec) {
|
|
|
20753
20792
|
};
|
|
20754
20793
|
}
|
|
20755
20794
|
function parsePage(value, input, auth, spec) {
|
|
20756
|
-
const validated =
|
|
20795
|
+
const validated = validatePeopleBladeGhostgetInvocation(value, {
|
|
20757
20796
|
adapterId: "gmail",
|
|
20758
20797
|
operationId: "contacts.list",
|
|
20759
20798
|
authId: auth,
|
|
@@ -20766,20 +20805,20 @@ function parsePage(value, input, auth, spec) {
|
|
|
20766
20805
|
finalOrigin: null
|
|
20767
20806
|
});
|
|
20768
20807
|
const execution = validated.execution;
|
|
20769
|
-
const output = record5(validated.output, "
|
|
20770
|
-
exact3(output, ["provider", "operation", "accountSubject", "contactCollection", "statsIncluded", "contacts", "nextCursor", "totalItems", "statsScanLimit", "statsScope"], "
|
|
20808
|
+
const output = record5(validated.output, "Ghostget output");
|
|
20809
|
+
exact3(output, ["provider", "operation", "accountSubject", "contactCollection", "statsIncluded", "contacts", "nextCursor", "totalItems", "statsScanLimit", "statsScope"], "Ghostget output");
|
|
20771
20810
|
if (output.provider !== "gmail" || output.operation !== "contacts.list" || output.contactCollection !== spec.id || output.statsIncluded !== false || output.statsScanLimit !== null || output.statsScope !== "not-requested") {
|
|
20772
|
-
throw new Error("
|
|
20811
|
+
throw new Error("Ghostget Gmail contact projection drifted");
|
|
20773
20812
|
}
|
|
20774
|
-
const accountSubject = text4(output.accountSubject, "
|
|
20813
|
+
const accountSubject = text4(output.accountSubject, "Ghostget account subject", 254).toLowerCase();
|
|
20775
20814
|
if (!ACCOUNT.test(accountSubject) || accountSubject !== output.accountSubject)
|
|
20776
|
-
throw new Error("
|
|
20815
|
+
throw new Error("Ghostget Gmail account subject is invalid");
|
|
20777
20816
|
const limit = integer5(input.limit, "Google page limit", 1, 100);
|
|
20778
|
-
const contacts = array2(output.contacts, "
|
|
20817
|
+
const contacts = array2(output.contacts, "Ghostget contacts", limit).map((entry, index) => parseContact3(entry, `contacts[${index}]`, spec));
|
|
20779
20818
|
if (new Set(contacts.map((contact) => contact.resourceName)).size !== contacts.length)
|
|
20780
|
-
throw new Error("
|
|
20781
|
-
const nextCursor = optionalText2(output.nextCursor, "
|
|
20782
|
-
const totalItems = output.totalItems === null ? null : integer5(output.totalItems, "
|
|
20819
|
+
throw new Error("Ghostget contact page contains duplicate resources");
|
|
20820
|
+
const nextCursor = optionalText2(output.nextCursor, "Ghostget next cursor", 4096);
|
|
20821
|
+
const totalItems = output.totalItems === null ? null : integer5(output.totalItems, "Ghostget total items", 0, MAX_CONTACTS3);
|
|
20783
20822
|
return { collection: spec.id, input, accountSubject, contacts, nextCursor, totalItems, execution };
|
|
20784
20823
|
}
|
|
20785
20824
|
function authId(value) {
|
|
@@ -20788,11 +20827,11 @@ function authId(value) {
|
|
|
20788
20827
|
throw new Error("Google auth ID is invalid");
|
|
20789
20828
|
return parsed;
|
|
20790
20829
|
}
|
|
20791
|
-
function
|
|
20830
|
+
function ghostgetGoogleContactsSnapshot(options = {}, collection = "contacts") {
|
|
20792
20831
|
const spec = collectionSpec(collection);
|
|
20793
20832
|
const auth = authId(options.authId ?? DEFAULT_GOOGLE_AUTH);
|
|
20794
20833
|
const pageSize = options.pageSize === undefined ? DEFAULT_GOOGLE_PAGE_SIZE : integer5(options.pageSize, "Google page size", 1, 100);
|
|
20795
|
-
const call = (input) =>
|
|
20834
|
+
const call = (input) => callPeopleBladeGhostget({
|
|
20796
20835
|
adapterId: "gmail",
|
|
20797
20836
|
operationId: "contacts.list",
|
|
20798
20837
|
authId: auth,
|
|
@@ -20815,7 +20854,7 @@ function wrenchGoogleContactsSnapshot(options = {}, collection = "contacts") {
|
|
|
20815
20854
|
const page = parsePage(call(input), input, auth, spec);
|
|
20816
20855
|
const priorExecution = pages.at(-1)?.execution;
|
|
20817
20856
|
if (priorExecution !== undefined && Date.parse(page.execution.startedAt) < Date.parse(priorExecution.finishedAt)) {
|
|
20818
|
-
throw new Error("
|
|
20857
|
+
throw new Error("Ghostget Google page timing moved backwards during pagination");
|
|
20819
20858
|
}
|
|
20820
20859
|
if (account !== null && page.accountSubject !== account)
|
|
20821
20860
|
throw new Error("Google account changed during pagination");
|
|
@@ -20844,25 +20883,25 @@ function wrenchGoogleContactsSnapshot(options = {}, collection = "contacts") {
|
|
|
20844
20883
|
throw new Error("Google contact total is incomplete or exceeds its bound");
|
|
20845
20884
|
const executions = pages.map((page) => page.execution);
|
|
20846
20885
|
if (new Set(executions.map((item) => item.runId)).size !== executions.length)
|
|
20847
|
-
throw new Error("
|
|
20886
|
+
throw new Error("Ghostget reused a Google run ID");
|
|
20848
20887
|
const first = executions[0];
|
|
20849
20888
|
for (const item of executions)
|
|
20850
20889
|
if (item.authId !== first.authId || item.authSha256 !== first.authSha256 || item.adapterVersion !== first.adapterVersion || item.adapterSha256 !== first.adapterSha256 || item.contractSha256 !== first.contractSha256) {
|
|
20851
|
-
throw new Error("
|
|
20890
|
+
throw new Error("Ghostget Google execution identity changed during pagination");
|
|
20852
20891
|
}
|
|
20853
20892
|
return { schemaVersion: 1, provider: "google-contacts", collection: spec.id, accountSubject: account, authId: auth, pages };
|
|
20854
20893
|
}
|
|
20855
|
-
function
|
|
20856
|
-
const snapshots = COLLECTIONS.map((spec) =>
|
|
20894
|
+
function ghostgetGoogleContactsSnapshots(options = {}) {
|
|
20895
|
+
const snapshots = COLLECTIONS.map((spec) => ghostgetGoogleContactsSnapshot(options, spec.id));
|
|
20857
20896
|
const first = snapshots[0];
|
|
20858
20897
|
const executions = snapshots.flatMap((snapshot) => snapshot.pages.map((page) => page.execution));
|
|
20859
20898
|
if (new Set(executions.map((execution) => execution.runId)).size !== executions.length) {
|
|
20860
|
-
throw new Error("
|
|
20899
|
+
throw new Error("Ghostget reused a Google run ID across contact collections");
|
|
20861
20900
|
}
|
|
20862
20901
|
for (const snapshot of snapshots) {
|
|
20863
20902
|
const execution = snapshot.pages[0].execution;
|
|
20864
20903
|
if (snapshot.accountSubject !== first.accountSubject || snapshot.authId !== first.authId || execution.authSha256 !== first.pages[0].execution.authSha256 || execution.adapterVersion !== first.pages[0].execution.adapterVersion || execution.adapterSha256 !== first.pages[0].execution.adapterSha256 || execution.contractSha256 !== first.pages[0].execution.contractSha256) {
|
|
20865
|
-
throw new Error("
|
|
20904
|
+
throw new Error("Ghostget Google execution identity changed across contact collections");
|
|
20866
20905
|
}
|
|
20867
20906
|
}
|
|
20868
20907
|
return snapshots;
|
|
@@ -21188,10 +21227,10 @@ function importGoogleContactsSnapshots(database, snapshots) {
|
|
|
21188
21227
|
}
|
|
21189
21228
|
}
|
|
21190
21229
|
function syncGoogleContacts(database, options = {}) {
|
|
21191
|
-
const snapshots =
|
|
21230
|
+
const snapshots = ghostgetGoogleContactsSnapshots(options);
|
|
21192
21231
|
for (const snapshot of snapshots)
|
|
21193
21232
|
for (const page of snapshot.pages) {
|
|
21194
|
-
|
|
21233
|
+
ledgerPeopleBladeGhostgetInvocation(database, {
|
|
21195
21234
|
provider: PROVIDER6,
|
|
21196
21235
|
accountKey: snapshot.accountSubject,
|
|
21197
21236
|
capability: "contacts",
|
|
@@ -24570,6 +24609,9 @@ function linkedInAccountKey(value) {
|
|
|
24570
24609
|
}
|
|
24571
24610
|
return key;
|
|
24572
24611
|
}
|
|
24612
|
+
function canonicalLinkedInProfileUrl(value, label) {
|
|
24613
|
+
return profileUrl(value, label);
|
|
24614
|
+
}
|
|
24573
24615
|
function ownerProfile(messages, supplied) {
|
|
24574
24616
|
const explicit = supplied === undefined ? null : profileUrl(supplied, "LinkedIn archive owner profile URL");
|
|
24575
24617
|
if (supplied !== undefined && explicit === null)
|
|
@@ -24974,6 +25016,342 @@ function linkedInStatsAll(database) {
|
|
|
24974
25016
|
WHERE account.provider='linkedin' ORDER BY account.account_key`, MODE7).map((row) => ({ ...row, remote_set_complete: false }));
|
|
24975
25017
|
}
|
|
24976
25018
|
|
|
25019
|
+
// src/local/providers/linkedin-contact-info.ts
|
|
25020
|
+
var LINKEDIN_CONTACT_INFO_ADAPTER_ID = "linkedin-web";
|
|
25021
|
+
var LINKEDIN_CONTACT_INFO_OPERATION_ID = "contacts.read";
|
|
25022
|
+
var LINKEDIN_CONTACT_INFO_ADAPTER_VERSION = "1.23.0";
|
|
25023
|
+
var LINKEDIN_CONTACT_INFO_CONTRACT_SHA256 = "8f80c234ca2b7706a49a5000012a11da78a36d38f3023c21854a240e16a13f43";
|
|
25024
|
+
var DEFAULT_LINKEDIN_AUTH = "linkedin-main";
|
|
25025
|
+
var PROVIDER11 = "linkedin";
|
|
25026
|
+
var MODE8 = "ghostget-contact-info";
|
|
25027
|
+
var RECORD_KIND = "contact-info";
|
|
25028
|
+
var OBSERVATION_BASIS2 = "linkedin-contact-info";
|
|
25029
|
+
var OBSERVATION_PRIORITY2 = 720;
|
|
25030
|
+
var MAX_PHONES = 8;
|
|
25031
|
+
var MAX_WEBSITES = 8;
|
|
25032
|
+
var EMAIL5 = /^(?=[\x21-\x7E]+$)[^@\s]+@[^@\s]+\.[^@\s]+$/u;
|
|
25033
|
+
var PHONE = /^\+?[0-9][0-9 .\-()]{6,30}[0-9]$/u;
|
|
25034
|
+
var VIEWER_SUBJECT = /^urn:li:fsd_profile:[0-9]{1,32}$/u;
|
|
25035
|
+
var PROFILE_URN = /^urn:li:fsd_profile:[A-Za-z0-9_-]{1,256}$/u;
|
|
25036
|
+
var VANITY = /^[A-Za-z0-9%._-]{1,1024}$/u;
|
|
25037
|
+
var DATE = /^[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])$/u;
|
|
25038
|
+
var BIRTHDAY = /^(?:[0-9]{4}-)?(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])$/u;
|
|
25039
|
+
var AUTH_ID3 = /^[a-z0-9][a-z0-9._:-]{0,255}$/u;
|
|
25040
|
+
function getRow9(database, sql, ...bindings) {
|
|
25041
|
+
return database.query(sql).get(...bindings);
|
|
25042
|
+
}
|
|
25043
|
+
function allRows7(database, sql, ...bindings) {
|
|
25044
|
+
return database.query(sql).all(...bindings);
|
|
25045
|
+
}
|
|
25046
|
+
function run8(database, sql, ...bindings) {
|
|
25047
|
+
return database.run(sql, bindings);
|
|
25048
|
+
}
|
|
25049
|
+
function insertedId9(value) {
|
|
25050
|
+
return Number(value.lastInsertRowid);
|
|
25051
|
+
}
|
|
25052
|
+
function record6(value, label) {
|
|
25053
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
25054
|
+
throw new Error(`${label} must be an object`);
|
|
25055
|
+
return value;
|
|
25056
|
+
}
|
|
25057
|
+
function exact4(value, keys, label) {
|
|
25058
|
+
const actual = Object.keys(value).sort();
|
|
25059
|
+
const expected = [...keys].sort();
|
|
25060
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
25061
|
+
throw new Error(`${label} changed its reviewed shape`);
|
|
25062
|
+
}
|
|
25063
|
+
}
|
|
25064
|
+
function text6(value, label, maximum) {
|
|
25065
|
+
if (typeof value !== "string")
|
|
25066
|
+
throw new Error(`${label} must be a string`);
|
|
25067
|
+
const normalized = value.normalize("NFC");
|
|
25068
|
+
if (normalized.length < 1 || Buffer.byteLength(normalized, "utf8") > maximum || /[\u0000-\u001F\u007F-\u009F]/u.test(normalized))
|
|
25069
|
+
throw new Error(`${label} is invalid`);
|
|
25070
|
+
return normalized;
|
|
25071
|
+
}
|
|
25072
|
+
function optionalText3(value, label, maximum) {
|
|
25073
|
+
return value === null ? null : text6(value, label, maximum);
|
|
25074
|
+
}
|
|
25075
|
+
function timestamp6(value, label) {
|
|
25076
|
+
const parsed = text6(value, label, 64);
|
|
25077
|
+
const milliseconds2 = Date.parse(parsed);
|
|
25078
|
+
if (!Number.isFinite(milliseconds2) || new Date(milliseconds2).toISOString() !== parsed) {
|
|
25079
|
+
throw new Error(`${label} must be a canonical UTC timestamp`);
|
|
25080
|
+
}
|
|
25081
|
+
return parsed;
|
|
25082
|
+
}
|
|
25083
|
+
function boundedList(value, label, maximum) {
|
|
25084
|
+
if (!Array.isArray(value) || value.length > maximum)
|
|
25085
|
+
throw new Error(`${label} exceeded its reviewed bound`);
|
|
25086
|
+
const items = value.map((item, index) => text6(item, `${label}[${index}]`, 2048));
|
|
25087
|
+
if (new Set(items).size !== items.length)
|
|
25088
|
+
throw new Error(`${label} contains duplicates`);
|
|
25089
|
+
return items;
|
|
25090
|
+
}
|
|
25091
|
+
function website(value, label) {
|
|
25092
|
+
let parsed;
|
|
25093
|
+
try {
|
|
25094
|
+
parsed = new URL(value);
|
|
25095
|
+
} catch (error) {
|
|
25096
|
+
throw new Error(`${label} is not an absolute URL`, { cause: error });
|
|
25097
|
+
}
|
|
25098
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:" || parsed.username !== "" || parsed.password !== "")
|
|
25099
|
+
throw new Error(`${label} must be a plain web URL`);
|
|
25100
|
+
return parsed.href;
|
|
25101
|
+
}
|
|
25102
|
+
function normalizedWebsite(value) {
|
|
25103
|
+
const parsed = new URL(value);
|
|
25104
|
+
parsed.hash = "";
|
|
25105
|
+
const path = parsed.pathname.length > 1 && parsed.pathname.endsWith("/") ? parsed.pathname.slice(0, -1) : parsed.pathname;
|
|
25106
|
+
return `${parsed.protocol}//${parsed.host.toLowerCase()}${path}${parsed.search}`;
|
|
25107
|
+
}
|
|
25108
|
+
function normalizedEmail3(value, label) {
|
|
25109
|
+
if (!EMAIL5.test(value) || Buffer.byteLength(value, "utf8") > 320)
|
|
25110
|
+
throw new Error(`${label} is not an email address`);
|
|
25111
|
+
return value.toLowerCase();
|
|
25112
|
+
}
|
|
25113
|
+
function parseFields(value, label) {
|
|
25114
|
+
const parsed = record6(value, label);
|
|
25115
|
+
exact4(parsed, ["email", "profileUrl", "connectedSince", "phones", "websites", "birthday"], label);
|
|
25116
|
+
const email2 = optionalText3(parsed.email, `${label}.email`, 320);
|
|
25117
|
+
if (email2 !== null)
|
|
25118
|
+
normalizedEmail3(email2, `${label}.email`);
|
|
25119
|
+
const profileUrl2 = optionalText3(parsed.profileUrl, `${label}.profileUrl`, 4096);
|
|
25120
|
+
if (profileUrl2 !== null && canonicalLinkedInProfileUrl(profileUrl2, `${label}.profileUrl`) === null) {
|
|
25121
|
+
throw new Error(`${label}.profileUrl is invalid`);
|
|
25122
|
+
}
|
|
25123
|
+
const connectedSince = optionalText3(parsed.connectedSince, `${label}.connectedSince`, 10);
|
|
25124
|
+
if (connectedSince !== null && !DATE.test(connectedSince))
|
|
25125
|
+
throw new Error(`${label}.connectedSince is invalid`);
|
|
25126
|
+
const phones = boundedList(parsed.phones, `${label}.phones`, MAX_PHONES);
|
|
25127
|
+
for (const [index, phone] of phones.entries()) {
|
|
25128
|
+
if (!PHONE.test(phone))
|
|
25129
|
+
throw new Error(`${label}.phones[${index}] is invalid`);
|
|
25130
|
+
}
|
|
25131
|
+
const websites = boundedList(parsed.websites, `${label}.websites`, MAX_WEBSITES).map((item, index) => website(item, `${label}.websites[${index}]`));
|
|
25132
|
+
const birthday2 = optionalText3(parsed.birthday, `${label}.birthday`, 10);
|
|
25133
|
+
if (birthday2 !== null && !BIRTHDAY.test(birthday2))
|
|
25134
|
+
throw new Error(`${label}.birthday is invalid`);
|
|
25135
|
+
return Object.freeze({ email: email2, profileUrl: profileUrl2, connectedSince, phones, websites, birthday: birthday2 });
|
|
25136
|
+
}
|
|
25137
|
+
function projectLinkedInContactInfo(value, expectedProfileUrl) {
|
|
25138
|
+
const output = record6(value, "Ghostget LinkedIn contact output");
|
|
25139
|
+
exact4(output, ["schemaVersion", "provider", "profile", "viewer", "observedAt", "completeness", "contact"], "contact output");
|
|
25140
|
+
if (output.schemaVersion !== 1 || output.provider !== PROVIDER11)
|
|
25141
|
+
throw new Error("Ghostget LinkedIn contact semantics drifted");
|
|
25142
|
+
const profile = record6(output.profile, "contact output.profile");
|
|
25143
|
+
exact4(profile, ["vanity", "profileUrn", "url", "relationship"], "contact output.profile");
|
|
25144
|
+
const vanity = text6(profile.vanity, "contact output.profile.vanity", 1024);
|
|
25145
|
+
if (!VANITY.test(vanity))
|
|
25146
|
+
throw new Error("Ghostget LinkedIn profile vanity is invalid");
|
|
25147
|
+
const profileUrn = text6(profile.profileUrn, "contact output.profile.profileUrn", 300);
|
|
25148
|
+
if (!PROFILE_URN.test(profileUrn))
|
|
25149
|
+
throw new Error("Ghostget LinkedIn profile URN is invalid");
|
|
25150
|
+
const url = canonicalLinkedInProfileUrl(text6(profile.url, "contact output.profile.url", 4096), "contact output.profile.url");
|
|
25151
|
+
if (url === null || url !== expectedProfileUrl) {
|
|
25152
|
+
throw new Error("Ghostget LinkedIn contact output does not bind the requested profile");
|
|
25153
|
+
}
|
|
25154
|
+
if (profile.relationship !== "first-degree")
|
|
25155
|
+
throw new Error("Ghostget LinkedIn contact output is not a first-degree read");
|
|
25156
|
+
const viewer = record6(output.viewer, "contact output.viewer");
|
|
25157
|
+
exact4(viewer, ["subject"], "contact output.viewer");
|
|
25158
|
+
const viewerSubject = text6(viewer.subject, "contact output.viewer.subject", 64);
|
|
25159
|
+
if (!VIEWER_SUBJECT.test(viewerSubject))
|
|
25160
|
+
throw new Error("Ghostget LinkedIn viewer subject is invalid");
|
|
25161
|
+
const observedAt = timestamp6(output.observedAt, "contact output.observedAt");
|
|
25162
|
+
if (output.completeness !== "complete" && output.completeness !== "partial") {
|
|
25163
|
+
throw new Error("Ghostget LinkedIn contact completeness is invalid");
|
|
25164
|
+
}
|
|
25165
|
+
const contact = parseFields(output.contact, "contact output.contact");
|
|
25166
|
+
const hasAny = contact.email !== null || contact.connectedSince !== null || contact.phones.length > 0 || contact.websites.length > 0 || contact.birthday !== null;
|
|
25167
|
+
if (hasAny !== (output.completeness === "complete")) {
|
|
25168
|
+
throw new Error("Ghostget LinkedIn contact completeness disagrees with its fields");
|
|
25169
|
+
}
|
|
25170
|
+
return Object.freeze({
|
|
25171
|
+
profileUrl: url,
|
|
25172
|
+
vanity,
|
|
25173
|
+
profileUrn,
|
|
25174
|
+
relationship: "first-degree",
|
|
25175
|
+
viewerSubject,
|
|
25176
|
+
observedAt,
|
|
25177
|
+
completeness: output.completeness,
|
|
25178
|
+
contact
|
|
25179
|
+
});
|
|
25180
|
+
}
|
|
25181
|
+
function authId2(value) {
|
|
25182
|
+
const parsed = (value ?? DEFAULT_LINKEDIN_AUTH).trim();
|
|
25183
|
+
if (!AUTH_ID3.test(parsed))
|
|
25184
|
+
throw new Error("Ghostget auth ID is invalid");
|
|
25185
|
+
return parsed;
|
|
25186
|
+
}
|
|
25187
|
+
function envelope(value, input, auth) {
|
|
25188
|
+
return validatePeopleBladeGhostgetInvocation(value, {
|
|
25189
|
+
adapterId: LINKEDIN_CONTACT_INFO_ADAPTER_ID,
|
|
25190
|
+
operationId: LINKEDIN_CONTACT_INFO_OPERATION_ID,
|
|
25191
|
+
authId: auth,
|
|
25192
|
+
input
|
|
25193
|
+
}, {
|
|
25194
|
+
adapterVersion: LINKEDIN_CONTACT_INFO_ADAPTER_VERSION,
|
|
25195
|
+
authKind: "browser-profile",
|
|
25196
|
+
transport: "web-session-api",
|
|
25197
|
+
contractSha256: LINKEDIN_CONTACT_INFO_CONTRACT_SHA256,
|
|
25198
|
+
finalOrigin: "https://www.linkedin.com"
|
|
25199
|
+
});
|
|
25200
|
+
}
|
|
25201
|
+
function storedResultFromJson(value) {
|
|
25202
|
+
const parsed = record6(JSON.parse(value), "stored LinkedIn contact-info result");
|
|
25203
|
+
exact4(parsed, [
|
|
25204
|
+
"account_key",
|
|
25205
|
+
"person_id",
|
|
25206
|
+
"provider_resource_id",
|
|
25207
|
+
"profile_url",
|
|
25208
|
+
"vanity",
|
|
25209
|
+
"observed_at",
|
|
25210
|
+
"completeness",
|
|
25211
|
+
"connected_since",
|
|
25212
|
+
"email_recorded",
|
|
25213
|
+
"phones_recorded",
|
|
25214
|
+
"phones_unresolved",
|
|
25215
|
+
"websites_recorded",
|
|
25216
|
+
"birthday_recorded"
|
|
25217
|
+
], "stored LinkedIn contact-info result");
|
|
25218
|
+
return parsed;
|
|
25219
|
+
}
|
|
25220
|
+
function metadata3(value) {
|
|
25221
|
+
return record6(JSON.parse(value), "LinkedIn resource metadata");
|
|
25222
|
+
}
|
|
25223
|
+
function readLinkedInContactInfo(database, options) {
|
|
25224
|
+
const account = linkedInAccountKey(options.accountKey);
|
|
25225
|
+
const auth = authId2(options.authId);
|
|
25226
|
+
if (!Number.isSafeInteger(options.personId) || options.personId < 1)
|
|
25227
|
+
throw new Error("person ID is invalid");
|
|
25228
|
+
const resources = allRows7(database, `SELECT id,person_id,profile_url,resource_id,metadata_json FROM provider_resources
|
|
25229
|
+
WHERE provider=? AND account_key=? AND resource_type='profile' AND active=1 AND person_id=?
|
|
25230
|
+
ORDER BY id`, PROVIDER11, account, options.personId);
|
|
25231
|
+
if (resources.length === 0) {
|
|
25232
|
+
throw new Error("This person has no active LinkedIn profile in that account; import the official LinkedIn export first");
|
|
25233
|
+
}
|
|
25234
|
+
if (resources.length > 1) {
|
|
25235
|
+
throw new Error("This person has more than one active LinkedIn profile in that account; review its identity decisions first");
|
|
25236
|
+
}
|
|
25237
|
+
const resource = resources[0];
|
|
25238
|
+
const profileUrl2 = canonicalLinkedInProfileUrl(resource.profile_url ?? resource.resource_id, "stored LinkedIn profile URL");
|
|
25239
|
+
if (profileUrl2 === null)
|
|
25240
|
+
throw new Error("stored LinkedIn profile resource lacks an exact profile URL");
|
|
25241
|
+
const owner = getRow9(database, "SELECT owner_key FROM archive_accounts WHERE provider=? AND account_key=?", PROVIDER11, account);
|
|
25242
|
+
if (owner !== null && owner.owner_key === profileUrl2) {
|
|
25243
|
+
throw new Error("Contact info is read for connections, not the account owner's own profile");
|
|
25244
|
+
}
|
|
25245
|
+
const input = Object.freeze({ profile_url: profileUrl2 });
|
|
25246
|
+
const invocation = callPeopleBladeGhostget({
|
|
25247
|
+
adapterId: LINKEDIN_CONTACT_INFO_ADAPTER_ID,
|
|
25248
|
+
operationId: LINKEDIN_CONTACT_INFO_OPERATION_ID,
|
|
25249
|
+
authId: auth,
|
|
25250
|
+
input
|
|
25251
|
+
}, options.invoke);
|
|
25252
|
+
const parsed = envelope(invocation, input, auth);
|
|
25253
|
+
const projection2 = projectLinkedInContactInfo(parsed.output, profileUrl2);
|
|
25254
|
+
const sourceSha256 = sha256(canonicalJson(projection2));
|
|
25255
|
+
const execution = parsed.execution;
|
|
25256
|
+
const personId = resource.person_id;
|
|
25257
|
+
const resourceId = resource.id;
|
|
25258
|
+
const resolvedPhones = projection2.contact.phones.map((phone) => ({ phone, normalized: resolvePhoneIdentity(phone, null) }));
|
|
25259
|
+
const stored = {
|
|
25260
|
+
account_key: account,
|
|
25261
|
+
person_id: personId,
|
|
25262
|
+
provider_resource_id: resourceId,
|
|
25263
|
+
profile_url: profileUrl2,
|
|
25264
|
+
vanity: projection2.vanity,
|
|
25265
|
+
observed_at: projection2.observedAt,
|
|
25266
|
+
completeness: projection2.completeness,
|
|
25267
|
+
connected_since: projection2.contact.connectedSince,
|
|
25268
|
+
email_recorded: projection2.contact.email !== null,
|
|
25269
|
+
phones_recorded: resolvedPhones.filter((entry) => entry.normalized !== null).length,
|
|
25270
|
+
phones_unresolved: resolvedPhones.filter((entry) => entry.normalized === null).length,
|
|
25271
|
+
websites_recorded: projection2.contact.websites.length,
|
|
25272
|
+
birthday_recorded: projection2.contact.birthday !== null
|
|
25273
|
+
};
|
|
25274
|
+
database.exec("BEGIN IMMEDIATE");
|
|
25275
|
+
try {
|
|
25276
|
+
const cached = getRow9(database, "SELECT id,result_json FROM source_runs WHERE provider=? AND account_key=? AND mode=? AND source_sha256=?", PROVIDER11, account, MODE8, sourceSha256);
|
|
25277
|
+
if (cached !== null) {
|
|
25278
|
+
database.exec("COMMIT");
|
|
25279
|
+
return { ...storedResultFromJson(cached.result_json), run_id: cached.id, replayed: true };
|
|
25280
|
+
}
|
|
25281
|
+
const runId = insertedId9(run8(database, `INSERT INTO source_runs(
|
|
25282
|
+
provider,account_key,mode,source_sha256,source_locator_sha256,completeness,result_json,started_at,completed_at
|
|
25283
|
+
) VALUES (?,?,?,?,?,?,?,?,?)`, PROVIDER11, account, MODE8, sourceSha256, sha256(profileUrl2), projection2.completeness, canonicalJson(stored), execution.startedAt, execution.finishedAt));
|
|
25284
|
+
const methodLabel = `${OBSERVATION_BASIS2}:${sha256(profileUrl2).slice(0, 16)}`;
|
|
25285
|
+
const methodMetadata = canonicalJson({ observedIn: OBSERVATION_BASIS2, observedAt: projection2.observedAt });
|
|
25286
|
+
run8(database, "UPDATE contact_methods SET active=0 WHERE provider_resource_id=? AND label=?", resourceId, methodLabel);
|
|
25287
|
+
if (projection2.contact.email !== null) {
|
|
25288
|
+
run8(database, `INSERT INTO contact_methods(
|
|
25289
|
+
person_id,kind,value,normalized_value,label,confidence,first_seen_run_id,last_seen_run_id,
|
|
25290
|
+
metadata_json,provider_resource_id,active,identity_eligible
|
|
25291
|
+
) VALUES (?,'email',?,?,?,'exact',?,?,?,?,1,1)
|
|
25292
|
+
ON CONFLICT(person_id,kind,normalized_value,label) DO UPDATE SET
|
|
25293
|
+
value=excluded.value,last_seen_run_id=excluded.last_seen_run_id,metadata_json=excluded.metadata_json,
|
|
25294
|
+
provider_resource_id=excluded.provider_resource_id,active=1,identity_eligible=1`, personId, projection2.contact.email, normalizedEmail3(projection2.contact.email, "contact email"), methodLabel, runId, runId, methodMetadata, resourceId);
|
|
25295
|
+
}
|
|
25296
|
+
for (const { phone, normalized } of resolvedPhones) {
|
|
25297
|
+
if (normalized === null)
|
|
25298
|
+
continue;
|
|
25299
|
+
run8(database, `INSERT INTO contact_methods(
|
|
25300
|
+
person_id,kind,value,normalized_value,label,confidence,first_seen_run_id,last_seen_run_id,
|
|
25301
|
+
metadata_json,provider_resource_id,active,identity_eligible
|
|
25302
|
+
) VALUES (?,'phone',?,?,?,'exact',?,?,?,?,1,1)
|
|
25303
|
+
ON CONFLICT(person_id,kind,normalized_value,label) DO UPDATE SET
|
|
25304
|
+
value=excluded.value,last_seen_run_id=excluded.last_seen_run_id,metadata_json=excluded.metadata_json,
|
|
25305
|
+
provider_resource_id=excluded.provider_resource_id,active=1,identity_eligible=1`, personId, phone, normalized, methodLabel, runId, runId, methodMetadata, resourceId);
|
|
25306
|
+
}
|
|
25307
|
+
for (const site of projection2.contact.websites) {
|
|
25308
|
+
run8(database, `INSERT INTO contact_methods(
|
|
25309
|
+
person_id,kind,value,normalized_value,label,confidence,first_seen_run_id,last_seen_run_id,
|
|
25310
|
+
metadata_json,provider_resource_id,active,identity_eligible
|
|
25311
|
+
) VALUES (?,'url',?,?,?,'exact',?,?,?,?,1,0)
|
|
25312
|
+
ON CONFLICT(person_id,kind,normalized_value,label) DO UPDATE SET
|
|
25313
|
+
value=excluded.value,last_seen_run_id=excluded.last_seen_run_id,metadata_json=excluded.metadata_json,
|
|
25314
|
+
provider_resource_id=excluded.provider_resource_id,active=1,identity_eligible=0`, personId, site, normalizedWebsite(site), methodLabel, runId, runId, methodMetadata, resourceId);
|
|
25315
|
+
}
|
|
25316
|
+
run8(database, "UPDATE person_field_observations SET active=0 WHERE provider_resource_id=? AND basis=?", resourceId, OBSERVATION_BASIS2);
|
|
25317
|
+
if (projection2.contact.birthday !== null) {
|
|
25318
|
+
run8(database, `INSERT INTO person_field_observations(
|
|
25319
|
+
provider_resource_id,field,value,basis,priority,active,first_seen_run_id,last_seen_run_id,metadata_json
|
|
25320
|
+
) VALUES (?,'birthday',?,?,?,1,?,?,'{}')
|
|
25321
|
+
ON CONFLICT(provider_resource_id,field,value,basis) DO UPDATE SET
|
|
25322
|
+
priority=excluded.priority,active=1,last_seen_run_id=excluded.last_seen_run_id`, resourceId, projection2.contact.birthday, OBSERVATION_BASIS2, OBSERVATION_PRIORITY2, runId, runId);
|
|
25323
|
+
run8(database, "UPDATE people SET birthday=coalesce(birthday,?) WHERE id=?", projection2.contact.birthday, personId);
|
|
25324
|
+
}
|
|
25325
|
+
const resourceMetadata = {
|
|
25326
|
+
...metadata3(resource.metadata_json),
|
|
25327
|
+
linkedInContactInfo: {
|
|
25328
|
+
observedAt: projection2.observedAt,
|
|
25329
|
+
completeness: projection2.completeness,
|
|
25330
|
+
connectedSince: projection2.contact.connectedSince,
|
|
25331
|
+
viewerSubject: projection2.viewerSubject,
|
|
25332
|
+
runId
|
|
25333
|
+
}
|
|
25334
|
+
};
|
|
25335
|
+
run8(database, "UPDATE provider_resources SET last_seen_run_id=?,metadata_json=? WHERE id=?", runId, canonicalJson(resourceMetadata), resourceId);
|
|
25336
|
+
run8(database, `INSERT INTO source_records(
|
|
25337
|
+
run_id,ordinal,record_kind,record_sha256,person_id,provider_resource_id,resolution,projection_json,error_text
|
|
25338
|
+
) VALUES (?,1,?,?,?,?,'matched',?,NULL)`, runId, RECORD_KIND, sourceSha256, personId, resourceId, canonicalJson(projection2));
|
|
25339
|
+
ledgerPeopleBladeGhostgetInvocation(database, {
|
|
25340
|
+
provider: PROVIDER11,
|
|
25341
|
+
accountKey: account,
|
|
25342
|
+
capability: "contact_info",
|
|
25343
|
+
execution,
|
|
25344
|
+
costBasis: "local-browser-read",
|
|
25345
|
+
metadata: { profileUrlSha256: sha256(profileUrl2), completeness: projection2.completeness }
|
|
25346
|
+
});
|
|
25347
|
+
database.exec("COMMIT");
|
|
25348
|
+
return { ...stored, run_id: runId, replayed: false };
|
|
25349
|
+
} catch (error) {
|
|
25350
|
+
database.exec("ROLLBACK");
|
|
25351
|
+
throw error;
|
|
25352
|
+
}
|
|
25353
|
+
}
|
|
25354
|
+
|
|
24977
25355
|
// src/local/providers/whatsapp.ts
|
|
24978
25356
|
import { createHash as createHash9 } from "crypto";
|
|
24979
25357
|
var CONTACTS_CONTRACT_SHA2562 = "0bd3c994f16608d5886f1192dd58d41736e4bd01add66a5a3e029efca8b14a38";
|
|
@@ -24994,18 +25372,18 @@ var DEVICE_LID_JID = /^([0-9]{5,32}):[0-9]{1,5}@lid$/u;
|
|
|
24994
25372
|
var GROUP_JID = /^[0-9]{5,32}(?:-[0-9]{5,20})?@g\.us$/u;
|
|
24995
25373
|
var OTHER_JID = /^(?:0@s\.whatsapp\.net|[0-9]{5,32}@newsletter|(?:status|[0-9]{5,32})@broadcast)$/u;
|
|
24996
25374
|
var MESSAGE_ID = /^[A-Za-z0-9._~:-]{1,256}$/u;
|
|
24997
|
-
var
|
|
25375
|
+
var PROVIDER12 = "whatsapp";
|
|
24998
25376
|
var STREAM2 = "messages";
|
|
24999
|
-
function
|
|
25377
|
+
function getRow10(database, sql, ...values) {
|
|
25000
25378
|
return database.query(sql).get(...values);
|
|
25001
25379
|
}
|
|
25002
|
-
function
|
|
25380
|
+
function run9(database, sql, ...values) {
|
|
25003
25381
|
return database.run(sql, values);
|
|
25004
25382
|
}
|
|
25005
|
-
function
|
|
25383
|
+
function insertedId10(value) {
|
|
25006
25384
|
return Number(value.lastInsertRowid);
|
|
25007
25385
|
}
|
|
25008
|
-
function
|
|
25386
|
+
function record7(value, label) {
|
|
25009
25387
|
if (typeof value !== "object" || value === null || Array.isArray(value) || Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
25010
25388
|
throw new Error(`${label} must be a plain object`);
|
|
25011
25389
|
}
|
|
@@ -25017,14 +25395,14 @@ function record6(value, label) {
|
|
|
25017
25395
|
throw new Error(`${label} has symbol keys`);
|
|
25018
25396
|
return value;
|
|
25019
25397
|
}
|
|
25020
|
-
function
|
|
25398
|
+
function exact5(value, keys, label) {
|
|
25021
25399
|
const actual = Object.keys(value).sort();
|
|
25022
25400
|
const expected = [...keys].sort();
|
|
25023
25401
|
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
25024
25402
|
throw new Error(`${label} contains unsupported or missing fields`);
|
|
25025
25403
|
}
|
|
25026
25404
|
}
|
|
25027
|
-
function
|
|
25405
|
+
function text7(value, label, maximum = 4096, empty = false) {
|
|
25028
25406
|
if (typeof value !== "string" || !empty && value.length === 0 || Buffer.byteLength(value, "utf8") > maximum || /[\u0000\r]/u.test(value)) {
|
|
25029
25407
|
throw new Error(`${label} must be bounded text`);
|
|
25030
25408
|
}
|
|
@@ -25041,7 +25419,7 @@ function bool3(value, label) {
|
|
|
25041
25419
|
return value;
|
|
25042
25420
|
}
|
|
25043
25421
|
function digest5(value, label) {
|
|
25044
|
-
const parsed =
|
|
25422
|
+
const parsed = text7(value, label, 64);
|
|
25045
25423
|
if (!SHA2563.test(parsed))
|
|
25046
25424
|
throw new Error(`${label} is invalid`);
|
|
25047
25425
|
return parsed;
|
|
@@ -25059,11 +25437,11 @@ function stableJson6(value) {
|
|
|
25059
25437
|
}
|
|
25060
25438
|
if (Array.isArray(value))
|
|
25061
25439
|
return `[${value.map(stableJson6).join(",")}]`;
|
|
25062
|
-
const parsed =
|
|
25440
|
+
const parsed = record7(value, "canonical JSON");
|
|
25063
25441
|
return `{${Object.keys(parsed).sort().map((key) => `${JSON.stringify(key)}:${stableJson6(parsed[key])}`).join(",")}}`;
|
|
25064
25442
|
}
|
|
25065
|
-
function
|
|
25066
|
-
const parsed =
|
|
25443
|
+
function timestamp7(value, label) {
|
|
25444
|
+
const parsed = text7(value, label, 40);
|
|
25067
25445
|
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/u.test(parsed))
|
|
25068
25446
|
throw new Error(`${label} is invalid`);
|
|
25069
25447
|
const milliseconds2 = Date.parse(parsed);
|
|
@@ -25072,25 +25450,25 @@ function timestamp6(value, label) {
|
|
|
25072
25450
|
return new Date(milliseconds2).toISOString();
|
|
25073
25451
|
}
|
|
25074
25452
|
function decimal(value, label) {
|
|
25075
|
-
const parsed =
|
|
25453
|
+
const parsed = text7(value, label, 19);
|
|
25076
25454
|
if (!DECIMAL.test(parsed) || BigInt(parsed) > BigInt("9223372036854775807"))
|
|
25077
25455
|
throw new Error(`${label} is invalid`);
|
|
25078
25456
|
return parsed;
|
|
25079
25457
|
}
|
|
25080
|
-
function
|
|
25081
|
-
const parsed =
|
|
25458
|
+
function authId3(value) {
|
|
25459
|
+
const parsed = text7(value, "WhatsApp auth ID", 48);
|
|
25082
25460
|
if (!/^[a-z][a-z0-9-]{0,47}$/u.test(parsed))
|
|
25083
25461
|
throw new Error("WhatsApp auth ID is invalid");
|
|
25084
25462
|
return parsed;
|
|
25085
25463
|
}
|
|
25086
25464
|
function subject(value) {
|
|
25087
|
-
const parsed =
|
|
25465
|
+
const parsed = text7(value, "WhatsApp account subject", 64);
|
|
25088
25466
|
if (!SUBJECT2.test(parsed))
|
|
25089
25467
|
throw new Error("WhatsApp account subject is invalid");
|
|
25090
25468
|
return parsed;
|
|
25091
25469
|
}
|
|
25092
25470
|
function canonicalJid(value, label, device = false) {
|
|
25093
|
-
const parsed =
|
|
25471
|
+
const parsed = text7(value, label, 96);
|
|
25094
25472
|
const user = USER_JID.exec(parsed) ?? (device ? DEVICE_USER_JID.exec(parsed) : null);
|
|
25095
25473
|
if (user?.[1] !== undefined)
|
|
25096
25474
|
return { jid: `${user[1]}@s.whatsapp.net`, kind: "user" };
|
|
@@ -25112,8 +25490,8 @@ function directJid(value, label) {
|
|
|
25112
25490
|
function ownerJid(accountSubject) {
|
|
25113
25491
|
return accountSubject.startsWith("whatsapp:pn:") ? `${accountSubject.slice(12)}@s.whatsapp.net` : `${accountSubject.slice(13)}@lid`;
|
|
25114
25492
|
}
|
|
25115
|
-
function
|
|
25116
|
-
const parsed =
|
|
25493
|
+
function envelope2(value, input, auth) {
|
|
25494
|
+
const parsed = validatePeopleBladeGhostgetInvocation(value, {
|
|
25117
25495
|
adapterId: "whatsapp-web",
|
|
25118
25496
|
operationId: "contacts.list",
|
|
25119
25497
|
authId: auth,
|
|
@@ -25125,25 +25503,25 @@ function envelope(value, input, auth) {
|
|
|
25125
25503
|
contractSha256: CONTACTS_CONTRACT_SHA2562,
|
|
25126
25504
|
finalOrigin: "https://web.whatsapp.com"
|
|
25127
25505
|
});
|
|
25128
|
-
return { output:
|
|
25506
|
+
return { output: record7(parsed.output, "Ghostget output"), execution: parsed.execution };
|
|
25129
25507
|
}
|
|
25130
25508
|
var CONTACT_KEYS = ["providerId", "jidKind", "phone", "redactedPhone", "firstName", "fullName", "pushName", "businessName", "displayName", "displayNameBasis", "alias", "tags", "updatedAt", "localProjectionStatsComplete", "sentCount", "sentCountComplete", "sentCountLowerBound", "sentCountTruncated", "receivedCount", "receivedCountComplete", "receivedCountLowerBound", "receivedCountTruncated", "lastSentAt", "lastSentAtComplete", "lastSentAtBasis", "sentStatsIncompleteReasons", "lastReceivedAt", "lastReceivedAtComplete", "lastReceivedAtBasis", "receivedStatsIncompleteReasons"];
|
|
25131
25509
|
function parseContact4(value, label) {
|
|
25132
|
-
const parsed =
|
|
25133
|
-
|
|
25510
|
+
const parsed = record7(value, label);
|
|
25511
|
+
exact5(parsed, CONTACT_KEYS, label);
|
|
25134
25512
|
const identity = directJid(parsed.providerId, `${label}.providerId`);
|
|
25135
25513
|
if (parsed.jidKind !== identity.kind)
|
|
25136
25514
|
throw new Error(`${label}.jidKind drifted`);
|
|
25137
|
-
const phone = parsed.phone === null ? null :
|
|
25515
|
+
const phone = parsed.phone === null ? null : text7(parsed.phone, `${label}.phone`, 20);
|
|
25138
25516
|
if (phone !== (USER_JID.exec(identity.jid)?.[1] ?? null))
|
|
25139
25517
|
throw new Error(`${label}.phone drifted`);
|
|
25140
|
-
const displayName2 = parsed.displayName === null ? null :
|
|
25518
|
+
const displayName2 = parsed.displayName === null ? null : text7(parsed.displayName, `${label}.displayName`, 512);
|
|
25141
25519
|
const bases = ["full-name", "push-name", "business-name", "first-name", "redacted-phone", "phone", "unavailable"];
|
|
25142
25520
|
if (!bases.includes(parsed.displayNameBasis))
|
|
25143
25521
|
throw new Error(`${label}.displayNameBasis is invalid`);
|
|
25144
25522
|
for (const key of ["redactedPhone", "firstName", "fullName", "pushName", "businessName"])
|
|
25145
25523
|
if (parsed[key] !== null)
|
|
25146
|
-
|
|
25524
|
+
text7(parsed[key], `${label}.${key}`, 512);
|
|
25147
25525
|
if (parsed.alias !== null || parsed.updatedAt !== null || !Array.isArray(parsed.tags) || parsed.tags.length !== 0 || parsed.localProjectionStatsComplete !== false || parsed.sentCount !== null || parsed.sentCountComplete !== false || parsed.sentCountLowerBound !== false || parsed.sentCountTruncated !== false || parsed.receivedCount !== null || parsed.receivedCountComplete !== false || parsed.receivedCountLowerBound !== false || parsed.receivedCountTruncated !== false || parsed.lastSentAt !== null || parsed.lastSentAtComplete !== false || parsed.lastSentAtBasis !== "unavailable" || parsed.lastReceivedAt !== null || parsed.lastReceivedAtComplete !== false || parsed.lastReceivedAtBasis !== "unavailable" || stableJson6(parsed.sentStatsIncompleteReasons) !== stableJson6(["whatsapp-message-store-account-owner-unavailable"]) || stableJson6(parsed.receivedStatsIncompleteReasons) !== stableJson6(["whatsapp-message-store-account-owner-unavailable"])) {
|
|
25148
25526
|
throw new Error(`${label} changed bounded contact semantics`);
|
|
25149
25527
|
}
|
|
@@ -25156,11 +25534,11 @@ function parseContact4(value, label) {
|
|
|
25156
25534
|
};
|
|
25157
25535
|
}
|
|
25158
25536
|
function parseContactPage(value, input, auth) {
|
|
25159
|
-
const parsed =
|
|
25537
|
+
const parsed = envelope2(value, input, auth);
|
|
25160
25538
|
const output = parsed.output;
|
|
25161
|
-
|
|
25539
|
+
exact5(output, ["provider", "operation", "accountSubject", "projection", "contacts", "nextCursor", "localContactTablePageComplete", "remoteContactSetComplete", "contactSetIncompleteReasons", "statsScope", "statsCompleteness"], "contact output");
|
|
25162
25540
|
if (output.provider !== "whatsapp" || output.operation !== "contacts.list" || output.projection !== "quiescent-account-bound-session-store" || output.remoteContactSetComplete !== false || output.statsScope !== "unavailable" || output.statsCompleteness !== "unavailable" || stableJson6(output.contactSetIncompleteReasons) !== stableJson6(["linked-device-contact-sync-coverage-unknown"]) || input.collection !== "contacts") {
|
|
25163
|
-
throw new Error("
|
|
25541
|
+
throw new Error("Ghostget contact semantics drifted");
|
|
25164
25542
|
}
|
|
25165
25543
|
const limit = integer6(input.limit, "contact limit", 1, 100);
|
|
25166
25544
|
if (!Array.isArray(output.contacts) || output.contacts.length > limit)
|
|
@@ -25183,10 +25561,10 @@ function parseContactPage(value, input, auth) {
|
|
|
25183
25561
|
return { input, accountSubject, contacts, nextCursor, localPageComplete: complete, safeOutputSha256, execution: parsed.execution };
|
|
25184
25562
|
}
|
|
25185
25563
|
function generation(value) {
|
|
25186
|
-
const parsed =
|
|
25187
|
-
|
|
25188
|
-
const identity =
|
|
25189
|
-
|
|
25564
|
+
const parsed = record7(value, "projection generation");
|
|
25565
|
+
exact5(parsed, ["messageStoreIdentity", "schemaFingerprint"], "projection generation");
|
|
25566
|
+
const identity = record7(parsed.messageStoreIdentity, "message-store identity");
|
|
25567
|
+
exact5(identity, ["dev", "ino"], "message-store identity");
|
|
25190
25568
|
const dev = decimal(identity.dev, "message-store dev");
|
|
25191
25569
|
const ino = decimal(identity.ino, "message-store ino");
|
|
25192
25570
|
if (ino === "0" || parsed.schemaFingerprint !== PROJECTION_SCHEMA_FINGERPRINT)
|
|
@@ -25194,8 +25572,8 @@ function generation(value) {
|
|
|
25194
25572
|
return { messageStoreIdentity: { dev, ino }, schemaFingerprint: PROJECTION_SCHEMA_FINGERPRINT };
|
|
25195
25573
|
}
|
|
25196
25574
|
function checkpoint(value, label) {
|
|
25197
|
-
const parsed =
|
|
25198
|
-
|
|
25575
|
+
const parsed = record7(value, label);
|
|
25576
|
+
exact5(parsed, ["cursor", "anchor"], label);
|
|
25199
25577
|
const cursor = decimal(parsed.cursor, `${label}.cursor`);
|
|
25200
25578
|
const anchor = parsed.anchor === null ? null : digest5(parsed.anchor, `${label}.anchor`);
|
|
25201
25579
|
if (cursor === "0" !== (anchor === null))
|
|
@@ -25203,14 +25581,14 @@ function checkpoint(value, label) {
|
|
|
25203
25581
|
return { cursor, anchor };
|
|
25204
25582
|
}
|
|
25205
25583
|
function parseInteraction(value, label) {
|
|
25206
|
-
const parsed =
|
|
25207
|
-
|
|
25584
|
+
const parsed = record7(value, label);
|
|
25585
|
+
exact5(parsed, ["rowid", "chatJid", "messageId", "senderJid", "timestamp", "fromMe", "chatKind"], label);
|
|
25208
25586
|
const rowid = decimal(parsed.rowid, `${label}.rowid`);
|
|
25209
25587
|
if (rowid === "0")
|
|
25210
25588
|
throw new Error(`${label}.rowid is invalid`);
|
|
25211
25589
|
const chat = canonicalJid(parsed.chatJid, `${label}.chatJid`);
|
|
25212
25590
|
const sender = parsed.senderJid === null ? null : canonicalJid(parsed.senderJid, `${label}.senderJid`, true).jid;
|
|
25213
|
-
const messageId =
|
|
25591
|
+
const messageId = text7(parsed.messageId, `${label}.messageId`, 256);
|
|
25214
25592
|
if (!MESSAGE_ID.test(messageId))
|
|
25215
25593
|
throw new Error(`${label}.messageId is invalid`);
|
|
25216
25594
|
const kinds = ["dm", "group", "broadcast", "newsletter", "unknown"];
|
|
@@ -25224,7 +25602,7 @@ function parseInteraction(value, label) {
|
|
|
25224
25602
|
chatJid: chat.jid,
|
|
25225
25603
|
messageId,
|
|
25226
25604
|
senderJid: sender,
|
|
25227
|
-
timestamp:
|
|
25605
|
+
timestamp: timestamp7(parsed.timestamp, `${label}.timestamp`),
|
|
25228
25606
|
fromMe: bool3(parsed.fromMe, `${label}.fromMe`),
|
|
25229
25607
|
chatKind: parsed.chatKind
|
|
25230
25608
|
};
|
|
@@ -25233,9 +25611,9 @@ function interactionAnchor(item) {
|
|
|
25233
25611
|
return createHash9("sha256").update(item.rowid).update("\x00").update(item.chatJid).update("\x00").update(item.messageId).digest("hex");
|
|
25234
25612
|
}
|
|
25235
25613
|
function parseInteractionPage(value, input, auth) {
|
|
25236
|
-
const parsed =
|
|
25614
|
+
const parsed = envelope2(value, input, auth);
|
|
25237
25615
|
const output = parsed.output;
|
|
25238
|
-
|
|
25616
|
+
exact5(output, ["provider", "operation", "accountSubject", "contactCollection", "projection", "projectionGeneration", "interactions", "nextCursor", "localInsertPageComplete", "checkpoint", "remoteHistoryComplete", "incompleteReasons"], "interaction output");
|
|
25239
25617
|
if (output.provider !== "whatsapp" || output.operation !== "contacts.list" || output.contactCollection !== "interactions" || output.projection !== "quiescent-account-bound-local-message-inserts" || output.remoteHistoryComplete !== false || stableJson6(output.incompleteReasons) !== stableJson6(["linked-device-history-coverage-unknown", "rowid-cursor-discovers-inserts-only"]) || input.collection !== "interactions") {
|
|
25240
25618
|
throw new Error("interaction projection semantics drifted");
|
|
25241
25619
|
}
|
|
@@ -25271,8 +25649,8 @@ function parseInteractionPage(value, input, auth) {
|
|
|
25271
25649
|
execution: parsed.execution
|
|
25272
25650
|
};
|
|
25273
25651
|
}
|
|
25274
|
-
function
|
|
25275
|
-
const auth =
|
|
25652
|
+
function ghostgetWhatsAppSnapshot(options = {}) {
|
|
25653
|
+
const auth = authId3(options.authId ?? DEFAULT_WHATSAPP_AUTH);
|
|
25276
25654
|
const contactPageSize = options.contactPageSize === undefined ? DEFAULT_WHATSAPP_CONTACT_PAGE_SIZE : integer6(options.contactPageSize, "contact page size", 1, 100);
|
|
25277
25655
|
const interactionPageSize = options.interactionPageSize === undefined ? DEFAULT_WHATSAPP_INTERACTION_PAGE_SIZE : integer6(options.interactionPageSize, "interaction page size", 1, 1000);
|
|
25278
25656
|
const supplied = options.checkpoint;
|
|
@@ -25281,7 +25659,7 @@ function wrenchWhatsAppSnapshot(options = {}) {
|
|
|
25281
25659
|
const startGeneration = supplied?.generation === undefined ? undefined : generation(supplied.generation);
|
|
25282
25660
|
if (startCursor === "0" !== (startAnchor === null))
|
|
25283
25661
|
throw new Error("checkpoint is inconsistent");
|
|
25284
|
-
const call = (_operation, input) =>
|
|
25662
|
+
const call = (_operation, input) => callPeopleBladeGhostget({
|
|
25285
25663
|
adapterId: "whatsapp-web",
|
|
25286
25664
|
operationId: "contacts.list",
|
|
25287
25665
|
authId: auth,
|
|
@@ -25333,11 +25711,11 @@ function wrenchWhatsAppSnapshot(options = {}) {
|
|
|
25333
25711
|
throw new Error("WhatsApp interaction pagination exceeded its bound");
|
|
25334
25712
|
const executions = [...contactPages, ...interactionPages].map((page) => page.execution);
|
|
25335
25713
|
if (new Set(executions.map((item) => item.runId)).size !== executions.length)
|
|
25336
|
-
throw new Error("
|
|
25714
|
+
throw new Error("Ghostget reused a WhatsApp run ID");
|
|
25337
25715
|
const first = executions[0];
|
|
25338
25716
|
for (const item of executions)
|
|
25339
25717
|
if (item.authId !== first.authId || item.authSha256 !== first.authSha256 || item.adapterVersion !== first.adapterVersion || item.adapterSha256 !== first.adapterSha256 || item.contractSha256 !== first.contractSha256)
|
|
25340
|
-
throw new Error("
|
|
25718
|
+
throw new Error("Ghostget execution identity changed during sync");
|
|
25341
25719
|
return {
|
|
25342
25720
|
schemaVersion: 2,
|
|
25343
25721
|
provider: "whatsapp",
|
|
@@ -25358,47 +25736,47 @@ function generationSha256(value) {
|
|
|
25358
25736
|
return sha2569(stableJson6(value));
|
|
25359
25737
|
}
|
|
25360
25738
|
function ensureResource2(database, account, sourceRealmId, jid, name, basis, runId) {
|
|
25361
|
-
const existing =
|
|
25362
|
-
WHERE provider=? AND account_key=? AND resource_type='jid' AND resource_id=?`,
|
|
25739
|
+
const existing = getRow10(database, `SELECT id,person_id,source_realm_id FROM provider_resources
|
|
25740
|
+
WHERE provider=? AND account_key=? AND resource_type='jid' AND resource_id=?`, PROVIDER12, account, jid);
|
|
25363
25741
|
if (existing !== null) {
|
|
25364
25742
|
if (existing.source_realm_id !== null && existing.source_realm_id !== sourceRealmId) {
|
|
25365
25743
|
throw new Error("WhatsApp contact resource crossed its bound account realm");
|
|
25366
25744
|
}
|
|
25367
|
-
|
|
25745
|
+
run9(database, `UPDATE provider_resources SET source_realm_id=?,display_name=coalesce(?,display_name),active=1,last_seen_run_id=?,
|
|
25368
25746
|
metadata_json=? WHERE id=?`, sourceRealmId, name, runId, stableJson6({ jidKind: USER_JID.test(jid) ? "user" : "lid", displayNameBasis: basis }), existing.id);
|
|
25369
25747
|
if (name !== null)
|
|
25370
|
-
|
|
25748
|
+
run9(database, "UPDATE people SET display_name=coalesce(display_name,?) WHERE id=?", name, existing.person_id);
|
|
25371
25749
|
return { personId: existing.person_id, resourceId: existing.id, imported: false };
|
|
25372
25750
|
}
|
|
25373
|
-
const personId =
|
|
25751
|
+
const personId = insertedId10(run9(database, "INSERT INTO people(stable_key,display_name,metadata_json) VALUES (?,?,?)", `whatsapp:${sha2569(`${account}\x00${jid}`)}`, name, stableJson6({ createdBy: "exact-whatsapp-jid" })));
|
|
25374
25752
|
const phone = phoneForJid(jid);
|
|
25375
25753
|
if (phone !== null)
|
|
25376
|
-
|
|
25754
|
+
run9(database, `INSERT INTO contact_methods(
|
|
25377
25755
|
person_id,kind,value,normalized_value,label,confidence,first_seen_run_id,last_seen_run_id,metadata_json
|
|
25378
25756
|
) VALUES (?,'phone',?,?,'whatsapp','exact',?,?,?)`, personId, phone, phone, runId, runId, stableJson6({ derivedFromJid: true }));
|
|
25379
|
-
const resourceId =
|
|
25757
|
+
const resourceId = insertedId10(run9(database, `INSERT INTO provider_resources(
|
|
25380
25758
|
provider,account_key,source_realm_id,resource_type,resource_id,person_id,display_name,active,
|
|
25381
25759
|
first_seen_run_id,last_seen_run_id,metadata_json
|
|
25382
|
-
) VALUES (?,?,?,'jid',?,?,?,1,?,?,?)`,
|
|
25760
|
+
) VALUES (?,?,?,'jid',?,?,?,1,?,?,?)`, PROVIDER12, account, sourceRealmId, jid, personId, name, runId, runId, stableJson6({ jidKind: USER_JID.test(jid) ? "user" : "lid", displayNameBasis: basis })));
|
|
25383
25761
|
return { personId, resourceId, imported: true };
|
|
25384
25762
|
}
|
|
25385
25763
|
function parseStoredGeneration(value) {
|
|
25386
|
-
const parsed =
|
|
25387
|
-
|
|
25764
|
+
const parsed = record7(JSON.parse(value), "stored WhatsApp checkpoint metadata");
|
|
25765
|
+
exact5(parsed, ["generation", "coverage"], "stored WhatsApp checkpoint metadata");
|
|
25388
25766
|
if (parsed.coverage !== "local-insert-rowid-scan")
|
|
25389
25767
|
throw new Error("stored WhatsApp coverage drifted");
|
|
25390
25768
|
return generation(parsed.generation);
|
|
25391
25769
|
}
|
|
25392
25770
|
function databaseCheckpoint(database, auth) {
|
|
25393
|
-
const row =
|
|
25771
|
+
const row = getRow10(database, `SELECT checkpoint.cursor,checkpoint.cursor_anchor_sha256,checkpoint.metadata_json
|
|
25394
25772
|
FROM provider_accounts account JOIN provider_checkpoints checkpoint
|
|
25395
25773
|
ON checkpoint.provider=account.provider AND checkpoint.account_key=account.account_key
|
|
25396
|
-
WHERE account.provider=? AND account.auth_id=? AND checkpoint.stream_key=?`,
|
|
25774
|
+
WHERE account.provider=? AND account.auth_id=? AND checkpoint.stream_key=?`, PROVIDER12, auth, STREAM2);
|
|
25397
25775
|
return row === null ? { cursor: "0", anchor: null } : { cursor: row.cursor, anchor: row.cursor_anchor_sha256, generation: parseStoredGeneration(row.metadata_json) };
|
|
25398
25776
|
}
|
|
25399
25777
|
function refreshMetrics2(database, account, runId) {
|
|
25400
|
-
|
|
25401
|
-
|
|
25778
|
+
run9(database, "DELETE FROM interaction_metrics WHERE provider=? AND account_key=?", PROVIDER12, account);
|
|
25779
|
+
run9(database, `INSERT INTO interaction_metrics(
|
|
25402
25780
|
provider,account_key,person_id,sent_count,received_count,interaction_count,conversation_count,
|
|
25403
25781
|
first_interaction_at,last_interaction_at,reciprocal,completeness,last_seen_run_id,metadata_json
|
|
25404
25782
|
) SELECT ?,?,person_id,sum(direction='outgoing'),sum(direction='incoming'),count(*),
|
|
@@ -25406,11 +25784,11 @@ function refreshMetrics2(database, account, runId) {
|
|
|
25406
25784
|
CASE WHEN sum(direction='outgoing')>0 AND sum(direction='incoming')>0 THEN 1 ELSE 0 END,
|
|
25407
25785
|
'lower-bound',?,'{"coverage":"local-insert-rowid-scan"}'
|
|
25408
25786
|
FROM interaction_events WHERE provider=? AND account_key=? AND resolution='matched' AND person_id IS NOT NULL
|
|
25409
|
-
GROUP BY person_id`,
|
|
25787
|
+
GROUP BY person_id`, PROVIDER12, account, runId, PROVIDER12, account);
|
|
25410
25788
|
}
|
|
25411
25789
|
function resultFromJson2(value) {
|
|
25412
|
-
const parsed =
|
|
25413
|
-
|
|
25790
|
+
const parsed = record7(JSON.parse(value), "stored WhatsApp result");
|
|
25791
|
+
exact5(parsed, ["account_subject", "contacts_seen", "contacts_imported", "contacts_matched", "contacts_skipped_self", "messages_seen", "messages_inserted", "matched_messages", "skipped_messages", "checkpoint_cursor", "local_insert_scan_complete", "remote_history_complete", "counts_complete", "counts_lower_bound"], "stored WhatsApp result");
|
|
25414
25792
|
const numeric = ["contacts_seen", "contacts_imported", "contacts_matched", "contacts_skipped_self", "messages_seen", "messages_inserted", "matched_messages", "skipped_messages"];
|
|
25415
25793
|
for (const key of numeric)
|
|
25416
25794
|
integer6(parsed[key], `stored WhatsApp result.${key}`, 0, Number.MAX_SAFE_INTEGER);
|
|
@@ -25461,26 +25839,26 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25461
25839
|
}));
|
|
25462
25840
|
database.exec("BEGIN IMMEDIATE");
|
|
25463
25841
|
try {
|
|
25464
|
-
const byAuth =
|
|
25465
|
-
const byAccount =
|
|
25842
|
+
const byAuth = getRow10(database, "SELECT account_key,auth_sha256 FROM provider_accounts WHERE provider=? AND auth_id=?", PROVIDER12, snapshot.authId);
|
|
25843
|
+
const byAccount = getRow10(database, "SELECT auth_id,auth_sha256 FROM provider_accounts WHERE provider=? AND account_key=?", PROVIDER12, account);
|
|
25466
25844
|
if (byAuth !== null && (byAuth.account_key !== account || byAuth.auth_sha256 !== firstExecution.authSha256) || byAccount !== null && (byAccount.auth_id !== snapshot.authId || byAccount.auth_sha256 !== firstExecution.authSha256)) {
|
|
25467
25845
|
throw new Error("WhatsApp durable auth-to-account binding drifted");
|
|
25468
25846
|
}
|
|
25469
25847
|
if (byAuth === null)
|
|
25470
|
-
|
|
25471
|
-
VALUES (?,?,?,?,?,?)`,
|
|
25848
|
+
run9(database, `INSERT INTO provider_accounts(provider,auth_id,account_key,auth_sha256,first_seen_at,last_seen_at)
|
|
25849
|
+
VALUES (?,?,?,?,?,?)`, PROVIDER12, snapshot.authId, account, firstExecution.authSha256, firstExecution.startedAt, lastExecution.finishedAt);
|
|
25472
25850
|
else
|
|
25473
|
-
|
|
25851
|
+
run9(database, "UPDATE provider_accounts SET last_seen_at=? WHERE provider=? AND auth_id=?", lastExecution.finishedAt, PROVIDER12, snapshot.authId);
|
|
25474
25852
|
const sourceRealmId = bindProviderAccountRealm(database, {
|
|
25475
|
-
provider:
|
|
25853
|
+
provider: PROVIDER12,
|
|
25476
25854
|
authId: snapshot.authId,
|
|
25477
25855
|
accountKey: account,
|
|
25478
25856
|
authSha256: firstExecution.authSha256,
|
|
25479
25857
|
firstSeenAt: firstExecution.startedAt,
|
|
25480
25858
|
lastSeenAt: lastExecution.finishedAt
|
|
25481
25859
|
}).id;
|
|
25482
|
-
const storedCheckpoint =
|
|
25483
|
-
FROM provider_checkpoints WHERE provider=? AND account_key=? AND stream_key=?`,
|
|
25860
|
+
const storedCheckpoint = getRow10(database, `SELECT cursor,cursor_anchor_sha256,generation_sha256,metadata_json
|
|
25861
|
+
FROM provider_checkpoints WHERE provider=? AND account_key=? AND stream_key=?`, PROVIDER12, account, STREAM2);
|
|
25484
25862
|
if (storedCheckpoint === null) {
|
|
25485
25863
|
if (snapshot.startCheckpoint.cursor !== "0" || snapshot.startCheckpoint.anchor !== null || snapshot.startCheckpoint.generation !== undefined)
|
|
25486
25864
|
throw new Error("WhatsApp initial scan must start at zero");
|
|
@@ -25490,8 +25868,8 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25490
25868
|
throw new Error("WhatsApp durable checkpoint or message-store generation drifted");
|
|
25491
25869
|
}
|
|
25492
25870
|
}
|
|
25493
|
-
const existingRun =
|
|
25494
|
-
WHERE provider=? AND account_key=? AND mode='linked-device-sync' AND source_sha256=?`,
|
|
25871
|
+
const existingRun = getRow10(database, `SELECT result_json FROM source_runs
|
|
25872
|
+
WHERE provider=? AND account_key=? AND mode='linked-device-sync' AND source_sha256=?`, PROVIDER12, account, sourceDigest);
|
|
25495
25873
|
if (existingRun !== null) {
|
|
25496
25874
|
database.exec("COMMIT");
|
|
25497
25875
|
return resultFromJson2(existingRun.result_json);
|
|
@@ -25508,8 +25886,8 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25508
25886
|
expectedContactsSkippedSelf += 1;
|
|
25509
25887
|
continue;
|
|
25510
25888
|
}
|
|
25511
|
-
const exists =
|
|
25512
|
-
WHERE provider=? AND account_key=? AND resource_type='jid' AND resource_id=?`,
|
|
25889
|
+
const exists = getRow10(database, `SELECT 1 FROM provider_resources
|
|
25890
|
+
WHERE provider=? AND account_key=? AND resource_type='jid' AND resource_id=?`, PROVIDER12, account, contact.providerId) !== null;
|
|
25513
25891
|
if (exists)
|
|
25514
25892
|
expectedContactsMatched += 1;
|
|
25515
25893
|
else
|
|
@@ -25542,11 +25920,11 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25542
25920
|
counts_complete: false,
|
|
25543
25921
|
counts_lower_bound: true
|
|
25544
25922
|
};
|
|
25545
|
-
const runInsert =
|
|
25923
|
+
const runInsert = run9(database, `INSERT INTO source_runs(
|
|
25546
25924
|
provider,account_key,mode,source_sha256,source_locator_sha256,completeness,
|
|
25547
25925
|
result_json,started_at,completed_at
|
|
25548
|
-
) VALUES (?,?,'linked-device-sync',?,?,'lower-bound',?,?,?)`,
|
|
25549
|
-
const runId =
|
|
25926
|
+
) VALUES (?,?,'linked-device-sync',?,?,'lower-bound',?,?,?)`, PROVIDER12, account, sourceDigest, sha2569(snapshot.authId), stableJson6(expectedResult), firstExecution.startedAt, lastExecution.finishedAt);
|
|
25927
|
+
const runId = insertedId10(runInsert);
|
|
25550
25928
|
let ordinal = 0;
|
|
25551
25929
|
let contactsSeen = 0;
|
|
25552
25930
|
let contactsImported = 0;
|
|
@@ -25559,7 +25937,7 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25559
25937
|
if (contact.providerId === owner) {
|
|
25560
25938
|
contactsSkippedSelf += 1;
|
|
25561
25939
|
const item2 = stableJson6({ type: "contact", ...contact, resolution: "skipped-self" });
|
|
25562
|
-
|
|
25940
|
+
run9(database, `INSERT INTO source_records(run_id,ordinal,record_kind,record_sha256,resolution,projection_json,error_text)
|
|
25563
25941
|
VALUES (?,?,'contact',?,'skipped',?,'owner-self-contact')`, runId, ordinal, sha2569(item2), item2);
|
|
25564
25942
|
continue;
|
|
25565
25943
|
}
|
|
@@ -25569,7 +25947,7 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25569
25947
|
else
|
|
25570
25948
|
contactsMatched += 1;
|
|
25571
25949
|
const item = stableJson6({ type: "contact", ...contact });
|
|
25572
|
-
|
|
25950
|
+
run9(database, `INSERT INTO source_records(
|
|
25573
25951
|
run_id,ordinal,record_kind,record_sha256,person_id,provider_resource_id,resolution,projection_json
|
|
25574
25952
|
) VALUES (?,?,'contact',?,?,?,?,?)`, runId, ordinal, sha2569(item), identity.personId, identity.resourceId, identity.imported ? "imported" : "matched", item);
|
|
25575
25953
|
}
|
|
@@ -25613,13 +25991,13 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25613
25991
|
reason
|
|
25614
25992
|
};
|
|
25615
25993
|
const evidenceJson = stableJson6(evidence);
|
|
25616
|
-
|
|
25994
|
+
run9(database, `INSERT INTO source_records(
|
|
25617
25995
|
run_id,ordinal,record_kind,record_sha256,person_id,provider_resource_id,resolution,projection_json,error_text
|
|
25618
25996
|
) VALUES (?,?,'message',?,?,?,?,?,?)`, runId, ordinal, sha2569(evidenceJson), personId, resourceId, resolution, evidenceJson, resolution === "matched" ? null : reason);
|
|
25619
|
-
|
|
25997
|
+
run9(database, `INSERT INTO interaction_events(
|
|
25620
25998
|
provider,account_key,external_id,person_id,occurred_at,direction,conversation_sha256,
|
|
25621
25999
|
event_kind,resolution,run_id,metadata_json
|
|
25622
|
-
) VALUES (?,?,?,?,?,?,?,'message',?,?,?)`,
|
|
26000
|
+
) VALUES (?,?,?,?,?,?,?,'message',?,?,?)`, PROVIDER12, account, `row:${message.rowid}`, personId, message.timestamp, direction, conversationHash, resolution, runId, stableJson6({ conversationKind, reason, messageIdSha256: sha2569(message.messageId) }));
|
|
25623
26001
|
messagesInserted += 1;
|
|
25624
26002
|
}
|
|
25625
26003
|
refreshMetrics2(database, account, runId);
|
|
@@ -25641,8 +26019,8 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25641
26019
|
};
|
|
25642
26020
|
if (stableJson6(result) !== stableJson6(expectedResult))
|
|
25643
26021
|
throw new Error("WhatsApp applied counts drifted from the reviewed snapshot");
|
|
25644
|
-
const
|
|
25645
|
-
|
|
26022
|
+
const metadata4 = stableJson6({ generation: scanGeneration, coverage: "local-insert-rowid-scan" });
|
|
26023
|
+
run9(database, `INSERT INTO provider_checkpoints(
|
|
25646
26024
|
provider,account_key,source_realm_id,stream_key,source_locator_sha256,schema_sha256,generation_sha256,
|
|
25647
26025
|
cursor,cursor_anchor_sha256,last_run_id,metadata_json
|
|
25648
26026
|
) VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
|
@@ -25650,7 +26028,7 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25650
26028
|
source_realm_id=excluded.source_realm_id,source_locator_sha256=excluded.source_locator_sha256,schema_sha256=excluded.schema_sha256,
|
|
25651
26029
|
generation_sha256=excluded.generation_sha256,cursor=excluded.cursor,
|
|
25652
26030
|
cursor_anchor_sha256=excluded.cursor_anchor_sha256,last_run_id=excluded.last_run_id,
|
|
25653
|
-
metadata_json=excluded.metadata_json,updated_at=CURRENT_TIMESTAMP`,
|
|
26031
|
+
metadata_json=excluded.metadata_json,updated_at=CURRENT_TIMESTAMP`, PROVIDER12, account, sourceRealmId, STREAM2, sha2569(snapshot.authId), CONTACTS_CONTRACT_SHA2562, generationSha256(scanGeneration), end2.cursor, end2.anchor, runId, metadata4);
|
|
25654
26032
|
database.exec("COMMIT");
|
|
25655
26033
|
return result;
|
|
25656
26034
|
} catch (error) {
|
|
@@ -25659,13 +26037,13 @@ function importWhatsAppSnapshot(database, snapshot) {
|
|
|
25659
26037
|
}
|
|
25660
26038
|
}
|
|
25661
26039
|
function syncWhatsAppRelationships(database, options = {}) {
|
|
25662
|
-
const auth =
|
|
26040
|
+
const auth = authId3(options.authId ?? DEFAULT_WHATSAPP_AUTH);
|
|
25663
26041
|
if (options.checkpoint !== undefined)
|
|
25664
26042
|
throw new Error("sync checkpoint is database-owned");
|
|
25665
|
-
const snapshot =
|
|
26043
|
+
const snapshot = ghostgetWhatsAppSnapshot({ ...options, authId: auth, checkpoint: databaseCheckpoint(database, auth) });
|
|
25666
26044
|
for (const page of [...snapshot.contactPages, ...snapshot.interactionPages]) {
|
|
25667
|
-
|
|
25668
|
-
provider:
|
|
26045
|
+
ledgerPeopleBladeGhostgetInvocation(database, {
|
|
26046
|
+
provider: PROVIDER12,
|
|
25669
26047
|
accountKey: snapshot.accountSubject,
|
|
25670
26048
|
capability: "contact_interactions",
|
|
25671
26049
|
execution: page.execution,
|
|
@@ -25679,7 +26057,7 @@ function syncWhatsAppRelationships(database, options = {}) {
|
|
|
25679
26057
|
}
|
|
25680
26058
|
|
|
25681
26059
|
// src/cli/version.ts
|
|
25682
|
-
var peoplebladeVersion = "0.
|
|
26060
|
+
var peoplebladeVersion = "0.3.0";
|
|
25683
26061
|
|
|
25684
26062
|
// src/cli/main.ts
|
|
25685
26063
|
var usage = `PeopleBlade \u2014 local-first contact intelligence
|
|
@@ -25719,20 +26097,23 @@ Core:
|
|
|
25719
26097
|
|
|
25720
26098
|
Sources (append --auto-accept to run identity auto-accept after a successful import):
|
|
25721
26099
|
contacts sync [--directory PATH] Sync macOS Contacts from the local AddressBook database
|
|
25722
|
-
beeper sync [--auth ID] Sync connected Beeper accounts sequentially through
|
|
26100
|
+
beeper sync [--auth ID] Sync connected Beeper accounts sequentially through Ghostget
|
|
25723
26101
|
[--with-relationships] Add body-free lower-bound direct interaction counts
|
|
25724
26102
|
beeper search --query TEXT Import bounded fuzzy candidates for explicit identity review
|
|
25725
26103
|
[--query TEXT ...] [--service NAME ...]
|
|
25726
26104
|
beeper rebind --confirm Explicitly transition a verified existing Beeper source binding
|
|
25727
26105
|
beeper stats Show aggregate Beeper realm and completeness statistics
|
|
25728
|
-
google sync [--auth ID] Sync saved and Other Google Contacts through
|
|
26106
|
+
google sync [--auth ID] Sync saved and Other Google Contacts through Ghostget
|
|
25729
26107
|
google stats Show aggregate Google contact statistics by collection
|
|
25730
26108
|
imessage sync [--database PATH] Sync content-free iMessage metadata incrementally
|
|
25731
26109
|
x import ZIP Import exact IDs from an official X data archive
|
|
25732
26110
|
x stats Show aggregate X archive statistics
|
|
25733
|
-
x mutuals sync Sync X mutuals through
|
|
25734
|
-
whatsapp sync [--auth ID] Sync WhatsApp through
|
|
26111
|
+
x mutuals sync Sync X mutuals through Ghostget (requires reviewed Ghostget capability)
|
|
26112
|
+
whatsapp sync [--auth ID] Sync WhatsApp through Ghostget's linked-device projection
|
|
25735
26113
|
linkedin import ZIP Import an official LinkedIn data-export ZIP
|
|
26114
|
+
linkedin contact-info --person-id ID
|
|
26115
|
+
Read one first-degree connection's Contact info through Ghostget
|
|
26116
|
+
[--account KEY] [--auth ID]
|
|
25736
26117
|
linkedin stats Show aggregate LinkedIn import statistics
|
|
25737
26118
|
instagram import ZIP Import an official Instagram JSON archive
|
|
25738
26119
|
instagram stats Show aggregate Instagram import statistics
|
|
@@ -26462,17 +26843,17 @@ ${verification}`) });
|
|
|
26462
26843
|
}
|
|
26463
26844
|
if (command === "beeper" && subcommand === "sync") {
|
|
26464
26845
|
const options = [...rest];
|
|
26465
|
-
const
|
|
26846
|
+
const authId4 = valueAfter(options, "--auth");
|
|
26466
26847
|
const contactLimit = positive(valueAfter(options, "--contact-limit"), "--contact-limit", 200, 200);
|
|
26467
26848
|
const withRelationships = options.includes("--with-relationships");
|
|
26468
26849
|
if (withRelationships)
|
|
26469
26850
|
options.splice(options.indexOf("--with-relationships"), 1);
|
|
26470
26851
|
if (options.length)
|
|
26471
26852
|
fail3(`Unknown argument: ${options[0]}`);
|
|
26472
|
-
const effectiveAuthId =
|
|
26853
|
+
const effectiveAuthId = authId4 ?? DEFAULT_BEEPER_AUTH;
|
|
26473
26854
|
if (withRelationships)
|
|
26474
|
-
console.error("Beeper relationships \xB7 deriving a body-free interaction summary through
|
|
26475
|
-
console.error("Beeper contacts \xB7 discovering connected accounts through
|
|
26855
|
+
console.error("Beeper relationships \xB7 deriving a body-free interaction summary through Ghostget");
|
|
26856
|
+
console.error("Beeper contacts \xB7 discovering connected accounts through Ghostget");
|
|
26476
26857
|
printImport(syncBeeperContacts(database, {
|
|
26477
26858
|
authId: effectiveAuthId,
|
|
26478
26859
|
contactLimit,
|
|
@@ -26491,23 +26872,23 @@ ${verification}`) });
|
|
|
26491
26872
|
}
|
|
26492
26873
|
if (command === "beeper" && subcommand === "rebind") {
|
|
26493
26874
|
const options = [...rest];
|
|
26494
|
-
const
|
|
26875
|
+
const authId4 = valueAfter(options, "--auth");
|
|
26495
26876
|
const confirm = options.includes("--confirm");
|
|
26496
26877
|
if (confirm)
|
|
26497
26878
|
options.splice(options.indexOf("--confirm"), 1);
|
|
26498
26879
|
if (options.length)
|
|
26499
26880
|
fail3(`Unknown argument: ${options[0]}`);
|
|
26500
26881
|
if (!confirm)
|
|
26501
|
-
fail3("beeper rebind requires --confirm after reviewing the current
|
|
26882
|
+
fail3("beeper rebind requires --confirm after reviewing the current Ghostget binding, release, and connected-account inventory.");
|
|
26502
26883
|
console.error("Beeper binding \xB7 verifying the complete connected-account inventory before an append-only transition");
|
|
26503
|
-
print(rebindBeeperSource(database, { ...
|
|
26884
|
+
print(rebindBeeperSource(database, { ...authId4 === undefined ? {} : { authId: authId4 } }), asJson);
|
|
26504
26885
|
return;
|
|
26505
26886
|
}
|
|
26506
26887
|
if (command === "beeper" && subcommand === "search") {
|
|
26507
26888
|
const options = [...rest];
|
|
26508
26889
|
const queries = valuesAfter(options, "--query");
|
|
26509
26890
|
const services = valuesAfter(options, "--service");
|
|
26510
|
-
const
|
|
26891
|
+
const authId4 = valueAfter(options, "--auth");
|
|
26511
26892
|
const limit = positive(valueAfter(options, "--limit"), "--limit", 20, 20);
|
|
26512
26893
|
if (queries.length === 0)
|
|
26513
26894
|
fail3("beeper search requires one or more --query values.");
|
|
@@ -26516,18 +26897,18 @@ ${verification}`) });
|
|
|
26516
26897
|
print(backfillBeeperSearch(database, {
|
|
26517
26898
|
queries,
|
|
26518
26899
|
...services.length === 0 ? {} : { services },
|
|
26519
|
-
...
|
|
26900
|
+
...authId4 === undefined ? {} : { authId: authId4 },
|
|
26520
26901
|
limit
|
|
26521
26902
|
}), asJson);
|
|
26522
26903
|
return;
|
|
26523
26904
|
}
|
|
26524
26905
|
if (command === "google" && subcommand === "sync") {
|
|
26525
26906
|
const options = [...rest];
|
|
26526
|
-
const
|
|
26907
|
+
const authId4 = valueAfter(options, "--auth");
|
|
26527
26908
|
const pageSize = positive(valueAfter(options, "--page-size"), "--page-size", 100, 100);
|
|
26528
26909
|
if (options.length)
|
|
26529
26910
|
fail3(`Unknown argument: ${options[0]}`);
|
|
26530
|
-
printImport(syncGoogleContacts(database, { ...
|
|
26911
|
+
printImport(syncGoogleContacts(database, { ...authId4 === undefined ? {} : { authId: authId4 }, pageSize }), asJson);
|
|
26531
26912
|
return;
|
|
26532
26913
|
}
|
|
26533
26914
|
if (command === "google" && subcommand === "stats") {
|
|
@@ -26552,13 +26933,13 @@ ${verification}`) });
|
|
|
26552
26933
|
}
|
|
26553
26934
|
if (command === "whatsapp" && subcommand === "sync") {
|
|
26554
26935
|
const options = [...rest];
|
|
26555
|
-
const
|
|
26936
|
+
const authId4 = valueAfter(options, "--auth");
|
|
26556
26937
|
const contactPageSize = positive(valueAfter(options, "--contact-page-size"), "--contact-page-size", 100, 100);
|
|
26557
26938
|
const interactionPageSize = positive(valueAfter(options, "--interaction-page-size"), "--interaction-page-size", 1000, 1000);
|
|
26558
26939
|
if (options.length)
|
|
26559
26940
|
fail3(`Unknown argument: ${options[0]}`);
|
|
26560
26941
|
printImport(syncWhatsAppRelationships(database, {
|
|
26561
|
-
...
|
|
26942
|
+
...authId4 === undefined ? {} : { authId: authId4 },
|
|
26562
26943
|
contactPageSize,
|
|
26563
26944
|
interactionPageSize
|
|
26564
26945
|
}), asJson);
|
|
@@ -26579,6 +26960,25 @@ ${verification}`) });
|
|
|
26579
26960
|
}), asJson);
|
|
26580
26961
|
return;
|
|
26581
26962
|
}
|
|
26963
|
+
if (command === "linkedin" && subcommand === "contact-info") {
|
|
26964
|
+
const options = [...rest];
|
|
26965
|
+
const personText = valueAfter(options, "--person-id");
|
|
26966
|
+
if (personText === undefined)
|
|
26967
|
+
fail3("linkedin contact-info requires --person-id ID.");
|
|
26968
|
+
const personId = Number(personText);
|
|
26969
|
+
if (!Number.isSafeInteger(personId) || personId < 1)
|
|
26970
|
+
fail3("--person-id must be a positive integer.");
|
|
26971
|
+
const accountKey5 = valueAfter(options, "--account");
|
|
26972
|
+
const authId4 = valueAfter(options, "--auth");
|
|
26973
|
+
if (options.length)
|
|
26974
|
+
fail3(`Unknown argument: ${options[0]}`);
|
|
26975
|
+
print(readLinkedInContactInfo(database, {
|
|
26976
|
+
personId,
|
|
26977
|
+
...accountKey5 === undefined ? {} : { accountKey: accountKey5 },
|
|
26978
|
+
...authId4 === undefined ? {} : { authId: authId4 }
|
|
26979
|
+
}), asJson);
|
|
26980
|
+
return;
|
|
26981
|
+
}
|
|
26582
26982
|
if (command === "linkedin" && subcommand === "stats") {
|
|
26583
26983
|
if (rest.length)
|
|
26584
26984
|
fail3(`Unknown argument: ${rest[0]}`);
|
|
@@ -26625,7 +27025,7 @@ ${verification}`) });
|
|
|
26625
27025
|
return;
|
|
26626
27026
|
}
|
|
26627
27027
|
if (command === "x" && subcommand === "mutuals" && rest[0] === "sync")
|
|
26628
|
-
fail3("X mutual sync is not enabled until
|
|
27028
|
+
fail3("X mutual sync is not enabled until Ghostget ships a reviewed, bounded x-web mutuals capability. No official X API is required.");
|
|
26629
27029
|
fail3(`Unknown command.
|
|
26630
27030
|
|
|
26631
27031
|
${usage}`);
|