@owlmetry/cli 0.1.1 → 0.1.3

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/index.cjs CHANGED
@@ -5757,6 +5757,10 @@ var OwlMetryClient = class {
5757
5757
  }
5758
5758
  return await response.json();
5759
5759
  }
5760
+ // Auth
5761
+ async whoami() {
5762
+ return this.request("GET", "/v1/auth/whoami");
5763
+ }
5760
5764
  // Projects
5761
5765
  async listProjects() {
5762
5766
  const result = await this.request("GET", "/v1/projects");
@@ -5916,6 +5920,7 @@ var import_node_fs = require("fs");
5916
5920
  var import_node_path = require("path");
5917
5921
  var import_node_os2 = require("os");
5918
5922
  var DEFAULT_ENDPOINT = "https://api.owlmetry.com";
5923
+ var DEFAULT_INGEST_ENDPOINT = "https://ingest.owlmetry.com";
5919
5924
  var CONFIG_DIR = (0, import_node_path.join)((0, import_node_os2.homedir)(), ".owlmetry");
5920
5925
  var CONFIG_FILE = (0, import_node_path.join)(CONFIG_DIR, "config.json");
5921
5926
  function loadConfig() {
@@ -5930,12 +5935,59 @@ function saveConfig(config) {
5930
5935
  (0, import_node_fs.mkdirSync)(CONFIG_DIR, { recursive: true });
5931
5936
  (0, import_node_fs.writeFileSync)(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
5932
5937
  }
5938
+ function getActiveProfile(config, teamHint) {
5939
+ const entries = Object.entries(config.teams);
5940
+ if (entries.length === 0) {
5941
+ throw new Error("No team profiles configured. Run `owlmetry auth verify` to add one.");
5942
+ }
5943
+ if (!teamHint) {
5944
+ const profile = config.teams[config.active_team];
5945
+ if (!profile) {
5946
+ throw new Error(
5947
+ `Active team "${config.active_team}" not found in config. Run \`owlmetry auth verify\` to re-authenticate.`
5948
+ );
5949
+ }
5950
+ return { teamId: config.active_team, profile };
5951
+ }
5952
+ if (config.teams[teamHint]) {
5953
+ return { teamId: teamHint, profile: config.teams[teamHint] };
5954
+ }
5955
+ for (const [id, profile] of entries) {
5956
+ if (profile.team_slug === teamHint) {
5957
+ return { teamId: id, profile };
5958
+ }
5959
+ }
5960
+ const lower = teamHint.toLowerCase();
5961
+ for (const [id, profile] of entries) {
5962
+ if (profile.team_name.toLowerCase() === lower) {
5963
+ return { teamId: id, profile };
5964
+ }
5965
+ }
5966
+ const available = entries.map(([id, p]) => ` ${id} ${p.team_name} (${p.team_slug})`).join("\n");
5967
+ throw new Error(`No team matching "${teamHint}". Available teams:
5968
+ ${available}`);
5969
+ }
5970
+ function listProfiles(config) {
5971
+ return Object.entries(config.teams).map(([teamId, profile]) => ({
5972
+ teamId,
5973
+ profile,
5974
+ active: teamId === config.active_team
5975
+ }));
5976
+ }
5933
5977
  function resolveConfig(opts) {
5934
5978
  const envEndpoint = opts.endpoint ?? process.env.OWLMETRY_ENDPOINT;
5935
5979
  const envApiKey = opts.apiKey ?? process.env.OWLMETRY_API_KEY;
5936
5980
  const file = envEndpoint && envApiKey ? null : loadConfig();
5937
- const endpoint = envEndpoint ?? file?.endpoint;
5938
- const api_key = envApiKey ?? file?.api_key;
5981
+ let endpoint = envEndpoint;
5982
+ let api_key = envApiKey;
5983
+ let ingest_endpoint = file?.ingest_endpoint;
5984
+ if (file && !api_key) {
5985
+ const { profile } = getActiveProfile(file, opts.team);
5986
+ api_key = profile.api_key;
5987
+ }
5988
+ if (!endpoint) {
5989
+ endpoint = file?.endpoint;
5990
+ }
5939
5991
  if (!endpoint) {
5940
5992
  throw new Error(
5941
5993
  "Missing endpoint. Use --endpoint, OWLMETRY_ENDPOINT env var, or run `owlmetry auth login`."
@@ -5946,7 +5998,7 @@ function resolveConfig(opts) {
5946
5998
  "Missing API key. Use --api-key, OWLMETRY_API_KEY env var, or run `owlmetry auth login`."
5947
5999
  );
5948
6000
  }
5949
- return { endpoint, api_key };
6001
+ return { endpoint, api_key, ingest_endpoint };
5950
6002
  }
5951
6003
  function getGlobals(cmd) {
5952
6004
  return cmd.optsWithGlobals();
@@ -5979,16 +6031,42 @@ var setupCommand = new Command("setup").description("Configure CLI endpoint and
5979
6031
  endpoint: globals.endpoint,
5980
6032
  apiKey: globals.apiKey
5981
6033
  });
6034
+ let teamId = "_manual";
6035
+ let teamName = "Manual Setup";
6036
+ let teamSlug = "manual";
5982
6037
  try {
5983
- await client.listProjects();
6038
+ const whoami = await client.whoami();
6039
+ if (whoami.team) {
6040
+ teamId = whoami.team.id;
6041
+ teamName = whoami.team.name;
6042
+ teamSlug = whoami.team.slug;
6043
+ }
5984
6044
  } catch (err) {
5985
6045
  console.error(
5986
6046
  source_default.red(`Failed to connect: ${err instanceof Error ? err.message : String(err)}`)
5987
6047
  );
5988
6048
  process.exit(1);
5989
6049
  }
5990
- saveConfig({ endpoint: globals.endpoint, api_key: globals.apiKey });
5991
- console.log(source_default.green("Configuration saved to ~/.owlmetry/config.json"));
6050
+ const ingestEndpoint = globals.ingestEndpoint || globals.endpoint;
6051
+ const existing = loadConfig();
6052
+ const config = {
6053
+ endpoint: globals.endpoint,
6054
+ ingest_endpoint: ingestEndpoint,
6055
+ active_team: teamId,
6056
+ teams: {
6057
+ ...existing?.teams,
6058
+ [teamId]: {
6059
+ api_key: globals.apiKey,
6060
+ team_name: teamName,
6061
+ team_slug: teamSlug
6062
+ }
6063
+ }
6064
+ };
6065
+ saveConfig(config);
6066
+ console.log(source_default.green("\u2713 Configuration saved to ~/.owlmetry/config.json"));
6067
+ console.log(` Team: ${teamName}`);
6068
+ console.log(` API endpoint: ${globals.endpoint}`);
6069
+ console.log(` Ingest endpoint: ${ingestEndpoint}`);
5992
6070
  });
5993
6071
 
5994
6072
  // src/commands/projects.ts
@@ -6184,10 +6262,19 @@ projectsCommand.command("view <id>").description("View project details").action(
6184
6262
  const project = await client.getProject(id);
6185
6263
  output(globals.format, project, () => formatProjectDetail(project));
6186
6264
  });
6187
- projectsCommand.command("create").description("Create a new project").requiredOption("--team-id <id>", "Team ID").requiredOption("--name <name>", "Project name").requiredOption("--slug <slug>", "Project slug").action(async (opts, cmd) => {
6265
+ projectsCommand.command("create").description("Create a new project").option("--team-id <id>", "Team ID (defaults to active team)").requiredOption("--name <name>", "Project name").requiredOption("--slug <slug>", "Project slug").action(async (opts, cmd) => {
6188
6266
  const { client, globals } = createClient(cmd);
6267
+ let teamId = opts.teamId;
6268
+ if (!teamId) {
6269
+ const config = loadConfig();
6270
+ if (!config) {
6271
+ throw new Error("No team ID specified and no config found. Use --team-id or run `owlmetry auth verify` first.");
6272
+ }
6273
+ const resolved = getActiveProfile(config, globals.team);
6274
+ teamId = resolved.teamId;
6275
+ }
6189
6276
  const project = await client.createProject({
6190
- team_id: opts.teamId,
6277
+ team_id: teamId,
6191
6278
  name: opts.name,
6192
6279
  slug: opts.slug
6193
6280
  });
@@ -6494,6 +6581,12 @@ init_cjs_shims();
6494
6581
  function resolveEndpoint(globals) {
6495
6582
  return globals.endpoint || process.env.OWLMETRY_ENDPOINT || DEFAULT_ENDPOINT;
6496
6583
  }
6584
+ function resolveIngestEndpointForAuth(globals, endpoint) {
6585
+ const explicit = globals.ingestEndpoint ?? process.env.OWLMETRY_INGEST_ENDPOINT;
6586
+ if (explicit) return explicit;
6587
+ if (endpoint === DEFAULT_ENDPOINT) return DEFAULT_INGEST_ENDPOINT;
6588
+ return endpoint;
6589
+ }
6497
6590
  async function apiPost(endpoint, path, body) {
6498
6591
  const res = await fetch(`${endpoint.replace(/\/+$/, "")}${path}`, {
6499
6592
  method: "POST",
@@ -6552,19 +6645,38 @@ authCommand.command("verify").description("Verify code and get an agent API key"
6552
6645
  }
6553
6646
  process.exit(1);
6554
6647
  }
6555
- const { api_key, team, project, app, is_new_setup } = data;
6556
- if (!api_key) {
6557
- console.error(source_default.red("No API key returned"));
6648
+ const { api_key, team } = data;
6649
+ if (!api_key || !team) {
6650
+ console.error(source_default.red("No API key or team info returned"));
6558
6651
  process.exit(1);
6559
6652
  }
6560
- saveConfig({ endpoint, api_key });
6653
+ const ingestEndpoint = resolveIngestEndpointForAuth(globals, endpoint);
6654
+ const existing = loadConfig();
6655
+ const config = {
6656
+ endpoint,
6657
+ ingest_endpoint: ingestEndpoint,
6658
+ active_team: team.id,
6659
+ teams: {
6660
+ ...existing?.teams,
6661
+ [team.id]: {
6662
+ api_key,
6663
+ team_name: team.name,
6664
+ team_slug: team.slug
6665
+ }
6666
+ }
6667
+ };
6668
+ saveConfig(config);
6669
+ const profileCount = Object.keys(config.teams).length;
6561
6670
  if (format === "json") {
6562
- console.log(JSON.stringify({ api_key, endpoint, team, project, app, is_new_setup }, null, 2));
6671
+ console.log(JSON.stringify({ api_key, endpoint, ingest_endpoint: ingestEndpoint, team }, null, 2));
6563
6672
  } else {
6564
6673
  console.log(source_default.green("\u2713 Authenticated! Config saved to ~/.owlmetry/config.json"));
6565
- console.log(` Team: ${team?.name}`);
6566
- if (project) console.log(` Project: ${project.name}`);
6567
- if (app) console.log(` App: ${app.name} (${app.platform})`);
6674
+ console.log(` Team: ${team.name}`);
6675
+ console.log(` API endpoint: ${endpoint}`);
6676
+ console.log(` Ingest endpoint: ${ingestEndpoint}`);
6677
+ if (profileCount > 1) {
6678
+ console.log(` Profiles: ${profileCount} teams configured`);
6679
+ }
6568
6680
  }
6569
6681
  });
6570
6682
 
@@ -6976,10 +7088,104 @@ ${source_default.dim("Point your AI agent to these files to teach it how to use
6976
7088
  );
6977
7089
  });
6978
7090
 
7091
+ // src/commands/whoami.ts
7092
+ init_cjs_shims();
7093
+ var whoamiCommand = new Command("whoami").description("Show current authentication status and identity").action(async (_opts, cmd) => {
7094
+ const { client, globals } = createClient(cmd);
7095
+ const data = await client.whoami();
7096
+ if (globals.format === "json") {
7097
+ const config2 = loadConfig();
7098
+ const profiles = config2 ? listProfiles(config2) : [];
7099
+ console.log(JSON.stringify({
7100
+ ...data,
7101
+ configured_profiles: profiles.map((p) => ({
7102
+ team_id: p.teamId,
7103
+ team_name: p.profile.team_name,
7104
+ team_slug: p.profile.team_slug,
7105
+ active: p.active
7106
+ }))
7107
+ }, null, 2));
7108
+ return;
7109
+ }
7110
+ if (data.type === "api_key") {
7111
+ const team = data.team;
7112
+ const permissions = data.permissions;
7113
+ console.log(source_default.green("\u2713 Authenticated"));
7114
+ console.log(` Team: ${team?.name ?? "unknown"}`);
7115
+ console.log(` Key type: ${data.key_type}`);
7116
+ console.log(` Permissions: ${permissions.join(", ")}`);
7117
+ } else {
7118
+ const teams = data.teams;
7119
+ console.log(source_default.green("\u2713 Authenticated"));
7120
+ console.log(` Email: ${data.email}`);
7121
+ console.log(` Teams: ${teams.map((t) => `${t.name} (${t.role})`).join(", ")}`);
7122
+ }
7123
+ const config = loadConfig();
7124
+ if (config && Object.keys(config.teams).length > 0) {
7125
+ const profiles = listProfiles(config);
7126
+ console.log();
7127
+ console.log("Configured profiles:");
7128
+ for (const { teamId, profile, active } of profiles) {
7129
+ const marker = active ? source_default.green("\u25CF") : " ";
7130
+ const name = active ? source_default.bold(profile.team_name) : profile.team_name;
7131
+ console.log(` ${marker} ${name} (${profile.team_slug}) ${source_default.dim(teamId)}`);
7132
+ }
7133
+ }
7134
+ });
7135
+
7136
+ // src/commands/switch.ts
7137
+ init_cjs_shims();
7138
+ var switchCommand = new Command("switch").description("Switch active team profile or list all profiles").argument("[team]", "Team ID, slug, or name to switch to").action(async (teamArg, _opts, cmd) => {
7139
+ const globals = getGlobals(cmd);
7140
+ const config = loadConfig();
7141
+ if (!config || Object.keys(config.teams).length === 0) {
7142
+ if (globals.format === "json") {
7143
+ console.log(JSON.stringify({ error: "No team profiles configured" }));
7144
+ } else {
7145
+ console.error(source_default.red("No team profiles configured. Run `owlmetry auth verify` to add one."));
7146
+ }
7147
+ process.exit(1);
7148
+ }
7149
+ if (!teamArg) {
7150
+ const profiles = listProfiles(config);
7151
+ if (globals.format === "json") {
7152
+ console.log(JSON.stringify(profiles.map((p) => ({
7153
+ team_id: p.teamId,
7154
+ team_name: p.profile.team_name,
7155
+ team_slug: p.profile.team_slug,
7156
+ active: p.active
7157
+ })), null, 2));
7158
+ return;
7159
+ }
7160
+ for (const { teamId: teamId2, profile: profile2, active } of profiles) {
7161
+ const marker = active ? source_default.green("\u25CF") : " ";
7162
+ const name = active ? source_default.bold(profile2.team_name) : profile2.team_name;
7163
+ console.log(` ${marker} ${name} (${profile2.team_slug}) ${source_default.dim(teamId2)}`);
7164
+ }
7165
+ return;
7166
+ }
7167
+ const { teamId, profile } = getActiveProfile(config, teamArg);
7168
+ if (teamId === config.active_team) {
7169
+ if (globals.format === "json") {
7170
+ console.log(JSON.stringify({ team_id: teamId, team_name: profile.team_name, already_active: true }));
7171
+ } else {
7172
+ console.log(`Already on ${source_default.bold(profile.team_name)} (${profile.team_slug})`);
7173
+ }
7174
+ return;
7175
+ }
7176
+ config.active_team = teamId;
7177
+ saveConfig(config);
7178
+ if (globals.format === "json") {
7179
+ console.log(JSON.stringify({ team_id: teamId, team_name: profile.team_name, team_slug: profile.team_slug }));
7180
+ } else {
7181
+ console.log(source_default.green(`\u2713 Switched to ${source_default.bold(profile.team_name)} (${profile.team_slug})`));
7182
+ }
7183
+ });
7184
+
6979
7185
  // src/index.ts
6980
- var program2 = new Command().name("owlmetry").version("0.1.1").description("OwlMetry CLI \u2014 query metrics and manage your apps from the terminal").addOption(
7186
+ var program2 = new Command().name("owlmetry").version("0.1.3").description("OwlMetry CLI \u2014 query metrics and manage your apps from the terminal").addOption(
6981
7187
  new Option("--format <format>", "Output format").choices(["table", "json", "log"]).default("table")
6982
- ).option("--endpoint <url>", "OwlMetry server URL").option("--api-key <key>", "API key");
7188
+ ).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");
6983
7189
  program2.addCommand(authCommand);
6984
7190
  program2.addCommand(setupCommand);
6985
7191
  program2.addCommand(projectsCommand);
@@ -6991,6 +7197,8 @@ program2.addCommand(metricsCommand);
6991
7197
  program2.addCommand(funnelsCommand);
6992
7198
  program2.addCommand(auditLogCommand);
6993
7199
  program2.addCommand(skillsCommand);
7200
+ program2.addCommand(whoamiCommand);
7201
+ program2.addCommand(switchCommand);
6994
7202
  program2.parseAsync().catch((err) => {
6995
7203
  const format = program2.opts().format;
6996
7204
  const message = err instanceof Error ? err.message : String(err);
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: owlmetry-cli
3
- version: 0.1.0
3
+ version: 0.1.3
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
@@ -25,33 +25,82 @@ If everything is current or the remote is unreachable, continue silently.
25
25
 
26
26
  ## Setup
27
27
 
28
+ Follow these steps in order. Skip any step that's already done.
29
+
30
+ ### Step 1 — Install the CLI
31
+
28
32
  **Prerequisites:** Node.js 20+
29
33
 
30
- **Install:**
31
34
  ```bash
32
35
  npm install -g @owlmetry/cli
33
36
  ```
34
37
 
35
- **Sign up / log in:**
38
+ ### Step 2 Check authentication
39
+
36
40
  ```bash
37
- owlmetry auth send-code --email <user-email>
41
+ owlmetry whoami --format json
38
42
  ```
39
- Ask the user for the 6-digit verification code that arrives by email, then:
43
+
44
+ If this succeeds, authentication is already configured — skip to Step 3.
45
+
46
+ If it fails (missing config or invalid key), run the auth flow:
47
+
48
+ 1. Ask the user for their email address.
49
+ 2. Send a verification code:
50
+ ```bash
51
+ owlmetry auth send-code --email <user-email>
52
+ ```
53
+ 3. Ask the user for the 6-digit code from their email.
54
+ 4. Verify and save credentials:
55
+ ```bash
56
+ owlmetry auth verify --email <user-email> --code <code> --format json
57
+ ```
58
+ This creates the user account and team (if new), generates an agent API key (`owl_agent_...`), and saves config to `~/.owlmetry/config.json`. Default API endpoint: `https://api.owlmetry.com`. Default ingest endpoint: `https://ingest.owlmetry.com`.
59
+
60
+ **Multi-team setup**: If the user belongs to multiple teams, run `auth verify` once per team (using `--team-id` to select each team). Each team's key is stored as a separate profile. The last verified team becomes the active profile.
61
+
62
+ **Switching teams**: Use `owlmetry switch` to list profiles or `owlmetry switch <name-or-slug>` to switch the active team. Use `--team <name-or-slug>` on any command to use a different team's key for a single command without switching.
63
+
64
+ **Manual setup** (if the user already has an API key):
40
65
  ```bash
41
- owlmetry auth verify --email <user-email> --code <code> --format json
66
+ owlmetry setup --endpoint <url> --api-key <key> [--ingest-endpoint <url>]
42
67
  ```
43
68
 
44
- - New users are auto-provisioned with a team, project, and backend app.
45
- - The response includes an agent API key (`owl_agent_...`), team ID, project ID, and app details.
46
- - Config is saved to `~/.owlmetry/config.json`.
47
- - Default endpoint: `https://api.owlmetry.com`
69
+ ### Step 3 Create project and app
48
70
 
49
- If the user already has a config file (`~/.owlmetry/config.json`), they're already set up skip auth.
71
+ After authentication, set up the resources the SDK needs. Check if any projects already exist:
50
72
 
51
- **Manual setup** (if the user already has an API key):
52
73
  ```bash
53
- owlmetry setup --endpoint <url> --api-key <key>
74
+ owlmetry projects --format json
75
+ ```
76
+
77
+ If the user already has a project and app, skip to SDK integration.
78
+
79
+ **Create a project** — infer a good name from the user's repository or directory name:
80
+ ```bash
81
+ owlmetry projects create --name "<ProjectName>" --slug "<project-slug>" --format json
54
82
  ```
83
+ Save the returned `id` — you need it for the next command.
84
+
85
+ **Create an app** — choose the platform based on the project type:
86
+ - Swift/SwiftUI → `apple`
87
+ - Kotlin/Android → `android`
88
+ - Web frontend → `web`
89
+ - Node.js/backend → `backend`
90
+
91
+ ```bash
92
+ owlmetry apps create --project-id <project-id> --name "<AppName>" --platform <platform> [--bundle-id <bundle-id>] --format json
93
+ ```
94
+ - `--bundle-id` is required for apple/android/web (e.g., `com.example.myapp`), omitted for backend.
95
+ - The response includes a `client_key` (`owl_client_...`) — this is the SDK API key for event ingestion. Save it.
96
+
97
+ ### Step 4 — Integrate the SDK
98
+
99
+ Use the `client_key` from Step 3 to configure the appropriate SDK:
100
+ - **Node.js projects** → follow the `owlmetry-node` skill file
101
+ - **Swift/iOS projects** → follow the `owlmetry-swift` skill file
102
+
103
+ Pass the **ingest endpoint** and client key to the SDK's `configure()` call. Read `~/.owlmetry/config.json` for the `ingest_endpoint` value (set during auth). For the hosted platform it's `https://ingest.owlmetry.com`. For self-hosted it defaults to the API endpoint.
55
104
 
56
105
  ## Resource Hierarchy
57
106
 
@@ -72,7 +121,7 @@ Projects group apps by product and scope metrics and funnels. Create one project
72
121
  ```bash
73
122
  owlmetry projects --format json # List all
74
123
  owlmetry projects view <id> --format json # View details + apps
75
- owlmetry projects create --team-id <id> --name <name> --slug <slug> --format json
124
+ owlmetry projects create --name <name> --slug <slug> [--team-id <id>] --format json
76
125
  owlmetry projects update <id> --name <new-name> --format json
77
126
  ```
78
127
 
@@ -197,7 +246,10 @@ owlmetry audit-log list --team <id> [--resource-type <type>] [--resource-id <id>
197
246
  ## Key Notes
198
247
 
199
248
  - Always use `--format json` when parsing output programmatically.
200
- - **Global flags** available on all commands: `--endpoint <url>`, `--api-key <key>`, `--format <format>`
249
+ - **Global flags** available on all commands: `--endpoint <url>`, `--api-key <key>`, `--ingest-endpoint <url>`, `--team <name-or-id>`, `--format <format>`
250
+ - **Team profiles**: Config stores multiple team profiles keyed by team ID. `owlmetry switch` lists/switches the active profile. `--team` flag on any command uses a specific team's key without switching the active profile.
251
+ - **Two endpoints**: `endpoint` is the API server (for CLI/agent queries). `ingest_endpoint` is where SDKs send events. Both are saved in `~/.owlmetry/config.json`. For the hosted platform: `api.owlmetry.com` and `ingest.owlmetry.com`. For self-hosted: ingest defaults to the same as the API endpoint.
252
+ - **Config format**: `~/.owlmetry/config.json` stores `endpoint`, `ingest_endpoint`, `active_team` (team ID), and `teams` (a map of team ID → `{ api_key, team_name, team_slug }`).
201
253
  - **Agent keys** (`owl_agent_...`) are for CLI queries. **Client keys** (`owl_client_...`) are for SDK event ingestion.
202
254
  - **Time format:** relative (`1h`, `30m`, `7d`) or ISO 8601 (`2026-03-20T00:00:00Z`). Relative times go backwards from now — `1h` means "the last hour", `7d` means "the last 7 days".
203
255
  - **Data mode:** `production` (default), `debug`, or `all` — filters events by their debug flag. SDKs auto-detect debug mode (DEBUG builds on iOS, `NODE_ENV !== "production"` on Node). Use `debug` mode during development to see test events; use `production` (the default) for real analytics.
@@ -207,10 +259,20 @@ owlmetry audit-log list --team <id> [--resource-type <type>] [--resource-id <id>
207
259
 
208
260
  A typical end-to-end flow for adding OwlMetry to a new project:
209
261
 
210
- 1. **Sign up**: `owlmetry auth send-code` → verify code → auto-provisioned with team, project, and backend app
211
- 2. **Create apps**: `owlmetry apps create --platform apple --bundle-id com.example.myapp` (and/or android, web, backend)
212
- 3. **Note the client key**: from the app creation response pass this to the SDK
213
- 4. **Instrument the app**: Add the Swift or Node SDK, configure with the endpoint and client key, add logging calls
214
- 5. **Define metrics**: `owlmetry metrics create --slug photo-upload --lifecycle` for operations you want to track with duration
215
- 6. **Define funnels**: `owlmetry funnels create --slug onboarding --steps '[...]'` for multi-step flows you want to measure conversion on
216
- 7. **Query data**: Use `owlmetry events`, `owlmetry metrics query`, and `owlmetry funnels query` to analyze behavior
262
+ 1. **Sign up** (CLI): `owlmetry auth send-code` → verify code → team created, config saved
263
+ 2. **Create a project** (CLI): `owlmetry projects create --name "..." --slug "..." --format json`
264
+ 3. **Create apps** (CLI): `owlmetry apps create --platform apple --bundle-id com.example.myapp` (and/or android, web, backend)
265
+ 4. **Note the client key**: from the app creation response pass this to the SDK
266
+ 5. **Integrate the SDK** (switch to `/owlmetry-swift` or `/owlmetry-node` skill): Add the SDK dependency, configure with `ingest_endpoint` and client key, verify the project builds
267
+
268
+ After setup, the SDK skill will prompt the user to choose which instrumentation to start with. The three areas and what's needed:
269
+
270
+ | Area | CLI setup needed? | SDK skill |
271
+ |------|-------------------|-----------|
272
+ | **Event & error logging** | No — just add SDK calls | `Owl.info()`, `Owl.error()`, `Owl.warn()`, `Owl.debug()` |
273
+ | **Structured metrics** | Yes — `owlmetry metrics create` for each metric slug | `Owl.startOperation()`, `Owl.recordMetric()` |
274
+ | **Funnel tracking** | Yes — `owlmetry funnels create` with steps JSON | `Owl.track("step-name")` |
275
+
276
+ For metrics and funnels, the CLI defines **what** to track (server-side definitions), and the SDK implements **where** to track it (code instrumentation). The definition must exist before the SDK emits events for that slug.
277
+
278
+ 6. **Query data** (CLI): Use `owlmetry events`, `owlmetry metrics query`, and `owlmetry funnels query` to analyze behavior
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: owlmetry-node
3
- version: 0.1.0
3
+ version: 0.1.3
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
@@ -23,13 +23,9 @@ Run these checks silently. Only inform the user if updates are available.
23
23
 
24
24
  ## Prerequisite
25
25
 
26
- You need an OwlMetry endpoint URL and a `client_key` (starts with `owl_client_...`) for an app with `platform: backend`.
26
+ You need an **ingest endpoint** and a **client key** (`owl_client_...`) for a backend app. Both come from the CLI setup flow.
27
27
 
28
- If the user doesn't have these yet, invoke `/owlmetry-cli` first to:
29
- 1. Sign up or log in
30
- 2. Create a project (if needed)
31
- 3. Create an app with `--platform backend` (no `--bundle-id` needed)
32
- 4. Note the `client_key` from the app creation response
28
+ If the user doesn't have these yet, follow the `/owlmetry-cli` skill first — it handles sign-up, project creation, and app creation. The ingest endpoint is saved to `~/.owlmetry/config.json` (`ingest_endpoint` field) and the client key is returned when creating an app.
33
29
 
34
30
  ## Install
35
31
 
@@ -61,6 +57,16 @@ Owl.configure({
61
57
  - Generates a fresh `sessionId` (UUID) on each `configure()` call
62
58
  - Registers a `beforeExit` handler to auto-flush on graceful shutdown
63
59
 
60
+ ## Next Steps — Codebase Instrumentation
61
+
62
+ Once `Owl.configure()` is in place and the project builds successfully, **you MUST stop here and ask the user** which area they'd like to instrument first — even if the user's original prompt asked you to "instrument the app." Do not proceed with any code changes until the user chooses. Present these three options:
63
+
64
+ 1. **Event & error logging** — Audit the codebase for request handlers, error paths, background jobs, and key operations. Add `Owl.info()`, `Owl.warn()`, `Owl.error()` calls at meaningful points throughout the service. This is SDK-only — no CLI setup required beyond what's already done.
65
+ 2. **Structured metrics** — Identify operations worth measuring (API response times, database queries, external service calls, etc.). Add `Owl.startOperation()` / `Owl.recordMetric()` to track durations and success rates. **Requires CLI first:** each metric slug must be defined on the server via `owlmetry metrics create` (use the `/owlmetry-cli` skill) before the SDK can emit events for it.
66
+ 3. **Funnel tracking** — Identify server-side user journeys (sign-up flow, payment processing, onboarding steps). Add `Owl.track()` calls at each step to measure drop-off. **Requires CLI first:** the funnel definition (with steps and event filters) must be created via `owlmetry funnels create` (use the `/owlmetry-cli` skill) before tracking makes sense.
67
+
68
+ After the user chooses, do a thorough audit of the entire codebase to find all relevant locations, then present a summary of proposed changes before making any edits.
69
+
64
70
  ## Buffering and Delivery
65
71
 
66
72
  The SDK buffers events in memory and sends them to the server in batches. Understanding how buffering works helps you avoid lost events:
@@ -313,3 +319,29 @@ fastify.post('/api/process', async (request, reply) => {
313
319
  return { ok: true };
314
320
  });
315
321
  ```
322
+
323
+ ## Instrumentation Strategy
324
+
325
+ When instrumenting a backend service, follow this priority:
326
+
327
+ **Always instrument (events — no CLI setup needed):**
328
+ - Server startup and shutdown (`info`)
329
+ - Request handling: key route hits, responses sent (`info` with method/path/status)
330
+ - Errors and failures: catch blocks, unhandled rejections, external API failures (`error`)
331
+ - Authentication events: login, logout, token refresh (`info`)
332
+ - Core business actions: order placed, payment processed, email sent (`info`)
333
+ - Background jobs: started, completed, failed (`info`/`error`)
334
+
335
+ **Instrument when relevant (metrics — requires CLI `owlmetry metrics create` first):**
336
+ - Lifecycle metrics for operations where duration matters: API response time, database queries, external service calls, file processing
337
+ - Single-shot metrics for point-in-time values: queue depth, cache hit rate, active connections
338
+
339
+ **Instrument when relevant (funnels — requires CLI `owlmetry funnels create` first):**
340
+ - Multi-step server-side flows you want to measure conversion on: signup pipeline, payment processing, onboarding sequence
341
+ - Always use `Owl.withUser()` for funnel events — funnel analytics require user IDs to calculate conversion
342
+
343
+ **What NOT to instrument:**
344
+ - PII (emails, passwords, tokens, IP addresses)
345
+ - High-frequency health checks or heartbeats
346
+ - Every database query (instrument categories, not every call)
347
+ - Sensitive business data (payment amounts, account balances)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: owlmetry-swift
3
- version: 0.1.0
3
+ version: 0.1.3
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
@@ -23,13 +23,9 @@ Run these checks silently. Only inform the user if updates are available.
23
23
 
24
24
  ## Prerequisite
25
25
 
26
- You need an OwlMetry endpoint URL and a `client_key` (starts with `owl_client_...`) for an app with `platform: apple`.
26
+ You need an **ingest endpoint** and a **client key** (`owl_client_...`) for an Apple-platform app. Both come from the CLI setup flow.
27
27
 
28
- If the user doesn't have these yet, invoke `/owlmetry-cli` first to:
29
- 1. Sign up or log in
30
- 2. Create a project (if needed)
31
- 3. Create an app with `--platform apple --bundle-id <bundle-id>`
32
- 4. Note the `client_key` from the app creation response
28
+ If the user doesn't have these yet, follow the `/owlmetry-cli` skill first — it handles sign-up, project creation, and app creation. The ingest endpoint is saved to `~/.owlmetry/config.json` (`ingest_endpoint` field) and the client key is returned when creating an app.
33
29
 
34
30
  ## Add Swift Package
35
31
 
@@ -50,6 +46,26 @@ Add to your target:
50
46
 
51
47
  **Minimum platforms:** iOS 16.0, macOS 13.0. Zero external dependencies.
52
48
 
49
+ ## Verify Package Integration
50
+
51
+ After adding the package, build the project to verify the dependency resolves and `import OwlMetry` compiles. Do not proceed with configuration until the build succeeds.
52
+
53
+ If the build fails with a "No such module 'OwlMetry'" error, ask the user to add the package manually in Xcode:
54
+
55
+ 1. Open the `.xcodeproj` or `.xcworkspace` in Xcode
56
+ 2. Select the project in the navigator (blue icon at the top)
57
+ 3. Select the app target under "Targets"
58
+ 4. Go to the "General" tab
59
+ 5. Scroll to "Frameworks, Libraries, and Embedded Content"
60
+ 6. Click the **+** button
61
+ 7. Click "Add Other…" > "Add Package Dependency…"
62
+ 8. Enter the URL: `https://github.com/Jasonvdb/owlmetry.git`
63
+ 9. Set "Dependency Rule" to **Branch** → `main`
64
+ 10. Click "Add Package"
65
+ 11. Select the **OwlMetry** library and click "Add Package"
66
+
67
+ Once the user confirms the package is added, retry the build to verify, then proceed with configuration.
68
+
53
69
  ## Configure
54
70
 
55
71
  Configuration must happen once, before any other `Owl` calls — typically in your `@main` App init or AppDelegate `didFinishLaunching`. Each `configure()` call generates a fresh `session_id` (UUID) that groups all subsequent events together. This means each app launch gets its own session, making it easy to see everything a user did in a single sitting.
@@ -81,6 +97,16 @@ struct MyApp: App {
81
97
 
82
98
  Auto-detects: bundle ID, debug mode (`#if DEBUG`). Auto-generates: session ID (fresh each launch).
83
99
 
100
+ ## Next Steps — Codebase Instrumentation
101
+
102
+ Once `Owl.configure()` is in place and the project builds successfully, **you MUST stop here and ask the user** which area they'd like to instrument first — even if the user's original prompt asked you to "instrument the app." Do not proceed with any code changes until the user chooses. Present these three options:
103
+
104
+ 1. **Event & error logging** — Audit the codebase for user actions, screen views, error handling, and key flows. Add `Owl.info()`, `Owl.warn()`, `Owl.error()` calls at meaningful points. This is SDK-only — no CLI setup required beyond what's already done.
105
+ 2. **Structured metrics** — Identify operations worth measuring (network requests, data loading, image processing, etc.). Add `Owl.startOperation()` / `Owl.recordMetric()` to track durations and success rates. **Requires CLI first:** each metric slug must be defined on the server via `owlmetry metrics create` (use the `/owlmetry-cli` skill) before the SDK can emit events for it.
106
+ 3. **Funnel tracking** — Identify user journeys (onboarding, checkout, key conversions). Add `Owl.track()` calls at each step to measure drop-off. **Requires CLI first:** the funnel definition (with steps and event filters) must be created via `owlmetry funnels create` (use the `/owlmetry-cli` skill) before tracking makes sense.
107
+
108
+ After the user chooses, do a thorough audit of the entire codebase to find all relevant locations, then present a summary of proposed changes before making any edits.
109
+
84
110
  ## Log Events
85
111
 
86
112
  Events are the core unit of data in OwlMetry. Use the four log levels to capture different kinds of information:
@@ -224,16 +250,19 @@ Owl.clearExperiments()
224
250
 
225
251
  When instrumenting a new app, follow this priority:
226
252
 
227
- **Always instrument:**
253
+ **Always instrument (events — no CLI setup needed):**
228
254
  - App launch / cold start (`info` in `init()` or `didFinishLaunching`)
229
255
  - Key screen views (`info` with `screenName` in `onAppear`)
230
256
  - Authentication events (login, logout, signup)
231
257
  - Errors and failures (`error` in `catch` blocks, error handlers)
232
258
  - Core business actions (purchase, share, create, delete)
233
259
 
234
- **Instrument when relevant:**
235
- - Funnel steps for multi-step flows you want to optimize
236
- - Lifecycle metrics for operations where duration matters (uploads, loads, syncs)
260
+ **Instrument when relevant (metrics — requires CLI `owlmetry metrics create` first):**
261
+ - Lifecycle metrics for operations where duration matters: image uploads, API calls, data syncs, video encoding
262
+ - Single-shot metrics for point-in-time values: app cold-start time, memory usage, items in cart
263
+
264
+ **Instrument when relevant (funnels — requires CLI `owlmetry funnels create` first):**
265
+ - Multi-step flows you want to measure conversion on: onboarding, checkout, activation
237
266
  - A/B experiments when testing alternative UI or flows
238
267
 
239
268
  **Where to place calls:**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmetry/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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",