tracksiy 0.3.0 → 0.5.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 +1 -0
- package/dist/tracksiy.mjs +106 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,6 +126,7 @@ Labels: `bug`, `feature`, `question`, `blocker`. `npx tracksiy support` prints t
|
|
|
126
126
|
| `monitors declare` | Declare monitors for a project up front, idempotently: heartbeats for jobs, HTTP checks, TLS, ingest silence. | write | `monitors_declare` |
|
|
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
|
+
| `queries` | Which database queries and outbound HTTP calls make this project slow, with N+1 detection and the routes that run them. | read | `queries` |
|
|
129
130
|
| `issues list` | Recent issues of a project, newest activity first, with the frames that matter inlined. | read | `issues_list` |
|
|
130
131
|
| `issues view <id>` | One issue in enough depth to act on: latest event, stack frames, tags, recent volume. | read | `issues_view` |
|
|
131
132
|
| `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.
|
|
7
|
+
var VERSION = true ? "0.5.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
|
|
@@ -851,10 +851,16 @@ var monitorsDeclare = {
|
|
|
851
851
|
ssl: { type: "list", value: "<slug>=<url>", description: "TLS certificate check (expiry is reported on every check)." },
|
|
852
852
|
ingest: { type: "list", value: "<slug>[:<surface>]", description: "Alert when this project (or surface) stops sending events." },
|
|
853
853
|
interval: { type: "integer", value: "<seconds>", description: "Interval for --get/--ping/--ssl/--ingest monitors." },
|
|
854
|
+
"expect-text": { type: "string", value: "<text>", description: "--get: down unless the body contains this (a 200 with an empty shell is down)." },
|
|
855
|
+
"expect-no-text": { type: "string", value: "<text>", description: "--get: down if the body contains this, e.g. Whoops or Exception." },
|
|
856
|
+
"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
|
+
"dry-run": { type: "boolean", description: "Show what would be created, adopted or changed; write nothing." },
|
|
854
858
|
body: { type: "json", value: "<json|@file|->", description: "Raw request body; overrides the shorthand flags." }
|
|
855
859
|
},
|
|
856
860
|
examples: [
|
|
857
|
-
{ command: "tracksiy monitors declare --heartbeat nightly-backup:93600 --get site=https://example.com", description: "A backup heartbeat and a homepage check." }
|
|
861
|
+
{ command: "tracksiy monitors declare --heartbeat nightly-backup:93600 --get site=https://example.com", description: "A backup heartbeat and a homepage check." },
|
|
862
|
+
{ command: "tracksiy monitors declare --get tr-landing=https://example.com/tr --expect-text '<h1' --expect-no-text Whoops --user-agent GPTBot/1.0", description: "The page as an answer engine gets it: real content, no error template." },
|
|
863
|
+
{ command: "tracksiy monitors declare --heartbeat tariff-sync:608400 --dry-run", description: "See the change before making it." }
|
|
858
864
|
],
|
|
859
865
|
output: "Server result per monitor, including check-in URLs for heartbeats.",
|
|
860
866
|
async run(ctx) {
|
|
@@ -870,7 +876,17 @@ var monitorsDeclare = {
|
|
|
870
876
|
for (const item of i[type] ?? []) {
|
|
871
877
|
const eq = String(item).indexOf("=");
|
|
872
878
|
if (eq < 1) throw usageError("bad_value", `--${type} expects <slug>=<url>, got ${JSON.stringify(item)}`);
|
|
873
|
-
monitors.push(
|
|
879
|
+
monitors.push(
|
|
880
|
+
compact({
|
|
881
|
+
slug: item.slice(0, eq),
|
|
882
|
+
type,
|
|
883
|
+
url: item.slice(eq + 1),
|
|
884
|
+
intervalSeconds: i.interval,
|
|
885
|
+
expectText: type === "get" ? i.expectText : void 0,
|
|
886
|
+
expectNoText: type === "get" ? i.expectNoText : void 0,
|
|
887
|
+
userAgent: type !== "ssl" ? i.userAgent : void 0
|
|
888
|
+
})
|
|
889
|
+
);
|
|
874
890
|
}
|
|
875
891
|
}
|
|
876
892
|
for (const item of i.ingest ?? []) {
|
|
@@ -880,6 +896,7 @@ var monitorsDeclare = {
|
|
|
880
896
|
if (!monitors.length) throw usageError("missing_flag", "Nothing to declare.", "Add --heartbeat, --get, --ping, --ssl, --ingest, or --body.");
|
|
881
897
|
body = { monitors };
|
|
882
898
|
}
|
|
899
|
+
if (i.dryRun) body = { ...body, dryRun: true };
|
|
883
900
|
return ctx.api.send("POST", await projectPath(ctx, "monitors"), body);
|
|
884
901
|
}
|
|
885
902
|
};
|
|
@@ -904,16 +921,26 @@ var performance = {
|
|
|
904
921
|
effect: "read",
|
|
905
922
|
endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/performance" },
|
|
906
923
|
flags: {
|
|
907
|
-
since: { type: "string", value: TIME, description: "Window start (default: server's, 24h)." },
|
|
908
|
-
op: { type: "string", value: "<op>", description: "Only this operation, e.g. http.server." },
|
|
909
|
-
sort: { type: "string",
|
|
910
|
-
limit: { type: "integer", value: "<n>", description: "How many transactions." }
|
|
924
|
+
since: { type: "string", value: TIME, description: "Window start (default: server's, 24h; 7d with --release)." },
|
|
925
|
+
op: { type: "string", value: "<op>", description: "Only this operation, e.g. http.server or pageload." },
|
|
926
|
+
sort: { type: "string", enum: ["slower", "duration", "p95", "failures"], description: "Order (default: slower first)." },
|
|
927
|
+
limit: { type: "integer", value: "<n>", description: "How many transactions." },
|
|
928
|
+
release: { type: "string", value: "<version>", description: "Compare this release's transactions with --against instead of two time windows." },
|
|
929
|
+
against: { type: "string", value: "<version>", description: "The release to compare with (default: the one before --release)." }
|
|
911
930
|
},
|
|
912
|
-
|
|
931
|
+
examples: [
|
|
932
|
+
{ command: "tracksiy performance --sort p95", description: "The slowest routes by p95." },
|
|
933
|
+
{ command: "tracksiy performance --release web@$(git rev-parse HEAD)", description: "Did this deploy make anything slower or failing?" },
|
|
934
|
+
{ command: "tracksiy performance --op pageload --select transactions.transaction,transactions.current.webVitals", description: "Core Web Vitals (p75) per page." }
|
|
935
|
+
],
|
|
936
|
+
output: "{since, until, release?, againstRelease?, transactions:[{transaction, op, current{count, avgDurationMs, p50Ms, p95Ms, p99Ms, failureRate, failed, status, webVitals}, previous{...}, changeRatio, slower}], uptime[...], blindSpots[...]}",
|
|
913
937
|
text: (d) => `${table(d.transactions ?? [], [
|
|
914
938
|
"transaction",
|
|
915
939
|
"op",
|
|
916
|
-
["
|
|
940
|
+
["p50", (r) => r.current?.p50Ms ?? "-"],
|
|
941
|
+
["p95", (r) => r.current?.p95Ms ?? "-"],
|
|
942
|
+
["p99", (r) => r.current?.p99Ms ?? "-"],
|
|
943
|
+
["fail", (r) => r.current?.failureRate == null ? "-" : `${(r.current.failureRate * 100).toFixed(1)}%`],
|
|
917
944
|
["count", (r) => r.current?.count ?? "-"],
|
|
918
945
|
["change", (r) => r.changeRatio == null ? "-" : `${r.changeRatio > 0 ? "+" : ""}${Math.round(r.changeRatio * 100)}%`],
|
|
919
946
|
["slower", (r) => r.slower]
|
|
@@ -926,13 +953,55 @@ ${table(d.uptime, ["name", "url", "upNow", "avgResponseMs", "downChecks"])}` : "
|
|
|
926
953
|
since: since(ctx.input.since),
|
|
927
954
|
op: ctx.input.op,
|
|
928
955
|
sort: ctx.input.sort,
|
|
956
|
+
limit: ctx.input.limit,
|
|
957
|
+
release: ctx.input.release,
|
|
958
|
+
against: ctx.input.against
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
var queries = {
|
|
963
|
+
path: ["queries"],
|
|
964
|
+
aliases: [["performance", "queries"]],
|
|
965
|
+
summary: "Which database queries and outbound HTTP calls make this project slow, with N+1 detection and the routes that run them.",
|
|
966
|
+
description: "Aggregated from the spans of sampled transactions (op db* and http.client), normalized so no literal is stored. Ranked by total time by default: the query that costs the most overall. nPlusOne marks a query one request ran 10+ times.",
|
|
967
|
+
context: "project",
|
|
968
|
+
effect: "read",
|
|
969
|
+
endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/queries" },
|
|
970
|
+
flags: {
|
|
971
|
+
since: { type: "string", value: TIME, description: "Window start (default 24h)." },
|
|
972
|
+
op: { type: "string", value: "<db|http.client|op>", description: "db (every db* op), http.client, or an exact op." },
|
|
973
|
+
transaction: { type: "string", value: "<route>", description: "Only queries run by this transaction, e.g. /orders." },
|
|
974
|
+
sort: { type: "string", enum: ["total", "avg", "max", "count", "perTransaction"], description: "Order (default total)." },
|
|
975
|
+
limit: { type: "integer", value: "<n>", description: "How many (max 100)." }
|
|
976
|
+
},
|
|
977
|
+
examples: [
|
|
978
|
+
{ command: "tracksiy queries --since 24h", description: "The queries that cost the most time today." },
|
|
979
|
+
{ command: "tracksiy queries --sort perTransaction --select queries.query,queries.maxPerTransaction,queries.routes", description: "N+1 candidates." }
|
|
980
|
+
],
|
|
981
|
+
output: "{since, until, queries:[{fingerprint, op, query, count, transactions, perTransaction, maxPerTransaction, totalMs, avgMs, maxMs, previous{count, avgMs}, changeRatio, nPlusOne, routes[{transaction, count, avgMs}]}], note}",
|
|
982
|
+
text: (d) => table(d.queries ?? [], [
|
|
983
|
+
["total ms", (r) => r.totalMs],
|
|
984
|
+
["avg", (r) => r.avgMs ?? "-"],
|
|
985
|
+
["max", (r) => r.maxMs],
|
|
986
|
+
["count", (r) => r.count],
|
|
987
|
+
["per req", (r) => r.perTransaction],
|
|
988
|
+
["n+1", (r) => r.nPlusOne ? "yes" : ""],
|
|
989
|
+
["query", (r) => String(r.query).slice(0, 90)]
|
|
990
|
+
]),
|
|
991
|
+
async run(ctx) {
|
|
992
|
+
return ctx.api.get(await projectPath(ctx, "queries"), {
|
|
993
|
+
since: since(ctx.input.since),
|
|
994
|
+
op: ctx.input.op,
|
|
995
|
+
transaction: ctx.input.transaction,
|
|
996
|
+
sort: ctx.input.sort,
|
|
929
997
|
limit: ctx.input.limit
|
|
930
998
|
});
|
|
931
999
|
}
|
|
932
1000
|
};
|
|
933
|
-
var fleet_default = [orgs, projects, overview, changes, health, monitorsList, monitorsDeclare, monitorsDelete, performance];
|
|
1001
|
+
var fleet_default = [orgs, projects, overview, changes, health, monitorsList, monitorsDeclare, monitorsDelete, performance, queries];
|
|
934
1002
|
|
|
935
1003
|
// src/commands/issues.ts
|
|
1004
|
+
import { writeFileSync } from "node:fs";
|
|
936
1005
|
var FILTERS = {
|
|
937
1006
|
status: { type: "string", enum: ["unresolved", "resolved", "ignored", "regressed", "all"], description: "Issue status. Server default: unresolved." },
|
|
938
1007
|
level: { type: "string", enum: ["sample", "debug", "info", "warning", "error", "fatal"], description: "Only this level." },
|
|
@@ -942,7 +1011,9 @@ var FILTERS = {
|
|
|
942
1011
|
since: { type: "string", value: TIME, description: "Last seen at or after this (lastSeenSince)." },
|
|
943
1012
|
until: { type: "string", value: TIME, description: "Last seen before this (lastSeenBefore)." },
|
|
944
1013
|
"first-seen-since": { type: "string", value: TIME, description: "First seen at or after this: new issues only." },
|
|
945
|
-
"first-seen-until": { type: "string", value: TIME, description: "First seen before this." }
|
|
1014
|
+
"first-seen-until": { type: "string", value: TIME, description: "First seen before this." },
|
|
1015
|
+
"exclude-bots": { type: "boolean", description: "Leave out issues every event of which came from a crawler (ClaudeBot, Googlebot, ...)." },
|
|
1016
|
+
"only-bots": { type: "boolean", description: "Only issues that came entirely from crawlers." }
|
|
946
1017
|
};
|
|
947
1018
|
var WHY = {
|
|
948
1019
|
reason: { type: "string", enum: ["fixed", "duplicate", "third_party", "expected", "wont_fix"], description: "Why, recorded on the issue's activity log." },
|
|
@@ -974,7 +1045,8 @@ function filterQuery(input) {
|
|
|
974
1045
|
lastSeenSince: since(input.since),
|
|
975
1046
|
lastSeenBefore: since(input.until, "until"),
|
|
976
1047
|
firstSeenSince: since(input.firstSeenSince, "first-seen-since"),
|
|
977
|
-
firstSeenBefore: since(input.firstSeenUntil, "first-seen-until")
|
|
1048
|
+
firstSeenBefore: since(input.firstSeenUntil, "first-seen-until"),
|
|
1049
|
+
bots: input.excludeBots ? "exclude" : input.onlyBots ? "only" : void 0
|
|
978
1050
|
});
|
|
979
1051
|
}
|
|
980
1052
|
var issueRows = (issues) => table(issues ?? [], [
|
|
@@ -1202,11 +1274,13 @@ var bulk = {
|
|
|
1202
1274
|
effect: "write",
|
|
1203
1275
|
endpoint: { method: "POST", path: "/api/agent/v1/projects/{org}/{project}/issues/status" },
|
|
1204
1276
|
flags: {
|
|
1205
|
-
to: { type: "string",
|
|
1277
|
+
to: { type: "string", enum: ["resolved", "ignored", "unresolved"], description: "New status (required unless --revert)." },
|
|
1206
1278
|
ids: { type: "list", value: "<id,...>", description: "Exactly these issues." },
|
|
1207
1279
|
...FILTERS,
|
|
1208
1280
|
all: { type: "boolean", description: "Every issue of the project (required when no other selector is given)." },
|
|
1209
1281
|
apply: { type: "boolean", description: "Actually change them. Default is a dry run." },
|
|
1282
|
+
out: { type: "string", value: "<file.json>", description: "With --apply: write every changed issue and its previous status here, for --revert." },
|
|
1283
|
+
revert: { type: "string", value: "<file.json>", description: "Undo a saved --out: put each issue back to the status it had." },
|
|
1210
1284
|
...FIXED_IN,
|
|
1211
1285
|
...WHY
|
|
1212
1286
|
},
|
|
@@ -1216,6 +1290,18 @@ var bulk = {
|
|
|
1216
1290
|
],
|
|
1217
1291
|
output: "{dryRun, matched, changed, ids, ...} as reported by the server.",
|
|
1218
1292
|
async run(ctx) {
|
|
1293
|
+
if (ctx.input.revert) {
|
|
1294
|
+
const saved = JSON.parse(textArg(`@${ctx.input.revert}`, "revert") ?? "{}");
|
|
1295
|
+
const changes2 = saved.changed ?? saved;
|
|
1296
|
+
const byStatus = /* @__PURE__ */ new Map();
|
|
1297
|
+
for (const c of changes2) if (c?.id && c.previousStatus) byStatus.set(c.previousStatus, [...byStatus.get(c.previousStatus) ?? [], String(c.id)]);
|
|
1298
|
+
const results = {};
|
|
1299
|
+
for (const [status2, ids] of byStatus) {
|
|
1300
|
+
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}` });
|
|
1301
|
+
}
|
|
1302
|
+
return { reverted: changes2.length, dryRun: !ctx.input.apply, results };
|
|
1303
|
+
}
|
|
1304
|
+
if (!ctx.input.to) throw usageError("missing_flag", "--to is required.", "Pass --to resolved|ignored|unresolved, or --revert <file>.");
|
|
1219
1305
|
const filter = filterQuery(ctx.input);
|
|
1220
1306
|
const hasFilter = Object.keys(filter).length > 0;
|
|
1221
1307
|
const body = compact({
|
|
@@ -1226,7 +1312,12 @@ var bulk = {
|
|
|
1226
1312
|
dryRun: !ctx.input.apply,
|
|
1227
1313
|
...whyBody(ctx.input)
|
|
1228
1314
|
});
|
|
1229
|
-
|
|
1315
|
+
const result = await ctx.api.send("POST", await projectPath(ctx, "issues/status"), body);
|
|
1316
|
+
if (ctx.input.out && ctx.input.apply) {
|
|
1317
|
+
writeFileSync(ctx.input.out, JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), status: ctx.input.to, changed: result?.changed ?? [] }, null, 2));
|
|
1318
|
+
ctx.log(`wrote ${result?.changed?.length ?? 0} change(s) to ${ctx.input.out}; undo with --revert ${ctx.input.out} --apply`);
|
|
1319
|
+
}
|
|
1320
|
+
return result;
|
|
1230
1321
|
}
|
|
1231
1322
|
};
|
|
1232
1323
|
var issues_default = [list, view, resolve2, ignore, unresolve, bulk, comment, activity, verifyFixed, claim, unclaim];
|
|
@@ -3101,7 +3192,7 @@ var COMMANDS = [
|
|
|
3101
3192
|
];
|
|
3102
3193
|
var GROUPS = [
|
|
3103
3194
|
["Start", "init auth onboarding describe"],
|
|
3104
|
-
["Look", "overview changes health orgs projects issues monitors performance releases alerts keys artifacts"],
|
|
3195
|
+
["Look", "overview changes health orgs projects issues monitors performance queries releases alerts keys artifacts"],
|
|
3105
3196
|
["Ship", "sourcemaps proguard"],
|
|
3106
3197
|
["Manage", "tokens"],
|
|
3107
3198
|
["Talk to us", "support"],
|