mcp-scraper 0.21.0 → 0.21.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 (36) hide show
  1. package/README.md +4 -2
  2. package/dist/bin/api-server.cjs +368 -224
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +2 -2
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +2 -2
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +2 -2
  11. package/dist/bin/mcp-stdio-server.cjs +12 -8
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +4 -4
  14. package/dist/{chunk-HXGZMMW5.js → chunk-2FF4GBQ4.js} +1 -5
  15. package/dist/{chunk-HXGZMMW5.js.map → chunk-2FF4GBQ4.js.map} +1 -1
  16. package/dist/chunk-NBF4F4UT.js +7 -0
  17. package/dist/chunk-NBF4F4UT.js.map +1 -0
  18. package/dist/{chunk-TUSLATZY.js → chunk-X3NIXHHG.js} +2 -2
  19. package/dist/{chunk-Q44DN6T2.js → chunk-YNHPZU6B.js} +2 -2
  20. package/dist/{chunk-Q44DN6T2.js.map → chunk-YNHPZU6B.js.map} +1 -1
  21. package/dist/{chunk-XHJJQRNQ.js → chunk-ZNNYPE2U.js} +13 -9
  22. package/dist/{chunk-XHJJQRNQ.js.map → chunk-ZNNYPE2U.js.map} +1 -1
  23. package/dist/{server-PH3VJ74X.js → server-CHTX6YQA.js} +336 -207
  24. package/dist/server-CHTX6YQA.js.map +1 -0
  25. package/dist/{site-extract-repository-YNPXEZCB.js → site-extract-repository-5V5EEQ72.js} +3 -3
  26. package/dist/{worker-45WEWCMQ.js → worker-GE5JMHE6.js} +2 -2
  27. package/docs/mcp-tool-manifest.generated.json +28 -10
  28. package/docs/specs/google-drive-bulk-access-and-mcp-schema-passthrough-spec.md +16 -7
  29. package/docs/specs/unified-credit-and-scheduled-execution-billing-spec.md +96 -117
  30. package/package.json +2 -2
  31. package/dist/chunk-L4LMSXLO.js +0 -7
  32. package/dist/chunk-L4LMSXLO.js.map +0 -1
  33. package/dist/server-PH3VJ74X.js.map +0 -1
  34. /package/dist/{chunk-TUSLATZY.js.map → chunk-X3NIXHHG.js.map} +0 -0
  35. /package/dist/{site-extract-repository-YNPXEZCB.js.map → site-extract-repository-5V5EEQ72.js.map} +0 -0
  36. /package/dist/{worker-45WEWCMQ.js.map → worker-GE5JMHE6.js.map} +0 -0
@@ -8525,7 +8525,7 @@ function insufficientBalanceResponse(balanceMc, requiredMc) {
8525
8525
  topup_url: topupUrl
8526
8526
  };
8527
8527
  }
8528
- var MC_COSTS, MC_PER_BROWSER_MS, BROWSER_OPEN_MIN_BALANCE_MC, MC_PER_CREDIT, BILLING_MC_PER_USD_NUMERATOR, BILLING_MC_PER_USD_DENOMINATOR, BILLING_BASIS_POINTS, USD_NANOS, SCHEDULED_RUN_BASE_CREDITS, SCHEDULED_RUN_BASE_MC, SCHEDULED_AGENT_LLM_MARKUP_BPS, SCHEDULED_AGENT_LLM_MARKUP, SCHEDULED_RUN_DEFAULT_MAX_CREDITS, CREDIT_COST_CATALOG, CONCURRENCY_PRICE_ID, SUBSCRIPTION_TIERS, SUBSCRIPTION_TIER_BY_KEY, CONCURRENCY_SLOT_PRICE_USD, CONCURRENCY_SLOT_PRICE_CURRENCY, CONCURRENCY_SLOT_PRICE_INTERVAL, CONCURRENCY_SLOT_PRICE_LABEL, CONCURRENCY_SLOT_TERMINAL_COMMAND, FREE_SIGNUP_MC, MEMORY_FREE_QUOTA_BYTES, MEMORY_PLANS, MEMORY_PLAN_QUOTA, MEMORY_MARGIN_MULTIPLE, MEMORY_PRO_COST_BUDGET_USD, SCHEDULING_PLANS, LedgerOperation, MEMORY_AI_MARGIN_MULTIPLE, SITE_AUDIT_LLM_MARGIN_MULTIPLE, VIDEO_ANALYSIS_LLM_MARGIN_MULTIPLE, MEDIA_TRANSCRIBE_MARGIN_MULTIPLE, MEDIA_TRANSCRIBE_HOLD_MC, MC_PER_USD;
8528
+ var MC_COSTS, MC_PER_BROWSER_MS, BROWSER_OPEN_MIN_BALANCE_MC, MC_PER_CREDIT, BILLING_MC_PER_USD_NUMERATOR, BILLING_MC_PER_USD_DENOMINATOR, BILLING_BASIS_POINTS, USD_NANOS, SCHEDULED_RUN_BASE_CREDITS, SCHEDULED_RUN_BASE_MC, SCHEDULED_AGENT_LLM_MARKUP_BPS, SCHEDULED_AGENT_LLM_MARKUP, SCHEDULED_RUN_DEFAULT_MAX_CREDITS, CREDIT_COST_CATALOG, CONCURRENCY_PRICE_ID, SUBSCRIPTION_TIERS, SUBSCRIPTION_TIER_BY_KEY, CONCURRENCY_SLOT_PRICE_USD, CONCURRENCY_SLOT_PRICE_CURRENCY, CONCURRENCY_SLOT_PRICE_INTERVAL, CONCURRENCY_SLOT_PRICE_LABEL, CONCURRENCY_SLOT_TERMINAL_COMMAND, FREE_SIGNUP_MC, MEMORY_FREE_QUOTA_BYTES, MEMORY_PLANS, MEMORY_PLAN_QUOTA, MEMORY_MARGIN_MULTIPLE, MEMORY_PRO_COST_BUDGET_USD, LedgerOperation, MEMORY_AI_MARGIN_MULTIPLE, SITE_AUDIT_LLM_MARGIN_MULTIPLE, VIDEO_ANALYSIS_LLM_MARGIN_MULTIPLE, MEDIA_TRANSCRIBE_MARGIN_MULTIPLE, MEDIA_TRANSCRIBE_HOLD_MC, MC_PER_USD;
8529
8529
  var init_rates = __esm({
8530
8530
  "src/api/rates.ts"() {
8531
8531
  "use strict";
@@ -8810,9 +8810,6 @@ var init_rates = __esm({
8810
8810
  MEMORY_PLAN_QUOTA = { free: MEMORY_FREE_QUOTA_BYTES, pro: 5e9, team: 5e10 };
8811
8811
  MEMORY_MARGIN_MULTIPLE = 3;
8812
8812
  MEMORY_PRO_COST_BUDGET_USD = 19 / MEMORY_MARGIN_MULTIPLE;
8813
- SCHEDULING_PLANS = {
8814
- "price_1ToXcHS8aAcsk3TGf3cW7zHx": { price_id: "price_1ToXcHS8aAcsk3TGf3cW7zHx", label: "Scheduled Actions", interval: "month", monthly_usd: 10, quota_per_period: 1e3 }
8815
- };
8816
8813
  LedgerOperation = {
8817
8814
  TOPUP: "topup",
8818
8815
  SUBSCRIPTION: "subscription",
@@ -11903,6 +11900,51 @@ var init_internal_memory_routes = __esm({
11903
11900
  }
11904
11901
  });
11905
11902
 
11903
+ // src/api/scheduling-access.ts
11904
+ function hasPaidSchedulingAccess(user) {
11905
+ return typeof user.subscription_tier === "string" && user.subscription_tier.trim().length > 0;
11906
+ }
11907
+ function paidPlanRequiredPayload() {
11908
+ return {
11909
+ ok: false,
11910
+ enabled: false,
11911
+ code: PAID_PLAN_REQUIRED_CODE,
11912
+ error: PAID_PLAN_REQUIRED_MESSAGE,
11913
+ billingMode: "credits",
11914
+ runBaseCredits: SCHEDULED_RUN_BASE_CREDITS,
11915
+ llmCostMultiplier: SCHEDULED_AGENT_LLM_MARKUP
11916
+ };
11917
+ }
11918
+ function memoryScheduleAccessPayload(upstreamName, user) {
11919
+ if (hasPaidSchedulingAccess(user)) return null;
11920
+ if (upstreamName === "getScheduleStatusTool") return paidPlanRequiredPayload();
11921
+ if (PLAN_GATED_MEMORY_SCHEDULE_TOOLS.has(upstreamName)) {
11922
+ return {
11923
+ ok: false,
11924
+ code: PAID_PLAN_REQUIRED_CODE,
11925
+ error: PAID_PLAN_REQUIRED_MESSAGE
11926
+ };
11927
+ }
11928
+ return null;
11929
+ }
11930
+ function memoryScheduleToolNeedsCredentials(upstreamName) {
11931
+ return upstreamName === "createScheduledActionTool" || upstreamName === "getScheduleLinkTool";
11932
+ }
11933
+ var PAID_PLAN_REQUIRED_CODE, PAID_PLAN_REQUIRED_MESSAGE, PLAN_GATED_MEMORY_SCHEDULE_TOOLS;
11934
+ var init_scheduling_access = __esm({
11935
+ "src/api/scheduling-access.ts"() {
11936
+ "use strict";
11937
+ init_rates();
11938
+ PAID_PLAN_REQUIRED_CODE = "paid_plan_required";
11939
+ PAID_PLAN_REQUIRED_MESSAGE = "Scheduled Actions requires an active MCP Scraper Starter plan or higher.";
11940
+ PLAN_GATED_MEMORY_SCHEDULE_TOOLS = /* @__PURE__ */ new Set([
11941
+ "createScheduledActionTool",
11942
+ "getScheduleLinkTool",
11943
+ "resumeScheduledActionTool"
11944
+ ]);
11945
+ }
11946
+ });
11947
+
11906
11948
  // src/api/unified-billing.ts
11907
11949
  function billingSecret() {
11908
11950
  const secret2 = process.env.INTERNAL_BILLING_SECRET ?? process.env.INTERNAL_MEMORY_DEBIT_SECRET;
@@ -11916,6 +11958,19 @@ function normalizeIdentity(identity) {
11916
11958
  }
11917
11959
  return normalized;
11918
11960
  }
11961
+ async function resolveSchedulingUser(identity) {
11962
+ const user = await getUserByEmail(normalizeIdentity(identity));
11963
+ if (!user) throw new UnifiedBillingError("user_not_found", "billing identity was not found", 404);
11964
+ return user;
11965
+ }
11966
+ async function getSchedulingAccess(identity) {
11967
+ const user = await getUserByEmail(normalizeIdentity(identity));
11968
+ if (!user) return { allowed: false, subscriptionTier: null };
11969
+ return {
11970
+ allowed: hasPaidSchedulingAccess(user),
11971
+ subscriptionTier: user.subscription_tier
11972
+ };
11973
+ }
11919
11974
  function normalizeIdempotencyKey(value) {
11920
11975
  const key = value.trim();
11921
11976
  if (!key || key.length > 500) {
@@ -11997,8 +12052,10 @@ function mapEvent(row, balanceMc, duplicate) {
11997
12052
  async function authorizeScheduledRun(args) {
11998
12053
  const identity = normalizeIdentity(args.identity);
11999
12054
  const idempotencyKey = normalizeIdempotencyKey(args.idempotencyKey);
12000
- const user = await getUserByEmail(identity);
12001
- if (!user) throw new UnifiedBillingError("user_not_found", "billing identity was not found", 404);
12055
+ const user = await resolveSchedulingUser(identity);
12056
+ if (!hasPaidSchedulingAccess(user)) {
12057
+ throw new UnifiedBillingError(PAID_PLAN_REQUIRED_CODE, PAID_PLAN_REQUIRED_MESSAGE, 403);
12058
+ }
12002
12059
  const pending = await getDb().execute({
12003
12060
  sql: "SELECT id FROM billing_events WHERE user_id = ? AND billing_class = ? AND status = 'cost_pending' LIMIT 1",
12004
12061
  args: [user.id, SCHEDULED_RUN_BILLING_CLASS]
@@ -12266,6 +12323,7 @@ var init_unified_billing = __esm({
12266
12323
  import_node_crypto4 = require("crypto");
12267
12324
  init_db();
12268
12325
  init_rates();
12326
+ init_scheduling_access();
12269
12327
  SCHEDULED_RUN_BILLING_CLASS = "scheduled_agent_run";
12270
12328
  SCHEDULED_RUN_SOURCE_SURFACE = "mastra_scheduler";
12271
12329
  AUTHORIZATION_TTL_MS = 30 * 60 * 1e3;
@@ -12284,6 +12342,192 @@ var init_unified_billing = __esm({
12284
12342
  }
12285
12343
  });
12286
12344
 
12345
+ // src/api/session.ts
12346
+ function getSessionSecret() {
12347
+ const configured = process.env.SESSION_SECRET?.trim();
12348
+ if (configured) return configured;
12349
+ if (isProduction()) throw new Error("SESSION_SECRET is not set \u2014 add it to your Vercel env vars (see src/api/env.ts)");
12350
+ return "dev-secret-change-me";
12351
+ }
12352
+ function safeEqualHex(a, b) {
12353
+ if (a.length !== b.length) return false;
12354
+ try {
12355
+ return (0, import_node_crypto5.timingSafeEqual)(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
12356
+ } catch {
12357
+ return false;
12358
+ }
12359
+ }
12360
+ function signSession(userId) {
12361
+ const payload = String(userId);
12362
+ const sig = (0, import_node_crypto5.createHmac)("sha256", secret()).update(payload).digest("hex");
12363
+ return `${payload}.${sig}`;
12364
+ }
12365
+ function verifySession(token) {
12366
+ const dot = token.lastIndexOf(".");
12367
+ if (dot === -1) return null;
12368
+ const payload = token.slice(0, dot);
12369
+ const sig = token.slice(dot + 1);
12370
+ const expected = (0, import_node_crypto5.createHmac)("sha256", secret()).update(payload).digest("hex");
12371
+ if (!safeEqualHex(sig, expected)) return null;
12372
+ const id = parseInt(payload);
12373
+ return isNaN(id) ? null : id;
12374
+ }
12375
+ var import_node_crypto5, isProduction, secret;
12376
+ var init_session = __esm({
12377
+ "src/api/session.ts"() {
12378
+ "use strict";
12379
+ import_node_crypto5 = require("crypto");
12380
+ isProduction = () => process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
12381
+ secret = () => getSessionSecret();
12382
+ }
12383
+ });
12384
+
12385
+ // src/api/memory.ts
12386
+ function parseMemoryRpcBody(text) {
12387
+ const dataLine = text.split("\n").find((line) => line.startsWith("data:"));
12388
+ const payload = dataLine ? dataLine.slice(5).trim() : text;
12389
+ return JSON.parse(payload);
12390
+ }
12391
+ function isMemoryOperator(email) {
12392
+ if (!email) return false;
12393
+ const ops = (process.env.MEMORY_OPERATOR_IDENTITIES ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
12394
+ return ops.includes(email.trim().toLowerCase());
12395
+ }
12396
+ function encKey() {
12397
+ return (0, import_node_crypto6.scryptSync)(getSessionSecret(), "mcp-memory-key-v1", 32);
12398
+ }
12399
+ function encryptMemoryKey(secret2) {
12400
+ const iv = (0, import_node_crypto6.randomBytes)(12);
12401
+ const cipher = (0, import_node_crypto6.createCipheriv)("aes-256-gcm", encKey(), iv);
12402
+ const enc = Buffer.concat([cipher.update(secret2, "utf8"), cipher.final()]);
12403
+ const tag = cipher.getAuthTag();
12404
+ return `${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
12405
+ }
12406
+ function decryptMemoryKey(stored) {
12407
+ try {
12408
+ const [ivB, tagB, dataB] = stored.split(":");
12409
+ const decipher = (0, import_node_crypto6.createDecipheriv)("aes-256-gcm", encKey(), Buffer.from(ivB, "base64"));
12410
+ decipher.setAuthTag(Buffer.from(tagB, "base64"));
12411
+ return Buffer.concat([decipher.update(Buffer.from(dataB, "base64")), decipher.final()]).toString("utf8");
12412
+ } catch {
12413
+ return null;
12414
+ }
12415
+ }
12416
+ async function memoryCall(toolName, args, userMemoryKey) {
12417
+ try {
12418
+ const res = await fetch(`${MEMORY_BASE_URL()}/mcp`, {
12419
+ method: "POST",
12420
+ headers: {
12421
+ "content-type": "application/json",
12422
+ accept: "application/json, text/event-stream",
12423
+ authorization: `Bearer ${userMemoryKey}`
12424
+ },
12425
+ body: JSON.stringify({
12426
+ jsonrpc: "2.0",
12427
+ id: 1,
12428
+ method: "tools/call",
12429
+ params: { name: toolName, arguments: args }
12430
+ })
12431
+ });
12432
+ const text = await res.text();
12433
+ if (!res.ok) return { ok: false, error: `memory ${toolName} failed (${res.status})` };
12434
+ const body = parseMemoryRpcBody(text);
12435
+ if (body.error) return { ok: false, error: body.error.message ?? `memory ${toolName} failed` };
12436
+ if (body.result?.structuredContent) return body.result.structuredContent;
12437
+ const textContent = body.result?.content?.find((c) => c.type === "text")?.text;
12438
+ if (textContent) {
12439
+ try {
12440
+ return JSON.parse(textContent);
12441
+ } catch {
12442
+ return { ok: false, error: textContent };
12443
+ }
12444
+ }
12445
+ return { ok: false, error: `memory ${toolName} returned no result` };
12446
+ } catch (err) {
12447
+ return { ok: false, error: err?.message ?? "memory call failed" };
12448
+ }
12449
+ }
12450
+ function memoryIdentity(user) {
12451
+ return user.email;
12452
+ }
12453
+ function resolveMemoryIdentity(user) {
12454
+ return memoryIdentity(user);
12455
+ }
12456
+ function memoryPlanForReads(user) {
12457
+ if (isMemoryOperator(user.email)) return "unlimited";
12458
+ if (user.memory_plan === "pro" || user.memory_plan === "team") return user.memory_plan;
12459
+ return "free";
12460
+ }
12461
+ async function provisionMemoryForUser(user) {
12462
+ try {
12463
+ const { key } = await getOrCreateUserMemoryKey(user);
12464
+ if (key) await setMemoryProvisioned(user.id, true);
12465
+ } catch {
12466
+ }
12467
+ }
12468
+ function resolveEffectiveMemoryPlan(user) {
12469
+ return user.memory_plan === "pro" || user.memory_plan === "team" ? user.memory_plan : "free";
12470
+ }
12471
+ async function getOrCreateUserMemoryKey(user) {
12472
+ const creds = await getUserMemoryCreds(user.id);
12473
+ if (creds.memory_key) {
12474
+ const decrypted = decryptMemoryKey(creds.memory_key);
12475
+ if (decrypted) return { key: decrypted };
12476
+ }
12477
+ const admin = ADMIN_KEY();
12478
+ if (!admin) return { key: null, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
12479
+ const identity = memoryIdentity(user);
12480
+ const plan = isMemoryOperator(user.email) ? "unlimited" : (await getMemoryPlan(user.id)).plan;
12481
+ const provisioned = await import_provision_defaults.provisionDefaultsTool.execute(
12482
+ { apiKey: admin, granteeIdentity: identity, issueKey: true, plan },
12483
+ {}
12484
+ );
12485
+ if (!provisioned.ok || !provisioned.secret) {
12486
+ return { key: null, error: provisioned.error ?? "failed to provision default vaults" };
12487
+ }
12488
+ await setUserMemoryCreds(user.id, encryptMemoryKey(provisioned.secret), identity);
12489
+ return { key: provisioned.secret };
12490
+ }
12491
+ async function syncMemoryKeyPlan(user, plan) {
12492
+ const effectivePlan = isMemoryOperator(user.email) ? "unlimited" : plan;
12493
+ const { key, error } = await getOrCreateUserMemoryKey(user);
12494
+ if (!key) return { ok: false, error: error ?? "memory unavailable" };
12495
+ const listed = await memoryCall(
12496
+ "listKeysTool",
12497
+ {},
12498
+ key
12499
+ );
12500
+ if (!listed.ok || !listed.keys?.length) return { ok: false, error: listed.error ?? "no memory keys found" };
12501
+ const target = listed.keys[0];
12502
+ if (target.plan === effectivePlan) return { ok: true };
12503
+ const res = await memoryCall("setScopeTool", { keyId: target.keyId, plan: effectivePlan }, key);
12504
+ return { ok: res.ok, error: res.error };
12505
+ }
12506
+ async function syncScheduledActionCredentials(user) {
12507
+ const admin = ADMIN_KEY();
12508
+ if (!admin) return { ok: false, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
12509
+ const identity = resolveMemoryIdentity(user);
12510
+ const res = await import_set_schedule_entitlement.setScheduleEntitlementTool.execute({
12511
+ apiKey: admin,
12512
+ granteeIdentity: identity,
12513
+ mcpScraperApiKey: user.api_key
12514
+ }, {});
12515
+ return { ok: res.ok, error: res.error };
12516
+ }
12517
+ var import_node_crypto6, import_provision_defaults, import_set_schedule_entitlement, MEMORY_BASE_URL, ADMIN_KEY;
12518
+ var init_memory = __esm({
12519
+ "src/api/memory.ts"() {
12520
+ "use strict";
12521
+ import_node_crypto6 = require("crypto");
12522
+ init_session();
12523
+ init_db();
12524
+ import_provision_defaults = require("mcpscraper-memory-tools/tools/vaults/provision-defaults");
12525
+ import_set_schedule_entitlement = require("mcpscraper-memory-tools/tools/schedule/set-schedule-entitlement");
12526
+ MEMORY_BASE_URL = () => (process.env.MCP_MEMORY_BASE_URL ?? "https://memory.mcpscraper.dev").replace(/\/$/, "");
12527
+ ADMIN_KEY = () => process.env.MCP_MEMORY_ADMIN_KEY?.trim() ?? "";
12528
+ }
12529
+ });
12530
+
12287
12531
  // src/api/internal-billing-routes.ts
12288
12532
  function internalRequestAuthorized(header) {
12289
12533
  const expected = process.env.INTERNAL_BILLING_SECRET ?? process.env.INTERNAL_MEMORY_DEBIT_SECRET;
@@ -12307,15 +12551,22 @@ function billingError(error) {
12307
12551
  status: 500
12308
12552
  };
12309
12553
  }
12310
- var import_hono3, import_zod12, internalBillingApp, metadataSchema, authorizeSchema, settleSchema, voidSchema;
12554
+ var import_hono3, import_zod12, internalBillingApp, metadataSchema, schedulingAccessSchema, authorizeSchema, settleSchema, voidSchema;
12311
12555
  var init_internal_billing_routes = __esm({
12312
12556
  "src/api/internal-billing-routes.ts"() {
12313
12557
  "use strict";
12314
12558
  import_hono3 = require("hono");
12315
12559
  import_zod12 = require("zod");
12316
12560
  init_unified_billing();
12561
+ init_scheduling_access();
12562
+ init_db();
12563
+ init_memory();
12317
12564
  internalBillingApp = new import_hono3.Hono();
12318
12565
  metadataSchema = import_zod12.z.record(import_zod12.z.unknown()).optional();
12566
+ schedulingAccessSchema = import_zod12.z.object({
12567
+ identity: import_zod12.z.string().min(1).max(320),
12568
+ provisionCredentials: import_zod12.z.boolean().optional()
12569
+ });
12319
12570
  authorizeSchema = import_zod12.z.object({
12320
12571
  identity: import_zod12.z.string().min(1).max(320),
12321
12572
  idempotencyKey: import_zod12.z.string().min(1).max(500),
@@ -12359,6 +12610,41 @@ var init_internal_billing_routes = __esm({
12359
12610
  return c.json(response.body, response.status);
12360
12611
  }
12361
12612
  });
12613
+ internalBillingApp.post("/scheduling-access", async (c) => {
12614
+ const parsed = schedulingAccessSchema.safeParse(await c.req.json().catch(() => null));
12615
+ if (!parsed.success) {
12616
+ return c.json({ ok: false, error_code: "invalid_request", error: "invalid scheduling access request" }, 400);
12617
+ }
12618
+ try {
12619
+ const access2 = await getSchedulingAccess(parsed.data.identity);
12620
+ if (access2.allowed && parsed.data.provisionCredentials) {
12621
+ const user = await getUserByEmail(parsed.data.identity.trim().toLowerCase());
12622
+ if (!user) {
12623
+ return c.json({ ok: false, error_code: "user_not_found", error: "billing identity was not found" }, 404);
12624
+ }
12625
+ const credentials = await syncScheduledActionCredentials(user);
12626
+ if (!credentials.ok) {
12627
+ return c.json({
12628
+ ok: false,
12629
+ error_code: "schedule_credentials_unavailable",
12630
+ error: credentials.error ?? "Scheduled Actions credentials are unavailable."
12631
+ }, 503);
12632
+ }
12633
+ }
12634
+ return c.json({
12635
+ ok: true,
12636
+ allowed: access2.allowed,
12637
+ subscriptionTier: access2.subscriptionTier,
12638
+ ...access2.allowed ? {} : {
12639
+ error_code: PAID_PLAN_REQUIRED_CODE,
12640
+ error: PAID_PLAN_REQUIRED_MESSAGE
12641
+ }
12642
+ });
12643
+ } catch (error) {
12644
+ const response = billingError(error);
12645
+ return c.json(response.body, response.status);
12646
+ }
12647
+ });
12362
12648
  internalBillingApp.post("/scheduled-run/settle", async (c) => {
12363
12649
  const parsed = settleSchema.safeParse(await c.req.json().catch(() => null));
12364
12650
  if (!parsed.success) {
@@ -14785,7 +15071,7 @@ async function acquireConcurrencyGate(user, operation, options = {}) {
14785
15071
  return { ok: true, lockId: null, active: await countActiveUsageForUser(user.id), limit, operation, reused: true };
14786
15072
  }
14787
15073
  await expireConcurrencyLocksForUser(user.id);
14788
- const lockId = `cl_${(0, import_node_crypto5.randomUUID)().replace(/-/g, "").slice(0, 24)}`;
15074
+ const lockId = `cl_${(0, import_node_crypto7.randomUUID)().replace(/-/g, "").slice(0, 24)}`;
14789
15075
  const res = await getDb().execute({
14790
15076
  sql: `INSERT INTO concurrency_locks (id, user_id, operation, status, expires_at, metadata)
14791
15077
  SELECT ?, ?, ?, 'active', datetime('now', ?), ?
@@ -14839,11 +15125,11 @@ async function extendConcurrencyGate(lockId, ttlSeconds = DEFAULT_LOCK_TTL_SECON
14839
15125
  args: [lockTtlModifier(ttlSeconds), lockId]
14840
15126
  });
14841
15127
  }
14842
- var import_node_crypto5, DEFAULT_LOCK_TTL_SECONDS, DEFAULT_RETRY_AFTER_SECONDS, MAX_LOCK_TTL_SECONDS, schemaReady;
15128
+ var import_node_crypto7, DEFAULT_LOCK_TTL_SECONDS, DEFAULT_RETRY_AFTER_SECONDS, MAX_LOCK_TTL_SECONDS, schemaReady;
14843
15129
  var init_concurrency_gates = __esm({
14844
15130
  "src/api/concurrency-gates.ts"() {
14845
15131
  "use strict";
14846
- import_node_crypto5 = require("crypto");
15132
+ import_node_crypto7 = require("crypto");
14847
15133
  init_db();
14848
15134
  init_rates();
14849
15135
  DEFAULT_LOCK_TTL_SECONDS = 15 * 60;
@@ -17786,194 +18072,6 @@ var init_reddit_routes = __esm({
17786
18072
  }
17787
18073
  });
17788
18074
 
17789
- // src/api/session.ts
17790
- function getSessionSecret() {
17791
- const configured = process.env.SESSION_SECRET?.trim();
17792
- if (configured) return configured;
17793
- if (isProduction()) throw new Error("SESSION_SECRET is not set \u2014 add it to your Vercel env vars (see src/api/env.ts)");
17794
- return "dev-secret-change-me";
17795
- }
17796
- function safeEqualHex(a, b) {
17797
- if (a.length !== b.length) return false;
17798
- try {
17799
- return (0, import_node_crypto6.timingSafeEqual)(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
17800
- } catch {
17801
- return false;
17802
- }
17803
- }
17804
- function signSession(userId) {
17805
- const payload = String(userId);
17806
- const sig = (0, import_node_crypto6.createHmac)("sha256", secret()).update(payload).digest("hex");
17807
- return `${payload}.${sig}`;
17808
- }
17809
- function verifySession(token) {
17810
- const dot = token.lastIndexOf(".");
17811
- if (dot === -1) return null;
17812
- const payload = token.slice(0, dot);
17813
- const sig = token.slice(dot + 1);
17814
- const expected = (0, import_node_crypto6.createHmac)("sha256", secret()).update(payload).digest("hex");
17815
- if (!safeEqualHex(sig, expected)) return null;
17816
- const id = parseInt(payload);
17817
- return isNaN(id) ? null : id;
17818
- }
17819
- var import_node_crypto6, isProduction, secret;
17820
- var init_session = __esm({
17821
- "src/api/session.ts"() {
17822
- "use strict";
17823
- import_node_crypto6 = require("crypto");
17824
- isProduction = () => process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
17825
- secret = () => getSessionSecret();
17826
- }
17827
- });
17828
-
17829
- // src/api/memory.ts
17830
- function parseMemoryRpcBody(text) {
17831
- const dataLine = text.split("\n").find((line) => line.startsWith("data:"));
17832
- const payload = dataLine ? dataLine.slice(5).trim() : text;
17833
- return JSON.parse(payload);
17834
- }
17835
- function isMemoryOperator(email) {
17836
- if (!email) return false;
17837
- const ops = (process.env.MEMORY_OPERATOR_IDENTITIES ?? "").split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
17838
- return ops.includes(email.trim().toLowerCase());
17839
- }
17840
- function encKey() {
17841
- return (0, import_node_crypto7.scryptSync)(getSessionSecret(), "mcp-memory-key-v1", 32);
17842
- }
17843
- function encryptMemoryKey(secret2) {
17844
- const iv = (0, import_node_crypto7.randomBytes)(12);
17845
- const cipher = (0, import_node_crypto7.createCipheriv)("aes-256-gcm", encKey(), iv);
17846
- const enc = Buffer.concat([cipher.update(secret2, "utf8"), cipher.final()]);
17847
- const tag = cipher.getAuthTag();
17848
- return `${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
17849
- }
17850
- function decryptMemoryKey(stored) {
17851
- try {
17852
- const [ivB, tagB, dataB] = stored.split(":");
17853
- const decipher = (0, import_node_crypto7.createDecipheriv)("aes-256-gcm", encKey(), Buffer.from(ivB, "base64"));
17854
- decipher.setAuthTag(Buffer.from(tagB, "base64"));
17855
- return Buffer.concat([decipher.update(Buffer.from(dataB, "base64")), decipher.final()]).toString("utf8");
17856
- } catch {
17857
- return null;
17858
- }
17859
- }
17860
- async function memoryCall(toolName, args, userMemoryKey) {
17861
- try {
17862
- const res = await fetch(`${MEMORY_BASE_URL()}/mcp`, {
17863
- method: "POST",
17864
- headers: {
17865
- "content-type": "application/json",
17866
- accept: "application/json, text/event-stream",
17867
- authorization: `Bearer ${userMemoryKey}`
17868
- },
17869
- body: JSON.stringify({
17870
- jsonrpc: "2.0",
17871
- id: 1,
17872
- method: "tools/call",
17873
- params: { name: toolName, arguments: args }
17874
- })
17875
- });
17876
- const text = await res.text();
17877
- if (!res.ok) return { ok: false, error: `memory ${toolName} failed (${res.status})` };
17878
- const body = parseMemoryRpcBody(text);
17879
- if (body.error) return { ok: false, error: body.error.message ?? `memory ${toolName} failed` };
17880
- if (body.result?.structuredContent) return body.result.structuredContent;
17881
- const textContent = body.result?.content?.find((c) => c.type === "text")?.text;
17882
- if (textContent) {
17883
- try {
17884
- return JSON.parse(textContent);
17885
- } catch {
17886
- return { ok: false, error: textContent };
17887
- }
17888
- }
17889
- return { ok: false, error: `memory ${toolName} returned no result` };
17890
- } catch (err) {
17891
- return { ok: false, error: err?.message ?? "memory call failed" };
17892
- }
17893
- }
17894
- function memoryIdentity(user) {
17895
- return user.email;
17896
- }
17897
- function resolveMemoryIdentity(user) {
17898
- return memoryIdentity(user);
17899
- }
17900
- function memoryPlanForReads(user) {
17901
- if (isMemoryOperator(user.email)) return "unlimited";
17902
- if (user.memory_plan === "pro" || user.memory_plan === "team") return user.memory_plan;
17903
- return "free";
17904
- }
17905
- async function provisionMemoryForUser(user) {
17906
- try {
17907
- const { key } = await getOrCreateUserMemoryKey(user);
17908
- if (key) await setMemoryProvisioned(user.id, true);
17909
- } catch {
17910
- }
17911
- }
17912
- function resolveEffectiveMemoryPlan(user) {
17913
- return user.memory_plan === "pro" || user.memory_plan === "team" ? user.memory_plan : "free";
17914
- }
17915
- async function getOrCreateUserMemoryKey(user) {
17916
- const creds = await getUserMemoryCreds(user.id);
17917
- if (creds.memory_key) {
17918
- const decrypted = decryptMemoryKey(creds.memory_key);
17919
- if (decrypted) return { key: decrypted };
17920
- }
17921
- const admin = ADMIN_KEY();
17922
- if (!admin) return { key: null, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
17923
- const identity = memoryIdentity(user);
17924
- const plan = isMemoryOperator(user.email) ? "unlimited" : (await getMemoryPlan(user.id)).plan;
17925
- const provisioned = await import_provision_defaults.provisionDefaultsTool.execute(
17926
- { apiKey: admin, granteeIdentity: identity, issueKey: true, plan },
17927
- {}
17928
- );
17929
- if (!provisioned.ok || !provisioned.secret) {
17930
- return { key: null, error: provisioned.error ?? "failed to provision default vaults" };
17931
- }
17932
- await setUserMemoryCreds(user.id, encryptMemoryKey(provisioned.secret), identity);
17933
- return { key: provisioned.secret };
17934
- }
17935
- async function syncMemoryKeyPlan(user, plan) {
17936
- const effectivePlan = isMemoryOperator(user.email) ? "unlimited" : plan;
17937
- const { key, error } = await getOrCreateUserMemoryKey(user);
17938
- if (!key) return { ok: false, error: error ?? "memory unavailable" };
17939
- const listed = await memoryCall(
17940
- "listKeysTool",
17941
- {},
17942
- key
17943
- );
17944
- if (!listed.ok || !listed.keys?.length) return { ok: false, error: listed.error ?? "no memory keys found" };
17945
- const target = listed.keys[0];
17946
- if (target.plan === effectivePlan) return { ok: true };
17947
- const res = await memoryCall("setScopeTool", { keyId: target.keyId, plan: effectivePlan }, key);
17948
- return { ok: res.ok, error: res.error };
17949
- }
17950
- async function syncScheduleEntitlement(user, enabled, quotaPerPeriod) {
17951
- const admin = ADMIN_KEY();
17952
- if (!admin) return { ok: false, error: "memory not configured (MCP_MEMORY_ADMIN_KEY missing)" };
17953
- const identity = resolveMemoryIdentity(user);
17954
- const res = await import_set_schedule_entitlement.setScheduleEntitlementTool.execute({
17955
- apiKey: admin,
17956
- granteeIdentity: identity,
17957
- enabled,
17958
- quotaPerPeriod,
17959
- mcpScraperApiKey: user.api_key
17960
- }, {});
17961
- return { ok: res.ok, error: res.error };
17962
- }
17963
- var import_node_crypto7, import_provision_defaults, import_set_schedule_entitlement, MEMORY_BASE_URL, ADMIN_KEY;
17964
- var init_memory = __esm({
17965
- "src/api/memory.ts"() {
17966
- "use strict";
17967
- import_node_crypto7 = require("crypto");
17968
- init_session();
17969
- init_db();
17970
- import_provision_defaults = require("mcpscraper-memory-tools/tools/vaults/provision-defaults");
17971
- import_set_schedule_entitlement = require("mcpscraper-memory-tools/tools/schedule/set-schedule-entitlement");
17972
- MEMORY_BASE_URL = () => (process.env.MCP_MEMORY_BASE_URL ?? "https://memory.mcpscraper.dev").replace(/\/$/, "");
17973
- ADMIN_KEY = () => process.env.MCP_MEMORY_ADMIN_KEY?.trim() ?? "";
17974
- }
17975
- });
17976
-
17977
18075
  // src/api/video-routes.ts
17978
18076
  function invalidRequest5(message) {
17979
18077
  return { error_code: "invalid_request", message };
@@ -28401,7 +28499,7 @@ var PACKAGE_VERSION;
28401
28499
  var init_version = __esm({
28402
28500
  "src/version.ts"() {
28403
28501
  "use strict";
28404
- PACKAGE_VERSION = "0.21.0";
28502
+ PACKAGE_VERSION = "0.21.2";
28405
28503
  }
28406
28504
  });
28407
28505
 
@@ -34324,7 +34422,7 @@ var init_memory_tool_schemas = __esm({
34324
34422
  CreateScheduledActionSchema = {
34325
34423
  id: "create-scheduled-action",
34326
34424
  upstreamName: "createScheduledActionTool",
34327
- description: "Create a Credit-metered scheduled action in agent mode (default) or connection_sync mode. Each execution has a 75-Credit base charge; agent model usage is added at 1.5 times OpenRouter's actual reported cost. Agent mode follows the description and writes a result into the target vault. connection_sync deterministically runs approved read-only tools on bound service connections and ingests their data. Cadence 'once' runs a single time then completes permanently. Requires write access to the target vault.",
34425
+ description: "Create a Credit-metered scheduled action for an active MCP Scraper Starter plan or higher, in agent mode (default) or connection_sync mode. Each execution has a 75-Credit base charge; agent model usage is added at 1.5 times OpenRouter's actual reported cost. Agent mode follows the description and writes a result into the target vault. connection_sync deterministically runs approved read-only tools on bound service connections and ingests their data. Cadence 'once' runs a single time then completes permanently. Requires write access to the target vault.",
34328
34426
  input: {
34329
34427
  description: import_zod36.z.string().min(1).describe("Free-text description of what this action should do each time it runs."),
34330
34428
  vault: import_zod36.z.string().min(1).describe("The vault this action writes its results into. You must already have write access to it."),
@@ -34373,12 +34471,13 @@ var init_memory_tool_schemas = __esm({
34373
34471
  GetScheduleLinkSchema = {
34374
34472
  id: "get-schedule-link",
34375
34473
  upstreamName: "getScheduleLinkTool",
34376
- description: "Get your durable, bookmarkable link to the hosted Scheduled Actions page \u2014 a login-free UI to create, view, edit, pause, resume, and delete scheduled actions. The embedded secret is shown only once, on first call; it cannot be re-shown, only revoked and reissued via revoke-schedule-link.",
34474
+ description: "Get your durable, bookmarkable link to the hosted Scheduled Actions page. Requires an active MCP Scraper Starter plan or higher. The embedded secret is shown only once, on first call; it cannot be re-shown, only revoked and reissued via revoke-schedule-link.",
34377
34475
  input: {},
34378
34476
  output: {
34379
34477
  ok: import_zod36.z.boolean().describe("True on success; false on auth error."),
34380
34478
  url: import_zod36.z.string().optional().describe("The schedule link. Present only the first time a link is minted for this identity."),
34381
34479
  alreadyExists: import_zod36.z.boolean().optional().describe("True when a link already exists and was NOT re-shown. Use revoke-schedule-link then call this again to get a fresh one."),
34480
+ code: import_zod36.z.string().optional().describe("Machine-readable denial code when the link cannot be issued."),
34382
34481
  error: import_zod36.z.string().optional().describe("Human-readable failure reason when ok is false.")
34383
34482
  },
34384
34483
  annotations: {
@@ -34392,7 +34491,7 @@ var init_memory_tool_schemas = __esm({
34392
34491
  GetScheduleStatusSchema = {
34393
34492
  id: "get-schedule-status",
34394
34493
  upstreamName: "getScheduleStatusTool",
34395
- description: "Get the Credit-metered Scheduled Actions policy. Scheduling is included rather than sold as a separate subscription: each execution has a 75-Credit base charge, and agent model usage is billed at 1.5 times OpenRouter's actual reported cost.",
34494
+ description: "Get the Credit-metered Scheduled Actions access and billing policy. Scheduling requires an active MCP Scraper Starter plan or higher but has no separate subscription: each execution has a 75-Credit base charge, and agent model usage is billed at 1.5 times OpenRouter's actual reported cost.",
34396
34495
  input: {},
34397
34496
  output: {
34398
34497
  ok: import_zod36.z.boolean(),
@@ -34403,6 +34502,8 @@ var init_memory_tool_schemas = __esm({
34403
34502
  quotaPerPeriod: import_zod36.z.number().optional(),
34404
34503
  usedThisPeriod: import_zod36.z.number().optional(),
34405
34504
  periodStart: import_zod36.z.string().optional(),
34505
+ defaultTimezone: import_zod36.z.string().nullable().optional().describe("IANA timezone applied to new schedules that name a time without a zone; null means UTC."),
34506
+ code: import_zod36.z.string().optional().describe("Machine-readable reason scheduling is unavailable."),
34406
34507
  error: import_zod36.z.string().optional()
34407
34508
  },
34408
34509
  annotations: {
@@ -34496,6 +34597,7 @@ var init_memory_tool_schemas = __esm({
34496
34597
  output: {
34497
34598
  ok: import_zod36.z.boolean(),
34498
34599
  nextRunAt: import_zod36.z.string().optional(),
34600
+ code: import_zod36.z.string().optional().describe("Machine-readable denial code when the action cannot be resumed."),
34499
34601
  error: import_zod36.z.string().optional()
34500
34602
  },
34501
34603
  annotations: {
@@ -34527,11 +34629,11 @@ var init_memory_tool_schemas = __esm({
34527
34629
  SetScheduleEntitlementSchema = {
34528
34630
  id: "set-schedule-entitlement",
34529
34631
  upstreamName: "setScheduleEntitlementTool",
34530
- description: "Legacy migration and account-recovery control for old Scheduled Actions entitlements. New scheduling is Credit-metered and does not require a separate subscription. This admin-only tool may preserve a legacy API-key delegation or recover historical entitlement rows during migration.",
34632
+ description: "Admin-only scheduled-action credential provisioning and historical-row recovery. Rotate the encrypted delegated MCP Scraper API key without changing legacy enabled/quota fields by omitting those fields. Legacy entitlement values are not runtime access controls; paid-plan access is checked against MCP Scraper directly.",
34531
34633
  input: {
34532
34634
  granteeIdentity: import_zod36.z.string().min(1).describe("Identity whose scheduling entitlement is being set (e.g. an email)."),
34533
- enabled: import_zod36.z.boolean().describe("True to enable scheduled actions for this identity, false on cancel/expire."),
34534
- quotaPerPeriod: import_zod36.z.number().optional().describe("Legacy monthly execution quota retained only for historical-row compatibility; it does not price new Credit-metered runs."),
34635
+ enabled: import_zod36.z.boolean().optional().describe("Historical entitlement value for migration/recovery only. Omit to preserve the stored value."),
34636
+ quotaPerPeriod: import_zod36.z.number().optional().describe("Historical monthly execution quota retained only for migration compatibility. Omit to preserve the stored value."),
34535
34637
  mcpScraperApiKey: import_zod36.z.string().optional().describe("The identity's mcp-scraper API key, stored encrypted, used to reach mcp-scraper tools during scheduled-action execution.")
34536
34638
  },
34537
34639
  output: {
@@ -35269,6 +35371,7 @@ var init_memory_inprocess_executor = __esm({
35269
35371
  "src/mcp/memory-inprocess-executor.ts"() {
35270
35372
  "use strict";
35271
35373
  init_memory();
35374
+ init_scheduling_access();
35272
35375
  CUTOVER = /* @__PURE__ */ new Map();
35273
35376
  HybridMemoryToolExecutor = class {
35274
35377
  constructor(httpExecutor, user) {
@@ -35285,6 +35388,29 @@ var init_memory_inprocess_executor = __esm({
35285
35388
  return this.mkKeyPromise;
35286
35389
  }
35287
35390
  async callMemoryTool(upstreamName, args) {
35391
+ const accessDenied = memoryScheduleAccessPayload(upstreamName, this.user);
35392
+ if (accessDenied) {
35393
+ return {
35394
+ content: [{ type: "text", text: JSON.stringify(accessDenied) }],
35395
+ structuredContent: accessDenied,
35396
+ isError: false
35397
+ };
35398
+ }
35399
+ if (memoryScheduleToolNeedsCredentials(upstreamName)) {
35400
+ const credentials = await syncScheduledActionCredentials(this.user);
35401
+ if (!credentials.ok) {
35402
+ const result = {
35403
+ ok: false,
35404
+ code: "schedule_credentials_unavailable",
35405
+ error: credentials.error ?? "Scheduled Actions credentials are unavailable."
35406
+ };
35407
+ return {
35408
+ content: [{ type: "text", text: JSON.stringify(result) }],
35409
+ structuredContent: result,
35410
+ isError: false
35411
+ };
35412
+ }
35413
+ }
35288
35414
  const handler = CUTOVER.get(upstreamName);
35289
35415
  if (!handler) return this.httpExecutor.callMemoryTool(upstreamName, args);
35290
35416
  const mkKey = await this.resolveMkKey();
@@ -38331,6 +38457,8 @@ var init_stripe_routes = __esm({
38331
38457
  if (user && invoice.id && !await ledgerExistsForStripePI(invoice.id)) {
38332
38458
  await creditMc(user.id, tier.credits_mc, LedgerOperation.SUBSCRIPTION, `${tier.label} subscription credits`, invoice.id);
38333
38459
  await setSubscriptionTier(user.id, tier.tier, tier.concurrency, invoice.subscription ?? user.subscription_id);
38460
+ const credentials = await syncScheduledActionCredentials(user);
38461
+ if (!credentials.ok) console.warn("[stripe] scheduled-action credential sync failed:", credentials.error ?? "unknown error");
38334
38462
  }
38335
38463
  }
38336
38464
  }
@@ -38348,15 +38476,6 @@ var init_stripe_routes = __esm({
38348
38476
  }
38349
38477
  return c.json({ received: true });
38350
38478
  }
38351
- if (priceId && priceId in SCHEDULING_PLANS) {
38352
- const sched = SCHEDULING_PLANS[priceId];
38353
- const user2 = await resolveUser(sub.customer, sub.customer_email);
38354
- if (user2) {
38355
- const live = sub.status === "active" || sub.status === "trialing";
38356
- await syncScheduleEntitlement(user2, live, sched.quota_per_period);
38357
- }
38358
- return c.json({ received: true });
38359
- }
38360
38479
  const user = await getUserByStripeCustomerId(sub.customer);
38361
38480
  if (!user) return c.json({ received: true });
38362
38481
  if (priceId && priceId in SUBSCRIPTION_TIERS) {
@@ -38364,6 +38483,10 @@ var init_stripe_routes = __esm({
38364
38483
  const live = sub.status === "active" || sub.status === "trialing";
38365
38484
  const newTier = live ? tier.tier : null;
38366
38485
  await setSubscriptionTier(user.id, newTier, live ? tier.concurrency : 0, live ? sub.id : null);
38486
+ if (live) {
38487
+ const credentials = await syncScheduledActionCredentials(user);
38488
+ if (!credentials.ok) console.warn("[stripe] scheduled-action credential sync failed:", credentials.error ?? "unknown error");
38489
+ }
38367
38490
  } else if (priceId === CONCURRENCY_PRICE_ID && event.type === "customer.subscription.created") {
38368
38491
  await setExtraConcurrencySlots(user.id, user.extra_concurrency_slots + 1);
38369
38492
  await setConcurrencySubId(user.id, sub.id);
@@ -38380,11 +38503,6 @@ var init_stripe_routes = __esm({
38380
38503
  }
38381
38504
  return c.json({ received: true });
38382
38505
  }
38383
- if (priceId && priceId in SCHEDULING_PLANS) {
38384
- const user2 = await getUserByStripeCustomerId(sub.customer);
38385
- if (user2) await syncScheduleEntitlement(user2, false);
38386
- return c.json({ received: true });
38387
- }
38388
38506
  const user = await getUserByStripeCustomerId(sub.customer);
38389
38507
  if (!user) return c.json({ received: true });
38390
38508
  if (priceId && priceId in SUBSCRIPTION_TIERS) {
@@ -42514,7 +42632,7 @@ async function checkHarvestLimits(user, reuseLockId) {
42514
42632
  }
42515
42633
  return null;
42516
42634
  }
42517
- var import_resend2, import_hono24, import_hono25, import_factory6, import_cookie2, import_stripe2, secureCookies2, isProduction2, sessionCookieOptions2, requireAllowedOrigin, auth2, adminAuth, sessionAuth, requireIntegrationsTier, app, STRIPE_API_VERSION, SYNC_HARVEST_TIMEOUT_OVERRIDE_MS;
42635
+ var import_resend2, import_hono24, import_hono25, import_factory6, import_cookie2, import_stripe2, secureCookies2, isProduction2, sessionCookieOptions2, requireAllowedOrigin, auth2, adminAuth, sessionAuth, requireIntegrationsTier, requirePaidSchedulingTier, app, STRIPE_API_VERSION, SYNC_HARVEST_TIMEOUT_OVERRIDE_MS;
42518
42636
  var init_server = __esm({
42519
42637
  "src/api/server.ts"() {
42520
42638
  "use strict";
@@ -42586,6 +42704,7 @@ var init_server = __esm({
42586
42704
  init_harvest_attempt_events();
42587
42705
  init_session();
42588
42706
  init_memory();
42707
+ init_scheduling_access();
42589
42708
  init_memory_db();
42590
42709
  init_nango_control();
42591
42710
  init_resend_control();
@@ -42645,6 +42764,11 @@ var init_server = __esm({
42645
42764
  }
42646
42765
  return next();
42647
42766
  });
42767
+ requirePaidSchedulingTier = (0, import_factory6.createMiddleware)(async (c, next) => {
42768
+ const user = c.get("user");
42769
+ if (!hasPaidSchedulingAccess(user)) return c.json(paidPlanRequiredPayload(), 403);
42770
+ return next();
42771
+ });
42648
42772
  app = new import_hono24.Hono();
42649
42773
  STRIPE_API_VERSION = "2026-02-25.clover";
42650
42774
  app.use("*", async (c, next) => {
@@ -42894,6 +43018,18 @@ var init_server = __esm({
42894
43018
  const user = c.get("user");
42895
43019
  const body = await c.req.json().catch(() => ({}));
42896
43020
  if (!body.toolName) return c.json({ ok: false, error: "toolName is required" }, 400);
43021
+ const accessDenied = memoryScheduleAccessPayload(body.toolName, user);
43022
+ if (accessDenied) return c.json(accessDenied);
43023
+ if (memoryScheduleToolNeedsCredentials(body.toolName)) {
43024
+ const credentials = await syncScheduledActionCredentials(user);
43025
+ if (!credentials.ok) {
43026
+ return c.json({
43027
+ ok: false,
43028
+ code: "schedule_credentials_unavailable",
43029
+ error: credentials.error ?? "Scheduled Actions credentials are unavailable."
43030
+ }, 503);
43031
+ }
43032
+ }
42897
43033
  const { key, error } = await getOrCreateUserMemoryKey(user);
42898
43034
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 502);
42899
43035
  const result = await memoryCall(body.toolName, body.args ?? {}, key);
@@ -43093,7 +43229,7 @@ var init_server = __esm({
43093
43229
  llmCostMultiplier: SCHEDULED_AGENT_LLM_MARKUP
43094
43230
  }, 410);
43095
43231
  });
43096
- app.get("/schedule-status", auth2, async (c) => {
43232
+ app.get("/schedule-status", auth2, requirePaidSchedulingTier, async (c) => {
43097
43233
  try {
43098
43234
  const user = c.get("user");
43099
43235
  const { key, error } = await getOrCreateUserMemoryKey(user);
@@ -43460,9 +43596,13 @@ var init_server = __esm({
43460
43596
  return scheduleConnectionError(c, err, "Unable to run this connection action.");
43461
43597
  }
43462
43598
  });
43463
- app.post("/schedule-link", auth2, async (c) => {
43599
+ app.post("/schedule-link", auth2, requirePaidSchedulingTier, async (c) => {
43464
43600
  try {
43465
43601
  const user = c.get("user");
43602
+ const credentials = await syncScheduledActionCredentials(user);
43603
+ if (!credentials.ok) {
43604
+ return c.json({ ok: false, code: "schedule_credentials_unavailable", error: credentials.error ?? "Scheduled Actions credentials are unavailable." }, 503);
43605
+ }
43466
43606
  const { key, error } = await getOrCreateUserMemoryKey(user);
43467
43607
  if (!key) return c.json({ error: error ?? "memory unavailable" }, 503);
43468
43608
  const r = await memoryCall("getScheduleLinkTool", {}, key);
@@ -43500,8 +43640,12 @@ var init_server = __esm({
43500
43640
  });
43501
43641
  }
43502
43642
  });
43503
- app.post("/schedule-actions", auth2, async (c) => {
43643
+ app.post("/schedule-actions", auth2, requirePaidSchedulingTier, async (c) => {
43504
43644
  const user = c.get("user");
43645
+ const credentials = await syncScheduledActionCredentials(user);
43646
+ if (!credentials.ok) {
43647
+ return c.json({ ok: false, code: "schedule_credentials_unavailable", error: credentials.error ?? "Scheduled Actions credentials are unavailable." }, 503);
43648
+ }
43505
43649
  const { key, error } = await getOrCreateUserMemoryKey(user);
43506
43650
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);
43507
43651
  const body = await c.req.json().catch(() => ({}));
@@ -43593,7 +43737,7 @@ var init_server = __esm({
43593
43737
  const r = await memoryCall("pauseScheduledActionTool", { id: c.req.param("id") }, key);
43594
43738
  return c.json(r);
43595
43739
  });
43596
- app.post("/schedule-actions/:id/resume", auth2, async (c) => {
43740
+ app.post("/schedule-actions/:id/resume", auth2, requirePaidSchedulingTier, async (c) => {
43597
43741
  const user = c.get("user");
43598
43742
  const { key, error } = await getOrCreateUserMemoryKey(user);
43599
43743
  if (!key) return c.json({ ok: false, error: error ?? "memory unavailable" }, 503);