@sentio/cli 3.6.0-rc.2 → 3.6.0-rc.4

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/lib/index.js CHANGED
@@ -141137,6 +141137,59 @@ function formatDebugBody(body) {
141137
141137
 
141138
141138
  // src/commands/data.ts
141139
141139
  var DEFAULT_RANGE_STEP = 3600;
141140
+ var VALID_METRIC_FUNCTIONS = /* @__PURE__ */ new Set([
141141
+ // Math
141142
+ "abs",
141143
+ "ceil",
141144
+ "floor",
141145
+ "round",
141146
+ "log2",
141147
+ "log10",
141148
+ "ln",
141149
+ // Rollup
141150
+ "rollup_avg",
141151
+ "rollup_count",
141152
+ "rollup_last",
141153
+ "rollup_max",
141154
+ "rollup_min",
141155
+ "rollup_sum",
141156
+ "rollup_delta",
141157
+ // Aggregate Over Time
141158
+ "avg_over_time",
141159
+ "count_over_time",
141160
+ "last_over_time",
141161
+ "max_over_time",
141162
+ "min_over_time",
141163
+ "sum_over_time",
141164
+ "delta_over_time",
141165
+ // Rate
141166
+ "rate",
141167
+ "irate",
141168
+ "delta",
141169
+ "moving_delta",
141170
+ // Rank
141171
+ "topk",
141172
+ "bottomk",
141173
+ // Time
141174
+ "timestamp",
141175
+ "day_of_year",
141176
+ "day_of_month",
141177
+ "day_of_week",
141178
+ "year",
141179
+ "month",
141180
+ "hour",
141181
+ "minute",
141182
+ // TimeShift
141183
+ "before",
141184
+ "after"
141185
+ ]);
141186
+ var VALID_EVENT_FUNCTIONS = /* @__PURE__ */ new Set([
141187
+ // Rank
141188
+ "topk",
141189
+ "bottomk",
141190
+ // Delta
141191
+ "delta"
141192
+ ]);
141140
141193
  function createDataCommand() {
141141
141194
  const dataCommand = new Command("data").description("Retrieve data from Sentio");
141142
141195
  dataCommand.addCommand(createDataQueryCommand());
@@ -141173,7 +141226,7 @@ function buildEventsInsightQueryBody(search, options) {
141173
141226
  aggregation: buildEventAggregation(options.aggr),
141174
141227
  selectorExpr: buildSelectorExpr(options.filter),
141175
141228
  groupBy: normalizeListOption(options.groupBy),
141176
- functions: buildFunctions(options.func),
141229
+ functions: buildFunctions(options.func, VALID_EVENT_FUNCTIONS),
141177
141230
  disabled: false
141178
141231
  }
141179
141232
  }
@@ -141197,7 +141250,7 @@ function buildMetricsInsightQueryBody(query, options) {
141197
141250
  query,
141198
141251
  labelSelector: buildMetricLabelSelector(options.filter),
141199
141252
  aggregate: buildMetricAggregate(options.aggr, options.groupBy),
141200
- functions: buildFunctions(options.func),
141253
+ functions: buildFunctions(options.func, VALID_METRIC_FUNCTIONS),
141201
141254
  disabled: false
141202
141255
  }
141203
141256
  }
@@ -141504,7 +141557,7 @@ function handleDataCommandError(error, command) {
141504
141557
  function shouldShowHelpForDataCommandError(error) {
141505
141558
  return error.message.startsWith("Project is required.") || error.message.startsWith(
141506
141559
  "Provide --file, --stdin, or exactly one of --event, --metric, or --price for data query"
141507
- ) || error.message.startsWith("Use exactly one of --event, --metric, or --price for data query.") || error.message.startsWith("Price queries only support --price") || error.message.startsWith("Provide --query, --result, --file, or --stdin.") || error.message.startsWith("Use only one of --query or --result.") || error.message.startsWith("--async only works with --query.") || error.message.startsWith("Execution id is required.");
141560
+ ) || error.message.startsWith("Use exactly one of --event, --metric, or --price for data query.") || error.message.startsWith("Price queries only support --price") || error.message.startsWith("Provide --query, --result, --file, or --stdin.") || error.message.startsWith("Use only one of --query or --result.") || error.message.startsWith("--async only works with --query.") || error.message.startsWith("Execution id is required.") || error.message.startsWith('Unknown function "');
141508
141561
  }
141509
141562
  function buildEventAggregation(aggregationName = "total") {
141510
141563
  switch (aggregationName.toUpperCase()) {
@@ -141633,8 +141686,14 @@ function parseAnyValue(rawValue) {
141633
141686
  }
141634
141687
  return { stringValue: unquotedValue };
141635
141688
  }
141636
- function buildFunctions(functions) {
141637
- return normalizeListOption(functions).map(parseFunctionCall);
141689
+ function buildFunctions(functions, validNames) {
141690
+ return normalizeListOption(functions).map((f14) => {
141691
+ const parsed = parseFunctionCall(f14);
141692
+ if (validNames && !validNames.has(parsed.name)) {
141693
+ throw new CliError(`Unknown function "${parsed.name}". Valid functions: ${[...validNames].sort().join(", ")}.`);
141694
+ }
141695
+ return parsed;
141696
+ });
141638
141697
  }
141639
141698
  function parseFunctionCall(value) {
141640
141699
  const trimmed = value.trim();
@@ -142476,7 +142535,11 @@ function createProcessorCommand() {
142476
142535
  function createProcessorStatusCommand() {
142477
142536
  return withOutputOptions3(
142478
142537
  withSharedProjectOptions3(withAuthOptions3(new Command("status").description("Get processor status")))
142479
- ).showHelpAfterError().option("--version <selector>", "Version selector: active, pending, or all").action(async (options, command) => {
142538
+ ).showHelpAfterError().option(
142539
+ "--version <selector>",
142540
+ "Version selector: active, pending, all, or a numeric version number",
142541
+ parseVersionSelector
142542
+ ).action(async (options, command) => {
142480
142543
  try {
142481
142544
  await runProcessorStatus(options);
142482
142545
  } catch (error) {
@@ -142556,22 +142619,19 @@ async function runProcessorStatus(options) {
142556
142619
  const context = createApiContext(options);
142557
142620
  const project = await resolveProjectRef(options, context, { ownerSlug: true });
142558
142621
  const requestedVersion = normalizeVersionSelector(options.version);
142622
+ const apiVersion = typeof requestedVersion === "number" ? "ALL" : requestedVersion ?? "ALL";
142559
142623
  const response = await import_api5.ProcessorService.getProcessorStatusV2({
142560
142624
  path: {
142561
142625
  owner: project.owner,
142562
142626
  slug: project.slug
142563
142627
  },
142564
142628
  query: {
142565
- version: requestedVersion ?? "ALL"
142629
+ version: apiVersion
142566
142630
  },
142567
142631
  headers: context.headers
142568
142632
  });
142569
142633
  const data4 = unwrapApiResult(response);
142570
- if (!options.json) {
142571
- printOutput3(options, shapeProcessorStatusOutput(data4, requestedVersion));
142572
- return;
142573
- }
142574
- printOutput3(options, data4);
142634
+ printOutput3(options, shapeProcessorStatusOutput(data4, requestedVersion));
142575
142635
  }
142576
142636
  async function runProcessorSource(options) {
142577
142637
  const context = createApiContext(options);
@@ -142810,7 +142870,7 @@ function withOutputOptions3(command) {
142810
142870
  return command.option("--json", "Print raw JSON response").option("--yaml", "Print raw YAML response");
142811
142871
  }
142812
142872
  function handleProcessorCommandError(error, command) {
142813
- if (error instanceof CliError && (error.message.startsWith("Project is required.") || error.message.startsWith("Invalid project ") || error.message.startsWith("Invalid version selector ") || error.message.startsWith("Source file not found:"))) {
142873
+ if (error instanceof CliError && (error.message.startsWith("Project is required.") || error.message.startsWith("Invalid project ") || error.message.startsWith("Invalid version selector") || error.message.startsWith("Source file not found:"))) {
142814
142874
  console.error(error.message);
142815
142875
  if (command) {
142816
142876
  console.error();
@@ -142904,14 +142964,24 @@ function formatOutput2(data4) {
142904
142964
  return JSON.stringify(data4, null, 2);
142905
142965
  }
142906
142966
  function normalizeVersionSelector(value) {
142907
- if (!value) {
142967
+ if (value === void 0) {
142908
142968
  return void 0;
142909
142969
  }
142970
+ if (typeof value === "number") {
142971
+ return value;
142972
+ }
142910
142973
  const normalized = value.toUpperCase();
142911
142974
  if (normalized === "ACTIVE" || normalized === "PENDING" || normalized === "ALL") {
142912
142975
  return normalized;
142913
142976
  }
142914
- throw new CliError(`Invalid version selector "${value}". Use active, pending, or all.`);
142977
+ throw new CliError(`Invalid version selector "${value}". Use active, pending, all, or a version number.`);
142978
+ }
142979
+ function parseVersionSelector(value) {
142980
+ const num = Number.parseInt(value, 10);
142981
+ if (!Number.isNaN(num) && String(num) === value) {
142982
+ return num;
142983
+ }
142984
+ return value;
142915
142985
  }
142916
142986
  function parseInteger2(value) {
142917
142987
  const parsedValue = Number.parseInt(value, 10);
@@ -142937,6 +143007,12 @@ function shapeProcessorStatusOutput(data4, versionSelector) {
142937
143007
  processors: processors.filter((processor) => asString3(processor.versionState) === versionSelector)
142938
143008
  };
142939
143009
  }
143010
+ if (typeof versionSelector === "number") {
143011
+ return {
143012
+ ...data4,
143013
+ processors: processors.filter((processor) => asNumber(processor.version) === versionSelector)
143014
+ };
143015
+ }
142940
143016
  return {
142941
143017
  ...data4,
142942
143018
  processors: processors.filter((processor) => {
@@ -143006,7 +143082,10 @@ function createAlertGetCommand() {
143006
143082
  function createAlertCreateCommand() {
143007
143083
  return withOutputOptions4(
143008
143084
  withSharedProjectOptions4(withAuthOptions4(new Command("create").description("Create an alert rule")))
143009
- ).showHelpAfterError().option("--file <path>", "Read request JSON or YAML from file. Use --doc to show the full alert request format").option("--stdin", "Read request JSON or YAML from stdin. Use --doc to show the full alert request format").option("--doc", "Print the full alert request file format and exit").option("--type <type>", "Alert type: METRIC, LOG, or SQL").option("--subject <text>", "Alert subject/title").option("--message <text>", "Optional alert message template").option("--query <text>", "Inline log query or SQL when not using --file/--stdin").option("--event <name>", "Inline event query for METRIC alerts").option("--metric <name>", "Inline metric query for METRIC alerts").option("--alias <alias>", "Alias for the inline METRIC query").option("--source-name <name>", "Optional source name for the inline METRIC query").option("--filter <selector>", "Inline METRIC query filter like amount>0 or meta.chain=1", collectOption2, []).option("--group-by <field>", "Inline METRIC query group-by field", collectOption2, []).option(
143085
+ ).showHelpAfterError().option("--file <path>", "Read request JSON or YAML from file. Use --doc to show the full alert request format").option("--stdin", "Read request JSON or YAML from stdin. Use --doc to show the full alert request format").option("--doc", "Print the full alert request file format and exit").option("--type <type>", "Alert type: METRIC, LOG, or SQL").option("--subject <text>", "Alert subject/title").option("--message <text>", "Optional alert message template").option(
143086
+ "--query <text>",
143087
+ "Inline query. LOG: Elasticsearch query-string syntax (e.g. amount:>1000, status:error). SQL: full SQL statement."
143088
+ ).option("--event <name>", "Inline event query for METRIC alerts").option("--metric <name>", "Inline metric query for METRIC alerts").option("--alias <alias>", "Alias for the inline METRIC query").option("--source-name <name>", "Optional source name for the inline METRIC query").option("--filter <selector>", "Inline METRIC query filter like amount>0 or meta.chain=1", collectOption2, []).option("--group-by <field>", "Inline METRIC query group-by field", collectOption2, []).option(
143010
143089
  "--aggr <aggregation>",
143011
143090
  "Inline aggregation. METRIC: avg|sum|min|max|count. EVENTS: total|unique|AAU|DAU|WAU|MAU"
143012
143091
  ).option("--func <function>", "Inline function like topk(1), bottomk(1), delta(1m)", collectOption2, []).option("--op <operator>", "Condition operator like >, >=, ==, !=, <, <=, between").option("--threshold <value>", "Condition threshold", parseNumber).option("--threshold2 <value>", "Second threshold for between", parseNumber).option("--for <duration>", "Evaluate over the last duration, for example 5m or 1h").option("--interval <duration>", "Alert evaluation interval, for example 1m or 5m").option("--time-column <column>", "SQL alert time column for column-based conditions").option("--value-column <column>", "SQL alert value column for column-based conditions").option("--sql-aggr <aggregation>", "SQL aggregation: COUNT, SUM, AVG, MAX, MIN, LAST").addHelpText(
@@ -143014,7 +143093,7 @@ function createAlertCreateCommand() {
143014
143093
  `
143015
143094
 
143016
143095
  Examples:
143017
- $ sentio alert create --project sentio/coinbase --type LOG --subject "Large transfer logs" --query 'amount > 1000' --op '>' --threshold 0
143096
+ $ sentio alert create --project sentio/coinbase --type LOG --subject "Large transfer logs" --query 'amount:>1000' --op '>' --threshold 0
143018
143097
  $ sentio alert create --project sentio/coinbase --type SQL --subject "Large transfer(SQL demo)" --query 'select timestamp, amount from transfer where amount > 1000' --time-column timestamp --value-column amount --sql-aggr MAX --op '>' --threshold 1000
143019
143098
  $ sentio alert create --project sentio/coinbase --type METRIC --subject "Burn spike" --metric burn --filter meta.chain=1 --aggr avg --group-by meta.address --op '>' --threshold 100
143020
143099
  $ sentio alert create --project sentio/coinbase --type METRIC --subject "Transfer anomaly" --event Transfer --filter amount>0 --aggr total --func 'delta(1m)' --op '>' --threshold 100
@@ -143637,11 +143716,14 @@ Metric alert example:
143637
143716
  disabled: false
143638
143717
 
143639
143718
  Log alert example:
143719
+ NOTE: logCondition.query uses Elasticsearch query-string syntax.
143720
+ Ranges MUST use field:>value form \u2014 C-style "field > value" is rejected at evaluation time.
143721
+ Examples: amount:>1000000 timestamp:>=2024-01-01 amount:[1000 TO 9999] status:error
143640
143722
  rule:
143641
143723
  alertType: LOG
143642
143724
  subject: large transfer logs
143643
143725
  logCondition:
143644
- query: amount > 1000
143726
+ query: amount:>1000
143645
143727
  comparisonOp: ">"
143646
143728
  threshold: 0
143647
143729
 
@@ -144874,7 +144956,13 @@ function createDashboardAddPanelCommand() {
144874
144956
  "Event filter or metric label selector like field:value or amount>0",
144875
144957
  collectOption3,
144876
144958
  []
144877
- ).option("--group-by <field>", "Group by event property or metric label", collectOption3, []).option("--aggr <aggregation>", "Event: total|unique|AAU|DAU|WAU|MAU. Metric: avg|sum|min|max|count").option("--func <function>", "Function like topk(1), bottomk(1)", collectOption3, []).addHelpText(
144959
+ ).option("--group-by <field>", "Group by event property or metric label", collectOption3, []).option("--aggr <aggregation>", "Event: total|unique|AAU|DAU|WAU|MAU. Metric: avg|sum|min|max|count").option("--func <function>", "Function like topk(1), bottomk(1)", collectOption3, []).option(
144960
+ "--time-range-start <value>",
144961
+ "Panel time range start: relative (e.g. -24h, -7d, -30m) or ISO date (e.g. 2024-01-01T00:00:00Z)"
144962
+ ).option(
144963
+ "--time-range-end <value>",
144964
+ "Panel time range end: relative (e.g. now, -1h) or ISO date. Defaults to now when --time-range-start is set."
144965
+ ).option("--time-range-step <seconds>", "Panel time range step in seconds (e.g. 3600)").addHelpText(
144878
144966
  "after",
144879
144967
  `
144880
144968
 
@@ -144911,7 +144999,17 @@ Metric insights panel examples:
144911
144999
  --metric burn --filter meta.chain=1 --aggr avg --group-by meta.address
144912
145000
  $ sentio dashboard add-panel abc123 --project owner/slug \\
144913
145001
  --panel-name "Burn Rate Delta" --type LINE \\
144914
- --metric burn --aggr sum
145002
+ --metric burn --aggr sum
145003
+
145004
+ Panel time range override examples:
145005
+ $ sentio dashboard add-panel abc123 --project owner/slug \\
145006
+ --panel-name "Last 24h Transfers" --type LINE \\
145007
+ --event Transfer --aggr total \\
145008
+ --time-range-start -24h --time-range-end now --time-range-step 3600
145009
+ $ sentio dashboard add-panel abc123 --project owner/slug \\
145010
+ --panel-name "Jan 2024 Volume" --type BAR \\
145011
+ --sql "SELECT date, sum(amount) FROM Transfer GROUP BY date" \\
145012
+ --time-range-start 2024-01-01T00:00:00Z --time-range-end 2024-02-01T00:00:00Z
144915
145013
  `
144916
145014
  ).action(async (dashboardId, options, command) => {
144917
145015
  try {
@@ -144979,14 +145077,18 @@ function buildDashboardCreateBody(options, project) {
144979
145077
  };
144980
145078
  }
144981
145079
  function normalizeDashboardInit(input) {
145080
+ const emptyLayouts = {
145081
+ responsiveLayouts: {
145082
+ lg: { layouts: [] },
145083
+ md: { layouts: [] },
145084
+ sm: { layouts: [] },
145085
+ xs: { layouts: [] }
145086
+ }
145087
+ };
144982
145088
  if (input === void 0) {
144983
145089
  return {
144984
145090
  panels: {},
144985
- layouts: {
144986
- responsiveLayouts: {
144987
- lg: { layouts: [] }
144988
- }
144989
- }
145091
+ layouts: emptyLayouts
144990
145092
  };
144991
145093
  }
144992
145094
  if (!input || typeof input !== "object" || Array.isArray(input)) {
@@ -144995,11 +145097,7 @@ function normalizeDashboardInit(input) {
144995
145097
  const dashboard = input;
144996
145098
  return {
144997
145099
  panels: isRecord(dashboard.panels) ? dashboard.panels : {},
144998
- layouts: isRecord(dashboard.layouts) ? dashboard.layouts : {
144999
- responsiveLayouts: {
145000
- lg: { layouts: [] }
145001
- }
145002
- }
145100
+ layouts: isRecord(dashboard.layouts) ? dashboard.layouts : emptyLayouts
145003
145101
  };
145004
145102
  }
145005
145103
  async function runDashboardAddPanel(dashboardId, options) {
@@ -145023,11 +145121,13 @@ async function runDashboardAddPanel(dashboardId, options) {
145023
145121
  const chartType = normalizeChartType(options.type);
145024
145122
  const panelId = generatePanelId();
145025
145123
  const chart = buildPanelChart(chartType, options);
145124
+ const timeRangeOverride = buildTimeRangeOverride(options);
145026
145125
  const newPanel = {
145027
145126
  id: panelId,
145028
145127
  name: options.panelName,
145029
145128
  dashboardId,
145030
- chart
145129
+ chart,
145130
+ ...timeRangeOverride ? { timeRangeOverride } : {}
145031
145131
  };
145032
145132
  const existingLayouts = dashboard.layouts?.responsiveLayouts?.lg?.layouts ?? [];
145033
145133
  let maxBottom = 0;
@@ -145046,15 +145146,17 @@ async function runDashboardAddPanel(dashboardId, options) {
145046
145146
  };
145047
145147
  const panels = { ...dashboard.panels ?? {} };
145048
145148
  panels[panelId] = newPanel;
145049
- const updatedLayouts = [...existingLayouts, newLayout];
145149
+ const existingResponsive = dashboard.layouts?.responsiveLayouts ?? {};
145150
+ const updatedResponsive = { ...existingResponsive };
145151
+ for (const bp2 of ["lg", "md", "sm", "xs"]) {
145152
+ const existing = existingResponsive[bp2]?.layouts ?? [];
145153
+ updatedResponsive[bp2] = { layouts: [...existing, newLayout] };
145154
+ }
145050
145155
  const dashboardJson = {
145051
145156
  ...dashboard,
145052
145157
  panels,
145053
145158
  layouts: {
145054
- responsiveLayouts: {
145055
- ...dashboard.layouts?.responsiveLayouts ?? {},
145056
- lg: { layouts: updatedLayouts }
145057
- }
145159
+ responsiveLayouts: updatedResponsive
145058
145160
  }
145059
145161
  };
145060
145162
  const importResponse = await import_api14.WebService.importDashboard({
@@ -145072,6 +145174,40 @@ async function runDashboardAddPanel(dashboardId, options) {
145072
145174
  dashboard: importData.dashboard
145073
145175
  });
145074
145176
  }
145177
+ function buildTimeRangeLike(value) {
145178
+ const relMatch = value.match(/^(-?\d+)\s*([smhdwMy])$/);
145179
+ if (relMatch) {
145180
+ return { relativeTime: { unit: relMatch[2], value: Number(relMatch[1]) } };
145181
+ }
145182
+ if (value === "now" || value === "0") {
145183
+ return { relativeTime: { unit: "h", value: 0 } };
145184
+ }
145185
+ const ts2 = Date.parse(value);
145186
+ if (!Number.isNaN(ts2)) {
145187
+ return { absoluteTime: String(ts2) };
145188
+ }
145189
+ throw new CliError(
145190
+ `Invalid time range value "${value}". Use a relative offset (e.g. -24h, -7d, -30m, now) or an ISO date string.`
145191
+ );
145192
+ }
145193
+ function buildTimeRangeOverride(options) {
145194
+ if (!options.timeRangeStart && !options.timeRangeEnd) {
145195
+ return void 0;
145196
+ }
145197
+ const timeRange = {};
145198
+ if (options.timeRangeStart) {
145199
+ timeRange.start = buildTimeRangeLike(options.timeRangeStart);
145200
+ }
145201
+ if (options.timeRangeEnd) {
145202
+ timeRange.end = buildTimeRangeLike(options.timeRangeEnd);
145203
+ } else {
145204
+ timeRange.end = { relativeTime: { unit: "h", value: 0 } };
145205
+ }
145206
+ if (options.timeRangeStep) {
145207
+ timeRange.step = options.timeRangeStep;
145208
+ }
145209
+ return { enabled: true, timeRange };
145210
+ }
145075
145211
  function buildPanelChart(chartType, options) {
145076
145212
  if (options.sql) {
145077
145213
  const sqlSize = Number.parseInt(String(options.size ?? "100"), 10) || 100;
@@ -145141,7 +145277,7 @@ function withOutputOptions8(command) {
145141
145277
  return command.option("--json", "Print raw JSON response").option("--yaml", "Print raw YAML response");
145142
145278
  }
145143
145279
  function handleDashboardCommandError(error, command) {
145144
- if (error instanceof CliError && (error.message.startsWith("Project is required.") || error.message.startsWith("Invalid project ") || error.message.startsWith("Dashboard ") || error.message.startsWith("Provide --file or --stdin") || error.message.startsWith("Use either --file or --stdin") || error.message.startsWith("Expected JSON or YAML") || error.message.startsWith("Invalid JSON or YAML") || error.message.startsWith("Dashboard initialization data") || error.message.startsWith("Provide exactly one data source") || error.message.startsWith("Use exactly one of --sql") || error.message.startsWith("Invalid chart type") || error.message.startsWith("Invalid aggregation") || error.message.startsWith("Invalid metric aggregation") || error.message.startsWith("Invalid filter") || error.message.startsWith("Invalid metric selector"))) {
145280
+ if (error instanceof CliError && (error.message.startsWith("Project is required.") || error.message.startsWith("Invalid project ") || error.message.startsWith("Dashboard ") || error.message.startsWith("Provide --file or --stdin") || error.message.startsWith("Use either --file or --stdin") || error.message.startsWith("Expected JSON or YAML") || error.message.startsWith("Invalid JSON or YAML") || error.message.startsWith("Dashboard initialization data") || error.message.startsWith("Provide exactly one data source") || error.message.startsWith("Use exactly one of --sql") || error.message.startsWith("Invalid chart type") || error.message.startsWith("Invalid aggregation") || error.message.startsWith("Invalid metric aggregation") || error.message.startsWith("Invalid filter") || error.message.startsWith("Invalid metric selector") || error.message.startsWith("Invalid time range value") || error.message.startsWith('Unknown function "'))) {
145145
145281
  console.error(error.message);
145146
145282
  if (command) {
145147
145283
  console.error();
@@ -145239,6 +145375,10 @@ await printVersions();
145239
145375
  if (process.argv.includes("--debug")) {
145240
145376
  enableApiDebug();
145241
145377
  }
145378
+ if (process.argv.length === 3 && process.argv[2] === "--version") {
145379
+ version();
145380
+ process.exit(0);
145381
+ }
145242
145382
  program2.addCommand(createLoginCommand());
145243
145383
  program2.addCommand(createCreateCommand());
145244
145384
  program2.addCommand(createVersionCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentio/cli",
3
- "version": "3.6.0-rc.2",
3
+ "version": "3.6.0-rc.4",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -136,7 +136,10 @@ function createAlertCreateCommand() {
136
136
  .option('--type <type>', 'Alert type: METRIC, LOG, or SQL')
137
137
  .option('--subject <text>', 'Alert subject/title')
138
138
  .option('--message <text>', 'Optional alert message template')
139
- .option('--query <text>', 'Inline log query or SQL when not using --file/--stdin')
139
+ .option(
140
+ '--query <text>',
141
+ 'Inline query. LOG: Elasticsearch query-string syntax (e.g. amount:>1000, status:error). SQL: full SQL statement.'
142
+ )
140
143
  .option('--event <name>', 'Inline event query for METRIC alerts')
141
144
  .option('--metric <name>', 'Inline metric query for METRIC alerts')
142
145
  .option('--alias <alias>', 'Alias for the inline METRIC query')
@@ -161,7 +164,7 @@ function createAlertCreateCommand() {
161
164
  `
162
165
 
163
166
  Examples:
164
- $ sentio alert create --project sentio/coinbase --type LOG --subject "Large transfer logs" --query 'amount > 1000' --op '>' --threshold 0
167
+ $ sentio alert create --project sentio/coinbase --type LOG --subject "Large transfer logs" --query 'amount:>1000' --op '>' --threshold 0
165
168
  $ sentio alert create --project sentio/coinbase --type SQL --subject "Large transfer(SQL demo)" --query 'select timestamp, amount from transfer where amount > 1000' --time-column timestamp --value-column amount --sql-aggr MAX --op '>' --threshold 1000
166
169
  $ sentio alert create --project sentio/coinbase --type METRIC --subject "Burn spike" --metric burn --filter meta.chain=1 --aggr avg --group-by meta.address --op '>' --threshold 100
167
170
  $ sentio alert create --project sentio/coinbase --type METRIC --subject "Transfer anomaly" --event Transfer --filter amount>0 --aggr total --func 'delta(1m)' --op '>' --threshold 100
@@ -916,11 +919,14 @@ Metric alert example:
916
919
  disabled: false
917
920
 
918
921
  Log alert example:
922
+ NOTE: logCondition.query uses Elasticsearch query-string syntax.
923
+ Ranges MUST use field:>value form — C-style "field > value" is rejected at evaluation time.
924
+ Examples: amount:>1000000 timestamp:>=2024-01-01 amount:[1000 TO 9999] status:error
919
925
  rule:
920
926
  alertType: LOG
921
927
  subject: large transfer logs
922
928
  logCondition:
923
- query: amount > 1000
929
+ query: amount:>1000
924
930
  comparisonOp: ">"
925
931
  threshold: 0
926
932
 
@@ -50,6 +50,9 @@ interface AddPanelOptions extends DashboardOptions {
50
50
  groupBy?: string[]
51
51
  aggr?: string
52
52
  func?: string[]
53
+ timeRangeStart?: string
54
+ timeRangeEnd?: string
55
+ timeRangeStep?: string
53
56
  }
54
57
 
55
58
  export function createDashboardCommand() {
@@ -162,6 +165,15 @@ function createDashboardAddPanelCommand() {
162
165
  .option('--group-by <field>', 'Group by event property or metric label', collectOption, [])
163
166
  .option('--aggr <aggregation>', 'Event: total|unique|AAU|DAU|WAU|MAU. Metric: avg|sum|min|max|count')
164
167
  .option('--func <function>', 'Function like topk(1), bottomk(1)', collectOption, [])
168
+ .option(
169
+ '--time-range-start <value>',
170
+ 'Panel time range start: relative (e.g. -24h, -7d, -30m) or ISO date (e.g. 2024-01-01T00:00:00Z)'
171
+ )
172
+ .option(
173
+ '--time-range-end <value>',
174
+ 'Panel time range end: relative (e.g. now, -1h) or ISO date. Defaults to now when --time-range-start is set.'
175
+ )
176
+ .option('--time-range-step <seconds>', 'Panel time range step in seconds (e.g. 3600)')
165
177
  .addHelpText(
166
178
  'after',
167
179
  `
@@ -199,7 +211,17 @@ Metric insights panel examples:
199
211
  --metric burn --filter meta.chain=1 --aggr avg --group-by meta.address
200
212
  $ sentio dashboard add-panel abc123 --project owner/slug \\
201
213
  --panel-name "Burn Rate Delta" --type LINE \\
202
- --metric burn --aggr sum
214
+ --metric burn --aggr sum
215
+
216
+ Panel time range override examples:
217
+ $ sentio dashboard add-panel abc123 --project owner/slug \\
218
+ --panel-name "Last 24h Transfers" --type LINE \\
219
+ --event Transfer --aggr total \\
220
+ --time-range-start -24h --time-range-end now --time-range-step 3600
221
+ $ sentio dashboard add-panel abc123 --project owner/slug \\
222
+ --panel-name "Jan 2024 Volume" --type BAR \\
223
+ --sql "SELECT date, sum(amount) FROM Transfer GROUP BY date" \\
224
+ --time-range-start 2024-01-01T00:00:00Z --time-range-end 2024-02-01T00:00:00Z
203
225
  `
204
226
  )
205
227
  .action(async (dashboardId, options, command) => {
@@ -279,14 +301,19 @@ function buildDashboardCreateBody(options: DashboardCreateOptions, project: { ow
279
301
  }
280
302
 
281
303
  function normalizeDashboardInit(input: unknown) {
304
+ const emptyLayouts = {
305
+ responsiveLayouts: {
306
+ lg: { layouts: [] },
307
+ md: { layouts: [] },
308
+ sm: { layouts: [] },
309
+ xs: { layouts: [] }
310
+ }
311
+ }
312
+
282
313
  if (input === undefined) {
283
314
  return {
284
315
  panels: {},
285
- layouts: {
286
- responsiveLayouts: {
287
- lg: { layouts: [] }
288
- }
289
- }
316
+ layouts: emptyLayouts
290
317
  }
291
318
  }
292
319
 
@@ -297,13 +324,7 @@ function normalizeDashboardInit(input: unknown) {
297
324
  const dashboard = input as Record<string, unknown>
298
325
  return {
299
326
  panels: isRecord(dashboard.panels) ? dashboard.panels : {},
300
- layouts: isRecord(dashboard.layouts)
301
- ? dashboard.layouts
302
- : {
303
- responsiveLayouts: {
304
- lg: { layouts: [] }
305
- }
306
- }
327
+ layouts: isRecord(dashboard.layouts) ? dashboard.layouts : emptyLayouts
307
328
  }
308
329
  }
309
330
 
@@ -335,11 +356,13 @@ async function runDashboardAddPanel(dashboardId: string, options: AddPanelOption
335
356
  const panelId = generatePanelId()
336
357
  const chart = buildPanelChart(chartType, options)
337
358
 
338
- const newPanel = {
359
+ const timeRangeOverride = buildTimeRangeOverride(options)
360
+ const newPanel: Record<string, unknown> = {
339
361
  id: panelId,
340
362
  name: options.panelName,
341
363
  dashboardId,
342
- chart
364
+ chart,
365
+ ...(timeRangeOverride ? { timeRangeOverride } : {})
343
366
  }
344
367
 
345
368
  // 3. Compute layout position: place below all existing panels
@@ -364,16 +387,18 @@ async function runDashboardAddPanel(dashboardId: string, options: AddPanelOption
364
387
  const panels = { ...(dashboard.panels ?? {}) }
365
388
  panels[panelId] = newPanel as never
366
389
 
367
- const updatedLayouts = [...existingLayouts, newLayout]
390
+ const existingResponsive = dashboard.layouts?.responsiveLayouts ?? {}
391
+ const updatedResponsive: Record<string, unknown> = { ...existingResponsive }
392
+ for (const bp of ['lg', 'md', 'sm', 'xs'] as const) {
393
+ const existing = (existingResponsive as Record<string, { layouts?: unknown[] } | undefined>)[bp]?.layouts ?? []
394
+ updatedResponsive[bp] = { layouts: [...existing, newLayout] }
395
+ }
368
396
 
369
397
  const dashboardJson: Record<string, unknown> = {
370
398
  ...dashboard,
371
399
  panels,
372
400
  layouts: {
373
- responsiveLayouts: {
374
- ...(dashboard.layouts?.responsiveLayouts ?? {}),
375
- lg: { layouts: updatedLayouts }
376
- }
401
+ responsiveLayouts: updatedResponsive
377
402
  }
378
403
  }
379
404
 
@@ -393,6 +418,43 @@ async function runDashboardAddPanel(dashboardId: string, options: AddPanelOption
393
418
  })
394
419
  }
395
420
 
421
+ function buildTimeRangeLike(value: string): Record<string, unknown> {
422
+ const relMatch = value.match(/^(-?\d+)\s*([smhdwMy])$/)
423
+ if (relMatch) {
424
+ return { relativeTime: { unit: relMatch[2], value: Number(relMatch[1]) } }
425
+ }
426
+ if (value === 'now' || value === '0') {
427
+ return { relativeTime: { unit: 'h', value: 0 } }
428
+ }
429
+ const ts = Date.parse(value)
430
+ if (!Number.isNaN(ts)) {
431
+ return { absoluteTime: String(ts) }
432
+ }
433
+ throw new CliError(
434
+ `Invalid time range value "${value}". Use a relative offset (e.g. -24h, -7d, -30m, now) or an ISO date string.`
435
+ )
436
+ }
437
+
438
+ function buildTimeRangeOverride(options: AddPanelOptions): Record<string, unknown> | undefined {
439
+ if (!options.timeRangeStart && !options.timeRangeEnd) {
440
+ return undefined
441
+ }
442
+ const timeRange: Record<string, unknown> = {}
443
+ if (options.timeRangeStart) {
444
+ timeRange.start = buildTimeRangeLike(options.timeRangeStart)
445
+ }
446
+ if (options.timeRangeEnd) {
447
+ timeRange.end = buildTimeRangeLike(options.timeRangeEnd)
448
+ } else {
449
+ // default end to "now" when start is provided
450
+ timeRange.end = { relativeTime: { unit: 'h', value: 0 } }
451
+ }
452
+ if (options.timeRangeStep) {
453
+ timeRange.step = options.timeRangeStep
454
+ }
455
+ return { enabled: true, timeRange }
456
+ }
457
+
396
458
  function buildPanelChart(chartType: string, options: AddPanelOptions) {
397
459
  if (options.sql) {
398
460
  const sqlSize = Number.parseInt(String(options.size ?? '100'), 10) || 100
@@ -496,7 +558,9 @@ function handleDashboardCommandError(error: unknown, command?: Command) {
496
558
  error.message.startsWith('Invalid aggregation') ||
497
559
  error.message.startsWith('Invalid metric aggregation') ||
498
560
  error.message.startsWith('Invalid filter') ||
499
- error.message.startsWith('Invalid metric selector'))
561
+ error.message.startsWith('Invalid metric selector') ||
562
+ error.message.startsWith('Invalid time range value') ||
563
+ error.message.startsWith('Unknown function "'))
500
564
  ) {
501
565
  console.error(error.message)
502
566
  if (command) {
@@ -16,6 +16,62 @@ import {
16
16
  } from '../api.js'
17
17
 
18
18
  const DEFAULT_RANGE_STEP = 3600
19
+
20
+ // Valid function names sourced from sentio/app/lib/functions.ts
21
+ const VALID_METRIC_FUNCTIONS: ReadonlySet<string> = new Set([
22
+ // Math
23
+ 'abs',
24
+ 'ceil',
25
+ 'floor',
26
+ 'round',
27
+ 'log2',
28
+ 'log10',
29
+ 'ln',
30
+ // Rollup
31
+ 'rollup_avg',
32
+ 'rollup_count',
33
+ 'rollup_last',
34
+ 'rollup_max',
35
+ 'rollup_min',
36
+ 'rollup_sum',
37
+ 'rollup_delta',
38
+ // Aggregate Over Time
39
+ 'avg_over_time',
40
+ 'count_over_time',
41
+ 'last_over_time',
42
+ 'max_over_time',
43
+ 'min_over_time',
44
+ 'sum_over_time',
45
+ 'delta_over_time',
46
+ // Rate
47
+ 'rate',
48
+ 'irate',
49
+ 'delta',
50
+ 'moving_delta',
51
+ // Rank
52
+ 'topk',
53
+ 'bottomk',
54
+ // Time
55
+ 'timestamp',
56
+ 'day_of_year',
57
+ 'day_of_month',
58
+ 'day_of_week',
59
+ 'year',
60
+ 'month',
61
+ 'hour',
62
+ 'minute',
63
+ // TimeShift
64
+ 'before',
65
+ 'after'
66
+ ])
67
+
68
+ const VALID_EVENT_FUNCTIONS: ReadonlySet<string> = new Set([
69
+ // Rank
70
+ 'topk',
71
+ 'bottomk',
72
+ // Delta
73
+ 'delta'
74
+ ])
19
75
  interface CommonDataOptions {
20
76
  host?: string
21
77
  apiKey?: string
@@ -113,7 +169,7 @@ export function buildEventsInsightQueryBody(
113
169
  aggregation: buildEventAggregation(options.aggr),
114
170
  selectorExpr: buildSelectorExpr(options.filter),
115
171
  groupBy: normalizeListOption(options.groupBy),
116
- functions: buildFunctions(options.func),
172
+ functions: buildFunctions(options.func, VALID_EVENT_FUNCTIONS),
117
173
  disabled: false
118
174
  }
119
175
  }
@@ -148,7 +204,7 @@ export function buildMetricsInsightQueryBody(
148
204
  query,
149
205
  labelSelector: buildMetricLabelSelector(options.filter),
150
206
  aggregate: buildMetricAggregate(options.aggr, options.groupBy),
151
- functions: buildFunctions(options.func),
207
+ functions: buildFunctions(options.func, VALID_METRIC_FUNCTIONS),
152
208
  disabled: false
153
209
  }
154
210
  }
@@ -584,7 +640,8 @@ function shouldShowHelpForDataCommandError(error: CliError) {
584
640
  error.message.startsWith('Provide --query, --result, --file, or --stdin.') ||
585
641
  error.message.startsWith('Use only one of --query or --result.') ||
586
642
  error.message.startsWith('--async only works with --query.') ||
587
- error.message.startsWith('Execution id is required.')
643
+ error.message.startsWith('Execution id is required.') ||
644
+ error.message.startsWith('Unknown function "')
588
645
  )
589
646
  }
590
647
 
@@ -730,8 +787,14 @@ function parseAnyValue(rawValue: string) {
730
787
  return { stringValue: unquotedValue }
731
788
  }
732
789
 
733
- function buildFunctions(functions?: string[]) {
734
- return normalizeListOption(functions).map(parseFunctionCall)
790
+ function buildFunctions(functions?: string[], validNames?: ReadonlySet<string>) {
791
+ return normalizeListOption(functions).map((f) => {
792
+ const parsed = parseFunctionCall(f)
793
+ if (validNames && !validNames.has(parsed.name)) {
794
+ throw new CliError(`Unknown function "${parsed.name}". Valid functions: ${[...validNames].sort().join(', ')}.`)
795
+ }
796
+ return parsed
797
+ })
735
798
  }
736
799
 
737
800
  function parseFunctionCall(value: string) {
@@ -27,7 +27,7 @@ interface ProcessorOptions {
27
27
  }
28
28
 
29
29
  interface ProcessorStatusOptions extends ProcessorOptions {
30
- version?: string
30
+ version?: string | number
31
31
  }
32
32
 
33
33
  interface ProcessorSourceOptions extends ProcessorOptions {
@@ -60,7 +60,11 @@ function createProcessorStatusCommand() {
60
60
  withSharedProjectOptions(withAuthOptions(new Command('status').description('Get processor status')))
61
61
  )
62
62
  .showHelpAfterError()
63
- .option('--version <selector>', 'Version selector: active, pending, or all')
63
+ .option(
64
+ '--version <selector>',
65
+ 'Version selector: active, pending, all, or a numeric version number',
66
+ parseVersionSelector
67
+ )
64
68
  .action(async (options, command) => {
65
69
  try {
66
70
  await runProcessorStatus(options)
@@ -176,22 +180,19 @@ async function runProcessorStatus(options: ProcessorStatusOptions) {
176
180
  const context = createApiContext(options)
177
181
  const project = await resolveProjectRef(options, context, { ownerSlug: true })
178
182
  const requestedVersion = normalizeVersionSelector(options.version)
183
+ const apiVersion = typeof requestedVersion === 'number' ? 'ALL' : (requestedVersion ?? 'ALL')
179
184
  const response = await ProcessorService.getProcessorStatusV2({
180
185
  path: {
181
186
  owner: project.owner,
182
187
  slug: project.slug
183
188
  },
184
189
  query: {
185
- version: requestedVersion ?? 'ALL'
190
+ version: apiVersion
186
191
  },
187
192
  headers: context.headers
188
193
  })
189
194
  const data = unwrapApiResult(response)
190
- if (!options.json) {
191
- printOutput(options, shapeProcessorStatusOutput(data, requestedVersion))
192
- return
193
- }
194
- printOutput(options, data)
195
+ printOutput(options, shapeProcessorStatusOutput(data, requestedVersion))
195
196
  }
196
197
 
197
198
  async function runProcessorSource(options: ProcessorSourceOptions) {
@@ -501,7 +502,7 @@ function handleProcessorCommandError(error: unknown, command?: Command) {
501
502
  error instanceof CliError &&
502
503
  (error.message.startsWith('Project is required.') ||
503
504
  error.message.startsWith('Invalid project ') ||
504
- error.message.startsWith('Invalid version selector ') ||
505
+ error.message.startsWith('Invalid version selector') ||
505
506
  error.message.startsWith('Source file not found:'))
506
507
  ) {
507
508
  console.error(error.message)
@@ -610,15 +611,26 @@ function formatOutput(data: unknown) {
610
611
  return JSON.stringify(data, null, 2)
611
612
  }
612
613
 
613
- function normalizeVersionSelector(value?: string): 'ACTIVE' | 'PENDING' | 'ALL' | undefined {
614
- if (!value) {
614
+ function normalizeVersionSelector(value?: string | number): 'ACTIVE' | 'PENDING' | 'ALL' | number | undefined {
615
+ if (value === undefined) {
615
616
  return undefined
616
617
  }
618
+ if (typeof value === 'number') {
619
+ return value
620
+ }
617
621
  const normalized = value.toUpperCase()
618
622
  if (normalized === 'ACTIVE' || normalized === 'PENDING' || normalized === 'ALL') {
619
623
  return normalized
620
624
  }
621
- throw new CliError(`Invalid version selector "${value}". Use active, pending, or all.`)
625
+ throw new CliError(`Invalid version selector "${value}". Use active, pending, all, or a version number.`)
626
+ }
627
+
628
+ function parseVersionSelector(value: string): string | number {
629
+ const num = Number.parseInt(value, 10)
630
+ if (!Number.isNaN(num) && String(num) === value) {
631
+ return num
632
+ }
633
+ return value
622
634
  }
623
635
 
624
636
  function parseInteger(value: string) {
@@ -639,7 +651,7 @@ function asNumber(value: unknown) {
639
651
 
640
652
  function shapeProcessorStatusOutput(
641
653
  data: { processors?: Array<Record<string, unknown>> },
642
- versionSelector?: 'ACTIVE' | 'PENDING' | 'ALL'
654
+ versionSelector?: 'ACTIVE' | 'PENDING' | 'ALL' | number
643
655
  ) {
644
656
  const processors = Array.isArray(data.processors) ? data.processors : []
645
657
  if (versionSelector === 'ALL') {
@@ -651,6 +663,12 @@ function shapeProcessorStatusOutput(
651
663
  processors: processors.filter((processor) => asString(processor.versionState) === versionSelector)
652
664
  }
653
665
  }
666
+ if (typeof versionSelector === 'number') {
667
+ return {
668
+ ...data,
669
+ processors: processors.filter((processor) => asNumber(processor.version) === versionSelector)
670
+ }
671
+ }
654
672
  return {
655
673
  ...data,
656
674
  processors: processors.filter((processor) => {
@@ -7,7 +7,7 @@ export function createVersionCommand() {
7
7
  })
8
8
  }
9
9
 
10
- function version() {
10
+ export function version() {
11
11
  console.log('CLI Version: ', getCliVersion())
12
12
  const sdkVersion = getSdkVersion()
13
13
  if (sdkVersion) {
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import { Command } from '@commander-js/extra-typings'
4
4
  import { createLoginCommand } from './commands/login.js'
5
5
  import { createCreateCommand } from './commands/create.js'
6
- import { createVersionCommand } from './commands/version.js'
6
+ import { createVersionCommand, version } from './commands/version.js'
7
7
  import { createTestCommand } from './commands/test.js'
8
8
  import { createAddCommand } from './commands/add.js'
9
9
  import { createCompileCommand } from './commands/compile.js'
@@ -34,6 +34,11 @@ if (process.argv.includes('--debug')) {
34
34
  enableApiDebug()
35
35
  }
36
36
 
37
+ if (process.argv.length === 3 && process.argv[2] === '--version') {
38
+ version()
39
+ process.exit(0)
40
+ }
41
+
37
42
  program.addCommand(createLoginCommand())
38
43
  program.addCommand(createCreateCommand())
39
44
  program.addCommand(createVersionCommand())