tracksiy 0.10.0 → 0.11.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
@@ -139,6 +139,10 @@ Labels: `bug`, `feature`, `question`, `blocker`. `npx tracksiy support` prints t
139
139
  | `monitors timing <slug>` | Where an HTTP monitor's time goes (DNS, connect, server, TTFB, download, page size); how long a job runs and how close to its max runtime. | read | `monitors_timing` |
140
140
  | `storage` | What each project holds on the server (events, transactions, source maps, debug files), largest first, with its retention limits. | read | `storage` |
141
141
  | `mobile` | How fast the app is on phones: app start (cold/warm) against the window before, per OS and release, and per screen TTID/TTFD and slow/frozen frames, with checks for what the SDK does not report. | read | `mobile` |
142
+ | `logs list` | What the app logged, newest first: by level, text, trace, release, attribute. --follow keeps printing new lines. | read | `logs_list` |
143
+ | `logs patterns` | The loudest log lines, counted across their values (message template, or the body with numbers and ids blanked). | read | `logs_patterns` |
144
+ | `logs stats` | Log counts per level per minute, hour or day. | read | `logs_stats` |
145
+ | `issues logs <id>` | What the app logged around an error: the logs in the traces of the issue's last events. | read | `issues_logs` |
142
146
  | `issues list` | Recent issues of a project, newest activity first, with the frames that matter inlined. | read | `issues_list` |
143
147
  | `issues view <id>` | One issue in enough depth to act on: latest event, stack frames, tags, recent volume. | read | `issues_view` |
144
148
  | `issues resolve <ids...>` | Mark issues resolved, optionally fixed in a release so old clients do not reopen them. Records why. | write | `issues_resolve` |
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.10.0" : "0.0.0-dev";
7
+ var VERSION = true ? "0.11.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
@@ -508,8 +508,8 @@ function table(rows, columns2) {
508
508
  widths[i]--;
509
509
  }
510
510
  const fit = (s, n) => s.length > n ? s.slice(0, Math.max(1, n - 1)) + "\u2026" : s.padEnd(n);
511
- const line = (vals) => vals.map((v, i) => fit(v, widths[i])).join(" ").trimEnd();
512
- return [line(cols.map(([n]) => n.toUpperCase())), ...cells.map(line)].join("\n");
511
+ const line2 = (vals) => vals.map((v, i) => fit(v, widths[i])).join(" ").trimEnd();
512
+ return [line2(cols.map(([n]) => n.toUpperCase())), ...cells.map(line2)].join("\n");
513
513
  }
514
514
  function kv(obj, keys) {
515
515
  const entries = (keys ?? Object.keys(obj)).filter((k) => k in obj && isScalar(obj[k]));
@@ -1050,7 +1050,7 @@ var settingsGet = {
1050
1050
  context: "project",
1051
1051
  effect: "read",
1052
1052
  endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/settings" },
1053
- output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[], performanceIssues, storage{maxEventsPerIssue, sourcemapKeepReleases, sourcemapKeepUsedDays}, releaseHealth{crashFreeSessionsMin, minSessions, maxDropPct, velocityPct, velocityMinEvents}}",
1053
+ output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[], performanceIssues, storage{maxEventsPerIssue, sourcemapKeepReleases, sourcemapKeepUsedDays}, releaseHealth{crashFreeSessionsMin, minSessions, maxDropPct, velocityPct, velocityMinEvents}, logs{enabled, minLevel, dailyLimit}}",
1054
1054
  async run(ctx) {
1055
1055
  return ctx.api.get(await projectPath(ctx, "settings"));
1056
1056
  }
@@ -1090,6 +1090,9 @@ var settingsSet = {
1090
1090
  "max-drop-pct": { type: "number", value: "<points>", description: "...or this many points worse than the previous release of the line (default 0.5)." },
1091
1091
  "velocity-pct": { type: "number", value: "<pct>", description: "Velocity alert: one issue's events in 30 minutes reaching this % of the release's sessions in the last hour (default 1)." },
1092
1092
  "velocity-min-events": { type: "integer", value: "<n>", description: "...with at least this many events (default 10)." },
1093
+ logs: { type: "boolean", description: "Store structured logs at all (default on). --no-logs drops them, counted as log:disabled." },
1094
+ "logs-min-level": { type: "string", enum: ["trace", "debug", "info", "warn", "error", "fatal"], description: "Drop log lines below this level at ingest (default trace)." },
1095
+ "logs-daily-limit": { type: "integer", value: "<n>", description: "At most this many log lines a day (default 100000); the rest are dropped and counted as log:daily_limit." },
1093
1096
  body: { type: "json", value: "<json|@file|->", description: "The whole declaration; overrides the flags." }
1094
1097
  },
1095
1098
  examples: [
@@ -1099,7 +1102,7 @@ var settingsSet = {
1099
1102
  { command: "tracksiy settings set --source-root 'strip=/var/www/shop/,prepend=backend/,surface=backend'", description: "The server checkout is only the repo's backend/ directory." },
1100
1103
  { command: "tracksiy settings set --max-events-per-issue 500 --sourcemap-keep-releases 5", description: "A noisy project keeps less on disk." }
1101
1104
  ],
1102
- output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[], performanceIssues, storage{maxEventsPerIssue, sourcemapKeepReleases, sourcemapKeepUsedDays}, releaseHealth{crashFreeSessionsMin, minSessions, maxDropPct, velocityPct, velocityMinEvents}}",
1105
+ output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[], performanceIssues, storage{maxEventsPerIssue, sourcemapKeepReleases, sourcemapKeepUsedDays}, releaseHealth{crashFreeSessionsMin, minSessions, maxDropPct, velocityPct, velocityMinEvents}, logs{enabled, minLevel, dailyLimit}}",
1103
1106
  async run(ctx) {
1104
1107
  const path = await projectPath(ctx, "settings");
1105
1108
  const i = ctx.input;
@@ -1118,6 +1121,12 @@ var settingsSet = {
1118
1121
  ...i.sourcemapKeepReleases !== void 0 ? { sourcemapKeepReleases: i.sourcemapKeepReleases } : {},
1119
1122
  ...i.sourcemapKeepUsedDays !== void 0 ? { sourcemapKeepUsedDays: i.sourcemapKeepUsedDays } : {}
1120
1123
  },
1124
+ logs: {
1125
+ ...current.logs ?? {},
1126
+ ...i.logs !== void 0 ? { enabled: i.logs } : {},
1127
+ ...i.logsMinLevel !== void 0 ? { minLevel: i.logsMinLevel } : {},
1128
+ ...i.logsDailyLimit !== void 0 ? { dailyLimit: i.logsDailyLimit } : {}
1129
+ },
1121
1130
  releaseHealth: {
1122
1131
  ...current.releaseHealth ?? {},
1123
1132
  ...i.crashFreeMin !== void 0 ? { crashFreeSessionsMin: i.crashFreeMin } : {},
@@ -1461,6 +1470,137 @@ ${checks}`;
1461
1470
  };
1462
1471
  var mobile_default = [mobile];
1463
1472
 
1473
+ // src/commands/logs.ts
1474
+ var LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"];
1475
+ var line = (l) => `${String(l.timestamp).slice(11, 23)} ${String(l.level).toUpperCase().padEnd(5)} ${l.body}${l.traceId ? ` [trace ${String(l.traceId).slice(0, 8)}]` : ""}`;
1476
+ function filters(i) {
1477
+ return {
1478
+ since: since(i.since),
1479
+ until: since(i.until, "until"),
1480
+ level: i.level,
1481
+ query: i.query,
1482
+ trace: i.trace,
1483
+ release: i.release,
1484
+ environment: i.environment,
1485
+ logger: i.logger,
1486
+ attribute: i.attribute,
1487
+ pattern: i.pattern,
1488
+ limit: i.limit,
1489
+ cursor: i.cursor
1490
+ };
1491
+ }
1492
+ var FILTER_FLAGS = {
1493
+ since: { type: "string", value: TIME, description: "Window start (default 1h ago; at most 31 days)." },
1494
+ until: { type: "string", value: TIME, description: "Window end (default now)." },
1495
+ level: { type: "string", enum: LEVELS, description: "The lowest level shown." },
1496
+ query: { type: "string", value: "<text>", description: "Case-insensitive text in the line." },
1497
+ trace: { type: "string", value: "<trace id>", description: "Only this trace (32 hex)." },
1498
+ release: { type: "string", value: "<version>", description: "Only this release." },
1499
+ environment: { type: "string", value: "<name>", description: "Only this environment." },
1500
+ logger: { type: "string", value: "<name>", description: "Only this logger / origin (sentry.origin, logger.name)." },
1501
+ attribute: { type: "string", value: "<key=value>", description: "Only lines with this attribute value." },
1502
+ pattern: { type: "integer", value: "<n>", description: "Only this pattern (from `logs patterns`)." }
1503
+ };
1504
+ var list = {
1505
+ path: ["logs", "list"],
1506
+ aliases: [["logs"]],
1507
+ summary: "What the app logged, newest first: by level, text, trace, release, attribute. --follow keeps printing new lines.",
1508
+ description: "Structured logs from the SDKs (Sentry.logger.*, sentry-laravel's sentry_logs channel, the mobile loggers). `next` pages older lines (--cursor). --follow 10m polls every 3 s and prints new lines as they arrive (stderr), then returns them all. Per-project limits: `settings set --logs/--logs-min-level/--logs-daily-limit`.",
1509
+ context: "project",
1510
+ effect: "read",
1511
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/logs" },
1512
+ flags: {
1513
+ ...FILTER_FLAGS,
1514
+ limit: { type: "integer", value: "<n>", description: "Lines per page (default 100, at most 500)." },
1515
+ cursor: { type: "string", value: "<id>", description: "The `next` of the previous page." },
1516
+ follow: { type: "string", value: "<duration>", description: "Keep polling for new lines this long: 5m, 1h." }
1517
+ },
1518
+ examples: [
1519
+ { command: "tracksiy logs --level warn --since 30m", description: "Warnings and errors of the last half hour." },
1520
+ { command: "tracksiy logs --trace 5b8efff798038103d269b633813fc60c", description: "Everything one request logged." },
1521
+ { command: "tracksiy logs --query checkout --follow 10m", description: "Watch a deploy." }
1522
+ ],
1523
+ output: "{since, until, logs:[{id, timestamp, level, severity, body, traceId, spanId, release, environment, logger, source, pattern, attributes}], next}",
1524
+ text: (d) => `${(d.logs ?? []).slice().reverse().map(line).join("\n") || "(no lines)"}${d.next ? `
1525
+
1526
+ older: --cursor ${d.next}` : ""}`,
1527
+ async run(ctx) {
1528
+ const path = await projectPath(ctx, "logs");
1529
+ const query = filters(ctx.input);
1530
+ const followSeconds = duration(ctx.input.follow, "follow") ?? 0;
1531
+ let data = await ctx.api.get(path, query);
1532
+ if (!followSeconds) return data;
1533
+ const seen = new Set((data.logs ?? []).map((l) => l.id));
1534
+ const all = [...data.logs ?? []];
1535
+ let lastTime = all.length ? all[0].timestamp : (/* @__PURE__ */ new Date()).toISOString();
1536
+ const deadline = Date.now() + followSeconds * 1e3;
1537
+ while (Date.now() < deadline) {
1538
+ await new Promise((r) => setTimeout(r, 3e3));
1539
+ const page = await ctx.api.get(path, { ...query, since: lastTime, cursor: void 0, limit: 500 });
1540
+ const fresh = (page.logs ?? []).filter((l) => !seen.has(l.id)).reverse();
1541
+ for (const l of fresh) {
1542
+ seen.add(l.id);
1543
+ all.unshift(l);
1544
+ ctx.log(line(l));
1545
+ lastTime = l.timestamp;
1546
+ }
1547
+ }
1548
+ return { ...data, logs: all, next: null };
1549
+ }
1550
+ };
1551
+ var patterns = {
1552
+ path: ["logs", "patterns"],
1553
+ summary: "The loudest log lines, counted across their values (message template, or the body with numbers and ids blanked).",
1554
+ context: "project",
1555
+ effect: "read",
1556
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/logs/patterns" },
1557
+ flags: {
1558
+ since: FILTER_FLAGS.since,
1559
+ until: FILTER_FLAGS.until,
1560
+ level: FILTER_FLAGS.level,
1561
+ limit: { type: "integer", value: "<n>", description: "Patterns (default 100, at most 200)." }
1562
+ },
1563
+ examples: [{ command: "tracksiy logs patterns --since 24h --level warn", description: "Which warnings repeat; then `logs --pattern <n>`." }],
1564
+ output: "{patterns:[{pattern, count, level, lastSeen, template, sample}]}",
1565
+ text: (d) => table(d.patterns ?? [], ["count", "level", ["line", (r) => (r.template ?? r.sample ?? "").slice(0, 100)], "pattern"]),
1566
+ async run(ctx) {
1567
+ const i = ctx.input;
1568
+ return ctx.api.get(await projectPath(ctx, "logs/patterns"), { since: since(i.since), until: since(i.until, "until"), level: i.level, limit: i.limit });
1569
+ }
1570
+ };
1571
+ var stats = {
1572
+ path: ["logs", "stats"],
1573
+ summary: "Log counts per level per minute, hour or day.",
1574
+ context: "project",
1575
+ effect: "read",
1576
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/logs/stats" },
1577
+ flags: { since: FILTER_FLAGS.since, until: FILTER_FLAGS.until },
1578
+ output: "{bucketSeconds, totals{level: n}, buckets:[{start, counts}]}",
1579
+ text: (d) => `${kv(d.totals ?? {})}
1580
+
1581
+ ${table(d.buckets ?? [], ["start", ...LEVELS.map((lvl) => [lvl, (r) => r.counts?.[lvl] ?? ""])])}`,
1582
+ async run(ctx) {
1583
+ const i = ctx.input;
1584
+ return ctx.api.get(await projectPath(ctx, "logs/stats"), { since: since(i.since), until: since(i.until, "until") });
1585
+ }
1586
+ };
1587
+ var issueLogs = {
1588
+ path: ["issues", "logs"],
1589
+ summary: "What the app logged around an error: the logs in the traces of the issue's last events.",
1590
+ context: "project",
1591
+ effect: "read",
1592
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/issues/{id}/logs" },
1593
+ args: [{ name: "id", required: true, description: "The issue id." }],
1594
+ output: "{issueId, traces[], logs[...], notes[]}",
1595
+ text: (d) => `${(d.logs ?? []).slice().reverse().map(line).join("\n") || "(no lines)"}${(d.notes ?? []).length ? `
1596
+
1597
+ ${d.notes.join("\n")}` : ""}`,
1598
+ async run(ctx) {
1599
+ return ctx.api.get(await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/logs`));
1600
+ }
1601
+ };
1602
+ var logs_default = [list, patterns, stats, issueLogs];
1603
+
1464
1604
  // src/commands/issues.ts
1465
1605
  import { writeFileSync } from "node:fs";
1466
1606
  var FILTERS = {
@@ -1518,7 +1658,7 @@ var issueRows = (issues) => table(issues ?? [], [
1518
1658
  ["status", (r) => r.regressedAt && r.status === "unresolved" ? "regressed" : r.status],
1519
1659
  ["title", (r) => r.title]
1520
1660
  ]);
1521
- var list = {
1661
+ var list2 = {
1522
1662
  path: ["issues", "list"],
1523
1663
  aliases: [["issues"]],
1524
1664
  summary: "Recent issues of a project, newest activity first, with the frames that matter inlined.",
@@ -1818,7 +1958,7 @@ var bulk = {
1818
1958
  return result;
1819
1959
  }
1820
1960
  };
1821
- var issues_default = [list, view, resolve2, ignore, unresolve, bulk, merge, comment, activity, verifyFixed, claim, unclaim];
1961
+ var issues_default = [list2, view, resolve2, ignore, unresolve, bulk, merge, comment, activity, verifyFixed, claim, unclaim];
1822
1962
 
1823
1963
  // src/commands/projects.ts
1824
1964
  var create = {
@@ -1933,8 +2073,8 @@ var DECLARE = {
1933
2073
  "replace-recipients": { type: "boolean", description: "Make the recipient list exactly this one (default: add to it)." },
1934
2074
  body: { type: "json", value: "<json|@file|->", description: "Raw request body; overrides the other flags." }
1935
2075
  };
1936
- function recipients(list5, delivery) {
1937
- return (list5 ?? []).map((raw) => {
2076
+ function recipients(list6, delivery) {
2077
+ return (list6 ?? []).map((raw) => {
1938
2078
  const i = raw.indexOf(":");
1939
2079
  const type = i > 0 ? raw.slice(0, i) : raw;
1940
2080
  if (!RECIPIENT_TYPES.includes(type)) {
@@ -2115,7 +2255,7 @@ var settingsSet2 = {
2115
2255
  var alerts_default = [get, set, test, coverage, defaults, settingsGet2, settingsSet2];
2116
2256
 
2117
2257
  // src/commands/keys.ts
2118
- var list2 = {
2258
+ var list3 = {
2119
2259
  path: ["keys", "list"],
2120
2260
  aliases: [["keys"]],
2121
2261
  summary: "Every ingest key of the project: its surface, whether ingest accepts it, its rate limit and volume.",
@@ -2172,11 +2312,11 @@ var rotate = {
2172
2312
  return ctx.api.send("POST", await projectPath(ctx, `keys/${seg(ctx.input.name)}/rotate`));
2173
2313
  }
2174
2314
  };
2175
- var keys_default = [list2, set2, rotate];
2315
+ var keys_default = [list3, set2, rotate];
2176
2316
 
2177
2317
  // src/commands/releases.ts
2178
2318
  import { execFileSync } from "node:child_process";
2179
- var list3 = {
2319
+ var list4 = {
2180
2320
  path: ["releases", "list"],
2181
2321
  aliases: [["releases"]],
2182
2322
  summary: "What did the last deploys break? Recent releases with their events, issues and the issues they introduced.",
@@ -2322,7 +2462,7 @@ ${table(d.newIssues, ["short_id", "count", "title"])}` : ""}`,
2322
2462
  return data;
2323
2463
  }
2324
2464
  };
2325
- var releases_default = [list3, health2, declare, verdict];
2465
+ var releases_default = [list4, health2, declare, verdict];
2326
2466
 
2327
2467
  // src/commands/artifacts.ts
2328
2468
  import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
@@ -2763,7 +2903,7 @@ var artifacts_default = [sourcemapsInject, sourcemapsUpload, proguardUpload, art
2763
2903
 
2764
2904
  // src/commands/tokens.ts
2765
2905
  var SCOPE_HINT = "e.g. org:read,project:read,event:read,event:write,project:write,project:releases,project:admin";
2766
- var list4 = {
2906
+ var list5 = {
2767
2907
  path: ["tokens", "list"],
2768
2908
  aliases: [["tokens"]],
2769
2909
  summary: "Your tokens, newest first, without their values.",
@@ -2876,7 +3016,7 @@ var self = {
2876
3016
  return ctx.api.get("tokens/self");
2877
3017
  }
2878
3018
  };
2879
- var tokens_default = [list4, self, create2, update, remove, rotate2];
3019
+ var tokens_default = [list5, self, create2, update, remove, rotate2];
2880
3020
 
2881
3021
  // src/commands/support.ts
2882
3022
  var SUPPORT = {
@@ -3540,15 +3680,15 @@ function makeContext(spec, input, globals, surface, io) {
3540
3680
  const org = () => {
3541
3681
  orgPromise ??= (async () => {
3542
3682
  if (config.org) return config.org;
3543
- const list5 = await visible();
3544
- if (list5.length === 1) {
3545
- log(`using organization ${list5[0].slug}`);
3546
- return list5[0].slug;
3683
+ const list6 = await visible();
3684
+ if (list6.length === 1) {
3685
+ log(`using organization ${list6[0].slug}`);
3686
+ return list6[0].slug;
3547
3687
  }
3548
3688
  throw usageError(
3549
3689
  "missing_org",
3550
- list5.length ? "Which organization? This token can see several." : "This token cannot see any organization.",
3551
- list5.length ? `Pass --org <slug>, set TRACKSIY_ORG, or add "org" to ${".tracksiy.json"}. Visible: ${list5.map((o) => o.slug).join(", ")}` : "Check the token with `tracksiy auth status`."
3690
+ list6.length ? "Which organization? This token can see several." : "This token cannot see any organization.",
3691
+ list6.length ? `Pass --org <slug>, set TRACKSIY_ORG, or add "org" to ${".tracksiy.json"}. Visible: ${list6.map((o) => o.slug).join(", ")}` : "Check the token with `tracksiy auth status`."
3552
3692
  );
3553
3693
  })();
3554
3694
  return orgPromise;
@@ -3558,8 +3698,8 @@ function makeContext(spec, input, globals, surface, io) {
3558
3698
  projectPromise ??= (async () => {
3559
3699
  const o = await org();
3560
3700
  if (config.project) return { org: o, project: config.project };
3561
- const list5 = await visible();
3562
- const projects2 = list5.find((x) => x.slug === o)?.projects ?? [];
3701
+ const list6 = await visible();
3702
+ const projects2 = list6.find((x) => x.slug === o)?.projects ?? [];
3563
3703
  if (projects2.length === 1) {
3564
3704
  log(`using project ${o}/${projects2[0].slug}`);
3565
3705
  return { org: o, project: projects2[0].slug };
@@ -3724,11 +3864,11 @@ async function serve(io, defaults2) {
3724
3864
  const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
3725
3865
  io.stderr(`tracksiy mcp ${VERSION}: ${server.toolCount} tools on stdio
3726
3866
  `);
3727
- for await (const line of rl) {
3728
- if (!line.trim()) continue;
3867
+ for await (const line2 of rl) {
3868
+ if (!line2.trim()) continue;
3729
3869
  let msg;
3730
3870
  try {
3731
- msg = JSON.parse(line);
3871
+ msg = JSON.parse(line2);
3732
3872
  } catch {
3733
3873
  write({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
3734
3874
  continue;
@@ -3782,6 +3922,7 @@ var COMMANDS = [
3782
3922
  ...perf_default,
3783
3923
  ...storage_default,
3784
3924
  ...mobile_default,
3925
+ ...logs_default,
3785
3926
  ...issues_default,
3786
3927
  ...projects_default,
3787
3928
  ...alerts_default,
@@ -3795,7 +3936,7 @@ var COMMANDS = [
3795
3936
  ];
3796
3937
  var GROUPS = [
3797
3938
  ["Start", "init auth onboarding describe"],
3798
- ["Look", "overview changes health storage orgs projects issues monitors performance mobile queries not-found releases alerts keys artifacts settings privacy-preview"],
3939
+ ["Look", "overview changes health storage orgs projects issues monitors performance mobile logs queries not-found releases alerts keys artifacts settings privacy-preview"],
3799
3940
  ["Ship", "sourcemaps proguard"],
3800
3941
  ["Manage", "tokens"],
3801
3942
  ["Talk to us", "support"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracksiy",
3
- "version": "0.10.0",
3
+ "version": "0.11.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": {