@owlmetry/cli 0.1.7 → 0.1.8

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adapted Hub LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs CHANGED
@@ -1169,7 +1169,7 @@ var require_command = __commonJS({
1169
1169
  var EventEmitter = require("events").EventEmitter;
1170
1170
  var childProcess = require("child_process");
1171
1171
  var path = require("path");
1172
- var fs = require("fs");
1172
+ var fs2 = require("fs");
1173
1173
  var process3 = require("process");
1174
1174
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1175
1175
  var { CommanderError: CommanderError2 } = require_error();
@@ -2150,7 +2150,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2150
2150
  * @param {string} subcommandName
2151
2151
  */
2152
2152
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2153
- if (fs.existsSync(executableFile)) return;
2153
+ if (fs2.existsSync(executableFile)) return;
2154
2154
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2155
2155
  const executableMissing = `'${executableFile}' does not exist
2156
2156
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2169,10 +2169,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2169
2169
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2170
2170
  function findFile(baseDir, baseName) {
2171
2171
  const localBin = path.resolve(baseDir, baseName);
2172
- if (fs.existsSync(localBin)) return localBin;
2172
+ if (fs2.existsSync(localBin)) return localBin;
2173
2173
  if (sourceExt.includes(path.extname(baseName))) return void 0;
2174
2174
  const foundExt = sourceExt.find(
2175
- (ext) => fs.existsSync(`${localBin}${ext}`)
2175
+ (ext) => fs2.existsSync(`${localBin}${ext}`)
2176
2176
  );
2177
2177
  if (foundExt) return `${localBin}${foundExt}`;
2178
2178
  return void 0;
@@ -2184,7 +2184,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2184
2184
  if (this._scriptPath) {
2185
2185
  let resolvedScriptPath;
2186
2186
  try {
2187
- resolvedScriptPath = fs.realpathSync(this._scriptPath);
2187
+ resolvedScriptPath = fs2.realpathSync(this._scriptPath);
2188
2188
  } catch {
2189
2189
  resolvedScriptPath = this._scriptPath;
2190
2190
  }
@@ -6360,11 +6360,11 @@ var METRIC_PHASES = ["start", "complete", "fail", "cancel", "record"];
6360
6360
  init_cjs_shims();
6361
6361
 
6362
6362
  // src/commands/apps.ts
6363
- var appsCommand = new Command("apps").description("List apps").option("--project <id>", "Filter by project ID").action(async (opts, cmd) => {
6363
+ var appsCommand = new Command("apps").description("List apps").enablePositionalOptions().option("--project-id <id>", "Filter by project ID").action(async (opts, cmd) => {
6364
6364
  const { client, globals } = createClient(cmd);
6365
6365
  let apps = await client.listApps();
6366
- if (opts.project) {
6367
- apps = apps.filter((a) => a.project_id === opts.project);
6366
+ if (opts.projectId) {
6367
+ apps = apps.filter((a) => a.project_id === opts.projectId);
6368
6368
  }
6369
6369
  output(globals.format, apps, () => formatAppsTable(apps));
6370
6370
  });
@@ -6454,6 +6454,7 @@ function formatMetricEventsLog(events, slug) {
6454
6454
 
6455
6455
  // src/utils/parse.ts
6456
6456
  init_cjs_shims();
6457
+ var import_node_fs2 = __toESM(require("fs"), 1);
6457
6458
  function parsePositiveInt(value, flagName) {
6458
6459
  const n = parseInt(value, 10);
6459
6460
  if (Number.isNaN(n) || n <= 0) {
@@ -6461,6 +6462,34 @@ function parsePositiveInt(value, flagName) {
6461
6462
  }
6462
6463
  return n;
6463
6464
  }
6465
+ function resolveJsonArray(inline, filePath, opts) {
6466
+ if (inline && filePath) {
6467
+ return "Error: --steps and --steps-file are mutually exclusive";
6468
+ }
6469
+ if (!inline && !filePath) {
6470
+ return opts.required ? "Error: either --steps or --steps-file is required" : "";
6471
+ }
6472
+ let json;
6473
+ if (filePath) {
6474
+ try {
6475
+ json = import_node_fs2.default.readFileSync(filePath, "utf-8");
6476
+ } catch (err) {
6477
+ return `Error reading --steps-file: ${err.message}`;
6478
+ }
6479
+ } else {
6480
+ json = inline;
6481
+ }
6482
+ let parsed;
6483
+ try {
6484
+ parsed = JSON.parse(json);
6485
+ } catch {
6486
+ return "Error: steps must be valid JSON";
6487
+ }
6488
+ if (!Array.isArray(parsed) || parsed.length === 0) {
6489
+ return "Error: steps must be a non-empty JSON array";
6490
+ }
6491
+ return parsed;
6492
+ }
6464
6493
 
6465
6494
  // src/utils/time.ts
6466
6495
  init_cjs_shims();
@@ -6500,9 +6529,9 @@ ${source_default.dim(`More results available. Use --cursor ${result.cursor}`)}`;
6500
6529
  }
6501
6530
 
6502
6531
  // src/commands/events.ts
6503
- var eventsCommand = new Command("events").description("Query events").option("--project <id>", "Filter by project ID").option("--app <id>", "Filter by app ID").option("--since <time>", "Start time (e.g. 1h, 30m, 7d, or ISO 8601)").option("--until <time>", "End time").addOption(
6532
+ var eventsCommand = new Command("events").description("Query events").option("--project-id <id>", "Filter by project ID").option("--app-id <id>", "Filter by app ID").option("--since <time>", "Start time (e.g. 1h, 30m, 7d, or ISO 8601)").option("--until <time>", "End time").addOption(
6504
6533
  new Option("--level <level>", "Filter by log level").choices(LOG_LEVELS)
6505
- ).option("--user <id>", "Filter by user ID").option("--session <id>", "Filter by session ID").option("--screen <name>", "Filter by screen name").addOption(
6534
+ ).option("--user-id <id>", "Filter by user ID").option("--session-id <id>", "Filter by session ID").option("--screen-name <name>", "Filter by screen name").addOption(
6506
6535
  new Option("--limit <n>", "Max events to return").argParser((v) => parsePositiveInt(v, "--limit"))
6507
6536
  ).option("--cursor <cursor>", "Pagination cursor").addOption(
6508
6537
  new Option("--data-mode <mode>", "Data mode: production, development, or all").choices(["production", "development", "all"]).default("production")
@@ -6511,14 +6540,14 @@ var eventsCommand = new Command("events").description("Query events").option("--
6511
6540
  const since = opts.since ? parseTimeInput(opts.since) : !opts.until ? parseTimeInput("24h") : void 0;
6512
6541
  const until = opts.until ? parseTimeInput(opts.until) : void 0;
6513
6542
  const result = await client.queryEvents({
6514
- project_id: opts.project,
6515
- app_id: opts.app,
6543
+ project_id: opts.projectId,
6544
+ app_id: opts.appId,
6516
6545
  since,
6517
6546
  until,
6518
6547
  level: opts.level,
6519
- user_id: opts.user,
6520
- session_id: opts.session,
6521
- screen_name: opts.screen,
6548
+ user_id: opts.userId,
6549
+ session_id: opts.sessionId,
6550
+ screen_name: opts.screenName,
6522
6551
  limit: opts.limit,
6523
6552
  cursor: opts.cursor,
6524
6553
  data_mode: opts.dataMode
@@ -6753,14 +6782,14 @@ function formatQueryResult(result) {
6753
6782
  }
6754
6783
  return lines.join("\n");
6755
6784
  }
6756
- var metricsCommand = new Command("metrics").description("List metric definitions").requiredOption("--project <id>", "Project ID").action(async (opts, cmd) => {
6785
+ var metricsCommand = new Command("metrics").description("List metric definitions").enablePositionalOptions().requiredOption("--project-id <id>", "Project ID").action(async (opts, cmd) => {
6757
6786
  const { client, globals } = createClient(cmd);
6758
- const metrics = await client.listMetrics(opts.project);
6787
+ const metrics = await client.listMetrics(opts.projectId);
6759
6788
  output(globals.format, metrics, () => formatMetricsTable(metrics));
6760
6789
  });
6761
- metricsCommand.command("events <slug>").description("Query raw metric events for a metric").requiredOption("--project <id>", "Project ID").addOption(
6790
+ metricsCommand.command("events <slug>").description("Query raw metric events for a metric").requiredOption("--project-id <id>", "Project ID").addOption(
6762
6791
  new Option("--phase <phase>", "Filter by phase").choices(METRIC_PHASES)
6763
- ).option("--tracking-id <id>", "Filter by tracking ID").option("--user <id>", "Filter by user ID").option("--since <time>", "Start time (e.g. 1h, 30m, 7d, or ISO 8601)").option("--until <time>", "End time").addOption(
6792
+ ).option("--tracking-id <id>", "Filter by tracking ID").option("--user-id <id>", "Filter by user ID").option("--since <time>", "Start time (e.g. 1h, 30m, 7d, or ISO 8601)").option("--until <time>", "End time").addOption(
6764
6793
  new Option("--limit <n>", "Max events to return").argParser((v) => parsePositiveInt(v, "--limit"))
6765
6794
  ).option("--cursor <cursor>", "Pagination cursor").option("--environment <env>", "Filter by environment (ios, ipados, macos, android, web, backend)").addOption(
6766
6795
  new Option("--data-mode <mode>", "Data mode: production, development, or all").choices(["production", "development", "all"]).default("production")
@@ -6768,10 +6797,10 @@ metricsCommand.command("events <slug>").description("Query raw metric events for
6768
6797
  const { client, globals } = createClient(cmd);
6769
6798
  const since = opts.since ? parseTimeInput(opts.since) : !opts.until ? parseTimeInput("24h") : void 0;
6770
6799
  const until = opts.until ? parseTimeInput(opts.until) : void 0;
6771
- const result = await client.queryMetricEvents(slug, opts.project, {
6800
+ const result = await client.queryMetricEvents(slug, opts.projectId, {
6772
6801
  phase: opts.phase,
6773
6802
  tracking_id: opts.trackingId,
6774
- user_id: opts.user,
6803
+ user_id: opts.userId,
6775
6804
  environment: opts.environment,
6776
6805
  since,
6777
6806
  until,
@@ -6787,12 +6816,12 @@ metricsCommand.command("events <slug>").description("Query raw metric events for
6787
6816
  () => formatMetricEventsLog(result.events, slug) + hint
6788
6817
  );
6789
6818
  });
6790
- metricsCommand.command("view <slug>").description("View metric definition details").requiredOption("--project <id>", "Project ID").action(async (slug, opts, cmd) => {
6819
+ metricsCommand.command("view <slug>").description("View metric definition details").requiredOption("--project-id <id>", "Project ID").action(async (slug, opts, cmd) => {
6791
6820
  const { client, globals } = createClient(cmd);
6792
- const metric = await client.getMetric(slug, opts.project);
6821
+ const metric = await client.getMetric(slug, opts.projectId);
6793
6822
  output(globals.format, metric, () => formatMetricDetail(metric));
6794
6823
  });
6795
- metricsCommand.command("create").description("Create a new metric definition").requiredOption("--project <id>", "Project ID").requiredOption("--name <name>", "Metric name").requiredOption("--slug <slug>", "Metric slug").option("--description <desc>", "Description").option("--docs <markdown>", "Documentation (markdown)").option("--lifecycle", "Mark as lifecycle metric (has start/complete/fail phases)").action(async (opts, cmd) => {
6824
+ metricsCommand.command("create").description("Create a new metric definition").requiredOption("--project-id <id>", "Project ID").requiredOption("--name <name>", "Metric name").requiredOption("--slug <slug>", "Metric slug").option("--description <desc>", "Description").option("--docs <markdown>", "Documentation (markdown)").option("--lifecycle", "Mark as lifecycle metric (has start/complete/fail phases)").action(async (opts, cmd) => {
6796
6825
  const slugError = validateMetricSlug(opts.slug);
6797
6826
  if (slugError) {
6798
6827
  console.error(source_default.red(`Error: ${slugError}`));
@@ -6800,7 +6829,7 @@ metricsCommand.command("create").description("Create a new metric definition").r
6800
6829
  return;
6801
6830
  }
6802
6831
  const { client, globals } = createClient(cmd);
6803
- const metric = await client.createMetric(opts.project, {
6832
+ const metric = await client.createMetric(opts.projectId, {
6804
6833
  name: opts.name,
6805
6834
  slug: opts.slug,
6806
6835
  description: opts.description,
@@ -6809,36 +6838,36 @@ metricsCommand.command("create").description("Create a new metric definition").r
6809
6838
  });
6810
6839
  output(globals.format, metric, () => formatMetricDetail(metric));
6811
6840
  });
6812
- metricsCommand.command("query <slug>").description("Query metric aggregation").requiredOption("--project <id>", "Project ID").option("--since <date>", "Start date (ISO)").option("--until <date>", "End date (ISO)").option("--app <id>", "Filter by app ID").option("--app-version <version>", "Filter by app version").option("--device-model <model>", "Filter by device model").option("--os-version <version>", "Filter by OS version").option("--user <id>", "Filter by user ID").option("--environment <env>", "Filter by environment (ios, ipados, macos, android, web, backend)").option("--group-by <field>", "Group by: app_id, app_version, device_model, os_version, environment, time:hour, time:day, time:week").addOption(
6841
+ metricsCommand.command("query <slug>").description("Query metric aggregation").requiredOption("--project-id <id>", "Project ID").option("--since <date>", "Start date (ISO)").option("--until <date>", "End date (ISO)").option("--app-id <id>", "Filter by app ID").option("--app-version <version>", "Filter by app version").option("--device-model <model>", "Filter by device model").option("--os-version <version>", "Filter by OS version").option("--user-id <id>", "Filter by user ID").option("--environment <env>", "Filter by environment (ios, ipados, macos, android, web, backend)").option("--group-by <field>", "Group by: app_id, app_version, device_model, os_version, environment, time:hour, time:day, time:week").addOption(
6813
6842
  new Option("--data-mode <mode>", "Data mode: production, development, or all").choices(["production", "development", "all"]).default("production")
6814
6843
  ).action(async (slug, opts, cmd) => {
6815
6844
  const { client, globals } = createClient(cmd);
6816
- const result = await client.queryMetric(slug, opts.project, {
6845
+ const result = await client.queryMetric(slug, opts.projectId, {
6817
6846
  since: opts.since,
6818
6847
  until: opts.until,
6819
- app_id: opts.app,
6848
+ app_id: opts.appId,
6820
6849
  app_version: opts.appVersion,
6821
6850
  device_model: opts.deviceModel,
6822
6851
  os_version: opts.osVersion,
6823
- user_id: opts.user,
6852
+ user_id: opts.userId,
6824
6853
  environment: opts.environment,
6825
6854
  data_mode: opts.dataMode,
6826
6855
  group_by: opts.groupBy
6827
6856
  });
6828
6857
  output(globals.format, result, () => formatQueryResult(result));
6829
6858
  });
6830
- metricsCommand.command("update <slug>").description("Update a metric definition").requiredOption("--project <id>", "Project ID").option("--name <name>", "New name").option("--description <desc>", "New description").option("--status <status>", "active or paused").action(async (slug, opts, cmd) => {
6859
+ metricsCommand.command("update <slug>").description("Update a metric definition").requiredOption("--project-id <id>", "Project ID").option("--name <name>", "New name").option("--description <desc>", "New description").option("--status <status>", "active or paused").action(async (slug, opts, cmd) => {
6831
6860
  const { client, globals } = createClient(cmd);
6832
- const metric = await client.updateMetric(slug, opts.project, {
6861
+ const metric = await client.updateMetric(slug, opts.projectId, {
6833
6862
  name: opts.name,
6834
6863
  description: opts.description,
6835
6864
  status: opts.status
6836
6865
  });
6837
6866
  output(globals.format, metric, () => formatMetricDetail(metric));
6838
6867
  });
6839
- metricsCommand.command("delete <slug>").description("Delete a metric definition").requiredOption("--project <id>", "Project ID").action(async (slug, opts, cmd) => {
6868
+ metricsCommand.command("delete <slug>").description("Delete a metric definition").requiredOption("--project-id <id>", "Project ID").action(async (slug, opts, cmd) => {
6840
6869
  const { client, globals } = createClient(cmd);
6841
- await client.deleteMetric(slug, opts.project);
6870
+ await client.deleteMetric(slug, opts.projectId);
6842
6871
  console.log(source_default.green(`Metric "${slug}" deleted.`));
6843
6872
  });
6844
6873
 
@@ -6924,82 +6953,65 @@ function formatQueryResult2(result) {
6924
6953
  }
6925
6954
  return lines.join("\n");
6926
6955
  }
6927
- var funnelsCommand = new Command("funnels").description("List funnel definitions").option("--project <id>", "Project ID").action(async (opts, cmd) => {
6928
- if (!opts.project) {
6929
- console.error(source_default.red("Error: --project is required"));
6930
- process.exitCode = 1;
6931
- return;
6932
- }
6956
+ var funnelsCommand = new Command("funnels").description("List funnel definitions").enablePositionalOptions().requiredOption("--project-id <id>", "Project ID").action(async (opts, cmd) => {
6933
6957
  const { client, globals } = createClient(cmd);
6934
- const result = await client.listFunnels(opts.project);
6958
+ const result = await client.listFunnels(opts.projectId);
6935
6959
  output(globals.format, result.funnels, () => formatFunnelsTable(result.funnels));
6936
6960
  });
6937
- funnelsCommand.command("view <slug>").description("View funnel definition details").requiredOption("--project <id>", "Project ID").action(async (slug, opts, cmd) => {
6961
+ funnelsCommand.command("view <slug>").description("View funnel definition details").requiredOption("--project-id <id>", "Project ID").action(async (slug, opts, cmd) => {
6938
6962
  const { client, globals } = createClient(cmd);
6939
- const funnel = await client.getFunnel(slug, opts.project);
6963
+ const funnel = await client.getFunnel(slug, opts.projectId);
6940
6964
  output(globals.format, funnel, () => formatFunnelDetail(funnel));
6941
6965
  });
6942
- funnelsCommand.command("create").description("Create a new funnel definition").requiredOption("--project <id>", "Project ID").requiredOption("--name <name>", "Funnel name").requiredOption("--slug <slug>", "Funnel slug").option("--description <desc>", "Description").requiredOption("--steps <json>", "Steps as JSON array").action(async (opts, cmd) => {
6966
+ funnelsCommand.command("create").description("Create a new funnel definition").requiredOption("--project-id <id>", "Project ID").requiredOption("--name <name>", "Funnel name").requiredOption("--slug <slug>", "Funnel slug").option("--description <desc>", "Description").option("--steps <json>", "Steps as JSON array").option("--steps-file <path>", "Read steps from a JSON file").action(async (opts, cmd) => {
6943
6967
  const slugError = validateFunnelSlug(opts.slug);
6944
6968
  if (slugError) {
6945
6969
  console.error(source_default.red(`Error: ${slugError}`));
6946
6970
  process.exitCode = 1;
6947
6971
  return;
6948
6972
  }
6949
- let steps;
6950
- try {
6951
- steps = JSON.parse(opts.steps);
6952
- } catch {
6953
- console.error(source_default.red("Error: --steps must be valid JSON"));
6954
- process.exitCode = 1;
6955
- return;
6956
- }
6957
- if (!Array.isArray(steps) || steps.length === 0) {
6958
- console.error(source_default.red("Error: --steps must be a non-empty JSON array"));
6973
+ const stepsResult = resolveJsonArray(opts.steps, opts.stepsFile, { required: true });
6974
+ if (typeof stepsResult === "string") {
6975
+ console.error(source_default.red(stepsResult));
6959
6976
  process.exitCode = 1;
6960
6977
  return;
6961
6978
  }
6962
6979
  const { client, globals } = createClient(cmd);
6963
- const funnel = await client.createFunnel(opts.project, {
6980
+ const funnel = await client.createFunnel(opts.projectId, {
6964
6981
  name: opts.name,
6965
6982
  slug: opts.slug,
6966
6983
  description: opts.description,
6967
- steps
6984
+ steps: stepsResult
6968
6985
  });
6969
6986
  output(globals.format, funnel, () => formatFunnelDetail(funnel));
6970
6987
  });
6971
- funnelsCommand.command("update <slug>").description("Update a funnel definition").requiredOption("--project <id>", "Project ID").option("--name <name>", "New name").option("--description <desc>", "New description").option("--steps <json>", "New steps as JSON array").action(async (slug, opts, cmd) => {
6988
+ funnelsCommand.command("update <slug>").description("Update a funnel definition").requiredOption("--project-id <id>", "Project ID").option("--name <name>", "New name").option("--description <desc>", "New description").option("--steps <json>", "New steps as JSON array").option("--steps-file <path>", "Read steps from a JSON file").action(async (slug, opts, cmd) => {
6972
6989
  const body = {};
6973
6990
  if (opts.name !== void 0) body.name = opts.name;
6974
6991
  if (opts.description !== void 0) body.description = opts.description;
6975
- if (opts.steps !== void 0) {
6976
- try {
6977
- body.steps = JSON.parse(opts.steps);
6978
- } catch {
6979
- console.error(source_default.red("Error: --steps must be valid JSON"));
6980
- process.exitCode = 1;
6981
- return;
6982
- }
6983
- if (!Array.isArray(body.steps) || body.steps.length === 0) {
6984
- console.error(source_default.red("Error: --steps must be a non-empty JSON array"));
6992
+ if (opts.steps || opts.stepsFile) {
6993
+ const stepsResult = resolveJsonArray(opts.steps, opts.stepsFile, { required: false });
6994
+ if (typeof stepsResult === "string") {
6995
+ console.error(source_default.red(stepsResult));
6985
6996
  process.exitCode = 1;
6986
6997
  return;
6987
6998
  }
6999
+ body.steps = stepsResult;
6988
7000
  }
6989
7001
  const { client, globals } = createClient(cmd);
6990
- const funnel = await client.updateFunnel(slug, opts.project, body);
7002
+ const funnel = await client.updateFunnel(slug, opts.projectId, body);
6991
7003
  output(globals.format, funnel, () => formatFunnelDetail(funnel));
6992
7004
  });
6993
- funnelsCommand.command("delete <slug>").description("Delete a funnel definition").requiredOption("--project <id>", "Project ID").action(async (slug, opts, cmd) => {
7005
+ funnelsCommand.command("delete <slug>").description("Delete a funnel definition").requiredOption("--project-id <id>", "Project ID").action(async (slug, opts, cmd) => {
6994
7006
  const { client, globals } = createClient(cmd);
6995
- await client.deleteFunnel(slug, opts.project);
7007
+ await client.deleteFunnel(slug, opts.projectId);
6996
7008
  console.log(source_default.green(`Funnel "${slug}" deleted.`));
6997
7009
  });
6998
- funnelsCommand.command("query <slug>").description("Query funnel analytics").requiredOption("--project <id>", "Project ID").option("--since <date>", "Start date (ISO)").option("--until <date>", "End date (ISO)").option("--open", "Make this an open funnel. In an open funnel, users don't have to complete a previous step in order to be included in a subsequent step.").option("--app-version <version>", "Filter by app version").option("--environment <env>", "Filter by environment (ios, ipados, macos, android, web, backend)").option("--experiment <name:variant>", "Filter by experiment (format: name:variant)").option("--group-by <field>", "Group by: environment, app_version, or experiment:<name>").addOption(
7010
+ funnelsCommand.command("query <slug>").description("Query funnel analytics").requiredOption("--project-id <id>", "Project ID").option("--since <date>", "Start date (ISO)").option("--until <date>", "End date (ISO)").option("--open", "Make this an open funnel. In an open funnel, users don't have to complete a previous step in order to be included in a subsequent step.").option("--app-version <version>", "Filter by app version").option("--environment <env>", "Filter by environment (ios, ipados, macos, android, web, backend)").option("--experiment <name:variant>", "Filter by experiment (format: name:variant)").option("--group-by <field>", "Group by: environment, app_version, or experiment:<name>").addOption(
6999
7011
  new Option("--data-mode <mode>", "Data mode: production, development, or all").choices(["production", "development", "all"]).default("production")
7000
7012
  ).action(async (slug, opts, cmd) => {
7001
7013
  const { client, globals } = createClient(cmd);
7002
- const result = await client.queryFunnel(slug, opts.project, {
7014
+ const result = await client.queryFunnel(slug, opts.projectId, {
7003
7015
  since: opts.since,
7004
7016
  until: opts.until,
7005
7017
  mode: opts.open ? "open" : "closed",
@@ -7015,7 +7027,7 @@ funnelsCommand.command("query <slug>").description("Query funnel analytics").req
7015
7027
  // src/commands/audit-logs.ts
7016
7028
  init_cjs_shims();
7017
7029
  var auditLogCommand = new Command("audit-log").description("View audit logs");
7018
- auditLogCommand.command("list").description("List audit log entries").requiredOption("--team <id>", "Team ID").option("--resource-type <type>", "Filter by resource type").option("--resource-id <id>", "Filter by resource ID").option("--actor <id>", "Filter by actor ID").addOption(
7030
+ auditLogCommand.command("list").description("List audit log entries").requiredOption("--team-id <id>", "Team ID").option("--resource-type <type>", "Filter by resource type").option("--resource-id <id>", "Filter by resource ID").option("--actor-id <id>", "Filter by actor ID").addOption(
7019
7031
  new Option("--action <action>", "Filter by action").choices(["create", "update", "delete"])
7020
7032
  ).option("--since <time>", "Start time (e.g. 1h, 30m, 7d, or ISO 8601)").option("--until <time>", "End time").addOption(
7021
7033
  new Option("--limit <n>", "Max entries to return").argParser((v) => parsePositiveInt(v, "--limit"))
@@ -7023,10 +7035,10 @@ auditLogCommand.command("list").description("List audit log entries").requiredOp
7023
7035
  const { client, globals } = createClient(cmd);
7024
7036
  const since = opts.since ? parseTimeInput(opts.since) : void 0;
7025
7037
  const until = opts.until ? parseTimeInput(opts.until) : void 0;
7026
- const result = await client.queryAuditLogs(opts.team, {
7038
+ const result = await client.queryAuditLogs(opts.teamId, {
7027
7039
  resource_type: opts.resourceType,
7028
7040
  resource_id: opts.resourceId,
7029
- actor_id: opts.actor,
7041
+ actor_id: opts.actorId,
7030
7042
  action: opts.action,
7031
7043
  since,
7032
7044
  until,
@@ -7053,7 +7065,7 @@ auditLogCommand.command("list").description("List audit log entries").requiredOp
7053
7065
 
7054
7066
  // src/commands/skills.ts
7055
7067
  init_cjs_shims();
7056
- var import_node_fs2 = require("fs");
7068
+ var import_node_fs3 = require("fs");
7057
7069
  var import_node_path2 = require("path");
7058
7070
  var import_node_url = require("url");
7059
7071
  var SKILLS = [
@@ -7065,7 +7077,7 @@ var skillsCommand = new Command("skills").description("Show paths to AI skill fi
7065
7077
  const __filename2 = (0, import_node_url.fileURLToPath)(importMetaUrl);
7066
7078
  const __dirname = (0, import_node_path2.dirname)(__filename2);
7067
7079
  const skillsDir = (0, import_node_path2.resolve)(__dirname, "skills");
7068
- if (!(0, import_node_fs2.existsSync)(skillsDir)) {
7080
+ if (!(0, import_node_fs3.existsSync)(skillsDir)) {
7069
7081
  console.error(
7070
7082
  source_default.red("Skills directory not found. This may indicate a broken installation.")
7071
7083
  );
@@ -7076,7 +7088,7 @@ var skillsCommand = new Command("skills").description("Show paths to AI skill fi
7076
7088
  for (const skill of SKILLS) {
7077
7089
  const skillPath = (0, import_node_path2.join)(skillsDir, skill.dir, "SKILL.md");
7078
7090
  const label = skill.label.padEnd(maxLabelLen);
7079
- if ((0, import_node_fs2.existsSync)(skillPath)) {
7091
+ if ((0, import_node_fs3.existsSync)(skillPath)) {
7080
7092
  console.log(` ${source_default.cyan(label)} ${skillPath}`);
7081
7093
  } else {
7082
7094
  console.log(` ${source_default.cyan(label)} ${source_default.dim("(not found)")}`);
@@ -7183,7 +7195,7 @@ var switchCommand = new Command("switch").description("Switch active team profil
7183
7195
  });
7184
7196
 
7185
7197
  // src/index.ts
7186
- var program2 = new Command().name("owlmetry").version("0.1.7").description("OwlMetry CLI \u2014 query metrics and manage your apps from the terminal").addOption(
7198
+ var program2 = new Command().name("owlmetry").version("0.1.8").description("OwlMetry CLI \u2014 query metrics and manage your apps from the terminal").addOption(
7187
7199
  new Option("--format <format>", "Output format").choices(["table", "json", "log"]).default("table")
7188
7200
  ).option("--endpoint <url>", "OwlMetry API server URL").option("--api-key <key>", "API key").option("--ingest-endpoint <url>", "OwlMetry ingest endpoint URL (for SDKs; defaults to API endpoint for self-hosted)").option("--team <name-or-id>", "Use a specific team profile for this command");
7189
7201
  program2.addCommand(authCommand);
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: owlmetry-cli
3
- version: 0.1.7
3
+ version: 0.1.8
4
4
  description: >-
5
5
  Install the OwlMetry CLI, sign up, and manage projects, apps, metrics,
6
6
  funnels, and events. Use when adding OwlMetry to a project, querying
@@ -135,7 +135,7 @@ An app represents a single deployable target. The `client_key` returned on creat
135
135
 
136
136
  ```bash
137
137
  owlmetry apps --format json # List all
138
- owlmetry apps --project <id> --format json # List by project
138
+ owlmetry apps --project-id <id> --format json # List by project
139
139
  owlmetry apps view <id> --format json # View details
140
140
  owlmetry apps create --project-id <id> --name <name> --platform <platform> [--bundle-id <id>] --format json
141
141
  owlmetry apps update <id> --name <new-name> --format json
@@ -155,11 +155,11 @@ Metrics are project-scoped definitions that tell OwlMetry what structured data t
155
155
  The metric definition must exist on the server **before** the SDK emits events for that slug, otherwise the server will reject the events.
156
156
 
157
157
  ```bash
158
- owlmetry metrics --project <id> --format json # List all
159
- owlmetry metrics view <slug> --project <id> --format json # View details
160
- owlmetry metrics create --project <id> --name <name> --slug <slug> [--lifecycle] [--description <desc>] --format json
161
- owlmetry metrics update <slug> --project <id> [--name <name>] [--status active|paused] --format json
162
- owlmetry metrics delete <slug> --project <id>
158
+ owlmetry metrics --project-id <id> --format json # List all
159
+ owlmetry metrics view <slug> --project-id <id> --format json # View details
160
+ owlmetry metrics create --project-id <id> --name <name> --slug <slug> [--lifecycle] [--description <desc>] --format json
161
+ owlmetry metrics update <slug> --project-id <id> [--name <name>] [--status active|paused] --format json
162
+ owlmetry metrics delete <slug> --project-id <id>
163
163
  ```
164
164
 
165
165
  Slugs: lowercase letters, numbers, hyphens only (`/^[a-z0-9-]+$/`).
@@ -177,13 +177,34 @@ Funnels support two analysis modes:
177
177
  Maximum 20 steps per funnel.
178
178
 
179
179
  ```bash
180
- owlmetry funnels --project <id> --format json # List all
181
- owlmetry funnels view <slug> --project <id> --format json # View details
182
- owlmetry funnels create --project <id> --name <name> --slug <slug> --steps '<json>' [--description <desc>] --format json
183
- owlmetry funnels update <slug> --project <id> [--name <name>] [--steps '<json>'] --format json
184
- owlmetry funnels delete <slug> --project <id>
180
+ owlmetry funnels --project-id <id> --format json # List all
181
+ owlmetry funnels view <slug> --project-id <id> --format json # View details
182
+ owlmetry funnels delete <slug> --project-id <id>
185
183
  ```
186
184
 
185
+ **Creating funnels** — use `--steps-file` to avoid shell quoting issues with JSON:
186
+
187
+ ```bash
188
+ # 1. Write steps to a JSON file
189
+ cat > /tmp/funnel-steps.json << 'EOF'
190
+ [
191
+ {"name": "Step Name", "event_filter": {"message": "track:step-name"}},
192
+ {"name": "Next Step", "event_filter": {"message": "track:next-step"}}
193
+ ]
194
+ EOF
195
+
196
+ # 2. Create the funnel referencing the file
197
+ owlmetry funnels create --project-id <id> --name <name> --slug <slug> \
198
+ --steps-file /tmp/funnel-steps.json [--description <desc>] --format json
199
+ ```
200
+
201
+ **Updating funnel steps** — same pattern:
202
+ ```bash
203
+ owlmetry funnels update <slug> --project-id <id> --steps-file /tmp/updated-steps.json --format json
204
+ ```
205
+
206
+ Inline `--steps '<json>'` also works but is error-prone in shell environments due to JSON quoting. Prefer `--steps-file`.
207
+
187
208
  Steps JSON format: `[{"name":"Step Name","event_filter":{"message":"track:step-name"}}]`
188
209
 
189
210
  ## Querying
@@ -193,7 +214,7 @@ Steps JSON format: `[{"name":"Step Name","event_filter":{"message":"track:step-n
193
214
  Events are the raw log records emitted by SDKs — every `Owl.info()`, `Owl.error()`, `Owl.track()`, etc. Query events when debugging specific issues, investigating user behavior, or reviewing what happened in a time window.
194
215
 
195
216
  ```bash
196
- owlmetry events [--project <id>] [--app <id>] [--since <time>] [--until <time>] [--level info|debug|warn|error] [--user <id>] [--session <id>] [--screen <name>] [--limit <n>] [--cursor <cursor>] [--data-mode production|development|all] --format json
217
+ owlmetry events [--project-id <id>] [--app-id <id>] [--since <time>] [--until <time>] [--level info|debug|warn|error] [--user-id <id>] [--session-id <id>] [--screen-name <name>] [--limit <n>] [--cursor <cursor>] [--data-mode production|development|all] --format json
197
218
  owlmetry events view <id> --format json
198
219
  ```
199
220
 
@@ -225,8 +246,8 @@ There are two ways to look at metric data:
225
246
  - **`metrics query`** — aggregated statistics (count, avg/p50/p95/p99 duration, error rate), useful for spotting trends and regressions. Supports grouping by app, version, environment, device, or time bucket.
226
247
 
227
248
  ```bash
228
- owlmetry metrics events <slug> --project <id> [--phase start|complete|fail|cancel|record] [--tracking-id <id>] [--user <id>] [--since <time>] [--until <time>] [--environment <env>] [--data-mode <mode>] --format json
229
- owlmetry metrics query <slug> --project <id> [--since <date>] [--until <date>] [--app <id>] [--app-version <v>] [--environment <env>] [--user <id>] [--group-by app_id|app_version|device_model|os_version|environment|time:hour|time:day|time:week] [--data-mode <mode>] --format json
249
+ owlmetry metrics events <slug> --project-id <id> [--phase start|complete|fail|cancel|record] [--tracking-id <id>] [--user-id <id>] [--since <time>] [--until <time>] [--environment <env>] [--data-mode <mode>] --format json
250
+ owlmetry metrics query <slug> --project-id <id> [--since <date>] [--until <date>] [--app-id <id>] [--app-version <v>] [--environment <env>] [--user-id <id>] [--group-by app_id|app_version|device_model|os_version|environment|time:hour|time:day|time:week] [--data-mode <mode>] --format json
230
251
  ```
231
252
 
232
253
  ### Funnel Analytics
@@ -234,7 +255,7 @@ owlmetry metrics query <slug> --project <id> [--since <date>] [--until <date>] [
234
255
  Funnel queries return conversion rates and drop-off between steps. The output shows how many users entered each step and what percentage continued to the next. Use `--group-by` to segment results and compare conversion across environments, app versions, or A/B experiment variants.
235
256
 
236
257
  ```bash
237
- owlmetry funnels query <slug> --project <id> [--since <date>] [--until <date>] [--open] [--app-version <v>] [--environment <env>] [--experiment <name:variant>] [--group-by environment|app_version|experiment:<name>] [--data-mode <mode>] --format json
258
+ owlmetry funnels query <slug> --project-id <id> [--since <date>] [--until <date>] [--open] [--app-version <v>] [--environment <env>] [--experiment <name:variant>] [--group-by environment|app_version|experiment:<name>] [--data-mode <mode>] --format json
238
259
  ```
239
260
 
240
261
  `--open` = open funnel mode (steps evaluated independently, not sequentially).
@@ -244,7 +265,7 @@ owlmetry funnels query <slug> --project <id> [--since <date>] [--until <date>] [
244
265
  Audit logs record who performed what action on which resource — creating an app, revoking an API key, changing a team member's role, etc. Query them when investigating configuration changes or tracking administrative activity. Requires `audit_logs:read` permission on the agent key (included in default agent key permissions).
245
266
 
246
267
  ```bash
247
- owlmetry audit-log list --team <id> [--resource-type <type>] [--resource-id <id>] [--actor <id>] [--action create|update|delete] [--since <time>] [--until <time>] [--limit <n>] --format json
268
+ owlmetry audit-log list --team-id <id> [--resource-type <type>] [--resource-id <id>] [--actor-id <id>] [--action create|update|delete] [--since <time>] [--until <time>] [--limit <n>] --format json
248
269
  ```
249
270
 
250
271
  ## Key Notes
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: owlmetry-node
3
- version: 0.1.7
3
+ version: 0.1.8
4
4
  description: >-
5
5
  Integrate the OwlMetry Node.js SDK into a backend service for server-side
6
6
  analytics, event tracking, metrics, funnels, and A/B experiments. Use when
@@ -206,7 +206,7 @@ op.cancel({ reason: 'timeout' });
206
206
 
207
207
  `duration_ms` and `tracking_id` (UUID) are auto-added. Create the metric definition first:
208
208
  ```bash
209
- owlmetry metrics create --project <id> --name "Database Query" --slug database-query --lifecycle --format json
209
+ owlmetry metrics create --project-id <id> --name "Database Query" --slug database-query --lifecycle --format json
210
210
  ```
211
211
 
212
212
  ### Single-shot measurements
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: owlmetry-swift
3
- version: 0.1.7
3
+ version: 0.1.8
4
4
  description: >-
5
5
  Integrate the OwlMetry Swift SDK into an iOS or macOS app for analytics,
6
6
  event tracking, metrics, funnels, and A/B experiments. Use when
@@ -272,9 +272,18 @@ Owl.track("first-post")
272
272
 
273
273
  Each `track()` call emits an info-level event with message `"track:<stepName>"`. Define matching funnel definitions via `/owlmetry-cli`:
274
274
  ```bash
275
- owlmetry funnels create --project <id> --name "Onboarding" --slug onboarding \
276
- --steps '[{"name":"Welcome","event_filter":{"message":"track:welcome-screen"}},{"name":"Account","event_filter":{"message":"track:create-account"}},{"name":"Profile","event_filter":{"message":"track:complete-profile"}},{"name":"First Post","event_filter":{"message":"track:first-post"}}]' \
277
- --format json
275
+ # Write steps to a JSON file (avoids shell quoting issues)
276
+ cat > /tmp/funnel-steps.json << 'EOF'
277
+ [
278
+ {"name": "Welcome", "event_filter": {"message": "track:welcome-screen"}},
279
+ {"name": "Account", "event_filter": {"message": "track:create-account"}},
280
+ {"name": "Profile", "event_filter": {"message": "track:complete-profile"}},
281
+ {"name": "First Post", "event_filter": {"message": "track:first-post"}}
282
+ ]
283
+ EOF
284
+
285
+ owlmetry funnels create --project-id <id> --name "Onboarding" --slug onboarding \
286
+ --steps-file /tmp/funnel-steps.json --format json
278
287
  ```
279
288
 
280
289
  ## Structured Metrics
@@ -304,7 +313,7 @@ op.cancel(attributes: ["reason": "user_cancelled"])
304
313
 
305
314
  `duration_ms` and `tracking_id` (UUID) are auto-added. Create the metric definition first:
306
315
  ```bash
307
- owlmetry metrics create --project <id> --name "Photo Upload" --slug photo-upload --lifecycle --format json
316
+ owlmetry metrics create --project-id <id> --name "Photo Upload" --slug photo-upload --lifecycle --format json
308
317
  ```
309
318
 
310
319
  ### Single-shot measurements
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmetry/cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "OwlMetry CLI — manage projects, apps, metrics, funnels, and events from the terminal. Includes AI skill files for agent-assisted development.",
5
5
  "type": "module",
6
6
  "license": "MIT",