@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.
Files changed (114) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/README.md +16 -9
  3. package/dist/apple-photos-client.js +2 -1
  4. package/dist/beeper-client.js +2 -1
  5. package/dist/client.js +1 -0
  6. package/dist/imessage-direct-install-2gm3kge7.js +324 -0
  7. package/dist/index-01eeae9e.js +5 -0
  8. package/dist/index-2ymnp8xv.js +145 -0
  9. package/dist/index-en5hycxp.js +175 -0
  10. package/dist/{index-cytf8d9p.js → index-hfbygww8.js} +1 -1
  11. package/dist/index-n4szk3nw.js +50 -0
  12. package/dist/index-yq6maz71.js +1105 -0
  13. package/dist/index-z1w83f81.js +4 -0
  14. package/dist/index.js +4 -47
  15. package/dist/messaging-automation-api.js +21 -0
  16. package/dist/messaging-automation-j4274hvc.js +609 -0
  17. package/dist/messaging-native-install-8w1j36ah.js +16 -0
  18. package/dist/messaging.js +1 -0
  19. package/dist/omni-client.js +1 -0
  20. package/dist/whatsapp-automation-runtime-hgh1e9rm.js +845 -0
  21. package/dist/whatsapp-client.js +1 -0
  22. package/docs/control-panel.md +147 -0
  23. package/docs/imessage-direct-provider.md +31 -7
  24. package/docs/messaging-automation.md +103 -0
  25. package/package.json +66 -7
  26. package/skills/ghostget/SKILL.md +3 -1
  27. package/skills/ghostget/references/control-panel.md +43 -0
  28. package/skills/ghostget/references/install.md +5 -5
  29. package/skills/ghostget/references/linkedin-adapter.md +222 -12
  30. package/skills/ghostget/references/platform-patterns.md +1 -1
  31. package/src/args.ts +18 -5
  32. package/src/assets/adapters/imessage/wrench-web-adapter.json +126 -0
  33. package/src/assets/adapters/linkedin/wrench-web-adapter.json +1 -1
  34. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +144 -0
  35. package/src/assets/messaging-runtime/NOTICE.txt +2580 -0
  36. package/src/assets/messaging-runtime/imsg-darwin-arm64.gz +0 -0
  37. package/src/assets/messaging-runtime/phone-number-info.plist.gz +0 -0
  38. package/src/assets/messaging-runtime/phone-number-metadata.json.gz +0 -0
  39. package/src/assets/messaging-runtime/phone-number-privacy.plist.gz +0 -0
  40. package/src/assets/messaging-runtime/wacli-darwin-arm64.gz +0 -0
  41. package/src/auth.ts +35 -1
  42. package/src/beeper-client-types.ts +1 -1
  43. package/src/cli.ts +18 -0
  44. package/src/confirmed-write-platform.ts +16 -1
  45. package/src/control/account-revision.ts +16 -0
  46. package/src/control/activity.ts +104 -0
  47. package/src/control/approval-broker.ts +59 -0
  48. package/src/control/approval-client.ts +49 -0
  49. package/src/control/bundled-interfaces.ts +20 -0
  50. package/src/control/cli.ts +20 -0
  51. package/src/control/connections.ts +87 -0
  52. package/src/control/credential-helper.ts +152 -0
  53. package/src/control/helper.ts +79 -0
  54. package/src/control/interface-cli.ts +22 -0
  55. package/src/control/interface-json.ts +94 -0
  56. package/src/control/interface-schema.ts +120 -0
  57. package/src/control/interfaces.ts +438 -0
  58. package/src/control/protocol.ts +182 -0
  59. package/src/control/service.ts +104 -0
  60. package/src/control/validation.ts +103 -0
  61. package/src/control/vault.ts +105 -0
  62. package/src/control/web-gateway.ts +62 -0
  63. package/src/control/web-policy.ts +56 -0
  64. package/src/ghostget.ts +18 -4
  65. package/src/messaging-automation-api.ts +12 -0
  66. package/src/messaging-automation-descriptors.ts +29 -0
  67. package/src/messaging-automation-factory.ts +148 -0
  68. package/src/messaging-automation-server.ts +183 -0
  69. package/src/messaging-automation-types.ts +155 -0
  70. package/src/messaging-automation-validation.ts +110 -0
  71. package/src/messaging-automation.ts +356 -0
  72. package/src/messaging-runtime.ts +3 -0
  73. package/src/oauth-google.ts +11 -5
  74. package/src/omni-runtime.ts +18 -3
  75. package/src/operation-permission-store.ts +92 -0
  76. package/src/operation-permission.ts +308 -0
  77. package/src/pinned-https.ts +5 -0
  78. package/src/plugins/imessage-direct/plugin.ts +12 -1
  79. package/src/plugins/imessage-direct/vendor/0003-feat-rpc-add-no-fetch-rich-cards.patch +276 -0
  80. package/src/plugins/imessage-direct/vendor/provenance.json +84 -10
  81. package/src/plugins/linkedin-web/plugin.ts +1 -1
  82. package/src/plugins/whatsapp-linked-device/plugin.ts +7 -2
  83. package/src/plugins/whatsapp-linked-device/vendor/0001-ghostget-private-messaging.patch +1093 -0
  84. package/src/plugins/whatsapp-linked-device/vendor/README.md +39 -0
  85. package/src/plugins/whatsapp-linked-device/vendor/provenance.json +45 -0
  86. package/src/provider-http.ts +11 -3
  87. package/src/provider-plugin-contract-identity.ts +2 -2
  88. package/src/provider-plugin-import-analysis.ts +52 -0
  89. package/src/provider-plugin-module-analysis.ts +21 -1
  90. package/src/provider-plugin-registry.ts +4 -8
  91. package/src/provider-plugin.ts +18 -8
  92. package/src/providers/imessage-automation.ts +250 -0
  93. package/src/providers/imessage-direct-install.ts +7 -0
  94. package/src/providers/imessage-direct-runtime.ts +32 -2
  95. package/src/providers/imessage-direct.ts +13 -3
  96. package/src/providers/linkedin-contact-failure.ts +21 -0
  97. package/src/providers/linkedin-contact-platform.ts +8 -1
  98. package/src/providers/linkedin-contact-program.ts +34 -5
  99. package/src/providers/linkedin-web-contact.ts +1223 -35
  100. package/src/providers/linkedin-web-profile-browser.ts +659 -45
  101. package/src/providers/linkedin-web-runtime.ts +1 -0
  102. package/src/providers/linkedin-web.ts +46 -2
  103. package/src/providers/messaging-native-artifacts.ts +38 -0
  104. package/src/providers/messaging-native-install.ts +75 -0
  105. package/src/providers/whatsapp-automation-runtime.ts +240 -0
  106. package/src/providers/whatsapp-automation.ts +153 -0
  107. package/src/read-client.ts +12 -2
  108. package/src/runtime.ts +81 -9
  109. package/src/state-helper.ts +2 -0
  110. package/src/storage.ts +71 -1
  111. package/src/usage.ts +9 -1
  112. package/src/version.ts +1 -1
  113. package/src/web-session-contract-definitions.ts +1 -1
  114. package/tsconfig.json +3 -0
@@ -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
- return withInvocationAuthorityAdmission(
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
- export async function revalidatePreparedCapability(
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
- return revalidatePreparedInvocation({
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
- return revalidatePreparedInvocation({
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.auth)
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.auth)
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 runPrepared(
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
- export async function confirmInvocation(
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
- export async function confirmMessagingInvocation(
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,
@@ -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
- export function ghostgetStateHome(environment: Readonly<Record<string, string | undefined>> = process.env): string {
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 imessage transport install --binary <absolute-reviewed-imsg-file> [--json]
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.17.6" as const;
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 a 1st-degree relationship on the requested vanity from classic Profile embeds or Como rehydration, then project page-embedded Contact-info fields or GET voyagerIdentityDashProfileContactInfo and project email plus any shown profile link, connected-since date, phones, websites, or birthday",
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(
package/tsconfig.json CHANGED
@@ -29,6 +29,9 @@
29
29
  ],
30
30
  "@hraness/ghostget/messaging": [
31
31
  "./src/messaging.ts"
32
+ ],
33
+ "@hraness/ghostget/messaging-automation": [
34
+ "./src/messaging-automation-types.ts"
32
35
  ]
33
36
  },
34
37
  "verbatimModuleSyntax": true,