tracksiy 0.11.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
@@ -143,6 +143,10 @@ Labels: `bug`, `feature`, `question`, `blocker`. `npx tracksiy support` prints t
143
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
144
  | `logs stats` | Log counts per level per minute, hour or day. | read | `logs_stats` |
145
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` |
146
150
  | `issues list` | Recent issues of a project, newest activity first, with the frames that matter inlined. | read | `issues_list` |
147
151
  | `issues view <id>` | One issue in enough depth to act on: latest event, stack frames, tags, recent volume. | read | `issues_view` |
148
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.11.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);
@@ -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}, logs{enabled, minLevel, dailyLimit}}",
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
  }
@@ -1093,6 +1093,10 @@ var settingsSet = {
1093
1093
  logs: { type: "boolean", description: "Store structured logs at all (default on). --no-logs drops them, counted as log:disabled." },
1094
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
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)." },
1096
1100
  body: { type: "json", value: "<json|@file|->", description: "The whole declaration; overrides the flags." }
1097
1101
  },
1098
1102
  examples: [
@@ -1102,7 +1106,7 @@ var settingsSet = {
1102
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." },
1103
1107
  { command: "tracksiy settings set --max-events-per-issue 500 --sourcemap-keep-releases 5", description: "A noisy project keeps less on disk." }
1104
1108
  ],
1105
- output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[], performanceIssues, storage{maxEventsPerIssue, sourcemapKeepReleases, sourcemapKeepUsedDays}, releaseHealth{crashFreeSessionsMin, minSessions, maxDropPct, velocityPct, velocityMinEvents}, logs{enabled, minLevel, dailyLimit}}",
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}}",
1106
1110
  async run(ctx) {
1107
1111
  const path = await projectPath(ctx, "settings");
1108
1112
  const i = ctx.input;
@@ -1127,6 +1131,13 @@ var settingsSet = {
1127
1131
  ...i.logsMinLevel !== void 0 ? { minLevel: i.logsMinLevel } : {},
1128
1132
  ...i.logsDailyLimit !== void 0 ? { dailyLimit: i.logsDailyLimit } : {}
1129
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
+ },
1130
1141
  releaseHealth: {
1131
1142
  ...current.releaseHealth ?? {},
1132
1143
  ...i.crashFreeMin !== void 0 ? { crashFreeSessionsMin: i.crashFreeMin } : {},
@@ -1601,8 +1612,98 @@ ${d.notes.join("\n")}` : ""}`,
1601
1612
  };
1602
1613
  var logs_default = [list, patterns, stats, issueLogs];
1603
1614
 
1604
- // src/commands/issues.ts
1615
+ // src/commands/evidence.ts
1605
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";
1606
1707
  var FILTERS = {
1607
1708
  status: { type: "string", enum: ["unresolved", "resolved", "ignored", "regressed", "all"], description: "Issue status. Server default: unresolved." },
1608
1709
  level: { type: "string", enum: ["sample", "debug", "info", "warning", "error", "fatal"], description: "Only this level." },
@@ -1952,7 +2053,7 @@ var bulk = {
1952
2053
  });
1953
2054
  const result = await ctx.api.send("POST", await projectPath(ctx, "issues/status"), body);
1954
2055
  if (ctx.input.out && ctx.input.apply) {
1955
- 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));
1956
2057
  ctx.log(`wrote ${result?.changed?.length ?? 0} change(s) to ${ctx.input.out}; undo with --revert ${ctx.input.out} --apply`);
1957
2058
  }
1958
2059
  return result;
@@ -3923,6 +4024,7 @@ var COMMANDS = [
3923
4024
  ...storage_default,
3924
4025
  ...mobile_default,
3925
4026
  ...logs_default,
4027
+ ...evidence_default,
3926
4028
  ...issues_default,
3927
4029
  ...projects_default,
3928
4030
  ...alerts_default,
@@ -3936,7 +4038,7 @@ var COMMANDS = [
3936
4038
  ];
3937
4039
  var GROUPS = [
3938
4040
  ["Start", "init auth onboarding describe"],
3939
- ["Look", "overview changes health storage orgs projects issues monitors performance mobile logs 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"],
3940
4042
  ["Ship", "sourcemaps proguard"],
3941
4043
  ["Manage", "tokens"],
3942
4044
  ["Talk to us", "support"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracksiy",
3
- "version": "0.11.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": {