tracksiy 0.9.1 → 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 +7 -0
- package/dist/tracksiy.mjs +346 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -137,6 +137,12 @@ Labels: `bug`, `feature`, `question`, `blocker`. `npx tracksiy support` prints t
|
|
|
137
137
|
| `budgets check` | Judge every budget now -- for one release with --release. Exit code 10 if one is crossed. For a deploy to stop on. | read | `budgets_check` |
|
|
138
138
|
| `performance findings` | Routes that got slower after a release and N+1 queries, which Tracksiy raised as issues on its own. | read | `performance_findings` |
|
|
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
|
+
| `storage` | What each project holds on the server (events, transactions, source maps, debug files), largest first, with its retention limits. | read | `storage` |
|
|
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` |
|
|
140
146
|
| `issues list` | Recent issues of a project, newest activity first, with the frames that matter inlined. | read | `issues_list` |
|
|
141
147
|
| `issues view <id>` | One issue in enough depth to act on: latest event, stack frames, tags, recent volume. | read | `issues_view` |
|
|
142
148
|
| `issues resolve <ids...>` | Mark issues resolved, optionally fixed in a release so old clients do not reopen them. Records why. | write | `issues_resolve` |
|
|
@@ -165,6 +171,7 @@ Labels: `bug`, `feature`, `question`, `blocker`. `npx tracksiy support` prints t
|
|
|
165
171
|
| `releases list` | What did the last deploys break? Recent releases with their events, issues and the issues they introduced. | read | `releases_list` |
|
|
166
172
|
| `releases health` | Is the app crashing for its users, and did the last release make it worse? Crash-free sessions and users per release. | read | `releases_health` |
|
|
167
173
|
| `releases declare <version>` | Create or update a release idempotently: its commit, URL, and when it was released. Run it from the deploy. | write | `releases_declare` |
|
|
174
|
+
| `releases verdict <version>` | Should the rollout of this release continue? Crash-free vs threshold and the previous build, issues spreading fast, new issues, budgets, regressions. Exit code 10 on fail. | read | `releases_verdict` |
|
|
168
175
|
| `sourcemaps inject <paths...>` | Stamp built JavaScript and its source maps with a debug ID, so errors map to the exact build. Run after the build, before deploying. | write | `sourcemaps_inject` |
|
|
169
176
|
| `sourcemaps upload <paths...>` | Upload built JavaScript and source maps so stack traces show original files and lines. Only missing bytes are sent. | write | `sourcemaps_upload` |
|
|
170
177
|
| `proguard upload <mapping>` | Upload an Android ProGuard/R8 mapping so obfuscated stack traces are readable. | write | `proguard_upload` |
|
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.
|
|
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
|
|
@@ -81,7 +81,7 @@ var seg = (value) => encodeURIComponent(String(value));
|
|
|
81
81
|
var IDEMPOTENT = /* @__PURE__ */ new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
82
82
|
var RETRY_STATUS = /* @__PURE__ */ new Set([429, 502, 503, 504]);
|
|
83
83
|
var MAX_ATTEMPTS = 3;
|
|
84
|
-
var sleep = (
|
|
84
|
+
var sleep = (ms2) => new Promise((r) => setTimeout(r, ms2));
|
|
85
85
|
var Api = class {
|
|
86
86
|
url;
|
|
87
87
|
hasToken;
|
|
@@ -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
|
|
512
|
-
return [
|
|
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}",
|
|
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
|
}
|
|
@@ -1082,15 +1082,27 @@ var settingsSet = {
|
|
|
1082
1082
|
"fingerprint-rule": { type: "list", split: false, value: "type=<glob>,message=<glob>,fingerprint=<name>", description: "Group matching events into one issue. Repeatable; replaces the list." },
|
|
1083
1083
|
"privacy-rule": { type: "list", split: false, value: "dropHeader=<name> | mask=<email|iban|card_number|tckn|jwt>,field=<path|*>", description: "Scrub before storage. Repeatable; replaces the list." },
|
|
1084
1084
|
"performance-issues": { type: "boolean", description: "Raise issues on its own for routes slower after a release and N+1 queries (default on). --no-performance-issues turns it off." },
|
|
1085
|
+
"max-events-per-issue": { type: "integer", value: "<n>", description: "Stored events kept per issue: the newest n-1 plus the first (default 1000; 0 keeps all, otherwise at least 10). The issue's count is unaffected. Trimmed hourly." },
|
|
1086
|
+
"sourcemap-keep-releases": { type: "integer", value: "<n>", description: "Source maps kept per release line (<name>@...): the newest n releases (default 10; 0 keeps every release). Pruned nightly." },
|
|
1087
|
+
"sourcemap-keep-used-days": { type: "integer", value: "<days>", description: "Also keep bundles a stack trace used in this many days (default 30; mobile builds also while their release has events or sessions)." },
|
|
1088
|
+
"crash-free-min": { type: "number", value: "<pct>", description: "Release verdict / alert: crash-free sessions under this percentage fail a release (default 99)." },
|
|
1089
|
+
"min-sessions": { type: "integer", value: "<n>", description: "...judged from this many sessions on; below it the verdict is unknown (default 100)." },
|
|
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
|
+
"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
|
+
"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." },
|
|
1085
1096
|
body: { type: "json", value: "<json|@file|->", description: "The whole declaration; overrides the flags." }
|
|
1086
1097
|
},
|
|
1087
1098
|
examples: [
|
|
1088
1099
|
{ command: "tracksiy settings set --environments production,staging --environment-policy drop", description: "local and local1 stop reaching Tracksiy." },
|
|
1089
1100
|
{ command: "tracksiy settings set --fingerprint-rule 'type=ViewException,message=syntax error*,fingerprint=blade-syntax'", description: "Every half-written template is one issue." },
|
|
1090
1101
|
{ command: "tracksiy settings set --source-root /home/admin/web/shop/", description: "Frames carry repoPath." },
|
|
1091
|
-
{ 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." }
|
|
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." },
|
|
1103
|
+
{ command: "tracksiy settings set --max-events-per-issue 500 --sourcemap-keep-releases 5", description: "A noisy project keeps less on disk." }
|
|
1092
1104
|
],
|
|
1093
|
-
output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[], performanceIssues}",
|
|
1105
|
+
output: "{sourceRoots[], environments[], environmentPolicy, fingerprintRules[], privacyRules[], performanceIssues, storage{maxEventsPerIssue, sourcemapKeepReleases, sourcemapKeepUsedDays}, releaseHealth{crashFreeSessionsMin, minSessions, maxDropPct, velocityPct, velocityMinEvents}, logs{enabled, minLevel, dailyLimit}}",
|
|
1094
1106
|
async run(ctx) {
|
|
1095
1107
|
const path = await projectPath(ctx, "settings");
|
|
1096
1108
|
const i = ctx.input;
|
|
@@ -1102,7 +1114,27 @@ var settingsSet = {
|
|
|
1102
1114
|
environmentPolicy: i.environmentPolicy ?? current.environmentPolicy ?? "flag",
|
|
1103
1115
|
fingerprintRules: pairs(i.fingerprintRule, "fingerprint-rule") ?? current.fingerprintRules ?? [],
|
|
1104
1116
|
privacyRules: pairs(i.privacyRule, "privacy-rule") ?? current.privacyRules ?? [],
|
|
1105
|
-
performanceIssues: i.performanceIssues ?? current.performanceIssues ?? true
|
|
1117
|
+
performanceIssues: i.performanceIssues ?? current.performanceIssues ?? true,
|
|
1118
|
+
storage: {
|
|
1119
|
+
...current.storage ?? {},
|
|
1120
|
+
...i.maxEventsPerIssue !== void 0 ? { maxEventsPerIssue: i.maxEventsPerIssue } : {},
|
|
1121
|
+
...i.sourcemapKeepReleases !== void 0 ? { sourcemapKeepReleases: i.sourcemapKeepReleases } : {},
|
|
1122
|
+
...i.sourcemapKeepUsedDays !== void 0 ? { sourcemapKeepUsedDays: i.sourcemapKeepUsedDays } : {}
|
|
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
|
+
},
|
|
1130
|
+
releaseHealth: {
|
|
1131
|
+
...current.releaseHealth ?? {},
|
|
1132
|
+
...i.crashFreeMin !== void 0 ? { crashFreeSessionsMin: i.crashFreeMin } : {},
|
|
1133
|
+
...i.minSessions !== void 0 ? { minSessions: i.minSessions } : {},
|
|
1134
|
+
...i.maxDropPct !== void 0 ? { maxDropPct: i.maxDropPct } : {},
|
|
1135
|
+
...i.velocityPct !== void 0 ? { velocityPct: i.velocityPct } : {},
|
|
1136
|
+
...i.velocityMinEvents !== void 0 ? { velocityMinEvents: i.velocityMinEvents } : {}
|
|
1137
|
+
}
|
|
1106
1138
|
};
|
|
1107
1139
|
return ctx.api.send("PUT", path, next);
|
|
1108
1140
|
}
|
|
@@ -1126,7 +1158,7 @@ var privacyPreview = {
|
|
|
1126
1158
|
var fleet_default = [orgs, projects, overview, changes, health, monitorsList, monitorsDeclare, monitorsDelete, monitorsDeleted, performance, queries, notFound, settingsGet, settingsSet, privacyPreview];
|
|
1127
1159
|
|
|
1128
1160
|
// src/commands/perf.ts
|
|
1129
|
-
var METRICS = "p50|p75|p95|p99|failure_rate|lcp|fcp|inp|cls|ttfb (transactions); response_p95|ttfb_p95 (HTTP monitors); duration_p95 (heartbeats)";
|
|
1161
|
+
var METRICS = "p50|p75|p95|p99|failure_rate|lcp|fcp|inp|cls|ttfb|app_start_cold|app_start_warm|ttid|ttfd|slow_frame_rate|frozen_frame_rate (transactions); response_p95|ttfb_p95 (HTTP monitors); duration_p95 (heartbeats)";
|
|
1130
1162
|
var BUDGET_KEYS = {
|
|
1131
1163
|
name: "name",
|
|
1132
1164
|
transaction: "transaction",
|
|
@@ -1208,7 +1240,7 @@ var budgetsGet = {
|
|
|
1208
1240
|
var budgetsSet = {
|
|
1209
1241
|
path: ["budgets", "set"],
|
|
1210
1242
|
summary: "Declare performance budgets: told once when one is crossed and once when it is met again.",
|
|
1211
|
-
description: `The list replaces the stored one, by name (--merge keeps the others). Metrics: ${METRICS}. max is an upper bound: ms, a 0..1 ratio for failure_rate, unitless for cls. Below min-samples the verdict is unknown, never pass. Alerts go through the project's rules with performance on (the default). --body takes the API's shape, {"budgets": [{"name", "metric", "max", "transaction"?, "op"?, "monitor"? (slug or id), "windowMinutes"?, "minSamples"?}]}, or a bare list; the flag spellings window and min-samples are accepted there too, and --dry-run and --merge apply to it.`,
|
|
1243
|
+
description: `The list replaces the stored one, by name (--merge keeps the others). Metrics: ${METRICS}. max is an upper bound: ms, a 0..1 ratio for failure_rate and the frame rates, unitless for cls. Below min-samples the verdict is unknown, never pass. Alerts go through the project's rules with performance on (the default). --body takes the API's shape, {"budgets": [{"name", "metric", "max", "transaction"?, "op"?, "monitor"? (slug or id), "windowMinutes"?, "minSamples"?}]}, or a bare list; the flag spellings window and min-samples are accepted there too, and --dry-run and --merge apply to it.`,
|
|
1212
1244
|
context: "project",
|
|
1213
1245
|
effect: "write",
|
|
1214
1246
|
idempotent: true,
|
|
@@ -1337,6 +1369,238 @@ ${table(
|
|
|
1337
1369
|
};
|
|
1338
1370
|
var perf_default = [budgetsGet, budgetsSet, budgetsCheck, findings, monitorsTiming];
|
|
1339
1371
|
|
|
1372
|
+
// src/commands/storage.ts
|
|
1373
|
+
function mb(bytes) {
|
|
1374
|
+
const n = Number(bytes ?? 0);
|
|
1375
|
+
if (n >= 1e9) return `${(n / 1e9).toFixed(2)} GB`;
|
|
1376
|
+
return `${(n / 1e6).toFixed(1)} MB`;
|
|
1377
|
+
}
|
|
1378
|
+
var storage = {
|
|
1379
|
+
path: ["storage"],
|
|
1380
|
+
summary: "What each project holds on the server (events, transactions, source maps, debug files), largest first, with its retention limits.",
|
|
1381
|
+
description: "Limits are per project: `tracksiy settings set --max-events-per-issue/--sourcemap-keep-releases/--sourcemap-keep-used-days`. Staff tokens also see the server's free disk and database size. Cached ten minutes; --fresh recounts.",
|
|
1382
|
+
context: "org",
|
|
1383
|
+
effect: "read",
|
|
1384
|
+
endpoint: { method: "GET", path: "/api/agent/v1/organizations/{org}/storage" },
|
|
1385
|
+
flags: { fresh: { type: "boolean", description: "Recount instead of the ten-minute cache (a few seconds)." } },
|
|
1386
|
+
examples: [
|
|
1387
|
+
{ command: "tracksiy storage", description: "Which project is filling the disk?" },
|
|
1388
|
+
{ command: "tracksiy storage --select instance,notes", description: "Free disk and warnings only (staff)." }
|
|
1389
|
+
],
|
|
1390
|
+
output: "{generatedAt, cached, projects[{project, events{stored, approxBytes, issuesOverCap, largestIssueEvents}, transactions{stored, approxBytes}, sourceMaps{bundles, releases, releaseLines, bytes}, debugFiles{files, bytes}, approxTotalBytes, limits{...}}], instance{diskTotalBytes, diskFreeBytes, diskUsedPct, databaseBytes, uploadsBytes, eventRetentionDays, transactionRetentionDays}|null, notes[]}",
|
|
1391
|
+
text: (d) => `${d.instance ? kv({ disk: `${d.instance.diskUsedPct}% used, ${mb(d.instance.diskFreeBytes)} free`, database: mb(d.instance.databaseBytes), uploads: mb(d.instance.uploadsBytes) }) + "\n\n" : ""}${table(d.projects ?? [], [
|
|
1392
|
+
"project",
|
|
1393
|
+
["total", (r) => mb(r.approxTotalBytes)],
|
|
1394
|
+
["events", (r) => `${r.events.stored} (${mb(r.events.approxBytes)})`],
|
|
1395
|
+
["over cap", (r) => r.events.issuesOverCap],
|
|
1396
|
+
["transactions", (r) => `${r.transactions.stored} (${mb(r.transactions.approxBytes)})`],
|
|
1397
|
+
["source maps", (r) => `${r.sourceMaps.bundles} in ${r.sourceMaps.releases} rel (${mb(r.sourceMaps.bytes)})`],
|
|
1398
|
+
["debug files", (r) => `${r.debugFiles.files} (${mb(r.debugFiles.bytes)})`],
|
|
1399
|
+
["limits", (r) => `${r.limits.maxEventsPerIssue}/issue, ${r.limits.sourcemapKeepReleases} rel, ${r.limits.sourcemapKeepUsedDays}d`]
|
|
1400
|
+
])}${(d.notes ?? []).length ? `
|
|
1401
|
+
|
|
1402
|
+
${d.notes.join("\n")}` : ""}`,
|
|
1403
|
+
async run(ctx) {
|
|
1404
|
+
return ctx.api.get(await orgPath(ctx, "storage"), { fresh: ctx.input.fresh || void 0 });
|
|
1405
|
+
}
|
|
1406
|
+
};
|
|
1407
|
+
var storage_default = [storage];
|
|
1408
|
+
|
|
1409
|
+
// src/commands/mobile.ts
|
|
1410
|
+
var pct = (v) => v === null || v === void 0 ? "-" : `${(Number(v) * 100).toFixed(1)}%`;
|
|
1411
|
+
var ms = (v) => v === null || v === void 0 ? "-" : `${v} ms`;
|
|
1412
|
+
var mobile = {
|
|
1413
|
+
path: ["mobile"],
|
|
1414
|
+
aliases: [["mobile", "vitals"]],
|
|
1415
|
+
summary: "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.",
|
|
1416
|
+
description: "Read from the measurements the mobile SDKs already send with transactions (sentry-android, sentry-cocoa, sentry_flutter incl. Flutter 10 span streaming, @sentry/react-native); nothing to configure but tracesSampleRate > 0. Budgets take the same metrics: app_start_cold, app_start_warm, ttid, ttfd (p75 ms) and slow_frame_rate, frozen_frame_rate (0..1).",
|
|
1417
|
+
context: "project",
|
|
1418
|
+
effect: "read",
|
|
1419
|
+
endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/mobile" },
|
|
1420
|
+
flags: {
|
|
1421
|
+
since: { type: "string", value: TIME, description: "Window start (default 7d), compared with the window before." },
|
|
1422
|
+
release: { type: "string", value: "<version>", description: "Only this release." },
|
|
1423
|
+
os: { type: "string", value: "<name>", description: "Only this OS as the SDK names it: Android, iOS." },
|
|
1424
|
+
sort: { type: "string", enum: ["count", "ttid", "ttfd", "slowFrames", "frozenFrames"], description: "Screen order (default count)." },
|
|
1425
|
+
limit: { type: "integer", value: "<n>", description: "Screens (default 30, at most 100)." }
|
|
1426
|
+
},
|
|
1427
|
+
examples: [
|
|
1428
|
+
{ command: "tracksiy mobile --os Android --sort ttid", description: "The slowest Android screens to show." },
|
|
1429
|
+
{ command: "tracksiy budgets set --merge --budget 'name=cold start,metric=app_start_cold,max=2000'", description: "Alert when cold start p75 passes 2 s." }
|
|
1430
|
+
],
|
|
1431
|
+
output: "{transactions, appStart{cold{count,p50,p75,p95}, warm{...}, previousColdP75, previousWarmP75}, byOs[], byRelease[], screens[{name, op, count, ttidP50, ttidP75, ttidP95, ttfdP75, previousTtidP75, slowFrameRate, frozenFrameRate, frames, frameDelayP75, stallP75}], checks[]}",
|
|
1432
|
+
text: (d) => {
|
|
1433
|
+
const s = d.appStart ?? {};
|
|
1434
|
+
const head = kv({
|
|
1435
|
+
transactions: d.transactions,
|
|
1436
|
+
"cold start p75": `${ms(s.cold?.p75)} (before ${ms(s.previousColdP75)}, n=${s.cold?.count ?? 0})`,
|
|
1437
|
+
"warm start p75": `${ms(s.warm?.p75)} (before ${ms(s.previousWarmP75)}, n=${s.warm?.count ?? 0})`
|
|
1438
|
+
});
|
|
1439
|
+
const screens = table(d.screens ?? [], [
|
|
1440
|
+
"name",
|
|
1441
|
+
"count",
|
|
1442
|
+
["ttid p75", (r) => ms(r.ttidP75)],
|
|
1443
|
+
["before", (r) => ms(r.previousTtidP75)],
|
|
1444
|
+
["ttfd p75", (r) => ms(r.ttfdP75)],
|
|
1445
|
+
["slow", (r) => pct(r.slowFrameRate)],
|
|
1446
|
+
["frozen", (r) => pct(r.frozenFrameRate)]
|
|
1447
|
+
]);
|
|
1448
|
+
const releases = table(d.byRelease ?? [], ["release", "transactions", ["cold p75", (r) => ms(r.coldP75)], ["ttid p75", (r) => ms(r.ttidP75)], ["slow", (r) => pct(r.slowFrameRate)]]);
|
|
1449
|
+
const checks = (d.checks ?? []).map((c) => `${c.status.padEnd(7)} ${c.name}: ${c.detail}`).join("\n");
|
|
1450
|
+
return `${head}
|
|
1451
|
+
|
|
1452
|
+
Screens:
|
|
1453
|
+
${screens}
|
|
1454
|
+
|
|
1455
|
+
Releases:
|
|
1456
|
+
${releases}
|
|
1457
|
+
|
|
1458
|
+
${checks}`;
|
|
1459
|
+
},
|
|
1460
|
+
async run(ctx) {
|
|
1461
|
+
const i = ctx.input;
|
|
1462
|
+
return ctx.api.get(await projectPath(ctx, "mobile"), {
|
|
1463
|
+
since: since(i.since),
|
|
1464
|
+
release: i.release,
|
|
1465
|
+
os: i.os,
|
|
1466
|
+
sort: i.sort,
|
|
1467
|
+
limit: i.limit
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
var mobile_default = [mobile];
|
|
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
|
+
|
|
1340
1604
|
// src/commands/issues.ts
|
|
1341
1605
|
import { writeFileSync } from "node:fs";
|
|
1342
1606
|
var FILTERS = {
|
|
@@ -1394,7 +1658,7 @@ var issueRows = (issues) => table(issues ?? [], [
|
|
|
1394
1658
|
["status", (r) => r.regressedAt && r.status === "unresolved" ? "regressed" : r.status],
|
|
1395
1659
|
["title", (r) => r.title]
|
|
1396
1660
|
]);
|
|
1397
|
-
var
|
|
1661
|
+
var list2 = {
|
|
1398
1662
|
path: ["issues", "list"],
|
|
1399
1663
|
aliases: [["issues"]],
|
|
1400
1664
|
summary: "Recent issues of a project, newest activity first, with the frames that matter inlined.",
|
|
@@ -1694,7 +1958,7 @@ var bulk = {
|
|
|
1694
1958
|
return result;
|
|
1695
1959
|
}
|
|
1696
1960
|
};
|
|
1697
|
-
var issues_default = [
|
|
1961
|
+
var issues_default = [list2, view, resolve2, ignore, unresolve, bulk, merge, comment, activity, verifyFixed, claim, unclaim];
|
|
1698
1962
|
|
|
1699
1963
|
// src/commands/projects.ts
|
|
1700
1964
|
var create = {
|
|
@@ -1731,7 +1995,7 @@ ${(d.nextSteps ?? []).map((s, n) => `${n + 1}. ${s.action} -- ${s.why}`).join("\
|
|
|
1731
1995
|
);
|
|
1732
1996
|
}
|
|
1733
1997
|
};
|
|
1734
|
-
var sleep2 = (
|
|
1998
|
+
var sleep2 = (ms2) => new Promise((r) => setTimeout(r, ms2));
|
|
1735
1999
|
var verify = {
|
|
1736
2000
|
path: ["projects", "verify"],
|
|
1737
2001
|
aliases: [["verify"]],
|
|
@@ -1805,11 +2069,12 @@ var DECLARE = {
|
|
|
1805
2069
|
threshold: { type: "string", value: "<events>/<minutes>", description: "Also alert when an issue gets this many events in this many minutes, e.g. 50/10." },
|
|
1806
2070
|
uptime: { type: "boolean", description: "Alert when this project's monitors go down (default true)." },
|
|
1807
2071
|
performance: { type: "boolean", description: "Alert when a performance budget is crossed or met again (default true; see `budgets set`). Rides on the new-issue or uptime rule." },
|
|
2072
|
+
"release-health": { type: "boolean", description: "Alert when a release's crash-free rate drops under the threshold (and recovers), or one issue spreads fast through a release (default true; see `releases verdict`). Rides on the new-issue or uptime rule." },
|
|
1808
2073
|
"replace-recipients": { type: "boolean", description: "Make the recipient list exactly this one (default: add to it)." },
|
|
1809
2074
|
body: { type: "json", value: "<json|@file|->", description: "Raw request body; overrides the other flags." }
|
|
1810
2075
|
};
|
|
1811
|
-
function recipients(
|
|
1812
|
-
return (
|
|
2076
|
+
function recipients(list6, delivery) {
|
|
2077
|
+
return (list6 ?? []).map((raw) => {
|
|
1813
2078
|
const i = raw.indexOf(":");
|
|
1814
2079
|
const type = i > 0 ? raw.slice(0, i) : raw;
|
|
1815
2080
|
if (!RECIPIENT_TYPES.includes(type)) {
|
|
@@ -1841,6 +2106,7 @@ function declareBody(i) {
|
|
|
1841
2106
|
threshold: threshold(i.threshold),
|
|
1842
2107
|
uptime: i.uptime,
|
|
1843
2108
|
performance: i.performance,
|
|
2109
|
+
releaseHealth: i.releaseHealth,
|
|
1844
2110
|
replaceRecipients: i.replaceRecipients
|
|
1845
2111
|
});
|
|
1846
2112
|
}
|
|
@@ -1854,6 +2120,7 @@ ${table(d.rules ?? [], [
|
|
|
1854
2120
|
["threshold", (r) => r.threshold ? `${r.threshold.events}/${r.threshold.minutes}m` : "-"],
|
|
1855
2121
|
["uptime", (r) => r.uptime],
|
|
1856
2122
|
["perf", (r) => r.performance],
|
|
2123
|
+
["release", (r) => r.releaseHealth],
|
|
1857
2124
|
["recipients", (r) => (r.recipients ?? []).map((x) => `${x.type}${x.delivery === "digest" ? "(digest)" : ""}`).join(", ")]
|
|
1858
2125
|
])}${(d.notes ?? []).length ? `
|
|
1859
2126
|
|
|
@@ -1988,7 +2255,7 @@ var settingsSet2 = {
|
|
|
1988
2255
|
var alerts_default = [get, set, test, coverage, defaults, settingsGet2, settingsSet2];
|
|
1989
2256
|
|
|
1990
2257
|
// src/commands/keys.ts
|
|
1991
|
-
var
|
|
2258
|
+
var list3 = {
|
|
1992
2259
|
path: ["keys", "list"],
|
|
1993
2260
|
aliases: [["keys"]],
|
|
1994
2261
|
summary: "Every ingest key of the project: its surface, whether ingest accepts it, its rate limit and volume.",
|
|
@@ -2045,11 +2312,11 @@ var rotate = {
|
|
|
2045
2312
|
return ctx.api.send("POST", await projectPath(ctx, `keys/${seg(ctx.input.name)}/rotate`));
|
|
2046
2313
|
}
|
|
2047
2314
|
};
|
|
2048
|
-
var keys_default = [
|
|
2315
|
+
var keys_default = [list3, set2, rotate];
|
|
2049
2316
|
|
|
2050
2317
|
// src/commands/releases.ts
|
|
2051
2318
|
import { execFileSync } from "node:child_process";
|
|
2052
|
-
var
|
|
2319
|
+
var list4 = {
|
|
2053
2320
|
path: ["releases", "list"],
|
|
2054
2321
|
aliases: [["releases"]],
|
|
2055
2322
|
summary: "What did the last deploys break? Recent releases with their events, issues and the issues they introduced.",
|
|
@@ -2153,7 +2420,49 @@ function commitList(values, git = runGit) {
|
|
|
2153
2420
|
function runGit(args) {
|
|
2154
2421
|
return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
2155
2422
|
}
|
|
2156
|
-
var
|
|
2423
|
+
var verdict = {
|
|
2424
|
+
path: ["releases", "verdict"],
|
|
2425
|
+
summary: "Should the rollout of this release continue? Crash-free vs threshold and the previous build, issues spreading fast, new issues, budgets, regressions. Exit code 10 on fail.",
|
|
2426
|
+
description: "For a staged rollout step: `--wait 30m` re-asks every minute while the verdict is unknown (too few sessions yet). Unknown passes unless --strict. Thresholds are project settings (`settings set --crash-free-min/--min-sessions/--max-drop-pct/--velocity-pct/--velocity-min-events`). An upload token (project:releases) may run it.",
|
|
2427
|
+
context: "project",
|
|
2428
|
+
effect: "read",
|
|
2429
|
+
endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/releases/{version}/verdict" },
|
|
2430
|
+
scopes: ["project:releases", "event:read", "project:read", "project:admin", "org:read"],
|
|
2431
|
+
args: [{ name: "version", required: true, description: "The release, e.g. shop-android@1.4.2+57." }],
|
|
2432
|
+
flags: {
|
|
2433
|
+
wait: { type: "string", value: "<duration>", description: "Keep asking (every 60 s) while the verdict is unknown, up to this long: 30m, 2h." },
|
|
2434
|
+
strict: { type: "boolean", description: "An unknown verdict fails too (exit 10)." }
|
|
2435
|
+
},
|
|
2436
|
+
examples: [
|
|
2437
|
+
{ command: "tracksiy releases verdict shop-android@1.4.2+57 --wait 30m && fastlane supply --rollout 0.5", description: "Widen the rollout only if the 10% stage held." }
|
|
2438
|
+
],
|
|
2439
|
+
output: "{release, previousRelease, verdict, health{sessions, crashed, crashFreeSessionsPct, ...}, previousHealth, newIssues[], velocity[], budgets[], regressions[], checks[{name, status, detail}], rules}",
|
|
2440
|
+
text: (d) => `${kv({ release: d.release, previous: d.previousRelease ?? "-", verdict: d.verdict, sessions: d.health?.sessions, "crash-free %": d.health?.crashFreeSessionsPct ?? "-", "previous crash-free %": d.previousHealth?.crashFreeSessionsPct ?? "-" })}
|
|
2441
|
+
|
|
2442
|
+
${table(d.checks ?? [], ["name", "status", "detail"])}${(d.newIssues ?? []).length ? `
|
|
2443
|
+
|
|
2444
|
+
New issues:
|
|
2445
|
+
${table(d.newIssues, ["short_id", "count", "title"])}` : ""}`,
|
|
2446
|
+
async run(ctx) {
|
|
2447
|
+
const path = await projectPath(ctx, `releases/${seg(ctx.input.version)}/verdict`);
|
|
2448
|
+
const waitSeconds = duration(ctx.input.wait, "wait") ?? 0;
|
|
2449
|
+
const deadline = Date.now() + waitSeconds * 1e3;
|
|
2450
|
+
let data = await ctx.api.get(path);
|
|
2451
|
+
while (data.verdict === "unknown" && Date.now() + 6e4 <= deadline) {
|
|
2452
|
+
await new Promise((r) => setTimeout(r, 6e4));
|
|
2453
|
+
data = await ctx.api.get(path);
|
|
2454
|
+
}
|
|
2455
|
+
if (data.verdict === "fail") {
|
|
2456
|
+
const failed = (data.checks ?? []).filter((c) => c.status === "fail").map((c) => `${c.name}: ${c.detail}`).join(" | ");
|
|
2457
|
+
throw new CheckFailed(`Release ${data.release} failed: ${failed}`, data, "Halt the rollout; `tracksiy releases list` and `tracksiy issues list --release` show what broke.");
|
|
2458
|
+
}
|
|
2459
|
+
if (ctx.input.strict && data.verdict === "unknown") {
|
|
2460
|
+
throw new CheckFailed(`Release ${data.release} could not be judged yet (${data.health?.sessions ?? 0} sessions).`, data, "Wait longer (--wait), or lower releaseHealth.minSessions.");
|
|
2461
|
+
}
|
|
2462
|
+
return data;
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
var releases_default = [list4, health2, declare, verdict];
|
|
2157
2466
|
|
|
2158
2467
|
// src/commands/artifacts.ts
|
|
2159
2468
|
import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
@@ -2275,7 +2584,7 @@ function manifestFor(files, chunkSize) {
|
|
|
2275
2584
|
});
|
|
2276
2585
|
return { manifest, chunks };
|
|
2277
2586
|
}
|
|
2278
|
-
var sleep3 = (
|
|
2587
|
+
var sleep3 = (ms2) => new Promise((r) => setTimeout(r, ms2));
|
|
2279
2588
|
function packManifest(manifest, maxFiles, maxBytes) {
|
|
2280
2589
|
const units = [];
|
|
2281
2590
|
for (const entry of manifest) {
|
|
@@ -2594,7 +2903,7 @@ var artifacts_default = [sourcemapsInject, sourcemapsUpload, proguardUpload, art
|
|
|
2594
2903
|
|
|
2595
2904
|
// src/commands/tokens.ts
|
|
2596
2905
|
var SCOPE_HINT = "e.g. org:read,project:read,event:read,event:write,project:write,project:releases,project:admin";
|
|
2597
|
-
var
|
|
2906
|
+
var list5 = {
|
|
2598
2907
|
path: ["tokens", "list"],
|
|
2599
2908
|
aliases: [["tokens"]],
|
|
2600
2909
|
summary: "Your tokens, newest first, without their values.",
|
|
@@ -2707,7 +3016,7 @@ var self = {
|
|
|
2707
3016
|
return ctx.api.get("tokens/self");
|
|
2708
3017
|
}
|
|
2709
3018
|
};
|
|
2710
|
-
var tokens_default = [
|
|
3019
|
+
var tokens_default = [list5, self, create2, update, remove, rotate2];
|
|
2711
3020
|
|
|
2712
3021
|
// src/commands/support.ts
|
|
2713
3022
|
var SUPPORT = {
|
|
@@ -3371,15 +3680,15 @@ function makeContext(spec, input, globals, surface, io) {
|
|
|
3371
3680
|
const org = () => {
|
|
3372
3681
|
orgPromise ??= (async () => {
|
|
3373
3682
|
if (config.org) return config.org;
|
|
3374
|
-
const
|
|
3375
|
-
if (
|
|
3376
|
-
log(`using organization ${
|
|
3377
|
-
return
|
|
3683
|
+
const list6 = await visible();
|
|
3684
|
+
if (list6.length === 1) {
|
|
3685
|
+
log(`using organization ${list6[0].slug}`);
|
|
3686
|
+
return list6[0].slug;
|
|
3378
3687
|
}
|
|
3379
3688
|
throw usageError(
|
|
3380
3689
|
"missing_org",
|
|
3381
|
-
|
|
3382
|
-
|
|
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`."
|
|
3383
3692
|
);
|
|
3384
3693
|
})();
|
|
3385
3694
|
return orgPromise;
|
|
@@ -3389,8 +3698,8 @@ function makeContext(spec, input, globals, surface, io) {
|
|
|
3389
3698
|
projectPromise ??= (async () => {
|
|
3390
3699
|
const o = await org();
|
|
3391
3700
|
if (config.project) return { org: o, project: config.project };
|
|
3392
|
-
const
|
|
3393
|
-
const projects2 =
|
|
3701
|
+
const list6 = await visible();
|
|
3702
|
+
const projects2 = list6.find((x) => x.slug === o)?.projects ?? [];
|
|
3394
3703
|
if (projects2.length === 1) {
|
|
3395
3704
|
log(`using project ${o}/${projects2[0].slug}`);
|
|
3396
3705
|
return { org: o, project: projects2[0].slug };
|
|
@@ -3555,11 +3864,11 @@ async function serve(io, defaults2) {
|
|
|
3555
3864
|
const rl = createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
3556
3865
|
io.stderr(`tracksiy mcp ${VERSION}: ${server.toolCount} tools on stdio
|
|
3557
3866
|
`);
|
|
3558
|
-
for await (const
|
|
3559
|
-
if (!
|
|
3867
|
+
for await (const line2 of rl) {
|
|
3868
|
+
if (!line2.trim()) continue;
|
|
3560
3869
|
let msg;
|
|
3561
3870
|
try {
|
|
3562
|
-
msg = JSON.parse(
|
|
3871
|
+
msg = JSON.parse(line2);
|
|
3563
3872
|
} catch {
|
|
3564
3873
|
write({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
|
|
3565
3874
|
continue;
|
|
@@ -3611,6 +3920,9 @@ var COMMANDS = [
|
|
|
3611
3920
|
...auth_default,
|
|
3612
3921
|
...fleet_default,
|
|
3613
3922
|
...perf_default,
|
|
3923
|
+
...storage_default,
|
|
3924
|
+
...mobile_default,
|
|
3925
|
+
...logs_default,
|
|
3614
3926
|
...issues_default,
|
|
3615
3927
|
...projects_default,
|
|
3616
3928
|
...alerts_default,
|
|
@@ -3624,7 +3936,7 @@ var COMMANDS = [
|
|
|
3624
3936
|
];
|
|
3625
3937
|
var GROUPS = [
|
|
3626
3938
|
["Start", "init auth onboarding describe"],
|
|
3627
|
-
["Look", "overview changes health orgs projects issues monitors performance 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"],
|
|
3628
3940
|
["Ship", "sourcemaps proguard"],
|
|
3629
3941
|
["Manage", "tokens"],
|
|
3630
3942
|
["Talk to us", "support"],
|