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 +5 -0
- package/dist/tracksiy.mjs +181 -24
- package/package.json +1 -1
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.
|
|
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
|
|
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",
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
1949
|
+
pairs2.push({ js, map, root });
|
|
1804
1950
|
}
|
|
1805
1951
|
}
|
|
1806
|
-
return
|
|
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(
|
|
2123
|
+
async function inject(pairs2, dryRun) {
|
|
1978
2124
|
const out = [];
|
|
1979
|
-
for (const pair of
|
|
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
|
|
2057
|
-
if (!
|
|
2058
|
-
if (i.inject && !i.dryRun) await inject(
|
|
2059
|
-
const skipped =
|
|
2060
|
-
const withMaps =
|
|
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 ${
|
|
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"],
|