tracksiy 0.10.0 → 0.12.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,14 @@ 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` |
146
+ | `user-feedback` | What users wrote through the feedback widget or API: message, page or screen, release, device, the issue it is about and its screenshots. | read | `user_feedback` |
147
+ | `issues attachments <id>` | Screenshots, view hierarchies and files of an issue's recent events and of the feedback about it. | read | `issues_attachments` |
148
+ | `attachments get <id>` | Save one attachment (a screenshot, a view hierarchy) to a file -- or print a JSON one. | read | `attachments_get` |
149
+ | `issues flags <id>` | Feature flag values of an issue's events against the rest of the project; `suspect` marks a value far more common in this issue. | read | `issues_flags` |
142
150
  | `issues list` | Recent issues of a project, newest activity first, with the frames that matter inlined. | read | `issues_list` |
143
151
  | `issues view <id>` | One issue in enough depth to act on: latest event, stack frames, tags, recent volume. | read | `issues_view` |
144
152
  | `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.12.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
@@ -149,7 +149,7 @@ var Api = class {
149
149
  continue;
150
150
  }
151
151
  }
152
- const data = await readBody(res);
152
+ const data = opts.binary && res.ok ? new Uint8Array(await res.arrayBuffer()) : await readBody(res);
153
153
  const ok = opts.accept ? opts.accept.includes(res.status) : res.ok;
154
154
  if (ok) return { status: res.status, data, headers: res.headers };
155
155
  throw await this.httpError(method, path, res, data);
@@ -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}, attachments{store, maxKb, quotaMb, retentionDays}}",
1054
1054
  async run(ctx) {
1055
1055
  return ctx.api.get(await projectPath(ctx, "settings"));
1056
1056
  }
@@ -1090,6 +1090,13 @@ 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." },
1096
+ attachments: { type: "string", enum: ["off", "feedback", "all"], description: "Which attachments are kept: off, feedback (default: only what a user sent with their feedback), all (also crash screenshots, view hierarchies, files)." },
1097
+ "attachment-max-kb": { type: "integer", value: "<kb>", description: "Largest attachment kept (default 5120)." },
1098
+ "attachments-quota-mb": { type: "integer", value: "<mb>", description: "The project's attachment budget (default 500); past it new files are dropped (attachment:quota)." },
1099
+ "attachments-retention-days": { type: "integer", value: "<days>", description: "Keep attachments this long (default 30)." },
1093
1100
  body: { type: "json", value: "<json|@file|->", description: "The whole declaration; overrides the flags." }
1094
1101
  },
1095
1102
  examples: [
@@ -1099,7 +1106,7 @@ var settingsSet = {
1099
1106
  { 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
1107
  { command: "tracksiy settings set --max-events-per-issue 500 --sourcemap-keep-releases 5", description: "A noisy project keeps less on disk." }
1101
1108
  ],
1102
- output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[], performanceIssues, storage{maxEventsPerIssue, sourcemapKeepReleases, sourcemapKeepUsedDays}, releaseHealth{crashFreeSessionsMin, minSessions, maxDropPct, velocityPct, velocityMinEvents}}",
1109
+ output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[], performanceIssues, storage{maxEventsPerIssue, sourcemapKeepReleases, sourcemapKeepUsedDays}, releaseHealth{crashFreeSessionsMin, minSessions, maxDropPct, velocityPct, velocityMinEvents}, logs{enabled, minLevel, dailyLimit}, attachments{store, maxKb, quotaMb, retentionDays}}",
1103
1110
  async run(ctx) {
1104
1111
  const path = await projectPath(ctx, "settings");
1105
1112
  const i = ctx.input;
@@ -1118,6 +1125,19 @@ var settingsSet = {
1118
1125
  ...i.sourcemapKeepReleases !== void 0 ? { sourcemapKeepReleases: i.sourcemapKeepReleases } : {},
1119
1126
  ...i.sourcemapKeepUsedDays !== void 0 ? { sourcemapKeepUsedDays: i.sourcemapKeepUsedDays } : {}
1120
1127
  },
1128
+ logs: {
1129
+ ...current.logs ?? {},
1130
+ ...i.logs !== void 0 ? { enabled: i.logs } : {},
1131
+ ...i.logsMinLevel !== void 0 ? { minLevel: i.logsMinLevel } : {},
1132
+ ...i.logsDailyLimit !== void 0 ? { dailyLimit: i.logsDailyLimit } : {}
1133
+ },
1134
+ attachments: {
1135
+ ...current.attachments ?? {},
1136
+ ...i.attachments !== void 0 ? { store: i.attachments } : {},
1137
+ ...i.attachmentMaxKb !== void 0 ? { maxKb: i.attachmentMaxKb } : {},
1138
+ ...i.attachmentsQuotaMb !== void 0 ? { quotaMb: i.attachmentsQuotaMb } : {},
1139
+ ...i.attachmentsRetentionDays !== void 0 ? { retentionDays: i.attachmentsRetentionDays } : {}
1140
+ },
1121
1141
  releaseHealth: {
1122
1142
  ...current.releaseHealth ?? {},
1123
1143
  ...i.crashFreeMin !== void 0 ? { crashFreeSessionsMin: i.crashFreeMin } : {},
@@ -1461,8 +1481,229 @@ ${checks}`;
1461
1481
  };
1462
1482
  var mobile_default = [mobile];
1463
1483
 
1464
- // src/commands/issues.ts
1484
+ // src/commands/logs.ts
1485
+ var LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"];
1486
+ 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)}]` : ""}`;
1487
+ function filters(i) {
1488
+ return {
1489
+ since: since(i.since),
1490
+ until: since(i.until, "until"),
1491
+ level: i.level,
1492
+ query: i.query,
1493
+ trace: i.trace,
1494
+ release: i.release,
1495
+ environment: i.environment,
1496
+ logger: i.logger,
1497
+ attribute: i.attribute,
1498
+ pattern: i.pattern,
1499
+ limit: i.limit,
1500
+ cursor: i.cursor
1501
+ };
1502
+ }
1503
+ var FILTER_FLAGS = {
1504
+ since: { type: "string", value: TIME, description: "Window start (default 1h ago; at most 31 days)." },
1505
+ until: { type: "string", value: TIME, description: "Window end (default now)." },
1506
+ level: { type: "string", enum: LEVELS, description: "The lowest level shown." },
1507
+ query: { type: "string", value: "<text>", description: "Case-insensitive text in the line." },
1508
+ trace: { type: "string", value: "<trace id>", description: "Only this trace (32 hex)." },
1509
+ release: { type: "string", value: "<version>", description: "Only this release." },
1510
+ environment: { type: "string", value: "<name>", description: "Only this environment." },
1511
+ logger: { type: "string", value: "<name>", description: "Only this logger / origin (sentry.origin, logger.name)." },
1512
+ attribute: { type: "string", value: "<key=value>", description: "Only lines with this attribute value." },
1513
+ pattern: { type: "integer", value: "<n>", description: "Only this pattern (from `logs patterns`)." }
1514
+ };
1515
+ var list = {
1516
+ path: ["logs", "list"],
1517
+ aliases: [["logs"]],
1518
+ summary: "What the app logged, newest first: by level, text, trace, release, attribute. --follow keeps printing new lines.",
1519
+ 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`.",
1520
+ context: "project",
1521
+ effect: "read",
1522
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/logs" },
1523
+ flags: {
1524
+ ...FILTER_FLAGS,
1525
+ limit: { type: "integer", value: "<n>", description: "Lines per page (default 100, at most 500)." },
1526
+ cursor: { type: "string", value: "<id>", description: "The `next` of the previous page." },
1527
+ follow: { type: "string", value: "<duration>", description: "Keep polling for new lines this long: 5m, 1h." }
1528
+ },
1529
+ examples: [
1530
+ { command: "tracksiy logs --level warn --since 30m", description: "Warnings and errors of the last half hour." },
1531
+ { command: "tracksiy logs --trace 5b8efff798038103d269b633813fc60c", description: "Everything one request logged." },
1532
+ { command: "tracksiy logs --query checkout --follow 10m", description: "Watch a deploy." }
1533
+ ],
1534
+ output: "{since, until, logs:[{id, timestamp, level, severity, body, traceId, spanId, release, environment, logger, source, pattern, attributes}], next}",
1535
+ text: (d) => `${(d.logs ?? []).slice().reverse().map(line).join("\n") || "(no lines)"}${d.next ? `
1536
+
1537
+ older: --cursor ${d.next}` : ""}`,
1538
+ async run(ctx) {
1539
+ const path = await projectPath(ctx, "logs");
1540
+ const query = filters(ctx.input);
1541
+ const followSeconds = duration(ctx.input.follow, "follow") ?? 0;
1542
+ let data = await ctx.api.get(path, query);
1543
+ if (!followSeconds) return data;
1544
+ const seen = new Set((data.logs ?? []).map((l) => l.id));
1545
+ const all = [...data.logs ?? []];
1546
+ let lastTime = all.length ? all[0].timestamp : (/* @__PURE__ */ new Date()).toISOString();
1547
+ const deadline = Date.now() + followSeconds * 1e3;
1548
+ while (Date.now() < deadline) {
1549
+ await new Promise((r) => setTimeout(r, 3e3));
1550
+ const page = await ctx.api.get(path, { ...query, since: lastTime, cursor: void 0, limit: 500 });
1551
+ const fresh = (page.logs ?? []).filter((l) => !seen.has(l.id)).reverse();
1552
+ for (const l of fresh) {
1553
+ seen.add(l.id);
1554
+ all.unshift(l);
1555
+ ctx.log(line(l));
1556
+ lastTime = l.timestamp;
1557
+ }
1558
+ }
1559
+ return { ...data, logs: all, next: null };
1560
+ }
1561
+ };
1562
+ var patterns = {
1563
+ path: ["logs", "patterns"],
1564
+ summary: "The loudest log lines, counted across their values (message template, or the body with numbers and ids blanked).",
1565
+ context: "project",
1566
+ effect: "read",
1567
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/logs/patterns" },
1568
+ flags: {
1569
+ since: FILTER_FLAGS.since,
1570
+ until: FILTER_FLAGS.until,
1571
+ level: FILTER_FLAGS.level,
1572
+ limit: { type: "integer", value: "<n>", description: "Patterns (default 100, at most 200)." }
1573
+ },
1574
+ examples: [{ command: "tracksiy logs patterns --since 24h --level warn", description: "Which warnings repeat; then `logs --pattern <n>`." }],
1575
+ output: "{patterns:[{pattern, count, level, lastSeen, template, sample}]}",
1576
+ text: (d) => table(d.patterns ?? [], ["count", "level", ["line", (r) => (r.template ?? r.sample ?? "").slice(0, 100)], "pattern"]),
1577
+ async run(ctx) {
1578
+ const i = ctx.input;
1579
+ return ctx.api.get(await projectPath(ctx, "logs/patterns"), { since: since(i.since), until: since(i.until, "until"), level: i.level, limit: i.limit });
1580
+ }
1581
+ };
1582
+ var stats = {
1583
+ path: ["logs", "stats"],
1584
+ summary: "Log counts per level per minute, hour or day.",
1585
+ context: "project",
1586
+ effect: "read",
1587
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/logs/stats" },
1588
+ flags: { since: FILTER_FLAGS.since, until: FILTER_FLAGS.until },
1589
+ output: "{bucketSeconds, totals{level: n}, buckets:[{start, counts}]}",
1590
+ text: (d) => `${kv(d.totals ?? {})}
1591
+
1592
+ ${table(d.buckets ?? [], ["start", ...LEVELS.map((lvl) => [lvl, (r) => r.counts?.[lvl] ?? ""])])}`,
1593
+ async run(ctx) {
1594
+ const i = ctx.input;
1595
+ return ctx.api.get(await projectPath(ctx, "logs/stats"), { since: since(i.since), until: since(i.until, "until") });
1596
+ }
1597
+ };
1598
+ var issueLogs = {
1599
+ path: ["issues", "logs"],
1600
+ summary: "What the app logged around an error: the logs in the traces of the issue's last events.",
1601
+ context: "project",
1602
+ effect: "read",
1603
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/issues/{id}/logs" },
1604
+ args: [{ name: "id", required: true, description: "The issue id." }],
1605
+ output: "{issueId, traces[], logs[...], notes[]}",
1606
+ text: (d) => `${(d.logs ?? []).slice().reverse().map(line).join("\n") || "(no lines)"}${(d.notes ?? []).length ? `
1607
+
1608
+ ${d.notes.join("\n")}` : ""}`,
1609
+ async run(ctx) {
1610
+ return ctx.api.get(await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/logs`));
1611
+ }
1612
+ };
1613
+ var logs_default = [list, patterns, stats, issueLogs];
1614
+
1615
+ // src/commands/evidence.ts
1465
1616
  import { writeFileSync } from "node:fs";
1617
+ var feedback = {
1618
+ path: ["user-feedback"],
1619
+ aliases: [["feedback", "list"]],
1620
+ summary: "What users wrote through the feedback widget or API: message, page or screen, release, device, the issue it is about and its screenshots.",
1621
+ context: "project",
1622
+ effect: "read",
1623
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/feedback" },
1624
+ flags: {
1625
+ since: { type: "string", value: TIME, description: "Default 30 days." },
1626
+ limit: { type: "integer", value: "<n>", description: "At most 200 (default 50)." }
1627
+ },
1628
+ examples: [{ command: "tracksiy user-feedback --since 7d", description: "This week's feedback; `attachments get <id>` saves a screenshot." }],
1629
+ output: "{feedback:[{id, created, message, name, email, url, issueId, release, environment, source, os, device, attachments[{id, kind, name, contentType, size, url}]}]}",
1630
+ text: (d) => table(d.feedback ?? [], [
1631
+ ["when", (r) => ago(r.created)],
1632
+ ["message", (r) => String(r.message ?? "").slice(0, 80)],
1633
+ ["url", (r) => r.url ?? "-"],
1634
+ ["release", (r) => r.release ?? "-"],
1635
+ ["issue", (r) => r.issueId ?? "-"],
1636
+ ["files", (r) => (r.attachments ?? []).map((a) => a.id).join(",") || "-"]
1637
+ ]),
1638
+ async run(ctx) {
1639
+ return ctx.api.get(await projectPath(ctx, "feedback"), { since: since(ctx.input.since), limit: ctx.input.limit });
1640
+ }
1641
+ };
1642
+ var issueAttachments = {
1643
+ path: ["issues", "attachments"],
1644
+ summary: "Screenshots, view hierarchies and files of an issue's recent events and of the feedback about it.",
1645
+ context: "project",
1646
+ effect: "read",
1647
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/issues/{id}/attachments" },
1648
+ args: [{ name: "id", required: true, description: "The issue id." }],
1649
+ output: "{issueId, attachments:[{id, eventId, kind, name, contentType, size, created, url}], notes[]}",
1650
+ text: (d) => `${table(d.attachments ?? [], ["id", "kind", "name", "contentType", "size", ["when", (r) => ago(r.created)]])}${(d.notes ?? []).length ? `
1651
+
1652
+ ${d.notes.join("\n")}` : ""}`,
1653
+ async run(ctx) {
1654
+ return ctx.api.get(await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/attachments`));
1655
+ }
1656
+ };
1657
+ var attachmentGet = {
1658
+ path: ["attachments", "get"],
1659
+ summary: "Save one attachment (a screenshot, a view hierarchy) to a file -- or print a JSON one.",
1660
+ context: "project",
1661
+ effect: "read",
1662
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/attachments/{id}" },
1663
+ args: [{ name: "id", required: true, description: "The attachment id (from `feedback` or `issues attachments`)." }],
1664
+ flags: { out: { type: "string", value: "<file>", description: "Where to write it. Without it, JSON and text are printed; binary needs --out." } },
1665
+ examples: [{ command: "tracksiy attachments get 812 --out /tmp/screenshot.png", description: "Then look at the screenshot." }],
1666
+ output: "{id, bytes, out} or the JSON / text content",
1667
+ async run(ctx) {
1668
+ if (!/^\d+$/.test(String(ctx.input.id ?? ""))) {
1669
+ throw usageError("bad_value", `attachments get expects a numeric attachment id, got ${JSON.stringify(ctx.input.id)}.`, "Ids come from `user-feedback` or `issues attachments`.");
1670
+ }
1671
+ const path = await projectPath(ctx, `attachments/${encodeURIComponent(ctx.input.id)}`);
1672
+ const bytes = await ctx.api.get(path, void 0, { binary: true });
1673
+ if (ctx.input.out) {
1674
+ writeFileSync(ctx.input.out, bytes);
1675
+ return { id: ctx.input.id, bytes: bytes.length, out: ctx.input.out };
1676
+ }
1677
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
1678
+ if (/[\u0000-\u0008\u000e-\u001f]/.test(text.slice(0, 512))) {
1679
+ throw usageError("binary", `Attachment ${ctx.input.id} is binary (${bytes.length} bytes).`, "Pass --out <file> to save it.");
1680
+ }
1681
+ try {
1682
+ return JSON.parse(text);
1683
+ } catch {
1684
+ return { id: ctx.input.id, text };
1685
+ }
1686
+ }
1687
+ };
1688
+ var issueFlags = {
1689
+ path: ["issues", "flags"],
1690
+ summary: "Feature flag values of an issue's events against the rest of the project; `suspect` marks a value far more common in this issue.",
1691
+ context: "project",
1692
+ effect: "read",
1693
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/issues/{id}/flags" },
1694
+ args: [{ name: "id", required: true, description: "The issue id." }],
1695
+ output: "{issueId, issueEvents, baselineEvents, flags:[{flag, suspect, suspectValue, values:[{result, issueShare, baselineShare}]}], notes[]}",
1696
+ text: (d) => `${table(d.flags ?? [], ["flag", ["suspect", (r) => r.suspect ? `YES (${r.suspectValue})` : ""], ["values", (r) => (r.values ?? []).map((v) => `${v.result}: ${Math.round(v.issueShare * 100)}% vs ${v.baselineShare == null ? "-" : Math.round(v.baselineShare * 100) + "%"}`).join(" ")]])}${(d.notes ?? []).length ? `
1697
+
1698
+ ${d.notes.join("\n")}` : ""}`,
1699
+ async run(ctx) {
1700
+ return ctx.api.get(await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/flags`));
1701
+ }
1702
+ };
1703
+ var evidence_default = [feedback, issueAttachments, attachmentGet, issueFlags];
1704
+
1705
+ // src/commands/issues.ts
1706
+ import { writeFileSync as writeFileSync2 } from "node:fs";
1466
1707
  var FILTERS = {
1467
1708
  status: { type: "string", enum: ["unresolved", "resolved", "ignored", "regressed", "all"], description: "Issue status. Server default: unresolved." },
1468
1709
  level: { type: "string", enum: ["sample", "debug", "info", "warning", "error", "fatal"], description: "Only this level." },
@@ -1518,7 +1759,7 @@ var issueRows = (issues) => table(issues ?? [], [
1518
1759
  ["status", (r) => r.regressedAt && r.status === "unresolved" ? "regressed" : r.status],
1519
1760
  ["title", (r) => r.title]
1520
1761
  ]);
1521
- var list = {
1762
+ var list2 = {
1522
1763
  path: ["issues", "list"],
1523
1764
  aliases: [["issues"]],
1524
1765
  summary: "Recent issues of a project, newest activity first, with the frames that matter inlined.",
@@ -1812,13 +2053,13 @@ var bulk = {
1812
2053
  });
1813
2054
  const result = await ctx.api.send("POST", await projectPath(ctx, "issues/status"), body);
1814
2055
  if (ctx.input.out && ctx.input.apply) {
1815
- writeFileSync(ctx.input.out, JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), status: ctx.input.to, changed: result?.changed ?? [] }, null, 2));
2056
+ writeFileSync2(ctx.input.out, JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), status: ctx.input.to, changed: result?.changed ?? [] }, null, 2));
1816
2057
  ctx.log(`wrote ${result?.changed?.length ?? 0} change(s) to ${ctx.input.out}; undo with --revert ${ctx.input.out} --apply`);
1817
2058
  }
1818
2059
  return result;
1819
2060
  }
1820
2061
  };
1821
- var issues_default = [list, view, resolve2, ignore, unresolve, bulk, merge, comment, activity, verifyFixed, claim, unclaim];
2062
+ var issues_default = [list2, view, resolve2, ignore, unresolve, bulk, merge, comment, activity, verifyFixed, claim, unclaim];
1822
2063
 
1823
2064
  // src/commands/projects.ts
1824
2065
  var create = {
@@ -1933,8 +2174,8 @@ var DECLARE = {
1933
2174
  "replace-recipients": { type: "boolean", description: "Make the recipient list exactly this one (default: add to it)." },
1934
2175
  body: { type: "json", value: "<json|@file|->", description: "Raw request body; overrides the other flags." }
1935
2176
  };
1936
- function recipients(list5, delivery) {
1937
- return (list5 ?? []).map((raw) => {
2177
+ function recipients(list6, delivery) {
2178
+ return (list6 ?? []).map((raw) => {
1938
2179
  const i = raw.indexOf(":");
1939
2180
  const type = i > 0 ? raw.slice(0, i) : raw;
1940
2181
  if (!RECIPIENT_TYPES.includes(type)) {
@@ -2115,7 +2356,7 @@ var settingsSet2 = {
2115
2356
  var alerts_default = [get, set, test, coverage, defaults, settingsGet2, settingsSet2];
2116
2357
 
2117
2358
  // src/commands/keys.ts
2118
- var list2 = {
2359
+ var list3 = {
2119
2360
  path: ["keys", "list"],
2120
2361
  aliases: [["keys"]],
2121
2362
  summary: "Every ingest key of the project: its surface, whether ingest accepts it, its rate limit and volume.",
@@ -2172,11 +2413,11 @@ var rotate = {
2172
2413
  return ctx.api.send("POST", await projectPath(ctx, `keys/${seg(ctx.input.name)}/rotate`));
2173
2414
  }
2174
2415
  };
2175
- var keys_default = [list2, set2, rotate];
2416
+ var keys_default = [list3, set2, rotate];
2176
2417
 
2177
2418
  // src/commands/releases.ts
2178
2419
  import { execFileSync } from "node:child_process";
2179
- var list3 = {
2420
+ var list4 = {
2180
2421
  path: ["releases", "list"],
2181
2422
  aliases: [["releases"]],
2182
2423
  summary: "What did the last deploys break? Recent releases with their events, issues and the issues they introduced.",
@@ -2322,7 +2563,7 @@ ${table(d.newIssues, ["short_id", "count", "title"])}` : ""}`,
2322
2563
  return data;
2323
2564
  }
2324
2565
  };
2325
- var releases_default = [list3, health2, declare, verdict];
2566
+ var releases_default = [list4, health2, declare, verdict];
2326
2567
 
2327
2568
  // src/commands/artifacts.ts
2328
2569
  import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
@@ -2763,7 +3004,7 @@ var artifacts_default = [sourcemapsInject, sourcemapsUpload, proguardUpload, art
2763
3004
 
2764
3005
  // src/commands/tokens.ts
2765
3006
  var SCOPE_HINT = "e.g. org:read,project:read,event:read,event:write,project:write,project:releases,project:admin";
2766
- var list4 = {
3007
+ var list5 = {
2767
3008
  path: ["tokens", "list"],
2768
3009
  aliases: [["tokens"]],
2769
3010
  summary: "Your tokens, newest first, without their values.",
@@ -2876,7 +3117,7 @@ var self = {
2876
3117
  return ctx.api.get("tokens/self");
2877
3118
  }
2878
3119
  };
2879
- var tokens_default = [list4, self, create2, update, remove, rotate2];
3120
+ var tokens_default = [list5, self, create2, update, remove, rotate2];
2880
3121
 
2881
3122
  // src/commands/support.ts
2882
3123
  var SUPPORT = {
@@ -3540,15 +3781,15 @@ function makeContext(spec, input, globals, surface, io) {
3540
3781
  const org = () => {
3541
3782
  orgPromise ??= (async () => {
3542
3783
  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;
3784
+ const list6 = await visible();
3785
+ if (list6.length === 1) {
3786
+ log(`using organization ${list6[0].slug}`);
3787
+ return list6[0].slug;
3547
3788
  }
3548
3789
  throw usageError(
3549
3790
  "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`."
3791
+ list6.length ? "Which organization? This token can see several." : "This token cannot see any organization.",
3792
+ 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
3793
  );
3553
3794
  })();
3554
3795
  return orgPromise;
@@ -3558,8 +3799,8 @@ function makeContext(spec, input, globals, surface, io) {
3558
3799
  projectPromise ??= (async () => {
3559
3800
  const o = await org();
3560
3801
  if (config.project) return { org: o, project: config.project };
3561
- const list5 = await visible();
3562
- const projects2 = list5.find((x) => x.slug === o)?.projects ?? [];
3802
+ const list6 = await visible();
3803
+ const projects2 = list6.find((x) => x.slug === o)?.projects ?? [];
3563
3804
  if (projects2.length === 1) {
3564
3805
  log(`using project ${o}/${projects2[0].slug}`);
3565
3806
  return { org: o, project: projects2[0].slug };
@@ -3724,11 +3965,11 @@ async function serve(io, defaults2) {
3724
3965
  const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
3725
3966
  io.stderr(`tracksiy mcp ${VERSION}: ${server.toolCount} tools on stdio
3726
3967
  `);
3727
- for await (const line of rl) {
3728
- if (!line.trim()) continue;
3968
+ for await (const line2 of rl) {
3969
+ if (!line2.trim()) continue;
3729
3970
  let msg;
3730
3971
  try {
3731
- msg = JSON.parse(line);
3972
+ msg = JSON.parse(line2);
3732
3973
  } catch {
3733
3974
  write({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
3734
3975
  continue;
@@ -3782,6 +4023,8 @@ var COMMANDS = [
3782
4023
  ...perf_default,
3783
4024
  ...storage_default,
3784
4025
  ...mobile_default,
4026
+ ...logs_default,
4027
+ ...evidence_default,
3785
4028
  ...issues_default,
3786
4029
  ...projects_default,
3787
4030
  ...alerts_default,
@@ -3795,7 +4038,7 @@ var COMMANDS = [
3795
4038
  ];
3796
4039
  var GROUPS = [
3797
4040
  ["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"],
4041
+ ["Look", "overview changes health storage orgs projects issues monitors performance mobile logs user-feedback attachments queries not-found releases alerts keys artifacts settings privacy-preview"],
3799
4042
  ["Ship", "sourcemaps proguard"],
3800
4043
  ["Manage", "tokens"],
3801
4044
  ["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.12.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": {