@hraness/ghostget 0.17.6 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +301 -0
- package/README.md +16 -9
- package/dist/apple-photos-client.js +2 -1
- package/dist/beeper-client.js +2 -1
- package/dist/client.js +1 -0
- package/dist/imessage-direct-install-2gm3kge7.js +324 -0
- package/dist/index-01eeae9e.js +5 -0
- package/dist/index-2ymnp8xv.js +145 -0
- package/dist/index-en5hycxp.js +175 -0
- package/dist/{index-cytf8d9p.js → index-hfbygww8.js} +1 -1
- package/dist/index-n4szk3nw.js +50 -0
- package/dist/index-yq6maz71.js +1105 -0
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index.js +4 -47
- package/dist/messaging-automation-api.js +21 -0
- package/dist/messaging-automation-j4274hvc.js +609 -0
- package/dist/messaging-native-install-8w1j36ah.js +16 -0
- package/dist/messaging.js +1 -0
- package/dist/omni-client.js +1 -0
- package/dist/whatsapp-automation-runtime-hgh1e9rm.js +845 -0
- package/dist/whatsapp-client.js +1 -0
- package/docs/control-panel.md +147 -0
- package/docs/imessage-direct-provider.md +31 -7
- package/docs/messaging-automation.md +103 -0
- package/package.json +66 -7
- package/skills/ghostget/SKILL.md +3 -1
- package/skills/ghostget/references/control-panel.md +43 -0
- package/skills/ghostget/references/install.md +5 -5
- package/skills/ghostget/references/linkedin-adapter.md +222 -12
- package/skills/ghostget/references/platform-patterns.md +1 -1
- package/src/args.ts +18 -5
- package/src/assets/adapters/imessage/wrench-web-adapter.json +126 -0
- package/src/assets/adapters/linkedin/wrench-web-adapter.json +1 -1
- package/src/assets/adapters/whatsapp/wrench-web-adapter.json +144 -0
- package/src/assets/messaging-runtime/NOTICE.txt +2580 -0
- package/src/assets/messaging-runtime/imsg-darwin-arm64.gz +0 -0
- package/src/assets/messaging-runtime/phone-number-info.plist.gz +0 -0
- package/src/assets/messaging-runtime/phone-number-metadata.json.gz +0 -0
- package/src/assets/messaging-runtime/phone-number-privacy.plist.gz +0 -0
- package/src/assets/messaging-runtime/wacli-darwin-arm64.gz +0 -0
- package/src/auth.ts +35 -1
- package/src/beeper-client-types.ts +1 -1
- package/src/cli.ts +18 -0
- package/src/confirmed-write-platform.ts +16 -1
- package/src/control/account-revision.ts +16 -0
- package/src/control/activity.ts +104 -0
- package/src/control/approval-broker.ts +59 -0
- package/src/control/approval-client.ts +49 -0
- package/src/control/bundled-interfaces.ts +20 -0
- package/src/control/cli.ts +20 -0
- package/src/control/connections.ts +87 -0
- package/src/control/credential-helper.ts +152 -0
- package/src/control/helper.ts +79 -0
- package/src/control/interface-cli.ts +22 -0
- package/src/control/interface-json.ts +94 -0
- package/src/control/interface-schema.ts +120 -0
- package/src/control/interfaces.ts +438 -0
- package/src/control/protocol.ts +182 -0
- package/src/control/service.ts +104 -0
- package/src/control/validation.ts +103 -0
- package/src/control/vault.ts +105 -0
- package/src/control/web-gateway.ts +62 -0
- package/src/control/web-policy.ts +56 -0
- package/src/ghostget.ts +18 -4
- package/src/messaging-automation-api.ts +12 -0
- package/src/messaging-automation-descriptors.ts +29 -0
- package/src/messaging-automation-factory.ts +148 -0
- package/src/messaging-automation-server.ts +183 -0
- package/src/messaging-automation-types.ts +155 -0
- package/src/messaging-automation-validation.ts +110 -0
- package/src/messaging-automation.ts +356 -0
- package/src/messaging-runtime.ts +3 -0
- package/src/oauth-google.ts +11 -5
- package/src/omni-runtime.ts +18 -3
- package/src/operation-permission-store.ts +92 -0
- package/src/operation-permission.ts +308 -0
- package/src/pinned-https.ts +5 -0
- package/src/plugins/imessage-direct/plugin.ts +12 -1
- package/src/plugins/imessage-direct/vendor/0003-feat-rpc-add-no-fetch-rich-cards.patch +276 -0
- package/src/plugins/imessage-direct/vendor/provenance.json +84 -10
- package/src/plugins/linkedin-web/plugin.ts +1 -1
- package/src/plugins/whatsapp-linked-device/plugin.ts +7 -2
- package/src/plugins/whatsapp-linked-device/vendor/0001-ghostget-private-messaging.patch +1093 -0
- package/src/plugins/whatsapp-linked-device/vendor/README.md +39 -0
- package/src/plugins/whatsapp-linked-device/vendor/provenance.json +45 -0
- package/src/provider-http.ts +11 -3
- package/src/provider-plugin-contract-identity.ts +2 -2
- package/src/provider-plugin-import-analysis.ts +52 -0
- package/src/provider-plugin-module-analysis.ts +21 -1
- package/src/provider-plugin-registry.ts +4 -8
- package/src/provider-plugin.ts +18 -8
- package/src/providers/imessage-automation.ts +250 -0
- package/src/providers/imessage-direct-install.ts +7 -0
- package/src/providers/imessage-direct-runtime.ts +32 -2
- package/src/providers/imessage-direct.ts +13 -3
- package/src/providers/linkedin-contact-failure.ts +21 -0
- package/src/providers/linkedin-contact-platform.ts +8 -1
- package/src/providers/linkedin-contact-program.ts +34 -5
- package/src/providers/linkedin-web-contact.ts +1223 -35
- package/src/providers/linkedin-web-profile-browser.ts +659 -45
- package/src/providers/linkedin-web-runtime.ts +1 -0
- package/src/providers/linkedin-web.ts +46 -2
- package/src/providers/messaging-native-artifacts.ts +38 -0
- package/src/providers/messaging-native-install.ts +75 -0
- package/src/providers/whatsapp-automation-runtime.ts +240 -0
- package/src/providers/whatsapp-automation.ts +153 -0
- package/src/read-client.ts +12 -2
- package/src/runtime.ts +81 -9
- package/src/state-helper.ts +2 -0
- package/src/storage.ts +71 -1
- package/src/usage.ts +9 -1
- package/src/version.ts +1 -1
- package/src/web-session-contract-definitions.ts +1 -1
- package/tsconfig.json +3 -0
package/src/read-client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canonicalJson, sha256 } from "./canonical-json";
|
|
2
|
+
import { assertOperationPermission, withOperationPermission } from "./operation-permission";
|
|
2
3
|
import { loadAuthSnapshotIfPresent } from "./auth";
|
|
3
4
|
import {
|
|
4
5
|
createPortableProviderPluginCatalog,
|
|
@@ -239,8 +240,9 @@ export function readCachedPreparedCapability(
|
|
|
239
240
|
options: ReadCapabilityOptions & { readonly registry: ProviderPluginRegistry },
|
|
240
241
|
): ReadProjectionCacheResult {
|
|
241
242
|
const environment = options.environment ?? process.env;
|
|
243
|
+
assertOperationPermission(invocation, { environment, registry: options.registry });
|
|
242
244
|
validateReadOptions(options);
|
|
243
|
-
|
|
245
|
+
const result = withInvocationAuthorityAdmission(
|
|
244
246
|
invocation,
|
|
245
247
|
environment,
|
|
246
248
|
() => {
|
|
@@ -265,6 +267,8 @@ export function readCachedPreparedCapability(
|
|
|
265
267
|
return cached;
|
|
266
268
|
},
|
|
267
269
|
);
|
|
270
|
+
assertOperationPermission(invocation, { environment, registry: options.registry });
|
|
271
|
+
return result;
|
|
268
272
|
}
|
|
269
273
|
|
|
270
274
|
export function readCachedCapability(
|
|
@@ -279,7 +283,7 @@ export function readCachedCapability(
|
|
|
279
283
|
);
|
|
280
284
|
}
|
|
281
285
|
|
|
282
|
-
|
|
286
|
+
async function revalidatePreparedCapabilityCore(
|
|
283
287
|
invocation: PreparedInvocation,
|
|
284
288
|
options: PreparedReadOptions,
|
|
285
289
|
cachedBeforeOverride?: ReadProjectionCacheResult | null,
|
|
@@ -467,6 +471,12 @@ export async function revalidatePreparedCapability(
|
|
|
467
471
|
}
|
|
468
472
|
}
|
|
469
473
|
|
|
474
|
+
export async function revalidatePreparedCapability(invocation: PreparedInvocation, options: PreparedReadOptions, cachedBeforeOverride?: ReadProjectionCacheResult | null): Promise<RevalidatedCapability> {
|
|
475
|
+
const environment = options.environment ?? process.env;
|
|
476
|
+
return withOperationPermission(invocation, { environment, registry: options.registry, ...(options.signal === undefined ? {} : { signal: options.signal }) },
|
|
477
|
+
() => revalidatePreparedCapabilityCore(invocation, options, cachedBeforeOverride));
|
|
478
|
+
}
|
|
479
|
+
|
|
470
480
|
export async function revalidateCapability(
|
|
471
481
|
request: CapabilityReadRequest,
|
|
472
482
|
options: RevalidateCapabilityOptions = {},
|
package/src/runtime.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
|
+
import { assertOperationPreparationPermission, checkOperationPermission, withOperationPermission, withUnmanagedOperationPermission, readOperationPolicy } from "./operation-permission";
|
|
3
|
+
import type { ApprovalTarget } from "./control/protocol";
|
|
2
4
|
import { ConfirmedWritePlatform, makeConfirmedWritePlatform } from "./confirmed-write-platform";
|
|
3
5
|
import { confirmedWriteProgram } from "./confirmed-write-program";
|
|
4
6
|
import { runConfirmedWrite } from "./confirmed-write-runtime";
|
|
@@ -186,6 +188,7 @@ type InvocationPlanCommon = {
|
|
|
186
188
|
readonly id: string;
|
|
187
189
|
readonly hash: string;
|
|
188
190
|
readonly kind: GhostgetAuth["kind"];
|
|
191
|
+
readonly incarnationHash?: string;
|
|
189
192
|
};
|
|
190
193
|
readonly duplicateRisk?: InvocationDuplicateRiskV1;
|
|
191
194
|
readonly messagingComposite?: MessagingCompositeInvocationPlanV1;
|
|
@@ -1231,6 +1234,7 @@ export function prepareInvocation(
|
|
|
1231
1234
|
authId?: string,
|
|
1232
1235
|
environment: Readonly<Record<string, string | undefined>> = process.env,
|
|
1233
1236
|
registry: ProviderPluginRegistry = providerPluginRegistry,
|
|
1237
|
+
permissionMode: "enforce" | "inspect" = "enforce",
|
|
1234
1238
|
): PreparedInvocation {
|
|
1235
1239
|
const manifestResult = loadInstalledManifestWithRegistry(adapterId, environment, registry);
|
|
1236
1240
|
if (!manifestResult.ok) throw new Error(`adapter ${adapterId} is invalid: ${manifestResult.issues.join("; ")}`);
|
|
@@ -1257,7 +1261,7 @@ export function prepareInvocation(
|
|
|
1257
1261
|
);
|
|
1258
1262
|
}
|
|
1259
1263
|
const authority = authenticationPolicy.authority;
|
|
1260
|
-
|
|
1264
|
+
const invocation = revalidatePreparedInvocation({
|
|
1261
1265
|
manifest: manifestResult.value,
|
|
1262
1266
|
operationId,
|
|
1263
1267
|
input: platformInput.value,
|
|
@@ -1265,6 +1269,8 @@ export function prepareInvocation(
|
|
|
1265
1269
|
readProjectionAuthIdentityHash:
|
|
1266
1270
|
publicWebSessionAuthorityIdentityHash(authority),
|
|
1267
1271
|
}, registry).invocation;
|
|
1272
|
+
if (permissionMode === "enforce") assertOperationPreparationPermission(invocation, { environment, registry });
|
|
1273
|
+
return invocation;
|
|
1268
1274
|
}
|
|
1269
1275
|
const selectedAuthId = authId ?? adapterId;
|
|
1270
1276
|
const preparedAuth = withSettledReadProjectionAuthAdmission(
|
|
@@ -1282,7 +1288,7 @@ export function prepareInvocation(
|
|
|
1282
1288
|
});
|
|
1283
1289
|
},
|
|
1284
1290
|
);
|
|
1285
|
-
|
|
1291
|
+
const invocation = revalidatePreparedInvocation({
|
|
1286
1292
|
manifest: manifestResult.value,
|
|
1287
1293
|
operationId,
|
|
1288
1294
|
input: platformInput.value,
|
|
@@ -1290,6 +1296,8 @@ export function prepareInvocation(
|
|
|
1290
1296
|
readProjectionAuthIdentityHash:
|
|
1291
1297
|
preparedAuth.readProjectionAuthIdentityHash,
|
|
1292
1298
|
}, registry).invocation;
|
|
1299
|
+
if (permissionMode === "enforce") assertOperationPreparationPermission(invocation, { environment, registry });
|
|
1300
|
+
return invocation;
|
|
1293
1301
|
}
|
|
1294
1302
|
|
|
1295
1303
|
/**
|
|
@@ -1448,6 +1456,7 @@ export function createInvocationPlan(
|
|
|
1448
1456
|
id: planAuth.id,
|
|
1449
1457
|
hash: authHash(planAuth),
|
|
1450
1458
|
kind: planAuth.kind,
|
|
1459
|
+
...(invocation.readProjectionAuthIdentityHash === undefined ? {} : { incarnationHash: invocation.readProjectionAuthIdentityHash }),
|
|
1451
1460
|
},
|
|
1452
1461
|
};
|
|
1453
1462
|
const portablePluginContract = pluginResolution?.portableIdentity ?? null;
|
|
@@ -1678,6 +1687,12 @@ function planRunJournalContract(plan: InvocationPlan): RunJournal["contract"] {
|
|
|
1678
1687
|
: { transport: contract.transport, hash: contract.hash };
|
|
1679
1688
|
}
|
|
1680
1689
|
|
|
1690
|
+
/** Recovery v1 records the durable account selection. The full lifetime binding
|
|
1691
|
+
* remains in the successor scope hash and is revalidated before confirmation. */
|
|
1692
|
+
function planRecoveryAuth(plan: InvocationPlan): RunJournal["auth"] {
|
|
1693
|
+
return { id: plan.auth.id, hash: plan.auth.hash, kind: plan.auth.kind };
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1681
1696
|
function planFileInputs(input: OperationInput): readonly FileInputValue[] {
|
|
1682
1697
|
const files: FileInputValue[] = [];
|
|
1683
1698
|
for (const value of Object.values(input)) {
|
|
@@ -1753,7 +1768,7 @@ function resolveInvocationDuplicateRisk(
|
|
|
1753
1768
|
|| journal.operation !== plan.operation
|
|
1754
1769
|
|| journal.risk !== plan.risk
|
|
1755
1770
|
|| journal.inputHash !== plan.inputHash
|
|
1756
|
-
|| canonicalJson(journal.auth) !== canonicalJson(plan
|
|
1771
|
+
|| canonicalJson(journal.auth) !== canonicalJson(planRecoveryAuth(plan))
|
|
1757
1772
|
|| canonicalJson(journal.contract)
|
|
1758
1773
|
!== canonicalJson(planRunJournalContract(plan))
|
|
1759
1774
|
) {
|
|
@@ -1786,7 +1801,7 @@ function resolveInvocationDuplicateRisk(
|
|
|
1786
1801
|
|| capsule.risk !== plan.risk
|
|
1787
1802
|
|| capsule.inputHash !== plan.inputHash
|
|
1788
1803
|
|| canonicalJson(capsule.input) !== canonicalJson(plan.input)
|
|
1789
|
-
|| canonicalJson(capsule.auth) !== canonicalJson(plan
|
|
1804
|
+
|| canonicalJson(capsule.auth) !== canonicalJson(planRecoveryAuth(plan))
|
|
1790
1805
|
|| canonicalJson(capsule.contract) !== canonicalJson(planRecoveryContract(plan))
|
|
1791
1806
|
) {
|
|
1792
1807
|
throw new Error(
|
|
@@ -2433,7 +2448,8 @@ function parseStoredPlan(value: unknown): StoredPlan {
|
|
|
2433
2448
|
) throw new Error("stored plan adapter is malformed");
|
|
2434
2449
|
const auth = raw.auth;
|
|
2435
2450
|
if (
|
|
2436
|
-
!hasExactKeys(auth, ["id", "hash", "kind"])
|
|
2451
|
+
!hasExactKeys(auth, ["id", "hash", "kind", ...(Object.hasOwn(auth, "incarnationHash") ? ["incarnationHash"] : [])])
|
|
2452
|
+
|| (Object.hasOwn(auth, "incarnationHash") && (typeof auth.incarnationHash !== "string" || !/^[a-f0-9]{64}$/u.test(auth.incarnationHash)))
|
|
2437
2453
|
|| typeof auth.id !== "string"
|
|
2438
2454
|
|| !/^[a-z][a-z0-9-]{0,47}$/u.test(auth.id)
|
|
2439
2455
|
|| typeof auth.hash !== "string"
|
|
@@ -2550,7 +2566,7 @@ function parseStoredPlan(value: unknown): StoredPlan {
|
|
|
2550
2566
|
input,
|
|
2551
2567
|
inputHash,
|
|
2552
2568
|
dispatches,
|
|
2553
|
-
auth: { id: auth.id, hash: auth.hash, kind: auth.kind },
|
|
2569
|
+
auth: { id: auth.id, hash: auth.hash, kind: auth.kind, ...(typeof auth.incarnationHash === "string" ? { incarnationHash: auth.incarnationHash } : {}) },
|
|
2554
2570
|
...(Object.hasOwn(raw, "duplicateRisk")
|
|
2555
2571
|
? { duplicateRisk: parseInvocationDuplicateRisk(raw.duplicateRisk) }
|
|
2556
2572
|
: {}),
|
|
@@ -2977,14 +2993,41 @@ function validateFreshPlan(
|
|
|
2977
2993
|
} else if (auth.kind === "oauth-token-file") {
|
|
2978
2994
|
throw new Error("browser authentication changed after preview; preview the action again");
|
|
2979
2995
|
}
|
|
2996
|
+
const incarnation = withSettledReadProjectionAuthAdmission(auth.id, environment, () => {
|
|
2997
|
+
const current = loadAuth(auth.id, environment);
|
|
2998
|
+
if (authHash(current) !== authHash(auth)) throw new Error("authentication changed during confirmation preparation");
|
|
2999
|
+
return projectionAuthIdentityHash(auth.id, authHash(auth), environment);
|
|
3000
|
+
});
|
|
3001
|
+
if ((plan.auth.incarnationHash !== undefined && plan.auth.incarnationHash !== incarnation)
|
|
3002
|
+
|| (plan.auth.incarnationHash === undefined && readOperationPolicy(environment).managed)) {
|
|
3003
|
+
throw new Error("authentication lifetime changed or predates managed permissions; preview the action again");
|
|
3004
|
+
}
|
|
2980
3005
|
return revalidatePreparedInvocation({
|
|
2981
3006
|
manifest,
|
|
2982
3007
|
operationId: plan.operation,
|
|
2983
3008
|
input: platformInput.value,
|
|
2984
3009
|
auth,
|
|
3010
|
+
readProjectionAuthIdentityHash: incarnation,
|
|
2985
3011
|
}, registry).invocation;
|
|
2986
3012
|
}
|
|
2987
3013
|
|
|
3014
|
+
/** Trusted control-plane inspection. This only prepares; execution gates remain mandatory. */
|
|
3015
|
+
export function prepareOperationApprovalInvocation(
|
|
3016
|
+
target: Extract<ApprovalTarget, { readonly kind: "provider" }>,
|
|
3017
|
+
options: { readonly environment: Readonly<Record<string, string | undefined>>; readonly registry: ProviderPluginRegistry },
|
|
3018
|
+
): { readonly invocation: PreparedInvocation; readonly stored: StoredPlan | null } {
|
|
3019
|
+
if (target.planDigest === null) return {
|
|
3020
|
+
invocation: prepareInvocation(target.adapterId, target.operationId, target.input, target.authId ?? undefined, options.environment, options.registry, "inspect"), stored: null,
|
|
3021
|
+
};
|
|
3022
|
+
const stored = loadInvocationPlan(target.planDigest, options.environment);
|
|
3023
|
+
if (stored.plan.adapter.id !== target.adapterId || stored.plan.operation !== target.operationId || stored.plan.auth.id !== target.authId) {
|
|
3024
|
+
throw new Error("approval target does not match its exact saved plan");
|
|
3025
|
+
}
|
|
3026
|
+
const invocation = validateFreshPlan(stored, options.environment, new Date(), options.registry,
|
|
3027
|
+
(id, environment = options.environment) => loadInstalledManifestWithRegistry(id, environment, options.registry));
|
|
3028
|
+
return { invocation, stored };
|
|
3029
|
+
}
|
|
3030
|
+
|
|
2988
3031
|
|
|
2989
3032
|
|
|
2990
3033
|
|
|
@@ -4381,6 +4424,7 @@ async function runPreparedReadCore(invocation: PreparedInvocation, planDigest: s
|
|
|
4381
4424
|
|
|
4382
4425
|
},
|
|
4383
4426
|
execute: async () => {
|
|
4427
|
+
await checkOperationPermission(invocation, { environment: options.environment, registry, ...(options.signal === undefined ? {} : { signal: options.signal }) });
|
|
4384
4428
|
if (options.preflightFailure !== undefined) throw options.preflightFailure;
|
|
4385
4429
|
return providerOperation
|
|
4386
4430
|
? await (options.executeProvider ?? executeProviderOperation)(
|
|
@@ -4524,7 +4568,7 @@ async function runPreparedReadCore(invocation: PreparedInvocation, planDigest: s
|
|
|
4524
4568
|
|
|
4525
4569
|
|
|
4526
4570
|
|
|
4527
|
-
async function
|
|
4571
|
+
async function runPreparedCore(
|
|
4528
4572
|
invocation: PreparedInvocation,
|
|
4529
4573
|
planDigest: string | null,
|
|
4530
4574
|
options: RunPreparedOptions,
|
|
@@ -4593,6 +4637,12 @@ async function runPrepared(
|
|
|
4593
4637
|
);
|
|
4594
4638
|
}
|
|
4595
4639
|
|
|
4640
|
+
async function runPrepared(invocation: PreparedInvocation, planDigest: string | null, options: RunPreparedOptions): Promise<InvocationResult> {
|
|
4641
|
+
if (!readOperationPolicy(options.environment).managed) return withUnmanagedOperationPermission(options.environment, () => runPreparedCore(invocation, planDigest, options));
|
|
4642
|
+
return withOperationPermission(invocation, { environment: options.environment, registry: options.registry ?? providerPluginRegistry,
|
|
4643
|
+
...(options.signal === undefined ? {} : { signal: options.signal }) }, () => runPreparedCore(invocation, planDigest, options));
|
|
4644
|
+
}
|
|
4645
|
+
|
|
4596
4646
|
export async function executeReadInvocation(
|
|
4597
4647
|
invocation: PreparedInvocation,
|
|
4598
4648
|
options: {
|
|
@@ -4628,7 +4678,7 @@ export async function executeReadInvocation(
|
|
|
4628
4678
|
});
|
|
4629
4679
|
}
|
|
4630
4680
|
|
|
4631
|
-
|
|
4681
|
+
async function confirmInvocationCore(
|
|
4632
4682
|
digest: string,
|
|
4633
4683
|
options: {
|
|
4634
4684
|
readonly headed: boolean;
|
|
@@ -4681,6 +4731,17 @@ export async function confirmInvocation(
|
|
|
4681
4731
|
}, options))));
|
|
4682
4732
|
}
|
|
4683
4733
|
|
|
4734
|
+
export async function confirmInvocation(digest: string, options: Parameters<typeof confirmInvocationCore>[1]): Promise<InvocationResult> {
|
|
4735
|
+
const environment = options.environment ?? process.env;
|
|
4736
|
+
if (!readOperationPolicy(environment).managed) return withUnmanagedOperationPermission(environment, () => confirmInvocationCore(digest, options));
|
|
4737
|
+
const registry = options.registry ?? providerPluginRegistry;
|
|
4738
|
+
const stored = loadInvocationPlan(digest, environment);
|
|
4739
|
+
const invocation = validateFreshPlan(stored, environment, options.now ?? new Date(), registry,
|
|
4740
|
+
options.loadManifest ?? ((id, selected = environment) => loadInstalledManifestWithRegistry(id, selected, registry)));
|
|
4741
|
+
return withOperationPermission(invocation, { environment, registry, plan: stored, ...(options.signal === undefined ? {} : { signal: options.signal }) },
|
|
4742
|
+
() => confirmInvocationCore(digest, options));
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4684
4745
|
export type MessagingConfirmationResult = {
|
|
4685
4746
|
readonly run: MessagingRunV1;
|
|
4686
4747
|
readonly receipt: MessagingRunReceipt;
|
|
@@ -4728,7 +4789,7 @@ function terminalizeMessagingRecovery(
|
|
|
4728
4789
|
}
|
|
4729
4790
|
|
|
4730
4791
|
/** Confirm one composite messaging preview under one durable ownership claim. */
|
|
4731
|
-
|
|
4792
|
+
async function confirmMessagingInvocationCore(
|
|
4732
4793
|
digest: string,
|
|
4733
4794
|
options: {
|
|
4734
4795
|
readonly environment?: Readonly<Record<string, string | undefined>>;
|
|
@@ -4857,6 +4918,17 @@ export async function confirmMessagingInvocation(
|
|
|
4857
4918
|
}
|
|
4858
4919
|
}
|
|
4859
4920
|
|
|
4921
|
+
export async function confirmMessagingInvocation(digest: string, options: NonNullable<Parameters<typeof confirmMessagingInvocationCore>[1]> = {}): Promise<MessagingConfirmationResult> {
|
|
4922
|
+
const environment = options.environment ?? process.env;
|
|
4923
|
+
if (!readOperationPolicy(environment).managed) return withUnmanagedOperationPermission(environment, () => confirmMessagingInvocationCore(digest, options));
|
|
4924
|
+
const registry = options.registry ?? providerPluginRegistry;
|
|
4925
|
+
const stored = loadInvocationPlan(digest, environment);
|
|
4926
|
+
const invocation = validateFreshPlan(stored, environment, options.now ?? new Date(), registry,
|
|
4927
|
+
options.loadManifest ?? ((id, selected = environment) => loadInstalledManifestWithRegistry(id, selected, registry)));
|
|
4928
|
+
return withOperationPermission(invocation, { environment, registry, plan: stored, ...(options.signal === undefined ? {} : { signal: options.signal }) },
|
|
4929
|
+
() => confirmMessagingInvocationCore(digest, options));
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4860
4932
|
export function readRunReceipt(
|
|
4861
4933
|
runId: string,
|
|
4862
4934
|
environment: Readonly<Record<string, string | undefined>> = process.env,
|
package/src/state-helper.ts
CHANGED
|
@@ -41,11 +41,13 @@ const stateDirectories = new Set([
|
|
|
41
41
|
"auth",
|
|
42
42
|
"browser-snapshots",
|
|
43
43
|
"captures",
|
|
44
|
+
"control",
|
|
44
45
|
"derivations",
|
|
45
46
|
"idempotency",
|
|
46
47
|
"linked-device-stores",
|
|
47
48
|
"messaging",
|
|
48
49
|
"omni-read-projections",
|
|
50
|
+
"operation-permissions",
|
|
49
51
|
"plan-assets",
|
|
50
52
|
"plans",
|
|
51
53
|
"provider-plugin-state",
|
package/src/storage.ts
CHANGED
|
@@ -101,11 +101,13 @@ const stateDirectoryNames = [
|
|
|
101
101
|
"auth",
|
|
102
102
|
"browser-snapshots",
|
|
103
103
|
"captures",
|
|
104
|
+
"control",
|
|
104
105
|
"derivations",
|
|
105
106
|
"idempotency",
|
|
106
107
|
"linked-device-stores",
|
|
107
108
|
"messaging",
|
|
108
109
|
"omni-read-projections",
|
|
110
|
+
"operation-permissions",
|
|
109
111
|
"plan-assets",
|
|
110
112
|
"plans",
|
|
111
113
|
"provider-plugin-state",
|
|
@@ -1158,7 +1160,7 @@ function validateStateRoot(root: string): StateRootRecord {
|
|
|
1158
1160
|
return { claimed, creationAnchor: null, identity: { device: stats.dev.toString(), inode: stats.ino.toString() } };
|
|
1159
1161
|
}
|
|
1160
1162
|
|
|
1161
|
-
|
|
1163
|
+
function selectStateHome(environment: Readonly<Record<string, string | undefined>>): string {
|
|
1162
1164
|
const configuredRoots = [
|
|
1163
1165
|
["GHOSTGET_STATE_HOME", environment.GHOSTGET_STATE_HOME],
|
|
1164
1166
|
["WRENCH_STATE_HOME", environment.WRENCH_STATE_HOME],
|
|
@@ -1215,6 +1217,74 @@ export function ghostgetStateHome(environment: Readonly<Record<string, string |
|
|
|
1215
1217
|
if (forbiddenRoots.has(root) || isWithinPath(ghostgetSourcePackageRoot, root)) {
|
|
1216
1218
|
throw new Error(`GHOSTGET_STATE_HOME must be a dedicated child directory, not a filesystem, home, temporary, repository, or shared data root: ${root}`);
|
|
1217
1219
|
}
|
|
1220
|
+
return root;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Inspect only whether an optional policy may exist, without adopting a state
|
|
1225
|
+
* root. CLI validation and local file inputs must not claim or register state.
|
|
1226
|
+
* A present file still requires the ordinary inode-bound private state reader.
|
|
1227
|
+
*/
|
|
1228
|
+
export function privateStateFilesMayExist(
|
|
1229
|
+
collection: typeof stateDirectoryNames[number],
|
|
1230
|
+
fileNames: readonly string[],
|
|
1231
|
+
environment: Readonly<Record<string, string | undefined>> = process.env,
|
|
1232
|
+
): boolean {
|
|
1233
|
+
if (!stateDirectoryNames.includes(collection) || fileNames.length < 1 || fileNames.length > 8
|
|
1234
|
+
|| fileNames.some(name => !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/u.test(name))) {
|
|
1235
|
+
throw new Error("optional private state file selection is invalid");
|
|
1236
|
+
}
|
|
1237
|
+
const root = selectStateHome(environment);
|
|
1238
|
+
const inspect = (path: string): BigIntStats | null => {
|
|
1239
|
+
try { return lstatSync(path, { bigint: true }); }
|
|
1240
|
+
catch (error) { if (hasCode(error, "ENOENT")) return null; throw error; }
|
|
1241
|
+
};
|
|
1242
|
+
const inspectDirectory = (path: string): BigIntStats | null => {
|
|
1243
|
+
const stats = inspect(path);
|
|
1244
|
+
if (stats !== null && (!stats.isDirectory() || stats.isSymbolicLink()
|
|
1245
|
+
|| !ownedByCurrentUser(stats) || (stats.mode & 0o777n) !== 0o700n)) {
|
|
1246
|
+
throw new Error("optional private state directory is unsafe");
|
|
1247
|
+
}
|
|
1248
|
+
return stats;
|
|
1249
|
+
};
|
|
1250
|
+
const assertUnchanged = (path: string, before: BigIntStats | null): void => {
|
|
1251
|
+
const after = inspect(path);
|
|
1252
|
+
if (before === null ? after !== null : after === null
|
|
1253
|
+
|| before.dev !== after.dev || before.ino !== after.ino
|
|
1254
|
+
|| before.mode !== after.mode || before.uid !== after.uid
|
|
1255
|
+
|| before.ctimeNs !== after.ctimeNs || before.mtimeNs !== after.mtimeNs) {
|
|
1256
|
+
throw new Error("optional private state changed during inspection");
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
if (knownStateRoots.has(root)) assertStateRootIdentity(root);
|
|
1260
|
+
const rootBefore = inspectDirectory(root);
|
|
1261
|
+
// A missing root is observed from its existing, owned creation anchor; no
|
|
1262
|
+
// directory, mode, marker, or process-local state registration is changed.
|
|
1263
|
+
const anchor = rootBefore === null ? findCreationAnchor(root).path : null;
|
|
1264
|
+
const anchorBefore = anchor === null ? null : inspect(anchor);
|
|
1265
|
+
const markerPath = join(root, stateMarkerName);
|
|
1266
|
+
const markerBefore = inspect(markerPath);
|
|
1267
|
+
if (markerBefore !== null) readStateMarker(markerPath);
|
|
1268
|
+
else if (rootBefore !== null && !hasGhostgetPathIdentity(root)) {
|
|
1269
|
+
throw new Error("optional private state root does not identify dedicated Ghostget state");
|
|
1270
|
+
}
|
|
1271
|
+
const directory = join(root, collection);
|
|
1272
|
+
const directoryBefore = inspectDirectory(directory);
|
|
1273
|
+
let present = false;
|
|
1274
|
+
for (const name of fileNames) {
|
|
1275
|
+
if (inspect(join(directory, name)) !== null) present = true;
|
|
1276
|
+
}
|
|
1277
|
+
if (selectStateHome(environment) !== root) throw new Error("optional private state root selection changed");
|
|
1278
|
+
assertUnchanged(directory, directoryBefore);
|
|
1279
|
+
assertUnchanged(markerPath, markerBefore);
|
|
1280
|
+
assertUnchanged(root, rootBefore);
|
|
1281
|
+
if (anchor !== null) assertUnchanged(anchor, anchorBefore);
|
|
1282
|
+
if (knownStateRoots.has(root)) assertStateRootIdentity(root);
|
|
1283
|
+
return present;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
export function ghostgetStateHome(environment: Readonly<Record<string, string | undefined>> = process.env): string {
|
|
1287
|
+
const root = selectStateHome(environment);
|
|
1218
1288
|
const inspected = validateStateRoot(root);
|
|
1219
1289
|
const remembered = knownStateRoots.get(root);
|
|
1220
1290
|
if (remembered !== undefined) {
|
package/src/usage.ts
CHANGED
|
@@ -30,7 +30,14 @@ export const ghostgetUsage = `Usage:
|
|
|
30
30
|
ghostget transcriber setup --engine whisper-cpp --model <file> [media-options]
|
|
31
31
|
ghostget doctor [--json] Check capture, media, auth, and action dependencies
|
|
32
32
|
ghostget capabilities [adapter] [--json] List installed semantic capabilities
|
|
33
|
-
ghostget
|
|
33
|
+
ghostget web request <https-url> [--method GET|HEAD]
|
|
34
|
+
Retrieve public text through native web rules and approvals
|
|
35
|
+
ghostget interface list List user and imported OpenAPI drafts
|
|
36
|
+
ghostget interface export [adapter] Export installed semantic interfaces as OpenAPI
|
|
37
|
+
ghostget interface import <openapi.json> [--expected-digest <sha256>]
|
|
38
|
+
Save an inert draft; review and activate it in the native app
|
|
39
|
+
ghostget whatsapp automation install [--binary <absolute-reviewed-wacli-file>] [--json]
|
|
40
|
+
ghostget imessage transport install [--binary <absolute-reviewed-imsg-file>] [--json]
|
|
34
41
|
Install only the current reviewed iMessage transport bytes
|
|
35
42
|
ghostget plugin list [--json] List trusted source and installed portable plugins
|
|
36
43
|
ghostget plugin show <id> [--json] Inspect one source or portable plugin
|
|
@@ -94,6 +101,7 @@ export const ghostgetUsage = `Usage:
|
|
|
94
101
|
--output <new-absolute-directory> [--json]
|
|
95
102
|
# local wacli.db bundle only; no send, pairing, or cloud sync
|
|
96
103
|
|
|
104
|
+
ghostget messaging automation serve --stdio
|
|
97
105
|
ghostget messaging routes --input <-|@absolute-private-file>
|
|
98
106
|
--private-output <absolute-mode-0600-file> [--json]
|
|
99
107
|
ghostget messaging resolve --input <-|@absolute-private-file>
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Canonical immutable Ghostget package release identity. */
|
|
2
|
-
export const GHOSTGET_VERSION = "0.
|
|
2
|
+
export const GHOSTGET_VERSION = "0.18.2" as const;
|
|
@@ -577,7 +577,7 @@ const linkedin = {
|
|
|
577
577
|
"contacts.read",
|
|
578
578
|
LINKEDIN_WEB_OPERATIONS["contacts.read"].risk,
|
|
579
579
|
LINKEDIN_WEB_OPERATIONS["contacts.read"].state,
|
|
580
|
-
"reviewed contained-Chrome 1st-degree Contact-info read: bind the signed-in viewer, require
|
|
580
|
+
"reviewed contained-Chrome 1st-degree Contact-info read: bind the signed-in viewer, require 1st-degree from Profile embeds or Como, project embedded Contact-info fields while skipping non-bounded SDUI labels, or click the unique Contact info control on the opened profile and project that modal; keep synthetic navigation POST and navigation GETs rejected, treat an HTML-200 shell that omits Email as omitted fields, and do not invent Email",
|
|
581
581
|
LINKEDIN_WEB_OPERATIONS["contacts.read"].contractVersion,
|
|
582
582
|
),
|
|
583
583
|
"feeds.read": contract(
|