tracksiy 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -126,6 +126,7 @@ Labels: `bug`, `feature`, `question`, `blocker`. `npx tracksiy support` prints t
126
126
  | `monitors declare` | Declare monitors for a project up front, idempotently: heartbeats for jobs, HTTP checks, TLS, ingest silence. | write | `monitors_declare` |
127
127
  | `monitors delete <slug>` | Remove one of a project's monitors by the slug it was declared with. | **destructive** | `monitors_delete` |
128
128
  | `performance` | Is this project slow, and did it get slower? Transactions compared with the previous window, plus uptime. | read | `performance` |
129
+ | `queries` | Which database queries and outbound HTTP calls make this project slow, with N+1 detection and the routes that run them. | read | `queries` |
129
130
  | `issues list` | Recent issues of a project, newest activity first, with the frames that matter inlined. | read | `issues_list` |
130
131
  | `issues view <id>` | One issue in enough depth to act on: latest event, stack frames, tags, recent volume. | read | `issues_view` |
131
132
  | `issues resolve <ids...>` | Mark issues resolved, optionally fixed in a release so old clients do not reopen them. Records why. | write | `issues_resolve` |
@@ -134,6 +135,7 @@ Labels: `bug`, `feature`, `question`, `blocker`. `npx tracksiy support` prints t
134
135
  | `issues bulk-status` | Change the status of every issue matching a filter. Dry run unless --apply. | write | `issues_bulk_status` |
135
136
  | `issues comment <id>` | Leave a comment on an issue for the next agent or human: what you found, what you tried. | write | `issues_comment` |
136
137
  | `issues activity <id>` | Who did what to an issue and why: resolutions (and in which release), regressions, comments, claims. | read | `issues_activity` |
138
+ | `issues verify-fixed <id>` | Did the fix hold? Judged by the fix release's own traffic: fixed, regressed or undetermined. Exit 10 unless fixed. | read | `issues_verify_fixed` |
137
139
  | `issues claim <id>` | Take a lease on an issue so no other agent works on it meanwhile. Lapses on its own. | write | `issues_claim` |
138
140
  | `issues unclaim <id>` | Give back your lease on an issue. | write | `issues_unclaim` |
139
141
  | `projects create <name>` | Everything a new app needs in one idempotent call: the project, an ingest key per surface, an upload token, and the next steps. | write | `projects_create` |
package/dist/tracksiy.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  import { readFileSync as readFileSync2 } from "node:fs";
5
5
 
6
6
  // src/version.ts
7
- var VERSION = true ? "0.2.0" : "0.0.0-dev";
7
+ var VERSION = true ? "0.4.0" : "0.0.0-dev";
8
8
  var USER_AGENT = `tracksiy-cli/${VERSION} (node ${process.versions.node}; ${process.platform})`;
9
9
 
10
10
  // src/core/errors.ts
@@ -851,10 +851,16 @@ var monitorsDeclare = {
851
851
  ssl: { type: "list", value: "<slug>=<url>", description: "TLS certificate check (expiry is reported on every check)." },
852
852
  ingest: { type: "list", value: "<slug>[:<surface>]", description: "Alert when this project (or surface) stops sending events." },
853
853
  interval: { type: "integer", value: "<seconds>", description: "Interval for --get/--ping/--ssl/--ingest monitors." },
854
+ "expect-text": { type: "string", value: "<text>", description: "--get: down unless the body contains this (a 200 with an empty shell is down)." },
855
+ "expect-no-text": { type: "string", value: "<text>", description: "--get: down if the body contains this, e.g. Whoops or Exception." },
856
+ "user-agent": { type: "string", value: "<ua>", description: "--get/--ping: ask as this User-Agent, e.g. GPTBot/1.0, to see what crawlers get." },
857
+ "dry-run": { type: "boolean", description: "Show what would be created, adopted or changed; write nothing." },
854
858
  body: { type: "json", value: "<json|@file|->", description: "Raw request body; overrides the shorthand flags." }
855
859
  },
856
860
  examples: [
857
- { command: "tracksiy monitors declare --heartbeat nightly-backup:93600 --get site=https://example.com", description: "A backup heartbeat and a homepage check." }
861
+ { command: "tracksiy monitors declare --heartbeat nightly-backup:93600 --get site=https://example.com", description: "A backup heartbeat and a homepage check." },
862
+ { command: "tracksiy monitors declare --get tr-landing=https://example.com/tr --expect-text '<h1' --expect-no-text Whoops --user-agent GPTBot/1.0", description: "The page as an answer engine gets it: real content, no error template." },
863
+ { command: "tracksiy monitors declare --heartbeat tariff-sync:608400 --dry-run", description: "See the change before making it." }
858
864
  ],
859
865
  output: "Server result per monitor, including check-in URLs for heartbeats.",
860
866
  async run(ctx) {
@@ -870,7 +876,17 @@ var monitorsDeclare = {
870
876
  for (const item of i[type] ?? []) {
871
877
  const eq = String(item).indexOf("=");
872
878
  if (eq < 1) throw usageError("bad_value", `--${type} expects <slug>=<url>, got ${JSON.stringify(item)}`);
873
- monitors.push(compact({ slug: item.slice(0, eq), type, url: item.slice(eq + 1), intervalSeconds: i.interval }));
879
+ monitors.push(
880
+ compact({
881
+ slug: item.slice(0, eq),
882
+ type,
883
+ url: item.slice(eq + 1),
884
+ intervalSeconds: i.interval,
885
+ expectText: type === "get" ? i.expectText : void 0,
886
+ expectNoText: type === "get" ? i.expectNoText : void 0,
887
+ userAgent: type !== "ssl" ? i.userAgent : void 0
888
+ })
889
+ );
874
890
  }
875
891
  }
876
892
  for (const item of i.ingest ?? []) {
@@ -880,6 +896,7 @@ var monitorsDeclare = {
880
896
  if (!monitors.length) throw usageError("missing_flag", "Nothing to declare.", "Add --heartbeat, --get, --ping, --ssl, --ingest, or --body.");
881
897
  body = { monitors };
882
898
  }
899
+ if (i.dryRun) body = { ...body, dryRun: true };
883
900
  return ctx.api.send("POST", await projectPath(ctx, "monitors"), body);
884
901
  }
885
902
  };
@@ -904,16 +921,26 @@ var performance = {
904
921
  effect: "read",
905
922
  endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/performance" },
906
923
  flags: {
907
- since: { type: "string", value: TIME, description: "Window start (default: server's, 24h)." },
908
- op: { type: "string", value: "<op>", description: "Only this operation, e.g. http.server." },
909
- sort: { type: "string", value: "<key>", description: "Order (server default: slower first)." },
910
- limit: { type: "integer", value: "<n>", description: "How many transactions." }
924
+ since: { type: "string", value: TIME, description: "Window start (default: server's, 24h; 7d with --release)." },
925
+ op: { type: "string", value: "<op>", description: "Only this operation, e.g. http.server or pageload." },
926
+ sort: { type: "string", enum: ["slower", "duration", "p95", "failures"], description: "Order (default: slower first)." },
927
+ limit: { type: "integer", value: "<n>", description: "How many transactions." },
928
+ release: { type: "string", value: "<version>", description: "Compare this release's transactions with --against instead of two time windows." },
929
+ against: { type: "string", value: "<version>", description: "The release to compare with (default: the one before --release)." }
911
930
  },
912
- output: "{since, until, transactions:[{transaction, op, current{...}, previous{...}, changeRatio, slower}], uptime[...], blindSpots[...]}",
931
+ examples: [
932
+ { command: "tracksiy performance --sort p95", description: "The slowest routes by p95." },
933
+ { command: "tracksiy performance --release web@$(git rev-parse HEAD)", description: "Did this deploy make anything slower or failing?" },
934
+ { command: "tracksiy performance --op pageload --select transactions.transaction,transactions.current.webVitals", description: "Core Web Vitals (p75) per page." }
935
+ ],
936
+ output: "{since, until, release?, againstRelease?, transactions:[{transaction, op, current{count, avgDurationMs, p50Ms, p95Ms, p99Ms, failureRate, failed, status, webVitals}, previous{...}, changeRatio, slower}], uptime[...], blindSpots[...]}",
913
937
  text: (d) => `${table(d.transactions ?? [], [
914
938
  "transaction",
915
939
  "op",
916
- ["p95 ms", (r) => r.current?.p95Ms ?? r.current?.p95 ?? "-"],
940
+ ["p50", (r) => r.current?.p50Ms ?? "-"],
941
+ ["p95", (r) => r.current?.p95Ms ?? "-"],
942
+ ["p99", (r) => r.current?.p99Ms ?? "-"],
943
+ ["fail", (r) => r.current?.failureRate == null ? "-" : `${(r.current.failureRate * 100).toFixed(1)}%`],
917
944
  ["count", (r) => r.current?.count ?? "-"],
918
945
  ["change", (r) => r.changeRatio == null ? "-" : `${r.changeRatio > 0 ? "+" : ""}${Math.round(r.changeRatio * 100)}%`],
919
946
  ["slower", (r) => r.slower]
@@ -926,11 +953,52 @@ ${table(d.uptime, ["name", "url", "upNow", "avgResponseMs", "downChecks"])}` : "
926
953
  since: since(ctx.input.since),
927
954
  op: ctx.input.op,
928
955
  sort: ctx.input.sort,
956
+ limit: ctx.input.limit,
957
+ release: ctx.input.release,
958
+ against: ctx.input.against
959
+ });
960
+ }
961
+ };
962
+ var queries = {
963
+ path: ["queries"],
964
+ aliases: [["performance", "queries"]],
965
+ summary: "Which database queries and outbound HTTP calls make this project slow, with N+1 detection and the routes that run them.",
966
+ description: "Aggregated from the spans of sampled transactions (op db* and http.client), normalized so no literal is stored. Ranked by total time by default: the query that costs the most overall. nPlusOne marks a query one request ran 10+ times.",
967
+ context: "project",
968
+ effect: "read",
969
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/queries" },
970
+ flags: {
971
+ since: { type: "string", value: TIME, description: "Window start (default 24h)." },
972
+ op: { type: "string", value: "<db|http.client|op>", description: "db (every db* op), http.client, or an exact op." },
973
+ transaction: { type: "string", value: "<route>", description: "Only queries run by this transaction, e.g. /orders." },
974
+ sort: { type: "string", enum: ["total", "avg", "max", "count", "perTransaction"], description: "Order (default total)." },
975
+ limit: { type: "integer", value: "<n>", description: "How many (max 100)." }
976
+ },
977
+ examples: [
978
+ { command: "tracksiy queries --since 24h", description: "The queries that cost the most time today." },
979
+ { command: "tracksiy queries --sort perTransaction --select queries.query,queries.maxPerTransaction,queries.routes", description: "N+1 candidates." }
980
+ ],
981
+ output: "{since, until, queries:[{fingerprint, op, query, count, transactions, perTransaction, maxPerTransaction, totalMs, avgMs, maxMs, previous{count, avgMs}, changeRatio, nPlusOne, routes[{transaction, count, avgMs}]}], note}",
982
+ text: (d) => table(d.queries ?? [], [
983
+ ["total ms", (r) => r.totalMs],
984
+ ["avg", (r) => r.avgMs ?? "-"],
985
+ ["max", (r) => r.maxMs],
986
+ ["count", (r) => r.count],
987
+ ["per req", (r) => r.perTransaction],
988
+ ["n+1", (r) => r.nPlusOne ? "yes" : ""],
989
+ ["query", (r) => String(r.query).slice(0, 90)]
990
+ ]),
991
+ async run(ctx) {
992
+ return ctx.api.get(await projectPath(ctx, "queries"), {
993
+ since: since(ctx.input.since),
994
+ op: ctx.input.op,
995
+ transaction: ctx.input.transaction,
996
+ sort: ctx.input.sort,
929
997
  limit: ctx.input.limit
930
998
  });
931
999
  }
932
1000
  };
933
- var fleet_default = [orgs, projects, overview, changes, health, monitorsList, monitorsDeclare, monitorsDelete, performance];
1001
+ var fleet_default = [orgs, projects, overview, changes, health, monitorsList, monitorsDeclare, monitorsDelete, performance, queries];
934
1002
 
935
1003
  // src/commands/issues.ts
936
1004
  var FILTERS = {
@@ -1122,6 +1190,40 @@ var activity = {
1122
1190
  return ctx.api.get(await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/activity`), { limit: ctx.input.limit });
1123
1191
  }
1124
1192
  };
1193
+ var verifyFixed = {
1194
+ path: ["issues", "verify-fixed"],
1195
+ summary: "Did the fix hold? Judged by the fix release's own traffic: fixed, regressed or undetermined. Exit 10 unless fixed.",
1196
+ description: "regressed: the issue has an event from the fix release or a newer one. undetermined: the fix release has had too little traffic (--min-traffic), or the issue's old rate predicts fewer than 3 occurrences in that traffic. fixed otherwise. The fix release defaults to the one the issue was resolved in. --wait polls until the verdict is not undetermined.",
1197
+ context: "project",
1198
+ effect: "read",
1199
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/issues/{issue_id}/verify" },
1200
+ args: [{ name: "id", required: true, description: "Issue id." }],
1201
+ flags: {
1202
+ release: { type: "string", value: "<version>", description: "The fix release. Default: the one it was resolved in." },
1203
+ "min-traffic": { type: "integer", value: "<n>", description: "Transactions + errors + sessions from the fix release needed before silence counts (default 100)." },
1204
+ wait: { type: "integer", value: "<seconds>", description: "Poll every 30 s until fixed or regressed, up to this long." }
1205
+ },
1206
+ examples: [
1207
+ { command: "tracksiy issues verify-fixed 4812", description: "Verdict now." },
1208
+ { command: "tracksiy issues verify-fixed 4812 --wait 1800", description: "In a deploy script: wait up to 30 min for enough traffic." }
1209
+ ],
1210
+ output: "{verdict, reason, release, since, eventsSince, staleEvents, traffic{transactions, errors, sessions, total}, baselinePer1000, expectedEvents, latestEvent?}",
1211
+ text: (d) => kv(d, ["verdict", "reason", "release", "since", "eventsSince", "staleEvents", "expectedEvents"]) + `
1212
+ traffic: ${d.traffic?.total ?? 0} (${d.traffic?.transactions ?? 0} transactions, ${d.traffic?.errors ?? 0} errors, ${d.traffic?.sessions ?? 0} sessions)`,
1213
+ async run(ctx) {
1214
+ const path = await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/verify`);
1215
+ const query = { release: ctx.input.release, minTraffic: ctx.input.minTraffic };
1216
+ const deadline = Date.now() + (ctx.input.wait ?? 0) * 1e3;
1217
+ let data = await ctx.api.get(path, query);
1218
+ while (data?.verdict === "undetermined" && Date.now() + 3e4 <= deadline) {
1219
+ ctx.log(`undetermined: ${data.reason} -- checking again in 30 s`);
1220
+ await new Promise((r) => setTimeout(r, 3e4));
1221
+ data = await ctx.api.get(path, query);
1222
+ }
1223
+ if (data?.verdict !== "fixed") throw new CheckFailed(`${data?.verdict ?? "unknown"}: ${data?.reason ?? ""}`, data, data?.verdict === "regressed" ? "Reopened by its own release: look at latestEvent." : "Wait for traffic (--wait), or lower --min-traffic.");
1224
+ return data;
1225
+ }
1226
+ };
1125
1227
  var claim = {
1126
1228
  path: ["issues", "claim"],
1127
1229
  summary: "Take a lease on an issue so no other agent works on it meanwhile. Lapses on its own.",
@@ -1195,7 +1297,7 @@ var bulk = {
1195
1297
  return ctx.api.send("POST", await projectPath(ctx, "issues/status"), body);
1196
1298
  }
1197
1299
  };
1198
- var issues_default = [list, view, resolve2, ignore, unresolve, bulk, comment, activity, claim, unclaim];
1300
+ var issues_default = [list, view, resolve2, ignore, unresolve, bulk, comment, activity, verifyFixed, claim, unclaim];
1199
1301
 
1200
1302
  // src/commands/projects.ts
1201
1303
  var create = {
@@ -1294,12 +1396,12 @@ var onboarding = {
1294
1396
  var projects_default = [create, verify, onboarding];
1295
1397
 
1296
1398
  // src/commands/alerts.ts
1297
- var RECIPIENT_TYPES = ["email", "webhook", "discord", "googlechat", "telegram"];
1399
+ var RECIPIENT_TYPES = ["email", "webhook", "discord", "googlechat", "telegram", "peerpacket"];
1298
1400
  var DECLARE = {
1299
1401
  recipient: {
1300
1402
  type: "list",
1301
1403
  value: "email|<type>:<url>",
1302
- description: `Who is told. Repeatable. \`email\` alone reaches the members of the project's teams; webhook, discord, googlechat and telegram take a URL: \`<type>:<url>\` (telegram: the Bot API sendMessage URL with chat_id).`
1404
+ description: `Who is told. Repeatable. \`email\` alone reaches the members of the project's teams; webhook, discord, googlechat and telegram take a URL: \`<type>:<url>\` (telegram: the Bot API sendMessage URL with chat_id; peerpacket: https://peerpacket.com/channels/<channel>?to=<handle>&as=<author>&key=<pp_live_\u2026> -- each alert becomes a ticket for that agent, closed when its issues are resolved).`
1303
1405
  },
1304
1406
  delivery: { type: "string", enum: ["immediate", "digest"], description: "For --recipient: send each alert now, or batch into the digest hours. Default immediate." },
1305
1407
  "new-issue": { type: "boolean", description: "Alert on every new issue (default true). --no-new-issue to turn off." },
@@ -1376,7 +1478,8 @@ var set = {
1376
1478
  flags: DECLARE,
1377
1479
  examples: [
1378
1480
  { command: "tracksiy alerts set --recipient email --threshold 50/10", description: "Email the project's team on new issues, on bursts, and on downtime." },
1379
- { command: "tracksiy alerts set --recipient 'telegram:https://api.telegram.org/bot<token>/sendMessage?chat_id=<id>' --delivery digest", description: "Telegram, batched into the digest hours." }
1481
+ { command: "tracksiy alerts set --recipient 'telegram:https://api.telegram.org/bot<token>/sendMessage?chat_id=<id>' --delivery digest", description: "Telegram, batched into the digest hours." },
1482
+ { command: "tracksiy alerts set --recipient 'peerpacket:https://peerpacket.com/channels/shop-ops?to=shop-agent&as=tracksiy-alerts&key=pp_live_\u2026'", description: "Alerts land as PeerPacket tickets for the shop's agent." }
1380
1483
  ],
1381
1484
  output: "The stored alerting state, same shape as `alerts get`.",
1382
1485
  text: rulesText,
@@ -3066,7 +3169,7 @@ var COMMANDS = [
3066
3169
  ];
3067
3170
  var GROUPS = [
3068
3171
  ["Start", "init auth onboarding describe"],
3069
- ["Look", "overview changes health orgs projects issues monitors performance releases alerts keys artifacts"],
3172
+ ["Look", "overview changes health orgs projects issues monitors performance queries releases alerts keys artifacts"],
3070
3173
  ["Ship", "sourcemaps proguard"],
3071
3174
  ["Manage", "tokens"],
3072
3175
  ["Talk to us", "support"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracksiy",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Agent-first CLI and MCP server for Tracksiy: errors, uptime, alerts, releases and source maps, from one token.",
5
5
  "type": "module",
6
6
  "bin": {