@secondlayer/cli 3.2.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -13927,7 +13927,7 @@ __export(exports_node_impl, {
13927
13927
  runSetupWizard: () => runSetupWizard,
13928
13928
  restartNode: () => restartNode
13929
13929
  });
13930
- import { confirm as confirm3, input as input2, select as select3 } from "@inquirer/prompts";
13930
+ import { confirm as confirm4, input as input2, select as select3 } from "@inquirer/prompts";
13931
13931
  async function runSetupWizard() {
13932
13932
  console.log("");
13933
13933
  console.log(blue("Stacks Node Setup Wizard"));
@@ -13964,13 +13964,13 @@ async function runSetupWizard() {
13964
13964
  ],
13965
13965
  default: "mainnet"
13966
13966
  });
13967
- const autoStartIndexer = await confirm3({
13967
+ const autoStartIndexer = await confirm4({
13968
13968
  message: "Auto-start indexer when node starts?",
13969
13969
  default: true
13970
13970
  });
13971
13971
  let indexerPort = 3700;
13972
13972
  if (autoStartIndexer) {
13973
- const customPort = await confirm3({
13973
+ const customPort = await confirm4({
13974
13974
  message: "Use default indexer port (3700)?",
13975
13975
  default: true
13976
13976
  });
@@ -14076,7 +14076,7 @@ async function stopNode(_pathOverride, force, _wait) {
14076
14076
  return;
14077
14077
  }
14078
14078
  if (!force) {
14079
- const proceed = await confirm3({
14079
+ const proceed = await confirm4({
14080
14080
  message: "Stop the Stacks node?",
14081
14081
  default: false
14082
14082
  });
@@ -14106,7 +14106,7 @@ async function restartNode(pathOverride, force, _wait) {
14106
14106
  const wasRunning = await isNodeRunning();
14107
14107
  if (wasRunning) {
14108
14108
  if (!force) {
14109
- const proceed = await confirm3({
14109
+ const proceed = await confirm4({
14110
14110
  message: "Restart the Stacks node?",
14111
14111
  default: false
14112
14112
  });
@@ -14555,7 +14555,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
14555
14555
  if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => (key in env))) {
14556
14556
  return 3;
14557
14557
  }
14558
- if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => (sign in env)) || env.CI_NAME === "codeship") {
14558
+ if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign2) => (sign2 in env)) || env.CI_NAME === "codeship") {
14559
14559
  return 1;
14560
14560
  }
14561
14561
  return min;
@@ -24264,7 +24264,7 @@ Filtered results for: ${this.inputValue ? this.inputValue : color.gray("Enter so
24264
24264
  }
24265
24265
  function requireConfirm() {
24266
24266
  if (hasRequiredConfirm)
24267
- return confirm5;
24267
+ return confirm6;
24268
24268
  hasRequiredConfirm = 1;
24269
24269
  const color = requireKleur();
24270
24270
  const Prompt = requirePrompt();
@@ -24337,8 +24337,8 @@ function requireConfirm() {
24337
24337
  this.out.write(erase.line + cursor.to(0) + this.outputText);
24338
24338
  }
24339
24339
  }
24340
- confirm5 = ConfirmPrompt;
24341
- return confirm5;
24340
+ confirm6 = ConfirmPrompt;
24341
+ return confirm6;
24342
24342
  }
24343
24343
  function requireElements() {
24344
24344
  if (hasRequiredElements)
@@ -24575,7 +24575,7 @@ function requireSupportsColor() {
24575
24575
  return 1;
24576
24576
  }
24577
24577
  if ("CI" in env2) {
24578
- if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => (sign in env2)) || env2.CI_NAME === "codeship") {
24578
+ if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign2) => (sign2 in env2)) || env2.CI_NAME === "codeship") {
24579
24579
  return 1;
24580
24580
  }
24581
24581
  return min;
@@ -25018,7 +25018,7 @@ async function detect2({ autoInstall, programmatic, cwd } = {}) {
25018
25018
  }
25019
25019
  return agent;
25020
25020
  }
25021
- var ini$1, hasRequiredIni, iniExports, prompts$2, kleur, hasRequiredKleur, action, hasRequiredAction, strip, hasRequiredStrip, src, hasRequiredSrc, clear, hasRequiredClear, figures_1, hasRequiredFigures, style, hasRequiredStyle, lines, hasRequiredLines, wrap, hasRequiredWrap, entriesToDisplay, hasRequiredEntriesToDisplay, util, hasRequiredUtil, prompt, hasRequiredPrompt, text, hasRequiredText, select5, hasRequiredSelect, toggle, hasRequiredToggle, datepart, hasRequiredDatepart, meridiem, hasRequiredMeridiem, day, hasRequiredDay, hours, hasRequiredHours, milliseconds, hasRequiredMilliseconds, minutes, hasRequiredMinutes, month, hasRequiredMonth, seconds, hasRequiredSeconds, year, hasRequiredYear, dateparts, hasRequiredDateparts, date, hasRequiredDate, number, hasRequiredNumber, multiselect, hasRequiredMultiselect, autocomplete, hasRequiredAutocomplete, autocompleteMultiselect, hasRequiredAutocompleteMultiselect, confirm5, hasRequiredConfirm, elements, hasRequiredElements, hasRequiredPrompts$1, lib$1, hasRequiredLib$1, prompts$1, hasRequiredPrompts, promptsExports, prompts, isBrowser, platform, OSC = "\x1B]", BEL = "\x07", SEP = ";", link = (text2, url) => [
25021
+ var ini$1, hasRequiredIni, iniExports, prompts$2, kleur, hasRequiredKleur, action, hasRequiredAction, strip, hasRequiredStrip, src, hasRequiredSrc, clear, hasRequiredClear, figures_1, hasRequiredFigures, style, hasRequiredStyle, lines, hasRequiredLines, wrap, hasRequiredWrap, entriesToDisplay, hasRequiredEntriesToDisplay, util, hasRequiredUtil, prompt, hasRequiredPrompt, text, hasRequiredText, select5, hasRequiredSelect, toggle, hasRequiredToggle, datepart, hasRequiredDatepart, meridiem, hasRequiredMeridiem, day, hasRequiredDay, hours, hasRequiredHours, milliseconds, hasRequiredMilliseconds, minutes, hasRequiredMinutes, month, hasRequiredMonth, seconds, hasRequiredSeconds, year, hasRequiredYear, dateparts, hasRequiredDateparts, date, hasRequiredDate, number, hasRequiredNumber, multiselect, hasRequiredMultiselect, autocomplete, hasRequiredAutocomplete, autocompleteMultiselect, hasRequiredAutocompleteMultiselect, confirm6, hasRequiredConfirm, elements, hasRequiredElements, hasRequiredPrompts$1, lib$1, hasRequiredLib$1, prompts$1, hasRequiredPrompts, promptsExports, prompts, isBrowser, platform, OSC = "\x1B]", BEL = "\x07", SEP = ";", link = (text2, url) => [
25022
25022
  OSC,
25023
25023
  "8",
25024
25024
  SEP,
@@ -28352,7 +28352,7 @@ function prettyMilliseconds(milliseconds2, options3) {
28352
28352
  throw new TypeError("Expected a finite number or bigint");
28353
28353
  }
28354
28354
  options3 = { ...options3 };
28355
- const sign = milliseconds2 < 0 ? "-" : "";
28355
+ const sign2 = milliseconds2 < 0 ? "-" : "";
28356
28356
  milliseconds2 = milliseconds2 < 0 ? -milliseconds2 : milliseconds2;
28357
28357
  if (options3.colonNotation) {
28358
28358
  options3.compact = false;
@@ -28426,13 +28426,13 @@ function prettyMilliseconds(milliseconds2, options3) {
28426
28426
  }
28427
28427
  }
28428
28428
  if (result.length === 0) {
28429
- return sign + "0" + (options3.verbose ? " milliseconds" : "ms");
28429
+ return sign2 + "0" + (options3.verbose ? " milliseconds" : "ms");
28430
28430
  }
28431
28431
  const separator = options3.colonNotation ? ":" : " ";
28432
28432
  if (typeof options3.unitCount === "number") {
28433
28433
  result = result.slice(0, Math.max(options3.unitCount, 1));
28434
28434
  }
28435
- return sign + result.join(separator);
28435
+ return sign2 + result.join(separator);
28436
28436
  }
28437
28437
  var isZero = (value) => value === 0 || value === 0n, pluralize = (word, count2) => count2 === 1 || count2 === 1n ? word : `${word}s`, SECOND_ROUNDING_EPSILON = 0.0000001, ONE_DAY_IN_MILLISECONDS;
28438
28438
  var init_pretty_ms = __esm(() => {
@@ -32410,7 +32410,7 @@ var {
32410
32410
  // package.json
32411
32411
  var package_default = {
32412
32412
  name: "@secondlayer/cli",
32413
- version: "3.2.1",
32413
+ version: "3.3.0",
32414
32414
  description: "CLI for subgraphs and blockchain indexing on Stacks",
32415
32415
  type: "module",
32416
32416
  bin: {
@@ -32453,8 +32453,8 @@ var package_default = {
32453
32453
  dependencies: {
32454
32454
  "@inquirer/prompts": "^8.2.0",
32455
32455
  "@secondlayer/bundler": "^0.3.1",
32456
- "@secondlayer/sdk": "^3.0.1",
32457
- "@secondlayer/shared": "^4.0.0",
32456
+ "@secondlayer/sdk": "^3.1.0",
32457
+ "@secondlayer/shared": "^4.1.0",
32458
32458
  "@secondlayer/stacks": "^1.0.1",
32459
32459
  "@secondlayer/subgraphs": "^1.1.0",
32460
32460
  "@biomejs/js-api": "^0.7.0",
@@ -32963,7 +32963,6 @@ async function validateDatabaseConnection(url) {
32963
32963
  }
32964
32964
  }
32965
32965
  // src/commands/create.ts
32966
- init_output();
32967
32966
  import {
32968
32967
  copyFileSync,
32969
32968
  existsSync as existsSync2,
@@ -32977,6 +32976,96 @@ import { dirname as dirname3, join as join5, relative, resolve as resolve2 } fro
32977
32976
  import { fileURLToPath } from "node:url";
32978
32977
  import { input, select as select2 } from "@inquirer/prompts";
32979
32978
  import { SecondLayer as SecondLayer2 } from "@secondlayer/sdk";
32979
+
32980
+ // src/lib/filter-params.ts
32981
+ var FILTER_OPERATORS = ["eq", "neq", "gt", "gte", "lt", "lte"];
32982
+ var QUERY_ONLY_OPERATORS = ["like"];
32983
+ var ALL_OPERATORS = new Set([
32984
+ ...FILTER_OPERATORS,
32985
+ ...QUERY_ONLY_OPERATORS
32986
+ ]);
32987
+ var ALL_OPERATOR_LABELS = [...FILTER_OPERATORS, ...QUERY_ONLY_OPERATORS].join(", ");
32988
+ function parseFilterArg(input) {
32989
+ const eqIndex = input.indexOf("=");
32990
+ if (eqIndex <= 0) {
32991
+ throw new Error(`Invalid filter format: "${input}". Use key=value.`);
32992
+ }
32993
+ const key = input.slice(0, eqIndex).trim();
32994
+ const value = input.slice(eqIndex + 1);
32995
+ if (!key)
32996
+ throw new Error(`Invalid filter format: "${input}". Use key=value.`);
32997
+ const dotIndex = key.lastIndexOf(".");
32998
+ if (dotIndex > 0) {
32999
+ const field = key.slice(0, dotIndex);
33000
+ const operator = key.slice(dotIndex + 1);
33001
+ if (!field)
33002
+ throw new Error(`Invalid filter field in "${input}".`);
33003
+ if (!ALL_OPERATORS.has(operator)) {
33004
+ throw new Error(`Invalid filter operator ".${operator}" in "${input}". Supported operators: ${ALL_OPERATOR_LABELS}.`);
33005
+ }
33006
+ return {
33007
+ field,
33008
+ operator,
33009
+ value,
33010
+ explicitOperator: true
33011
+ };
33012
+ }
33013
+ return { field: key, operator: "eq", value, explicitOperator: false };
33014
+ }
33015
+ function parseFilterArgs(args) {
33016
+ return (args ?? []).map(parseFilterArg);
33017
+ }
33018
+ function parseQueryFilters(args) {
33019
+ const filters = {};
33020
+ for (const filter of parseFilterArgs(args)) {
33021
+ const key = filter.operator === "eq" ? filter.field : `${filter.field}.${filter.operator}`;
33022
+ filters[key] = filter.value;
33023
+ }
33024
+ return Object.keys(filters).length > 0 ? filters : undefined;
33025
+ }
33026
+ function parseSubscriptionFilter(args) {
33027
+ const filters = {};
33028
+ const seenFields = new Set;
33029
+ for (const filter of parseFilterArgs(args)) {
33030
+ if (filter.operator === "like") {
33031
+ throw new Error(`Subscription filters do not support ".like". Supported operators: ${FILTER_OPERATORS.join(", ")}.`);
33032
+ }
33033
+ if (seenFields.has(filter.field)) {
33034
+ throw new Error(`Subscription filters support one condition per field; got multiple filters for "${filter.field}".`);
33035
+ }
33036
+ seenFields.add(filter.field);
33037
+ if (!filter.explicitOperator || filter.operator === "eq") {
33038
+ filters[filter.field] = filter.value;
33039
+ } else {
33040
+ filters[filter.field] = { [filter.operator]: filter.value };
33041
+ }
33042
+ }
33043
+ return Object.keys(filters).length > 0 ? filters : undefined;
33044
+ }
33045
+
33046
+ // src/commands/create.ts
33047
+ init_output();
33048
+ init_resolve_tenant();
33049
+
33050
+ // src/lib/subscription-validation.ts
33051
+ import {
33052
+ validateSubscriptionFilterForTable
33053
+ } from "@secondlayer/shared/schemas/subscriptions";
33054
+ async function validateSubscriptionTargetFromApi(client, input) {
33055
+ const subgraph = await client.subgraphs.get(input.subgraphName);
33056
+ const errors = validateSubscriptionFilterForTable({
33057
+ subgraphName: input.subgraphName,
33058
+ tableName: input.tableName,
33059
+ filter: input.filter,
33060
+ tables: subgraph.tables
33061
+ });
33062
+ if (errors.length > 0) {
33063
+ throw new Error(errors.join(`
33064
+ `));
33065
+ }
33066
+ }
33067
+
33068
+ // src/commands/create.ts
32980
33069
  var RUNTIMES = ["inngest", "trigger", "cloudflare", "node"];
32981
33070
  var FORMAT_BY_RUNTIME = {
32982
33071
  inngest: "inngest",
@@ -33031,6 +33120,27 @@ async function promptFor(_name, opts) {
33031
33120
  }
33032
33121
  async function createSubscription(name, opts) {
33033
33122
  const { runtime, subgraph, table, url } = await promptFor(name, opts);
33123
+ let filter;
33124
+ try {
33125
+ filter = parseSubscriptionFilter(opts.filter);
33126
+ } catch (err) {
33127
+ error(err instanceof Error ? err.message : String(err));
33128
+ process.exit(1);
33129
+ }
33130
+ let sl = null;
33131
+ if (!opts.skipApi) {
33132
+ try {
33133
+ sl = await getSubscriptionClient(opts);
33134
+ await validateSubscriptionTargetFromApi(sl, {
33135
+ subgraphName: subgraph,
33136
+ tableName: table,
33137
+ filter
33138
+ });
33139
+ } catch (err) {
33140
+ error(err instanceof Error ? err.message : String(err));
33141
+ process.exit(1);
33142
+ }
33143
+ }
33034
33144
  const eventName = `${subgraph}.${table}.created`;
33035
33145
  const targetDir = resolve2(process.cwd(), name);
33036
33146
  if (existsSync2(targetDir)) {
@@ -33048,27 +33158,19 @@ async function createSubscription(name, opts) {
33048
33158
  let signingSecret = null;
33049
33159
  if (!opts.skipApi) {
33050
33160
  try {
33051
- const serviceKey = opts.serviceKey ?? process.env.SL_SERVICE_KEY;
33052
- const baseUrl = opts.baseUrl ?? process.env.SL_API_URL;
33053
- if (!serviceKey) {
33054
- warn("SL_SERVICE_KEY not set — template copied but subscription not provisioned.");
33055
- info("Provision it from the dashboard or re-run with SL_SERVICE_KEY set.");
33056
- } else {
33057
- const sl = new SecondLayer2({
33058
- apiKey: serviceKey,
33059
- ...baseUrl ? { baseUrl } : {}
33060
- });
33061
- const res = await sl.subscriptions.create({
33062
- name,
33063
- subgraphName: subgraph,
33064
- tableName: table,
33065
- url,
33066
- format: FORMAT_BY_RUNTIME[runtime],
33067
- runtime
33068
- });
33069
- signingSecret = res.signingSecret;
33070
- success(`Subscription provisioned: ${blue(res.subscription.id)}`);
33071
- }
33161
+ if (!sl)
33162
+ sl = await getSubscriptionClient(opts);
33163
+ const res = await sl.subscriptions.create({
33164
+ name,
33165
+ subgraphName: subgraph,
33166
+ tableName: table,
33167
+ url,
33168
+ format: FORMAT_BY_RUNTIME[runtime],
33169
+ runtime,
33170
+ ...filter ? { filter } : {}
33171
+ });
33172
+ signingSecret = res.signingSecret;
33173
+ success(`Subscription provisioned: ${blue(res.subscription.id)}`);
33072
33174
  } catch (err) {
33073
33175
  warn(`Subscription provisioning failed: ${err instanceof Error ? err.message : String(err)}`);
33074
33176
  info("Template copied — fix auth + run again, or provision via dashboard.");
@@ -33098,9 +33200,40 @@ ${dim(" ")}${signingSecret}`);
33098
33200
  bun install
33099
33201
  bun run dev`);
33100
33202
  }
33203
+ function resolveSubscriptionClientConfig(opts, env = process.env, resolved) {
33204
+ const needsResolvedKey = !opts.serviceKey && !env.SL_SERVICE_KEY;
33205
+ const needsResolvedUrl = !opts.baseUrl && !env.SL_API_URL;
33206
+ if ((needsResolvedKey || needsResolvedUrl) && !resolved) {
33207
+ return { needsTenantResolution: true };
33208
+ }
33209
+ const apiKey = opts.serviceKey ?? env.SL_SERVICE_KEY ?? resolved?.ephemeralKey;
33210
+ const baseUrl = opts.baseUrl ?? env.SL_API_URL ?? resolved?.apiUrl;
33211
+ if (!apiKey) {
33212
+ throw new Error("No service key available. Run `sl login` from an active project or pass --service-key.");
33213
+ }
33214
+ if (!baseUrl) {
33215
+ throw new Error("No tenant API URL available. Run `sl project use <slug>` or pass --base-url.");
33216
+ }
33217
+ return { needsTenantResolution: false, baseUrl, apiKey };
33218
+ }
33219
+ async function getSubscriptionClient(opts) {
33220
+ const config = resolveSubscriptionClientConfig(opts);
33221
+ if (!config.needsTenantResolution) {
33222
+ return new SecondLayer2({ baseUrl: config.baseUrl, apiKey: config.apiKey });
33223
+ }
33224
+ const resolved = await resolveActiveTenant();
33225
+ const resolvedConfig = resolveSubscriptionClientConfig(opts, process.env, resolved);
33226
+ if (resolvedConfig.needsTenantResolution) {
33227
+ throw new Error("Could not resolve active tenant credentials.");
33228
+ }
33229
+ return new SecondLayer2({
33230
+ baseUrl: resolvedConfig.baseUrl,
33231
+ apiKey: resolvedConfig.apiKey
33232
+ });
33233
+ }
33101
33234
  function registerCreateCommand(program2) {
33102
33235
  const create = program2.command("create").description("Scaffold new resources (subscription receivers, etc.)");
33103
- create.command("subscription <name>").description("Scaffold a subscription receiver for a runtime").option("-r, --runtime <runtime>", "inngest | trigger | cloudflare | node").option("-s, --subgraph <name>", "Subgraph to subscribe to").option("-t, --table <name>", "Table to subscribe to").option("-u, --url <url>", "Webhook URL").option("--service-key <key>", "SL_SERVICE_KEY override").option("--base-url <url>", "SL_API_URL override").option("--skip-api", "Copy template only, don't call the API").action(async (name, options) => {
33236
+ create.command("subscription <name>").description("Scaffold a subscription receiver for a runtime").option("-r, --runtime <runtime>", "inngest | trigger | cloudflare | node").option("-s, --subgraph <name>", "Subgraph to subscribe to").option("-t, --table <name>", "Table to subscribe to").option("-u, --url <url>", "Webhook URL").option("--filter <kv...>", "Filter as key=value (supports .eq/.neq/.gt/.gte/.lt/.lte suffixes)").option("--service-key <key>", "SL_SERVICE_KEY override").option("--base-url <url>", "SL_API_URL override").option("--skip-api", "Copy template only, don't call the API").action(async (name, options) => {
33104
33237
  await createSubscription(name, options);
33105
33238
  });
33106
33239
  }
@@ -33193,11 +33326,596 @@ function printStatus(status) {
33193
33326
  }
33194
33327
  console.log(dim(`Last updated: ${status.timestamp}`));
33195
33328
  }
33329
+ // src/commands/subscriptions.ts
33330
+ import { confirm } from "@inquirer/prompts";
33331
+ import { sign } from "@secondlayer/shared/crypto/standard-webhooks";
33332
+ init_output();
33333
+ function commonOptions(cmd) {
33334
+ return cmd.option("--service-key <key>", "SL_SERVICE_KEY override").option("--base-url <url>", "SL_API_URL override");
33335
+ }
33336
+ function parseIntegerOption(value, name, min) {
33337
+ if (value === undefined)
33338
+ return;
33339
+ const parsed = Number.parseInt(value, 10);
33340
+ if (!Number.isFinite(parsed) || String(parsed) !== value.trim()) {
33341
+ throw new Error(`${name} must be an integer`);
33342
+ }
33343
+ if (parsed < min)
33344
+ throw new Error(`${name} must be >= ${min}`);
33345
+ return parsed;
33346
+ }
33347
+ function requireIntegerOption(value, name) {
33348
+ const parsed = parseIntegerOption(value, name, 0);
33349
+ if (parsed === undefined)
33350
+ throw new Error(`${name} is required`);
33351
+ return parsed;
33352
+ }
33353
+ function truncate(value, max = 48) {
33354
+ return value.length <= max ? value : `${value.slice(0, max - 3)}...`;
33355
+ }
33356
+ function formatMaybeDate(value) {
33357
+ return value ? value.replace("T", " ").slice(0, 19) : dim("-");
33358
+ }
33359
+ function isSuccessDelivery(row) {
33360
+ return row.statusCode !== null && row.statusCode >= 200 && row.statusCode < 300;
33361
+ }
33362
+ function printJson(value) {
33363
+ console.log(JSON.stringify(value, null, 2));
33364
+ }
33365
+ async function resolveSubscriptionRef(client, ref) {
33366
+ const { data } = await client.subscriptions.list();
33367
+ const idMatch = data.find((sub) => sub.id === ref);
33368
+ if (idMatch) {
33369
+ return {
33370
+ id: idMatch.id,
33371
+ detail: await client.subscriptions.get(idMatch.id)
33372
+ };
33373
+ }
33374
+ const nameMatches = data.filter((sub) => sub.name === ref);
33375
+ if (nameMatches.length > 1) {
33376
+ throw new Error(`Subscription name "${ref}" is ambiguous; use the subscription id.`);
33377
+ }
33378
+ if (nameMatches[0]) {
33379
+ return {
33380
+ id: nameMatches[0].id,
33381
+ detail: await client.subscriptions.get(nameMatches[0].id)
33382
+ };
33383
+ }
33384
+ return {
33385
+ id: ref,
33386
+ detail: await client.subscriptions.get(ref)
33387
+ };
33388
+ }
33389
+ function buildUpdatePatch(options) {
33390
+ const patch = {};
33391
+ if (options.name)
33392
+ patch.name = options.name;
33393
+ if (options.url)
33394
+ patch.url = options.url;
33395
+ if (options.format) {
33396
+ patch.format = options.format;
33397
+ }
33398
+ if (options.runtime !== undefined) {
33399
+ patch.runtime = options.runtime === "none" || options.runtime === "null" ? null : options.runtime;
33400
+ }
33401
+ if (options.clearFilter)
33402
+ patch.filter = {};
33403
+ if (options.filter) {
33404
+ if (options.clearFilter) {
33405
+ throw new Error("Use either --filter or --clear-filter, not both");
33406
+ }
33407
+ patch.filter = parseSubscriptionFilter(options.filter) ?? {};
33408
+ }
33409
+ const maxRetries = parseIntegerOption(options.maxRetries, "--max-retries", 0);
33410
+ if (maxRetries !== undefined)
33411
+ patch.maxRetries = maxRetries;
33412
+ const timeoutMs = parseIntegerOption(options.timeoutMs, "--timeout-ms", 100);
33413
+ if (timeoutMs !== undefined)
33414
+ patch.timeoutMs = timeoutMs;
33415
+ const concurrency = parseIntegerOption(options.concurrency, "--concurrency", 1);
33416
+ if (concurrency !== undefined)
33417
+ patch.concurrency = concurrency;
33418
+ if (Object.keys(patch).length === 0) {
33419
+ throw new Error("No update fields provided");
33420
+ }
33421
+ return patch;
33422
+ }
33423
+ function printSubscriptionDetail(sub) {
33424
+ console.log(formatKeyValue([
33425
+ ["ID", sub.id],
33426
+ ["Name", sub.name],
33427
+ ["Status", sub.status],
33428
+ ["Target", `${sub.subgraphName}.${sub.tableName}`],
33429
+ ["Format", sub.format],
33430
+ ["Runtime", sub.runtime ?? "none"],
33431
+ ["URL", sub.url],
33432
+ ["Last Delivery", sub.lastDeliveryAt ?? "none"],
33433
+ ["Last Success", sub.lastSuccessAt ?? "none"],
33434
+ ["Circuit Failures", String(sub.circuitFailures)],
33435
+ ["Circuit Opened", sub.circuitOpenedAt ?? "none"],
33436
+ ["Last Error", sub.lastError ?? "none"],
33437
+ ["Max Retries", String(sub.maxRetries)],
33438
+ ["Timeout", `${sub.timeoutMs}ms`],
33439
+ ["Concurrency", String(sub.concurrency)],
33440
+ ["Created", sub.createdAt],
33441
+ ["Updated", sub.updatedAt]
33442
+ ]));
33443
+ console.log(dim(`
33444
+ Filter:`));
33445
+ console.log(JSON.stringify(sub.filter, null, 2));
33446
+ if (Object.keys(sub.authConfig).length > 0) {
33447
+ console.log(dim(`
33448
+ Auth config:`));
33449
+ console.log(JSON.stringify(sub.authConfig, null, 2));
33450
+ }
33451
+ }
33452
+ function buildDoctorReport(input2) {
33453
+ const successful = input2.deliveries.filter(isSuccessDelivery).length;
33454
+ const failed = input2.deliveries.length - successful;
33455
+ const subgraph = input2.subgraph ? {
33456
+ name: input2.subgraph.name,
33457
+ status: input2.subgraph.status,
33458
+ syncStatus: input2.subgraph.sync.status,
33459
+ lastProcessedBlock: input2.subgraph.sync.lastProcessedBlock,
33460
+ chainTip: input2.subgraph.sync.chainTip,
33461
+ gapCount: input2.subgraph.sync.gaps.count,
33462
+ integrity: input2.subgraph.sync.integrity
33463
+ } : null;
33464
+ const hints = [];
33465
+ if (input2.subscription.status === "paused") {
33466
+ hints.push(`Resume when the receiver is healthy: sl subscriptions resume ${input2.subscription.id}`);
33467
+ }
33468
+ if (input2.subscription.lastError) {
33469
+ hints.push("Run sl subscriptions test to reproduce the receiver request.");
33470
+ }
33471
+ if (input2.subscription.circuitOpenedAt || input2.subscription.circuitFailures > 0) {
33472
+ hints.push("Circuit breaker has failures; inspect receiver logs and delivery status codes.");
33473
+ }
33474
+ if (input2.dead.length > 0) {
33475
+ hints.push(`Dead-letter rows exist; inspect with sl subscriptions dead ${input2.subscription.id} and requeue selected rows.`);
33476
+ }
33477
+ if (subgraph?.gapCount && subgraph.gapCount > 0) {
33478
+ hints.push(`Linked subgraph has gaps; run sl subgraphs gaps ${input2.subscription.subgraphName}.`);
33479
+ }
33480
+ if (subgraph?.syncStatus === "catching_up") {
33481
+ hints.push("Linked subgraph is still catching up; new matching rows may arrive later.");
33482
+ }
33483
+ if (input2.deliveries.length === 0) {
33484
+ hints.push("No deliveries yet; confirm the table is receiving inserted rows that match the filter.");
33485
+ }
33486
+ if (hints.length === 0) {
33487
+ hints.push("No immediate action needed.");
33488
+ }
33489
+ return {
33490
+ subscription: input2.subscription,
33491
+ deliverySummary: {
33492
+ total: input2.deliveries.length,
33493
+ successful,
33494
+ failed,
33495
+ last: input2.deliveries[0] ?? null
33496
+ },
33497
+ deadCount: input2.dead.length,
33498
+ subgraph,
33499
+ hints
33500
+ };
33501
+ }
33502
+ function printDoctorReport(report) {
33503
+ const sub = report.subscription;
33504
+ console.log(formatKeyValue([
33505
+ ["Subscription", `${sub.name} (${sub.id})`],
33506
+ ["Status", sub.status],
33507
+ ["Target", `${sub.subgraphName}.${sub.tableName}`],
33508
+ ["Format", sub.format],
33509
+ ["Runtime", sub.runtime ?? "none"],
33510
+ ["URL", sub.url],
33511
+ [
33512
+ "Circuit",
33513
+ sub.circuitOpenedAt ? `open at ${sub.circuitOpenedAt}` : `${sub.circuitFailures} failures`
33514
+ ],
33515
+ ["Last Error", sub.lastError ?? "none"],
33516
+ ["Last Delivery", sub.lastDeliveryAt ?? "none"],
33517
+ ["Last Success", sub.lastSuccessAt ?? "none"]
33518
+ ]));
33519
+ console.log(dim(`
33520
+ Delivery summary:`));
33521
+ console.log(formatKeyValue([
33522
+ ["Recent Attempts", String(report.deliverySummary.total)],
33523
+ ["Successful", String(report.deliverySummary.successful)],
33524
+ ["Failed", String(report.deliverySummary.failed)],
33525
+ [
33526
+ "Last Attempt",
33527
+ report.deliverySummary.last ? `${report.deliverySummary.last.statusCode ?? "error"} at ${report.deliverySummary.last.dispatchedAt}` : "none"
33528
+ ],
33529
+ ["Dead Letter Rows", String(report.deadCount)]
33530
+ ]));
33531
+ if (report.subgraph) {
33532
+ console.log(dim(`
33533
+ Linked subgraph:`));
33534
+ console.log(formatKeyValue([
33535
+ ["Name", report.subgraph.name],
33536
+ ["Status", report.subgraph.status],
33537
+ ["Sync", report.subgraph.syncStatus],
33538
+ [
33539
+ "Blocks",
33540
+ `${report.subgraph.lastProcessedBlock} / ${report.subgraph.chainTip}`
33541
+ ],
33542
+ ["Integrity", report.subgraph.integrity],
33543
+ ["Gaps", String(report.subgraph.gapCount)]
33544
+ ]));
33545
+ }
33546
+ console.log(dim(`
33547
+ Next steps:`));
33548
+ for (const hint of report.hints)
33549
+ console.log(` - ${hint}`);
33550
+ }
33551
+ function syntheticValue(type) {
33552
+ switch (type) {
33553
+ case "uint":
33554
+ case "int":
33555
+ return "1000";
33556
+ case "boolean":
33557
+ return true;
33558
+ case "timestamp":
33559
+ return new Date(0).toISOString();
33560
+ case "jsonb":
33561
+ return {};
33562
+ case "principal":
33563
+ return "SP000000000000000000002Q6VF78";
33564
+ default:
33565
+ return "example";
33566
+ }
33567
+ }
33568
+ function buildSyntheticRow(subgraph, tableName) {
33569
+ const table = subgraph?.tables[tableName];
33570
+ if (!table)
33571
+ return { id: "example", value: "example" };
33572
+ const row = {};
33573
+ for (const [column, def] of Object.entries(table.columns)) {
33574
+ if (column.startsWith("_"))
33575
+ continue;
33576
+ row[column] = syntheticValue(def.type);
33577
+ }
33578
+ return Object.keys(row).length > 0 ? row : { id: "example", value: "example" };
33579
+ }
33580
+ function resolveSigningSecret(options, env = process.env) {
33581
+ const secret = options.signingSecret ?? env.SIGNING_SECRET;
33582
+ if (!secret) {
33583
+ throw new Error("Provide --signing-secret or set SIGNING_SECRET.");
33584
+ }
33585
+ return secret;
33586
+ }
33587
+ function shellQuote(value) {
33588
+ return `'${value.replace(/'/g, "'\\''")}'`;
33589
+ }
33590
+ function buildSubscriptionTestFixture(input2) {
33591
+ const nowSeconds = input2.nowSeconds ?? Math.floor(Date.now() / 1000);
33592
+ const body = JSON.stringify({
33593
+ type: `${input2.subscription.subgraphName}.${input2.subscription.tableName}.created`,
33594
+ timestamp: new Date(nowSeconds * 1000).toISOString(),
33595
+ data: input2.row
33596
+ });
33597
+ const headers = {
33598
+ "content-type": "application/json",
33599
+ ...sign(body, input2.signingSecret, {
33600
+ id: input2.id ?? `test-${input2.subscription.id}`,
33601
+ timestampSeconds: nowSeconds
33602
+ })
33603
+ };
33604
+ const headerArgs = Object.entries(headers).map(([key, value]) => ` -H ${shellQuote(`${key}: ${value}`)} \\`).join(`
33605
+ `);
33606
+ const curl = [
33607
+ `curl -X POST ${shellQuote(input2.subscription.url)} \\`,
33608
+ headerArgs,
33609
+ ` --data ${shellQuote(body)}`
33610
+ ].join(`
33611
+ `);
33612
+ return { body, headers, curl };
33613
+ }
33614
+ async function representativeRow(client, sub, subgraph) {
33615
+ try {
33616
+ const rows = await client.subgraphs.queryTable(sub.subgraphName, sub.tableName, {
33617
+ sort: "_block_height",
33618
+ order: "desc",
33619
+ limit: 1
33620
+ });
33621
+ if (rows[0] && typeof rows[0] === "object")
33622
+ return rows[0];
33623
+ } catch {}
33624
+ return buildSyntheticRow(subgraph, sub.tableName);
33625
+ }
33626
+ function printDeliveries(rows) {
33627
+ if (rows.length === 0) {
33628
+ console.log(dim("No delivery attempts"));
33629
+ return;
33630
+ }
33631
+ console.log(formatTable(["Dispatched", "Attempt", "Status", "Duration", "Error"], rows.map((row) => [
33632
+ formatMaybeDate(row.dispatchedAt),
33633
+ String(row.attempt),
33634
+ row.statusCode === null ? redStatus("error") : isSuccessDelivery(row) ? green(String(row.statusCode)) : yellow(String(row.statusCode)),
33635
+ row.durationMs === null ? dim("-") : `${row.durationMs}ms`,
33636
+ row.errorMessage ? truncate(row.errorMessage, 64) : dim("-")
33637
+ ])));
33638
+ }
33639
+ function redStatus(text) {
33640
+ return `\x1B[31m${text}\x1B[0m`;
33641
+ }
33642
+ function printDead(rows) {
33643
+ if (rows.length === 0) {
33644
+ console.log(dim("No dead-letter rows"));
33645
+ return;
33646
+ }
33647
+ console.log(formatTable(["ID", "Event", "Block", "Attempt", "Failed"], rows.map((row) => [
33648
+ row.id,
33649
+ row.eventType,
33650
+ String(row.blockHeight),
33651
+ String(row.attempt),
33652
+ formatMaybeDate(row.failedAt)
33653
+ ])));
33654
+ }
33655
+ async function confirmOrExit(message, yes) {
33656
+ if (yes)
33657
+ return true;
33658
+ const ok = await confirm({ message });
33659
+ if (!ok) {
33660
+ info("Cancelled");
33661
+ return false;
33662
+ }
33663
+ return true;
33664
+ }
33665
+ function registerSubscriptionsCommand(program2) {
33666
+ const subscriptions = commonOptions(program2.command("subscriptions").alias("subs").description("Manage subgraph table subscriptions"));
33667
+ commonOptions(subscriptions.command("list").alias("ls").description("List subscriptions").option("--json", "Output as JSON")).action(async (options) => {
33668
+ try {
33669
+ const client = await getSubscriptionClient(options);
33670
+ const { data } = await client.subscriptions.list();
33671
+ if (options.json) {
33672
+ printJson(data);
33673
+ return;
33674
+ }
33675
+ if (data.length === 0) {
33676
+ console.log(dim("No subscriptions"));
33677
+ return;
33678
+ }
33679
+ console.log(formatTable(["Name", "ID", "Status", "Target", "Format", "Last Success"], data.map((sub) => [
33680
+ sub.name,
33681
+ sub.id,
33682
+ sub.status === "active" ? green(sub.status) : sub.status === "paused" ? yellow(sub.status) : redStatus(sub.status),
33683
+ `${sub.subgraphName}.${sub.tableName}`,
33684
+ sub.format,
33685
+ formatMaybeDate(sub.lastSuccessAt)
33686
+ ])));
33687
+ console.log(dim(`
33688
+ ${data.length} subscription(s) total`));
33689
+ } catch (err) {
33690
+ handleApiError(err, "list subscriptions");
33691
+ }
33692
+ });
33693
+ commonOptions(subscriptions.command("get <idOrName>").description("Show subscription details").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33694
+ try {
33695
+ const client = await getSubscriptionClient(options);
33696
+ const { detail } = await resolveSubscriptionRef(client, idOrName);
33697
+ if (options.json)
33698
+ printJson(detail);
33699
+ else
33700
+ printSubscriptionDetail(detail);
33701
+ } catch (err) {
33702
+ handleApiError(err, "get subscription");
33703
+ }
33704
+ });
33705
+ commonOptions(subscriptions.command("update <idOrName>").description("Update subscription config").option("--name <name>", "Rename subscription").option("--url <url>", "Webhook URL").option("--format <format>", "standard-webhooks | inngest | trigger | cloudflare | cloudevents | raw").option("--runtime <runtime>", "inngest | trigger | cloudflare | node | none").option("--filter <kv...>", "Filter as key=value (supports .eq/.neq/.gt/.gte/.lt/.lte suffixes)").option("--clear-filter", "Replace filter with {}").option("--max-retries <n>", "Maximum delivery retries").option("--timeout-ms <n>", "Delivery timeout in milliseconds").option("--concurrency <n>", "Per-subscription delivery concurrency").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33706
+ try {
33707
+ const client = await getSubscriptionClient(options);
33708
+ const patch = buildUpdatePatch(options);
33709
+ const { id, detail } = await resolveSubscriptionRef(client, idOrName);
33710
+ if (patch.filter !== undefined) {
33711
+ await validateSubscriptionTargetFromApi(client, {
33712
+ subgraphName: detail.subgraphName,
33713
+ tableName: detail.tableName,
33714
+ filter: patch.filter
33715
+ });
33716
+ }
33717
+ const updated = await client.subscriptions.update(id, patch);
33718
+ if (options.json)
33719
+ printJson(updated);
33720
+ else
33721
+ success(`Updated subscription ${blue(updated.name)}`);
33722
+ } catch (err) {
33723
+ handleApiError(err, "update subscription");
33724
+ }
33725
+ });
33726
+ for (const action of ["pause", "resume"]) {
33727
+ commonOptions(subscriptions.command(`${action} <idOrName>`).description(`${action === "pause" ? "Pause" : "Resume"} a subscription`).option("--json", "Output as JSON")).action(async (idOrName, options) => {
33728
+ try {
33729
+ const client = await getSubscriptionClient(options);
33730
+ const { id } = await resolveSubscriptionRef(client, idOrName);
33731
+ const updated = await client.subscriptions[action](id);
33732
+ if (options.json)
33733
+ printJson(updated);
33734
+ else
33735
+ success(`${action === "pause" ? "Paused" : "Resumed"} ${blue(updated.name)}`);
33736
+ } catch (err) {
33737
+ handleApiError(err, `${action} subscription`);
33738
+ }
33739
+ });
33740
+ }
33741
+ commonOptions(subscriptions.command("delete <idOrName>").description("Delete a subscription").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33742
+ try {
33743
+ const client = await getSubscriptionClient(options);
33744
+ const { id, detail } = await resolveSubscriptionRef(client, idOrName);
33745
+ const ok = await confirmOrExit(`Delete subscription "${detail.name}"? Pending outbox rows will be removed.`, options.yes);
33746
+ if (!ok)
33747
+ return;
33748
+ const res = await client.subscriptions.delete(id);
33749
+ if (options.json)
33750
+ printJson(res);
33751
+ else
33752
+ success(`Deleted subscription ${blue(detail.name)}`);
33753
+ } catch (err) {
33754
+ handleApiError(err, "delete subscription");
33755
+ }
33756
+ });
33757
+ commonOptions(subscriptions.command("rotate-secret <idOrName>").description("Rotate the signing secret").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33758
+ try {
33759
+ const client = await getSubscriptionClient(options);
33760
+ const { id, detail } = await resolveSubscriptionRef(client, idOrName);
33761
+ const ok = await confirmOrExit(`Rotate signing secret for "${detail.name}"? Existing receivers using the old secret will fail verification.`, options.yes);
33762
+ if (!ok)
33763
+ return;
33764
+ const res = await client.subscriptions.rotateSecret(id);
33765
+ if (options.json)
33766
+ printJson(res);
33767
+ else {
33768
+ success(`Rotated signing secret for ${blue(res.subscription.name)}`);
33769
+ console.log(res.signingSecret);
33770
+ }
33771
+ } catch (err) {
33772
+ handleApiError(err, "rotate subscription secret");
33773
+ }
33774
+ });
33775
+ commonOptions(subscriptions.command("deliveries <idOrName>").description("Show recent delivery attempts").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33776
+ try {
33777
+ const client = await getSubscriptionClient(options);
33778
+ const { id } = await resolveSubscriptionRef(client, idOrName);
33779
+ const { data } = await client.subscriptions.recentDeliveries(id);
33780
+ if (options.json)
33781
+ printJson(data);
33782
+ else
33783
+ printDeliveries(data);
33784
+ } catch (err) {
33785
+ handleApiError(err, "list subscription deliveries");
33786
+ }
33787
+ });
33788
+ commonOptions(subscriptions.command("dead <idOrName>").description("Show dead-letter outbox rows").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33789
+ try {
33790
+ const client = await getSubscriptionClient(options);
33791
+ const { id } = await resolveSubscriptionRef(client, idOrName);
33792
+ const { data } = await client.subscriptions.dead(id);
33793
+ if (options.json)
33794
+ printJson(data);
33795
+ else
33796
+ printDead(data);
33797
+ } catch (err) {
33798
+ handleApiError(err, "list dead-letter rows");
33799
+ }
33800
+ });
33801
+ commonOptions(subscriptions.command("requeue <idOrName> <outboxId>").description("Requeue one dead-letter row").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, outboxId, options) => {
33802
+ try {
33803
+ const client = await getSubscriptionClient(options);
33804
+ const { id, detail } = await resolveSubscriptionRef(client, idOrName);
33805
+ const ok = await confirmOrExit(`Requeue ${outboxId} for "${detail.name}"?`, options.yes);
33806
+ if (!ok)
33807
+ return;
33808
+ const res = await client.subscriptions.requeueDead(id, outboxId);
33809
+ if (options.json)
33810
+ printJson(res);
33811
+ else
33812
+ success(`Requeued ${blue(outboxId)}`);
33813
+ } catch (err) {
33814
+ handleApiError(err, "requeue dead-letter row");
33815
+ }
33816
+ });
33817
+ commonOptions(subscriptions.command("replay <idOrName>").description("Replay a block range").requiredOption("--from-block <n>", "Start block height").requiredOption("--to-block <n>", "End block height").option("-y, --yes", "Skip confirmation").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33818
+ try {
33819
+ const fromBlock = requireIntegerOption(options.fromBlock, "--from-block");
33820
+ const toBlock = requireIntegerOption(options.toBlock, "--to-block");
33821
+ if (fromBlock > toBlock) {
33822
+ throw new Error("--from-block must be <= --to-block");
33823
+ }
33824
+ const client = await getSubscriptionClient(options);
33825
+ const { id, detail } = await resolveSubscriptionRef(client, idOrName);
33826
+ const ok = await confirmOrExit(`Replay ${detail.name} from block ${fromBlock} to ${toBlock}?`, options.yes);
33827
+ if (!ok)
33828
+ return;
33829
+ const res = await client.subscriptions.replay(id, {
33830
+ fromBlock,
33831
+ toBlock
33832
+ });
33833
+ if (options.json)
33834
+ printJson(res);
33835
+ else {
33836
+ success(`Replay enqueued: ${blue(res.replayId)}`);
33837
+ info(`${res.enqueuedCount} row(s) enqueued from ${res.scannedCount} scanned`);
33838
+ }
33839
+ } catch (err) {
33840
+ handleApiError(err, "replay subscription");
33841
+ }
33842
+ });
33843
+ commonOptions(subscriptions.command("doctor <idOrName>").description("Diagnose subscription health and next steps").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33844
+ try {
33845
+ const client = await getSubscriptionClient(options);
33846
+ const { id, detail } = await resolveSubscriptionRef(client, idOrName);
33847
+ const [deliveries, dead, subgraph] = await Promise.allSettled([
33848
+ client.subscriptions.recentDeliveries(id),
33849
+ client.subscriptions.dead(id),
33850
+ client.subgraphs.get(detail.subgraphName)
33851
+ ]);
33852
+ const report = buildDoctorReport({
33853
+ subscription: detail,
33854
+ deliveries: deliveries.status === "fulfilled" ? deliveries.value.data : [],
33855
+ dead: dead.status === "fulfilled" ? dead.value.data : [],
33856
+ subgraph: subgraph.status === "fulfilled" ? subgraph.value : null
33857
+ });
33858
+ if (options.json)
33859
+ printJson(report);
33860
+ else
33861
+ printDoctorReport(report);
33862
+ } catch (err) {
33863
+ handleApiError(err, "diagnose subscription");
33864
+ }
33865
+ });
33866
+ commonOptions(subscriptions.command("test <idOrName>").description("Build and optionally POST a signed Standard Webhooks fixture").option("--signing-secret <secret>", "Signing secret override").option("--post", "POST the fixture to the subscription URL").option("--json", "Output as JSON")).action(async (idOrName, options) => {
33867
+ try {
33868
+ const signingSecret = resolveSigningSecret(options);
33869
+ const client = await getSubscriptionClient(options);
33870
+ const { detail } = await resolveSubscriptionRef(client, idOrName);
33871
+ const subgraph = await client.subgraphs.get(detail.subgraphName).catch(() => null);
33872
+ const row = await representativeRow(client, detail, subgraph);
33873
+ const fixture = buildSubscriptionTestFixture({
33874
+ subscription: detail,
33875
+ row,
33876
+ signingSecret
33877
+ });
33878
+ let postResult = null;
33879
+ if (options.post) {
33880
+ const res = await fetch(detail.url, {
33881
+ method: "POST",
33882
+ headers: fixture.headers,
33883
+ body: fixture.body
33884
+ });
33885
+ postResult = {
33886
+ status: res.status,
33887
+ body: (await res.text()).slice(0, 2000)
33888
+ };
33889
+ }
33890
+ if (options.json) {
33891
+ printJson({ ...fixture, postResult });
33892
+ return;
33893
+ }
33894
+ console.log(dim("Body:"));
33895
+ console.log(fixture.body);
33896
+ console.log(dim(`
33897
+ Headers:`));
33898
+ console.log(JSON.stringify(fixture.headers, null, 2));
33899
+ console.log(dim(`
33900
+ Curl:`));
33901
+ console.log(fixture.curl);
33902
+ if (postResult) {
33903
+ console.log(dim(`
33904
+ POST result:`));
33905
+ console.log(`Status: ${postResult.status}`);
33906
+ if (postResult.body)
33907
+ console.log(postResult.body);
33908
+ }
33909
+ } catch (err) {
33910
+ handleApiError(err, "test subscription");
33911
+ }
33912
+ });
33913
+ }
33196
33914
  // src/commands/db.ts
33197
33915
  init_config();
33198
33916
  init_dev_state();
33199
33917
  init_output();
33200
- import { confirm } from "@inquirer/prompts";
33918
+ import { confirm as confirm2 } from "@inquirer/prompts";
33201
33919
  import { getDb, sql } from "@secondlayer/shared/db";
33202
33920
  import {
33203
33921
  countMissingBlocks,
@@ -33408,7 +34126,7 @@ async function resetDatabase(skipConfirm) {
33408
34126
  console.log(dim("Note: Subgraph configurations will be preserved."));
33409
34127
  console.log("");
33410
34128
  if (!skipConfirm) {
33411
- const confirmed = await confirm({
34129
+ const confirmed = await confirm2({
33412
34130
  message: "Are you sure you want to reset the database?",
33413
34131
  default: false
33414
34132
  });
@@ -33445,7 +34163,7 @@ async function resyncDatabase(skipConfirm, backfill) {
33445
34163
  console.log(dim("Note: Subgraph configurations will be preserved."));
33446
34164
  console.log("");
33447
34165
  if (!skipConfirm) {
33448
- const confirmed = await confirm({
34166
+ const confirmed = await confirm2({
33449
34167
  message: "Are you sure you want to resync?",
33450
34168
  default: false
33451
34169
  });
@@ -33549,7 +34267,7 @@ async function resyncDatabase(skipConfirm, backfill) {
33549
34267
  // src/commands/subgraphs.ts
33550
34268
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, watch } from "node:fs";
33551
34269
  import { resolve as resolve3 } from "node:path";
33552
- import { confirm as confirm2 } from "@inquirer/prompts";
34270
+ import { confirm as confirm3 } from "@inquirer/prompts";
33553
34271
 
33554
34272
  // src/generators/subgraph-scaffold.ts
33555
34273
  init_format();
@@ -33930,7 +34648,7 @@ Stopped watching.`);
33930
34648
  info(` + columns: ${t}.${cols.join(", ")}`);
33931
34649
  }
33932
34650
  }
33933
- const confirmed = options2.force || await confirm2({
34651
+ const confirmed = options2.force || await confirm3({
33934
34652
  message: "⚠ This will drop all data and reindex from scratch. Continue?"
33935
34653
  });
33936
34654
  if (!confirmed) {
@@ -34113,16 +34831,12 @@ ${result.meta.total} gap(s), ${result.meta.totalMissingBlocks} total missing blo
34113
34831
  });
34114
34832
  subgraphs.command("query <name> <table>").description("Query a subgraph table").option("--sort <column>", "Sort by column").option("--order <dir>", "Sort direction (asc|desc)", "asc").option("--limit <n>", "Max rows to return", "20").option("--offset <n>", "Skip first N rows").option("--fields <cols>", "Comma-separated columns to include").option("--filter <kv...>", "Filter as key=value (supports .gte/.lte/.gt/.lt/.neq suffixes)").option("--count", "Return row count only").option("--json", "Output as JSON").action(async (name, table, options2) => {
34115
34833
  try {
34116
- const filters = {};
34117
- if (options2.filter) {
34118
- for (const kv of options2.filter) {
34119
- const eqIndex = kv.indexOf("=");
34120
- if (eqIndex === -1) {
34121
- error(`Invalid filter format: "${kv}". Use key=value.`);
34122
- process.exit(1);
34123
- }
34124
- filters[kv.slice(0, eqIndex)] = kv.slice(eqIndex + 1);
34125
- }
34834
+ let filters;
34835
+ try {
34836
+ filters = parseQueryFilters(options2.filter);
34837
+ } catch (err) {
34838
+ error(err instanceof Error ? err.message : String(err));
34839
+ process.exit(1);
34126
34840
  }
34127
34841
  const params = {
34128
34842
  sort: options2.sort,
@@ -34130,7 +34844,7 @@ ${result.meta.total} gap(s), ${result.meta.totalMissingBlocks} total missing blo
34130
34844
  limit: Number.parseInt(options2.limit, 10),
34131
34845
  offset: options2.offset ? Number.parseInt(options2.offset, 10) : undefined,
34132
34846
  fields: options2.fields,
34133
- filters: Object.keys(filters).length > 0 ? filters : undefined
34847
+ filters
34134
34848
  };
34135
34849
  if (options2.count) {
34136
34850
  const result = await querySubgraphTableCount(name, table, params);
@@ -34172,8 +34886,8 @@ ${rows.length} row(s)`));
34172
34886
  subgraphs.command("delete <name>").description("Delete a subgraph and its data").option("-y, --yes", "Skip confirmation").action(async (name, options2) => {
34173
34887
  try {
34174
34888
  if (!options2.yes) {
34175
- const { confirm: confirm3 } = await import("@inquirer/prompts");
34176
- const ok = await confirm3({
34889
+ const { confirm: confirm4 } = await import("@inquirer/prompts");
34890
+ const ok = await confirm4({
34177
34891
  message: `Delete subgraph "${name}" and all its data? This cannot be undone.`
34178
34892
  });
34179
34893
  if (!ok) {
@@ -35167,7 +35881,7 @@ init_http();
35167
35881
  init_output();
35168
35882
  init_project_file();
35169
35883
  init_resolve_tenant();
35170
- import { confirm as confirm4, input as input4, select as select4 } from "@inquirer/prompts";
35884
+ import { confirm as confirm5, input as input4, select as select4 } from "@inquirer/prompts";
35171
35885
  function registerInstanceCommand(program2) {
35172
35886
  const instance = program2.command("instance").description("Manage your dedicated Secondlayer instance");
35173
35887
  instance.command("create").description("Provision a new dedicated instance for the active project").option("--plan <plan>", "Plan: hobby (free) | launch | grow | scale", "hobby").action(async (opts) => {
@@ -35218,7 +35932,7 @@ function registerInstanceCommand(program2) {
35218
35932
  target = answer;
35219
35933
  }
35220
35934
  if (!opts.yes) {
35221
- const ok = await confirm4({
35935
+ const ok = await confirm5({
35222
35936
  message: `Resize to ${target}? ~30s downtime while containers recreate. Data preserved.`,
35223
35937
  default: false
35224
35938
  });
@@ -35367,7 +36081,7 @@ function registerInstanceCommand(program2) {
35367
36081
  db.command("revoke-key").description("Revoke bastion access for this instance").option("-y, --yes", "Skip confirmation").action(async (opts) => {
35368
36082
  guardOssMode();
35369
36083
  if (!opts.yes) {
35370
- const ok = await confirm4({
36084
+ const ok = await confirm5({
35371
36085
  message: "Revoke bastion access for this instance?",
35372
36086
  default: false
35373
36087
  });
@@ -35570,6 +36284,7 @@ registerProjectCommand(program);
35570
36284
  registerInstanceCommand(program);
35571
36285
  registerSubgraphsCommand(program);
35572
36286
  registerCreateCommand(program);
36287
+ registerSubscriptionsCommand(program);
35573
36288
  registerStatusCommand(program);
35574
36289
  registerStackCommand(program);
35575
36290
  registerDbCommand(program);
@@ -35579,5 +36294,5 @@ registerLocalCommand(program);
35579
36294
  registerAccountCommand(program);
35580
36295
  program.parse();
35581
36296
 
35582
- //# debugId=C959E54B7099E8D964756E2164756E21
36297
+ //# debugId=CDED029144E4004564756E2164756E21
35583
36298
  //# sourceMappingURL=cli.js.map