tracksiy 0.4.0 → 0.6.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
@@ -127,12 +127,17 @@ Labels: `bug`, `feature`, `question`, `blocker`. `npx tracksiy support` prints t
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
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` |
130
+ | `not-found` | Which pages answer 404, and who links to them: broken internal links first. | read | `not_found` |
131
+ | `settings` | The project's reading rules: environments, grouping rules, privacy rules, source roots. | read | `settings` |
132
+ | `settings set` | Change the project's reading rules. Only the flags given change; the rest are kept. | write | `settings_set` |
133
+ | `privacy-preview` | Try privacy rules on the project's last 50 stored events: what each would change and what PII shapes would remain. Writes nothing. | read | `privacy_preview` |
130
134
  | `issues list` | Recent issues of a project, newest activity first, with the frames that matter inlined. | read | `issues_list` |
131
135
  | `issues view <id>` | One issue in enough depth to act on: latest event, stack frames, tags, recent volume. | read | `issues_view` |
132
136
  | `issues resolve <ids...>` | Mark issues resolved, optionally fixed in a release so old clients do not reopen them. Records why. | write | `issues_resolve` |
133
137
  | `issues ignore <ids...>` | Mark issues ignored: they stop alerting until unignored. Records why. | write | `issues_ignore` |
134
138
  | `issues unresolve <ids...>` | Reopen issues. | write | `issues_unresolve` |
135
139
  | `issues bulk-status` | Change the status of every issue matching a filter. Dry run unless --apply. | write | `issues_bulk_status` |
140
+ | `issues merge <ids...>` | Fold issues that are one root cause into one. Dry run unless --apply; later events of any of them land on the target. | write | `issues_merge` |
136
141
  | `issues comment <id>` | Leave a comment on an issue for the next agent or human: what you found, what you tried. | write | `issues_comment` |
137
142
  | `issues activity <id>` | Who did what to an issue and why: resolutions (and in which release), regressions, comments, claims. | read | `issues_activity` |
138
143
  | `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` |
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.4.0" : "0.0.0-dev";
7
+ var VERSION = true ? "0.6.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
@@ -116,6 +116,7 @@ var Api = class {
116
116
  const useAuth = opts.auth !== false;
117
117
  if (useAuth && this.opts.token) headers.Authorization = `Bearer ${this.opts.token}`;
118
118
  if (this.opts.agent) headers["X-Tracksiy-Agent"] = this.opts.agent;
119
+ if (method !== "GET" && method !== "HEAD") headers["Idempotency-Key"] = opts.idempotencyKey ?? crypto.randomUUID();
119
120
  let body;
120
121
  if (opts.form) body = opts.form;
121
122
  else if (opts.body !== void 0) {
@@ -855,6 +856,7 @@ var monitorsDeclare = {
855
856
  "expect-no-text": { type: "string", value: "<text>", description: "--get: down if the body contains this, e.g. Whoops or Exception." },
856
857
  "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
858
  "dry-run": { type: "boolean", description: "Show what would be created, adopted or changed; write nothing." },
859
+ prune: { type: "boolean", description: "The declaration is the whole list: delete this project's monitors of the declared types it does not name (listed in `pruned`)." },
858
860
  body: { type: "json", value: "<json|@file|->", description: "Raw request body; overrides the shorthand flags." }
859
861
  },
860
862
  examples: [
@@ -897,6 +899,7 @@ var monitorsDeclare = {
897
899
  body = { monitors };
898
900
  }
899
901
  if (i.dryRun) body = { ...body, dryRun: true };
902
+ if (i.prune) body = { ...body, prune: true };
900
903
  return ctx.api.send("POST", await projectPath(ctx, "monitors"), body);
901
904
  }
902
905
  };
@@ -998,9 +1001,105 @@ var queries = {
998
1001
  });
999
1002
  }
1000
1003
  };
1001
- var fleet_default = [orgs, projects, overview, changes, health, monitorsList, monitorsDeclare, monitorsDelete, performance, queries];
1004
+ var notFound = {
1005
+ path: ["not-found"],
1006
+ aliases: [["404"]],
1007
+ summary: "Which pages answer 404, and who links to them: broken internal links first.",
1008
+ context: "project",
1009
+ effect: "read",
1010
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/not-found" },
1011
+ flags: {
1012
+ since: { type: "string", value: TIME, description: "Window start (default 24h)." },
1013
+ internal: { type: "boolean", description: "Only 404s reached from the site's own pages." },
1014
+ limit: { type: "integer", value: "<n>", description: "How many paths (max 200)." }
1015
+ },
1016
+ examples: [{ command: "tracksiy not-found --internal --since 7d", description: "Broken links the site itself serves." }],
1017
+ output: "{since, until, scanned, pages:[{path, count, lastSeen, internal, bots, referrers[{referrer, count}]}], note}",
1018
+ text: (d) => table(d.pages ?? [], ["path", "count", ["internal", (r) => r.internal ? "yes" : ""], "bots", ["top referrer", (r) => r.referrers?.[0]?.referrer ?? "-"]]),
1019
+ async run(ctx) {
1020
+ return ctx.api.get(await projectPath(ctx, "not-found"), { since: since(ctx.input.since), internal: ctx.input.internal || void 0, limit: ctx.input.limit });
1021
+ }
1022
+ };
1023
+ var settingsGet = {
1024
+ path: ["settings"],
1025
+ summary: "The project's reading rules: environments, grouping rules, privacy rules, source roots.",
1026
+ context: "project",
1027
+ effect: "read",
1028
+ endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/settings" },
1029
+ output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[]}",
1030
+ async run(ctx) {
1031
+ return ctx.api.get(await projectPath(ctx, "settings"));
1032
+ }
1033
+ };
1034
+ function pairs(items, flag) {
1035
+ if (!items) return void 0;
1036
+ return items.map((item) => {
1037
+ const out = {};
1038
+ for (const part of String(item).split(/,(?=\s*\w+=)/)) {
1039
+ const eq = part.indexOf("=");
1040
+ if (eq < 1) throw usageError("bad_value", `--${flag} expects key=value,key=value, got ${JSON.stringify(item)}`);
1041
+ out[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();
1042
+ }
1043
+ return out;
1044
+ });
1045
+ }
1046
+ var settingsSet = {
1047
+ path: ["settings", "set"],
1048
+ summary: "Change the project's reading rules. Only the flags given change; the rest are kept.",
1049
+ description: "Applied to events ingested from then on. --environments + --environment-policy drop discards (and counts) events from any other environment; flag stores them tagged environment_unlisted. --fingerprint-rule groups matching events into one issue. --privacy-rule drops a header or masks a PII shape before storage -- try it first with `tracksiy privacy-preview`. --source-root makes frames carry repoPath.",
1050
+ context: "project",
1051
+ effect: "write",
1052
+ idempotent: true,
1053
+ endpoint: { method: "PUT", path: "/api/agent/v1/projects/{org}/{project}/settings" },
1054
+ flags: {
1055
+ environments: { type: "list", value: "<env,...>", description: "The environments this project reports from (empty string: any)." },
1056
+ "environment-policy": { type: "string", enum: ["flag", "drop"], description: "What happens to an event from another environment." },
1057
+ "source-root": { type: "list", value: "<path>", description: "A deployed-code prefix to strip from frame paths. Repeatable; replaces the list." },
1058
+ "fingerprint-rule": { type: "list", value: "type=<glob>,message=<glob>,fingerprint=<name>", description: "Group matching events into one issue. Repeatable; replaces the list." },
1059
+ "privacy-rule": { type: "list", value: "dropHeader=<name> | mask=<email|iban|card_number|tckn|jwt>,field=<path|*>", description: "Scrub before storage. Repeatable; replaces the list." },
1060
+ body: { type: "json", value: "<json|@file|->", description: "The whole declaration; overrides the flags." }
1061
+ },
1062
+ examples: [
1063
+ { command: "tracksiy settings set --environments production,staging --environment-policy drop", description: "local and local1 stop reaching Tracksiy." },
1064
+ { command: "tracksiy settings set --fingerprint-rule 'type=ViewException,message=syntax error*,fingerprint=blade-syntax'", description: "Every half-written template is one issue." },
1065
+ { command: "tracksiy settings set --source-root /home/admin/web/shop/", description: "Frames carry repoPath." }
1066
+ ],
1067
+ output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[]}",
1068
+ async run(ctx) {
1069
+ const path = await projectPath(ctx, "settings");
1070
+ const i = ctx.input;
1071
+ if (i.body) return ctx.api.send("PUT", path, i.body);
1072
+ const current = await ctx.api.get(path);
1073
+ const next = {
1074
+ sourceRoots: i.sourceRoot ?? current.sourceRoots ?? [],
1075
+ environments: i.environments ? i.environments.filter((e) => e) : current.environments ?? [],
1076
+ environmentPolicy: i.environmentPolicy ?? current.environmentPolicy ?? "flag",
1077
+ fingerprintRules: pairs(i.fingerprintRule, "fingerprint-rule") ?? current.fingerprintRules ?? [],
1078
+ privacyRules: pairs(i.privacyRule, "privacy-rule") ?? current.privacyRules ?? []
1079
+ };
1080
+ return ctx.api.send("PUT", path, next);
1081
+ }
1082
+ };
1083
+ var privacyPreview = {
1084
+ path: ["privacy-preview"],
1085
+ summary: "Try privacy rules on the project's last 50 stored events: what each would change and what PII shapes would remain. Writes nothing.",
1086
+ context: "project",
1087
+ effect: "read",
1088
+ endpoint: { method: "POST", path: "/api/agent/v1/projects/{org}/{project}/settings/privacy-preview" },
1089
+ flags: {
1090
+ "privacy-rule": { type: "list", value: "dropHeader=<name> | mask=<shape>,field=<path|*>", description: "Rules to try (default: the saved ones)." }
1091
+ },
1092
+ examples: [{ command: "tracksiy privacy-preview --privacy-rule 'mask=email,field=request.headers.User-Agent'", description: "Would this clear the PII check?" }],
1093
+ output: "{sampled, rules[{rule, events, values}], remaining[{pattern, field, events}]}",
1094
+ async run(ctx) {
1095
+ const rules = pairs(ctx.input.privacyRule, "privacy-rule");
1096
+ return ctx.api.send("POST", await projectPath(ctx, "settings/privacy-preview"), rules ? { privacyRules: rules } : {});
1097
+ }
1098
+ };
1099
+ var fleet_default = [orgs, projects, overview, changes, health, monitorsList, monitorsDeclare, monitorsDelete, performance, queries, notFound, settingsGet, settingsSet, privacyPreview];
1002
1100
 
1003
1101
  // src/commands/issues.ts
1102
+ import { writeFileSync } from "node:fs";
1004
1103
  var FILTERS = {
1005
1104
  status: { type: "string", enum: ["unresolved", "resolved", "ignored", "regressed", "all"], description: "Issue status. Server default: unresolved." },
1006
1105
  level: { type: "string", enum: ["sample", "debug", "info", "warning", "error", "fatal"], description: "Only this level." },
@@ -1010,7 +1109,9 @@ var FILTERS = {
1010
1109
  since: { type: "string", value: TIME, description: "Last seen at or after this (lastSeenSince)." },
1011
1110
  until: { type: "string", value: TIME, description: "Last seen before this (lastSeenBefore)." },
1012
1111
  "first-seen-since": { type: "string", value: TIME, description: "First seen at or after this: new issues only." },
1013
- "first-seen-until": { type: "string", value: TIME, description: "First seen before this." }
1112
+ "first-seen-until": { type: "string", value: TIME, description: "First seen before this." },
1113
+ "exclude-bots": { type: "boolean", description: "Leave out issues every event of which came from a crawler (ClaudeBot, Googlebot, ...)." },
1114
+ "only-bots": { type: "boolean", description: "Only issues that came entirely from crawlers." }
1014
1115
  };
1015
1116
  var WHY = {
1016
1117
  reason: { type: "string", enum: ["fixed", "duplicate", "third_party", "expected", "wont_fix"], description: "Why, recorded on the issue's activity log." },
@@ -1042,7 +1143,8 @@ function filterQuery(input) {
1042
1143
  lastSeenSince: since(input.since),
1043
1144
  lastSeenBefore: since(input.until, "until"),
1044
1145
  firstSeenSince: since(input.firstSeenSince, "first-seen-since"),
1045
- firstSeenBefore: since(input.firstSeenUntil, "first-seen-until")
1146
+ firstSeenBefore: since(input.firstSeenUntil, "first-seen-until"),
1147
+ bots: input.excludeBots ? "exclude" : input.onlyBots ? "only" : void 0
1046
1148
  });
1047
1149
  }
1048
1150
  var issueRows = (issues) => table(issues ?? [], [
@@ -1066,6 +1168,7 @@ var list = {
1066
1168
  limit: { type: "integer", value: "<n>", description: "How many issues (server default 25)." },
1067
1169
  frames: { type: "integer", value: "<n>", description: "In-app stack frames inlined per issue (default 3, 0 for none)." },
1068
1170
  unclaimed: { type: "boolean", description: "Only issues nobody holds a live claim on: what you can pick up." },
1171
+ cursor: { type: "string", value: "<nextCursor>", description: "Continue from the previous page's nextCursor (same filters and sort)." },
1069
1172
  "claimed-by": { type: "string", value: "<handle>", description: "Only issues claimed by this handle." }
1070
1173
  },
1071
1174
  examples: [
@@ -1074,7 +1177,7 @@ var list = {
1074
1177
  { command: "tracksiy issues list --status regressed --select issues.id,issues.title", description: "Only regressions, only ids and titles." },
1075
1178
  { command: "tracksiy issues list --first-seen-since 2h --environment production", description: "What is new in production." }
1076
1179
  ],
1077
- output: "{project, issueCount, total, hasMore, issues:[{id, shortId, title, culprit, level, status, count, firstSeen, lastSeen, regressedAt, timesRegressed, resolution?, staleEvents, claimedBy?, claimedUntil?, permalink, frames?}]}",
1180
+ output: "{project, issueCount, total, hasMore, nextCursor, issues:[{id, shortId, title, culprit, level, status, count, firstSeen, lastSeen, regressedAt, timesRegressed, resolution?, staleEvents, claimedBy?, claimedUntil?, permalink, frames?}]}",
1078
1181
  text: (d) => `${d.issueCount ?? d.issues?.length ?? 0} of ${d.total ?? "?"} issues${d.hasMore ? " (more: raise --limit)" : ""}
1079
1182
  ${issueRows(d.issues)}`,
1080
1183
  async run(ctx) {
@@ -1084,7 +1187,8 @@ ${issueRows(d.issues)}`,
1084
1187
  limit: ctx.input.limit,
1085
1188
  frames: ctx.input.frames,
1086
1189
  unclaimed: ctx.input.unclaimed || void 0,
1087
- claimedBy: ctx.input.claimedBy
1190
+ claimedBy: ctx.input.claimedBy,
1191
+ cursor: ctx.input.cursor
1088
1192
  });
1089
1193
  }
1090
1194
  };
@@ -1190,6 +1294,29 @@ var activity = {
1190
1294
  return ctx.api.get(await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/activity`), { limit: ctx.input.limit });
1191
1295
  }
1192
1296
  };
1297
+ var merge = {
1298
+ path: ["issues", "merge"],
1299
+ summary: "Fold issues that are one root cause into one. Dry run unless --apply; later events of any of them land on the target.",
1300
+ context: "project",
1301
+ effect: "write",
1302
+ endpoint: { method: "POST", path: "/api/agent/v1/projects/{org}/{project}/issues/merge" },
1303
+ args: [{ name: "ids", required: true, variadic: true, description: "The issues to fold in." }],
1304
+ flags: {
1305
+ into: { type: "string", required: true, value: "<id>", description: "The issue they become." },
1306
+ note: WHY.note,
1307
+ apply: { type: "boolean", description: "Actually merge. Default is a dry run." }
1308
+ },
1309
+ examples: [{ command: "tracksiy issues merge 5519 5526 --into 5515 --note 'same Blade template, same deploy' --apply", description: "Three issues, one cause." }],
1310
+ output: "{dryRun, into{...issue}, merged[], events}",
1311
+ async run(ctx) {
1312
+ return ctx.api.send("POST", await projectPath(ctx, "issues/merge"), {
1313
+ into: String(ctx.input.into),
1314
+ ids: ctx.input.ids.map(String),
1315
+ note: textArg(ctx.input.note, "note"),
1316
+ dryRun: !ctx.input.apply
1317
+ });
1318
+ }
1319
+ };
1193
1320
  var verifyFixed = {
1194
1321
  path: ["issues", "verify-fixed"],
1195
1322
  summary: "Did the fix hold? Judged by the fix release's own traffic: fixed, regressed or undetermined. Exit 10 unless fixed.",
@@ -1270,11 +1397,13 @@ var bulk = {
1270
1397
  effect: "write",
1271
1398
  endpoint: { method: "POST", path: "/api/agent/v1/projects/{org}/{project}/issues/status" },
1272
1399
  flags: {
1273
- to: { type: "string", required: true, enum: ["resolved", "ignored", "unresolved"], description: "New status." },
1400
+ to: { type: "string", enum: ["resolved", "ignored", "unresolved"], description: "New status (required unless --revert)." },
1274
1401
  ids: { type: "list", value: "<id,...>", description: "Exactly these issues." },
1275
1402
  ...FILTERS,
1276
1403
  all: { type: "boolean", description: "Every issue of the project (required when no other selector is given)." },
1277
1404
  apply: { type: "boolean", description: "Actually change them. Default is a dry run." },
1405
+ out: { type: "string", value: "<file.json>", description: "With --apply: write every changed issue and its previous status here, for --revert." },
1406
+ revert: { type: "string", value: "<file.json>", description: "Undo a saved --out: put each issue back to the status it had." },
1278
1407
  ...FIXED_IN,
1279
1408
  ...WHY
1280
1409
  },
@@ -1284,6 +1413,18 @@ var bulk = {
1284
1413
  ],
1285
1414
  output: "{dryRun, matched, changed, ids, ...} as reported by the server.",
1286
1415
  async run(ctx) {
1416
+ if (ctx.input.revert) {
1417
+ const saved = JSON.parse(textArg(`@${ctx.input.revert}`, "revert") ?? "{}");
1418
+ const changes2 = saved.changed ?? saved;
1419
+ const byStatus = /* @__PURE__ */ new Map();
1420
+ for (const c of changes2) if (c?.id && c.previousStatus) byStatus.set(c.previousStatus, [...byStatus.get(c.previousStatus) ?? [], String(c.id)]);
1421
+ const results = {};
1422
+ for (const [status2, ids] of byStatus) {
1423
+ results[status2] = await ctx.api.send("POST", await projectPath(ctx, "issues/status"), { status: status2, ids, dryRun: !ctx.input.apply, note: textArg(ctx.input.note, "note") ?? `revert of ${ctx.input.revert}` });
1424
+ }
1425
+ return { reverted: changes2.length, dryRun: !ctx.input.apply, results };
1426
+ }
1427
+ if (!ctx.input.to) throw usageError("missing_flag", "--to is required.", "Pass --to resolved|ignored|unresolved, or --revert <file>.");
1287
1428
  const filter = filterQuery(ctx.input);
1288
1429
  const hasFilter = Object.keys(filter).length > 0;
1289
1430
  const body = compact({
@@ -1294,10 +1435,15 @@ var bulk = {
1294
1435
  dryRun: !ctx.input.apply,
1295
1436
  ...whyBody(ctx.input)
1296
1437
  });
1297
- return ctx.api.send("POST", await projectPath(ctx, "issues/status"), body);
1438
+ const result = await ctx.api.send("POST", await projectPath(ctx, "issues/status"), body);
1439
+ if (ctx.input.out && ctx.input.apply) {
1440
+ writeFileSync(ctx.input.out, JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), status: ctx.input.to, changed: result?.changed ?? [] }, null, 2));
1441
+ ctx.log(`wrote ${result?.changed?.length ?? 0} change(s) to ${ctx.input.out}; undo with --revert ${ctx.input.out} --apply`);
1442
+ }
1443
+ return result;
1298
1444
  }
1299
1445
  };
1300
- var issues_default = [list, view, resolve2, ignore, unresolve, bulk, comment, activity, verifyFixed, claim, unclaim];
1446
+ var issues_default = [list, view, resolve2, ignore, unresolve, bulk, merge, comment, activity, verifyFixed, claim, unclaim];
1301
1447
 
1302
1448
  // src/commands/projects.ts
1303
1449
  var create = {
@@ -1544,7 +1690,7 @@ var defaults = {
1544
1690
  return ctx.api.send("PUT", await orgPath(ctx, "alerts/defaults"), body);
1545
1691
  }
1546
1692
  };
1547
- var settingsGet = {
1693
+ var settingsGet2 = {
1548
1694
  path: ["alerts", "settings"],
1549
1695
  summary: "When digest-mode recipients are told, and whether the last digests went out.",
1550
1696
  context: "org",
@@ -1559,7 +1705,7 @@ ${table(d.recentDigests ?? [], [["sent", (r) => ago(r.sentAt)], "type", "ok", "i
1559
1705
  return ctx.api.get(await orgPath(ctx, "alerts/settings"));
1560
1706
  }
1561
1707
  };
1562
- var settingsSet = {
1708
+ var settingsSet2 = {
1563
1709
  path: ["alerts", "settings", "set"],
1564
1710
  summary: "Declare the digest hours and timezone.",
1565
1711
  context: "org",
@@ -1581,7 +1727,7 @@ var settingsSet = {
1581
1727
  return ctx.api.send("PUT", await orgPath(ctx, "alerts/settings"), { digest: compact({ hours, timezone: ctx.input.timezone }) });
1582
1728
  }
1583
1729
  };
1584
- var alerts_default = [get, set, test, coverage, defaults, settingsGet, settingsSet];
1730
+ var alerts_default = [get, set, test, coverage, defaults, settingsGet2, settingsSet2];
1585
1731
 
1586
1732
  // src/commands/keys.ts
1587
1733
  var list2 = {
@@ -1781,7 +1927,7 @@ function injectMap(json, id) {
1781
1927
  return JSON.stringify(map);
1782
1928
  }
1783
1929
  async function findPairs(paths) {
1784
- const pairs = [];
1930
+ const pairs2 = [];
1785
1931
  const seen = /* @__PURE__ */ new Set();
1786
1932
  for (const p of paths) {
1787
1933
  const abs = resolve3(p);
@@ -1800,10 +1946,10 @@ async function findPairs(paths) {
1800
1946
  if (existsSync2(candidate)) map = candidate;
1801
1947
  }
1802
1948
  if (!map && existsSync2(`${js}.map`)) map = `${js}.map`;
1803
- pairs.push({ js, map, root });
1949
+ pairs2.push({ js, map, root });
1804
1950
  }
1805
1951
  }
1806
- return pairs;
1952
+ return pairs2;
1807
1953
  }
1808
1954
  function artifactName(file, root, prefix) {
1809
1955
  const rel = relative(root, file).split(sep).join("/");
@@ -1974,9 +2120,9 @@ async function scan(paths) {
1974
2120
  throw usageError("bad_path", err.message);
1975
2121
  }
1976
2122
  }
1977
- async function inject(pairs, dryRun) {
2123
+ async function inject(pairs2, dryRun) {
1978
2124
  const out = [];
1979
- for (const pair of pairs) {
2125
+ for (const pair of pairs2) {
1980
2126
  if (!pair.map) {
1981
2127
  out.push({ file: pair.js, map: null, debugId: "", action: "skipped-no-map" });
1982
2128
  continue;
@@ -2053,13 +2199,13 @@ ${table(d.files, ["kind", "state", "name", "detail"])}${d.skipped?.length ? `
2053
2199
  skipped, no source map: ${d.skipped.join(", ")}` : ""}`,
2054
2200
  async run(ctx) {
2055
2201
  const i = ctx.input;
2056
- const pairs = await scan(i.paths);
2057
- if (!pairs.length) throw usageError("nothing_to_upload", "No JavaScript files found.", "Point at the build output directory (dist, build, public/build).");
2058
- if (i.inject && !i.dryRun) await inject(pairs, false);
2059
- const skipped = pairs.filter((p) => !p.map).map((p) => artifactName(p.js, p.root, i.urlPrefix));
2060
- const withMaps = pairs.filter((p) => p.map);
2202
+ const pairs2 = await scan(i.paths);
2203
+ if (!pairs2.length) throw usageError("nothing_to_upload", "No JavaScript files found.", "Point at the build output directory (dist, build, public/build).");
2204
+ if (i.inject && !i.dryRun) await inject(pairs2, false);
2205
+ const skipped = pairs2.filter((p) => !p.map).map((p) => artifactName(p.js, p.root, i.urlPrefix));
2206
+ const withMaps = pairs2.filter((p) => p.map);
2061
2207
  if (!withMaps.length) {
2062
- throw usageError("nothing_to_upload", `None of the ${pairs.length} JavaScript file(s) has a source map.`, "Build with source maps on (e.g. Vite build.sourcemap: true, webpack devtool: 'source-map').");
2208
+ throw usageError("nothing_to_upload", `None of the ${pairs2.length} JavaScript file(s) has a source map.`, "Build with source maps on (e.g. Vite build.sourcemap: true, webpack devtool: 'source-map').");
2063
2209
  }
2064
2210
  const files = [];
2065
2211
  let withoutId = 0;
@@ -2880,6 +3026,17 @@ var describe = {
2880
3026
  summary: "Agent-first CLI and MCP server for Tracksiy: errors, uptime, alerts, releases and source maps from one token.",
2881
3027
  docs: `${DEFAULT_URL}/cli.md`,
2882
3028
  contract: contract(),
3029
+ limits: {
3030
+ issuesPerPage: 100,
3031
+ bulkStatusCap: 5e3,
3032
+ mergeIds: 100,
3033
+ filesPerAssembly: 1e3,
3034
+ monitorIntervalSeconds: [60, 3456e4],
3035
+ claimSeconds: [60, 604800],
3036
+ noteCharacters: 1e4,
3037
+ idempotencyKeyHours: 24,
3038
+ note: "The CLI splits uploads past filesPerAssembly itself and sends an Idempotency-Key with every write."
3039
+ },
2883
3040
  globalFlags: Object.entries(GLOBAL_FLAGS).map(([n, f]) => flagSchema(n, f)),
2884
3041
  commands: COMMANDS.map(describeCommand)
2885
3042
  };
@@ -3169,7 +3326,7 @@ var COMMANDS = [
3169
3326
  ];
3170
3327
  var GROUPS = [
3171
3328
  ["Start", "init auth onboarding describe"],
3172
- ["Look", "overview changes health orgs projects issues monitors performance queries releases alerts keys artifacts"],
3329
+ ["Look", "overview changes health orgs projects issues monitors performance queries not-found releases alerts keys artifacts settings privacy-preview"],
3173
3330
  ["Ship", "sourcemaps proguard"],
3174
3331
  ["Manage", "tokens"],
3175
3332
  ["Talk to us", "support"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracksiy",
3
- "version": "0.4.0",
3
+ "version": "0.6.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": {