tracksiy 0.1.1 → 0.2.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 +6 -2
- package/dist/tracksiy.mjs +175 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -128,10 +128,14 @@ Labels: `bug`, `feature`, `question`, `blocker`. `npx tracksiy support` prints t
|
|
|
128
128
|
| `performance` | Is this project slow, and did it get slower? Transactions compared with the previous window, plus uptime. | read | `performance` |
|
|
129
129
|
| `issues list` | Recent issues of a project, newest activity first, with the frames that matter inlined. | read | `issues_list` |
|
|
130
130
|
| `issues view <id>` | One issue in enough depth to act on: latest event, stack frames, tags, recent volume. | read | `issues_view` |
|
|
131
|
-
| `issues resolve <ids...>` | Mark issues resolved
|
|
132
|
-
| `issues ignore <ids...>` | Mark issues ignored: they stop alerting until unignored. | write | `issues_ignore` |
|
|
131
|
+
| `issues resolve <ids...>` | Mark issues resolved, optionally fixed in a release so old clients do not reopen them. Records why. | write | `issues_resolve` |
|
|
132
|
+
| `issues ignore <ids...>` | Mark issues ignored: they stop alerting until unignored. Records why. | write | `issues_ignore` |
|
|
133
133
|
| `issues unresolve <ids...>` | Reopen issues. | write | `issues_unresolve` |
|
|
134
134
|
| `issues bulk-status` | Change the status of every issue matching a filter. Dry run unless --apply. | write | `issues_bulk_status` |
|
|
135
|
+
| `issues comment <id>` | Leave a comment on an issue for the next agent or human: what you found, what you tried. | write | `issues_comment` |
|
|
136
|
+
| `issues activity <id>` | Who did what to an issue and why: resolutions (and in which release), regressions, comments, claims. | read | `issues_activity` |
|
|
137
|
+
| `issues claim <id>` | Take a lease on an issue so no other agent works on it meanwhile. Lapses on its own. | write | `issues_claim` |
|
|
138
|
+
| `issues unclaim <id>` | Give back your lease on an issue. | write | `issues_unclaim` |
|
|
135
139
|
| `projects create <name>` | Everything a new app needs in one idempotent call: the project, an ingest key per surface, an upload token, and the next steps. | write | `projects_create` |
|
|
136
140
|
| `projects verify` | Did the integration actually work? Checks events, releases, environments, source maps, privacy and alerting. Exit 10 if not. | read | `projects_verify` |
|
|
137
141
|
| `onboarding` | What to know before integrating an app. No token needed. | read | `onboarding` |
|
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.2.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
|
|
@@ -115,6 +115,7 @@ var Api = class {
|
|
|
115
115
|
const headers = { "User-Agent": USER_AGENT, Accept: "application/json" };
|
|
116
116
|
const useAuth = opts.auth !== false;
|
|
117
117
|
if (useAuth && this.opts.token) headers.Authorization = `Bearer ${this.opts.token}`;
|
|
118
|
+
if (this.opts.agent) headers["X-Tracksiy-Agent"] = this.opts.agent;
|
|
118
119
|
let body;
|
|
119
120
|
if (opts.form) body = opts.form;
|
|
120
121
|
else if (opts.body !== void 0) {
|
|
@@ -375,6 +376,8 @@ function resolveConfig(globals, env = process.env, cwd = process.cwd()) {
|
|
|
375
376
|
verbose: Boolean(globals.verbose),
|
|
376
377
|
quiet: Boolean(globals.quiet)
|
|
377
378
|
};
|
|
379
|
+
const agent = (globals.as ?? env.TRACKSIY_AGENT ?? "").trim();
|
|
380
|
+
if (agent) out.agent = agent.slice(0, 100);
|
|
378
381
|
if (pf) out.projectFile = pf.path;
|
|
379
382
|
if (globals.token) [out.token, out.tokenSource] = [globals.token, "--token"];
|
|
380
383
|
else if (env.TRACKSIY_TOKEN) [out.token, out.tokenSource] = [env.TRACKSIY_TOKEN, "TRACKSIY_TOKEN"];
|
|
@@ -641,6 +644,7 @@ function missingToken(url) {
|
|
|
641
644
|
var auth_default = [login, status, logout];
|
|
642
645
|
|
|
643
646
|
// src/commands/_util.ts
|
|
647
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
644
648
|
async function projectPath(ctx, suffix = "") {
|
|
645
649
|
const { org, project: project2 } = await ctx.project();
|
|
646
650
|
return `projects/${seg(org)}/${seg(project2)}${suffix ? `/${suffix}` : ""}`;
|
|
@@ -679,6 +683,26 @@ function rate(value, flag) {
|
|
|
679
683
|
function compact(obj) {
|
|
680
684
|
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
681
685
|
}
|
|
686
|
+
function textArg(value, flag) {
|
|
687
|
+
if (value === void 0 || value === null) return void 0;
|
|
688
|
+
const raw = String(value);
|
|
689
|
+
if (raw === "-") return readFileSync3(0, "utf8");
|
|
690
|
+
if (raw.startsWith("@")) {
|
|
691
|
+
try {
|
|
692
|
+
return readFileSync3(raw.slice(1), "utf8");
|
|
693
|
+
} catch (err) {
|
|
694
|
+
throw usageError("bad_value", `--${flag}: cannot read ${raw.slice(1)}: ${err.message}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return raw;
|
|
698
|
+
}
|
|
699
|
+
function duration(value, flag) {
|
|
700
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
701
|
+
const s = String(value).trim();
|
|
702
|
+
const m = /^(\d+)\s*([smhdw])?$/.exec(s);
|
|
703
|
+
if (!m) throw usageError("bad_value", `--${flag} expects a duration like 30m / 2h / 1d, got ${JSON.stringify(s)}`);
|
|
704
|
+
return Number(m[1]) * (m[2] ? UNITS[m[2]] : 1);
|
|
705
|
+
}
|
|
682
706
|
|
|
683
707
|
// src/commands/fleet.ts
|
|
684
708
|
var orgs = {
|
|
@@ -920,6 +944,26 @@ var FILTERS = {
|
|
|
920
944
|
"first-seen-since": { type: "string", value: TIME, description: "First seen at or after this: new issues only." },
|
|
921
945
|
"first-seen-until": { type: "string", value: TIME, description: "First seen before this." }
|
|
922
946
|
};
|
|
947
|
+
var WHY = {
|
|
948
|
+
reason: { type: "string", enum: ["fixed", "duplicate", "third_party", "expected", "wont_fix"], description: "Why, recorded on the issue's activity log." },
|
|
949
|
+
note: { type: "string", value: "<md|@file|->", description: "A note for the next agent (markdown), kept on the activity log." },
|
|
950
|
+
ref: { type: "string", value: "<sha|url>", description: "The evidence: a commit, PR or URL." }
|
|
951
|
+
};
|
|
952
|
+
var FIXED_IN = {
|
|
953
|
+
"in-release": { type: "string", value: "<version>", description: "Fixed in this release: events from older releases count as stale, not as a regression. Must exist (releases declare)." },
|
|
954
|
+
"in-commit": { type: "string", value: "<sha>", description: "Fixed in the release that carries this commit (its ref, or a version ending @<sha>); bound when that release appears." },
|
|
955
|
+
"in-next-release": { type: "boolean", description: "Fixed in whatever is released next: only newer releases can reopen it." }
|
|
956
|
+
};
|
|
957
|
+
function whyBody(input) {
|
|
958
|
+
return compact({
|
|
959
|
+
inRelease: input.inRelease,
|
|
960
|
+
inCommit: input.inCommit,
|
|
961
|
+
inNextRelease: input.inNextRelease || void 0,
|
|
962
|
+
reason: input.reason,
|
|
963
|
+
note: textArg(input.note, "note"),
|
|
964
|
+
ref: input.ref
|
|
965
|
+
});
|
|
966
|
+
}
|
|
923
967
|
function filterQuery(input) {
|
|
924
968
|
return compact({
|
|
925
969
|
status: input.status,
|
|
@@ -952,14 +996,17 @@ var list = {
|
|
|
952
996
|
...FILTERS,
|
|
953
997
|
sort: { type: "string", enum: ["lastSeen", "firstSeen", "count"], description: "Order. Default lastSeen." },
|
|
954
998
|
limit: { type: "integer", value: "<n>", description: "How many issues (server default 25)." },
|
|
955
|
-
frames: { type: "integer", value: "<n>", description: "In-app stack frames inlined per issue (default 3, 0 for none)." }
|
|
999
|
+
frames: { type: "integer", value: "<n>", description: "In-app stack frames inlined per issue (default 3, 0 for none)." },
|
|
1000
|
+
unclaimed: { type: "boolean", description: "Only issues nobody holds a live claim on: what you can pick up." },
|
|
1001
|
+
"claimed-by": { type: "string", value: "<handle>", description: "Only issues claimed by this handle." }
|
|
956
1002
|
},
|
|
957
1003
|
examples: [
|
|
958
1004
|
{ command: "tracksiy issues list --since 24h", description: "Unresolved issues seen in the last day." },
|
|
1005
|
+
{ command: "tracksiy issues list --unclaimed --limit 5", description: "Five issues no other agent is working on." },
|
|
959
1006
|
{ command: "tracksiy issues list --status regressed --select issues.id,issues.title", description: "Only regressions, only ids and titles." },
|
|
960
1007
|
{ command: "tracksiy issues list --first-seen-since 2h --environment production", description: "What is new in production." }
|
|
961
1008
|
],
|
|
962
|
-
output: "{project, issueCount, total, hasMore, issues:[{id, shortId, title, culprit, level, status, count, firstSeen, lastSeen, regressedAt, timesRegressed, permalink, frames?}]}",
|
|
1009
|
+
output: "{project, issueCount, total, hasMore, issues:[{id, shortId, title, culprit, level, status, count, firstSeen, lastSeen, regressedAt, timesRegressed, resolution?, staleEvents, claimedBy?, claimedUntil?, permalink, frames?}]}",
|
|
963
1010
|
text: (d) => `${d.issueCount ?? d.issues?.length ?? 0} of ${d.total ?? "?"} issues${d.hasMore ? " (more: raise --limit)" : ""}
|
|
964
1011
|
${issueRows(d.issues)}`,
|
|
965
1012
|
async run(ctx) {
|
|
@@ -967,7 +1014,9 @@ ${issueRows(d.issues)}`,
|
|
|
967
1014
|
...filterQuery(ctx.input),
|
|
968
1015
|
sort: ctx.input.sort,
|
|
969
1016
|
limit: ctx.input.limit,
|
|
970
|
-
frames: ctx.input.frames
|
|
1017
|
+
frames: ctx.input.frames,
|
|
1018
|
+
unclaimed: ctx.input.unclaimed || void 0,
|
|
1019
|
+
claimedBy: ctx.input.claimedBy
|
|
971
1020
|
});
|
|
972
1021
|
}
|
|
973
1022
|
};
|
|
@@ -980,7 +1029,7 @@ var view = {
|
|
|
980
1029
|
args: [{ name: "id", required: true, description: "Issue id (numeric, from `issues list`)." }],
|
|
981
1030
|
flags: { frames: { type: "integer", value: "<n>", description: "How many frames to include." } },
|
|
982
1031
|
examples: [{ command: "tracksiy issues view 4812", description: "Everything needed to start fixing issue 4812." }],
|
|
983
|
-
output: "{id, title, culprit, level, status, count, firstSeen, lastSeen, latestEvent{release, environment, exceptionType, exceptionValue, contexts}, frames[{filename, function, lineno, contextLine, inApp}], tags[], eventsLast24h, eventsLast7d, permalink}",
|
|
1032
|
+
output: "{id, title, culprit, level, status, count, firstSeen, lastSeen, resolution?, staleEvents, claimedBy?, latestEvent{release, environment, exceptionType, exceptionValue, contexts}, frames[{filename, function, lineno, contextLine, inApp}], tags[], eventsLast24h, eventsLast7d, activity[], recentComments[], permalink}",
|
|
984
1033
|
text: (d) => {
|
|
985
1034
|
const head = kv(d, ["id", "shortId", "title", "culprit", "level", "status", "count", "eventsLast24h", "eventsLast7d", "firstSeen", "lastSeen", "regressedAt", "permalink"]);
|
|
986
1035
|
const ev = d.latestEvent ? `
|
|
@@ -988,15 +1037,22 @@ latest event:
|
|
|
988
1037
|
${kv(d.latestEvent, ["release", "environment", "exceptionType", "exceptionValue", "transaction", "platform"]).replace(/^/gm, " ")}` : "";
|
|
989
1038
|
const frames = (d.frames ?? []).map((f) => ` ${f.inApp ? "*" : " "} ${f.filename ?? f.module ?? "?"}:${f.lineno ?? "?"} in ${f.function ?? "?"}${f.contextLine ? `
|
|
990
1039
|
${String(f.contextLine).trim()}` : ""}`).join("\n");
|
|
991
|
-
|
|
1040
|
+
const res = d.resolution ? `
|
|
1041
|
+
resolved ${d.resolution.type.replace(/_/g, " ")}: ${d.resolution.release ?? d.resolution.commit ?? "-"}${d.resolution.pending ? " (pending)" : ""}; ${d.staleEvents ?? 0} stale event(s)` : "";
|
|
1042
|
+
const claim2 = d.claimedBy ? `
|
|
1043
|
+
claimed by ${d.claimedBy} until ${d.claimedUntil}` : "";
|
|
1044
|
+
const act = (d.activity ?? []).slice(0, 5).map((a) => ` ${ago(a.at)} ${a.action}${a.reason ? ` (${a.reason})` : ""} by ${a.actor ?? a.user ?? a.via}${a.note ? `: ${String(a.note).split("\n")[0].slice(0, 100)}` : ""}`).join("\n");
|
|
1045
|
+
return `${head}${res}${claim2}${ev}${frames ? `
|
|
992
1046
|
frames (* = in app):
|
|
993
|
-
${frames}` : ""}
|
|
1047
|
+
${frames}` : ""}${act ? `
|
|
1048
|
+
activity:
|
|
1049
|
+
${act}` : ""}`;
|
|
994
1050
|
},
|
|
995
1051
|
async run(ctx) {
|
|
996
1052
|
return ctx.api.get(await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}`), { frames: ctx.input.frames });
|
|
997
1053
|
}
|
|
998
1054
|
};
|
|
999
|
-
function statusCommand(name, status2, summary) {
|
|
1055
|
+
function statusCommand(name, status2, summary, examples) {
|
|
1000
1056
|
return {
|
|
1001
1057
|
path: ["issues", name],
|
|
1002
1058
|
summary,
|
|
@@ -1005,19 +1061,105 @@ function statusCommand(name, status2, summary) {
|
|
|
1005
1061
|
idempotent: true,
|
|
1006
1062
|
endpoint: { method: "POST", path: "/api/agent/v1/projects/{org}/{project}/issues/{issue_id}/status" },
|
|
1007
1063
|
args: [{ name: "ids", required: true, variadic: true, description: "One or more issue ids." }],
|
|
1008
|
-
|
|
1009
|
-
|
|
1064
|
+
flags: name === "resolve" ? { ...FIXED_IN, ...WHY } : { ...WHY },
|
|
1065
|
+
examples,
|
|
1066
|
+
output: "One id: the issue's new state with changed, requested, applied. Several: {matched, updated, alreadyInStatus, resolution, dryRun:false}.",
|
|
1010
1067
|
async run(ctx) {
|
|
1011
1068
|
const ids = ctx.input.ids;
|
|
1012
1069
|
for (const id of ids) if (!/^\d+$/.test(id)) throw usageError("bad_value", `Issue ids are numeric; got ${JSON.stringify(id)}`);
|
|
1013
|
-
|
|
1014
|
-
return ctx.api.send("POST", await projectPath(ctx,
|
|
1070
|
+
const why = whyBody(ctx.input);
|
|
1071
|
+
if (ids.length === 1) return ctx.api.send("POST", await projectPath(ctx, `issues/${ids[0]}/status`), { status: status2, ...why });
|
|
1072
|
+
return ctx.api.send("POST", await projectPath(ctx, "issues/status"), { status: status2, ids, dryRun: false, ...why });
|
|
1015
1073
|
}
|
|
1016
1074
|
};
|
|
1017
1075
|
}
|
|
1018
|
-
var resolve2 = statusCommand(
|
|
1019
|
-
|
|
1020
|
-
|
|
1076
|
+
var resolve2 = statusCommand(
|
|
1077
|
+
"resolve",
|
|
1078
|
+
"resolved",
|
|
1079
|
+
"Mark issues resolved, optionally fixed in a release so old clients do not reopen them. Records why.",
|
|
1080
|
+
[
|
|
1081
|
+
{ command: "tracksiy issues resolve 4812 --in-release web@$(git rev-parse HEAD) --reason fixed --ref $(git rev-parse HEAD)", description: "Fixed in this deploy; old bundles count as stale." },
|
|
1082
|
+
{ command: "tracksiy issues resolve 4812 --in-next-release --note @why.md", description: "Fixed in whatever ships next, with the reasoning." },
|
|
1083
|
+
{ command: "tracksiy issues resolve 5562 --reason third_party --note 'WebView injects addEL_hook'", description: "Not our code: closed with the reason on record." }
|
|
1084
|
+
]
|
|
1085
|
+
);
|
|
1086
|
+
var ignore = statusCommand("ignore", "ignored", "Mark issues ignored: they stop alerting until unignored. Records why.", [
|
|
1087
|
+
{ command: "tracksiy issues ignore 4812 --reason expected --note 'Bots hitting a removed URL'", description: "Ignore, and say why for the next agent." }
|
|
1088
|
+
]);
|
|
1089
|
+
var unresolve = statusCommand("unresolve", "unresolved", "Reopen issues.", [{ command: "tracksiy issues unresolve 4812 --note 'Came back on Android'", description: "Reopen with a note." }]);
|
|
1090
|
+
var comment = {
|
|
1091
|
+
path: ["issues", "comment"],
|
|
1092
|
+
summary: "Leave a comment on an issue for the next agent or human: what you found, what you tried.",
|
|
1093
|
+
context: "project",
|
|
1094
|
+
effect: "write",
|
|
1095
|
+
endpoint: { method: "POST", path: "/api/agent/v1/projects/{org}/{project}/issues/{issue_id}/comments" },
|
|
1096
|
+
args: [{ name: "id", required: true, description: "Issue id." }],
|
|
1097
|
+
flags: { text: { type: "string", required: true, value: "<md|@file|->", description: "The comment (markdown). @file reads a file, - reads stdin." } },
|
|
1098
|
+
examples: [{ command: "tracksiy issues comment 4812 --text @findings.md", description: "Attach what you found." }],
|
|
1099
|
+
output: "{id, created, user, text}",
|
|
1100
|
+
async run(ctx) {
|
|
1101
|
+
return ctx.api.send("POST", await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/comments`), { text: textArg(ctx.input.text, "text") });
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
var activity = {
|
|
1105
|
+
path: ["issues", "activity"],
|
|
1106
|
+
summary: "Who did what to an issue and why: resolutions (and in which release), regressions, comments, claims.",
|
|
1107
|
+
context: "project",
|
|
1108
|
+
effect: "read",
|
|
1109
|
+
endpoint: { method: "GET", path: "/api/agent/v1/projects/{org}/{project}/issues/{issue_id}/activity" },
|
|
1110
|
+
args: [{ name: "id", required: true, description: "Issue id." }],
|
|
1111
|
+
flags: { limit: { type: "integer", value: "<n>", description: "How many entries (1..500, default 100)." } },
|
|
1112
|
+
examples: [{ command: "tracksiy issues activity 4812", description: "The issue's history, newest first." }],
|
|
1113
|
+
output: "{issue, activity:[{at, action, via, actor, user, reason, note, ref, data}], comments:[{id, created, user, text}]}",
|
|
1114
|
+
text: (d) => table(d.activity ?? [], [
|
|
1115
|
+
["when", (r) => ago(r.at)],
|
|
1116
|
+
"action",
|
|
1117
|
+
["by", (r) => r.actor ?? r.user ?? r.via],
|
|
1118
|
+
["reason", (r) => r.reason ?? ""],
|
|
1119
|
+
["note", (r) => String(r.note ?? (r.data?.release ? `release ${r.data.release}` : "")).split("\n")[0].slice(0, 80)]
|
|
1120
|
+
]),
|
|
1121
|
+
async run(ctx) {
|
|
1122
|
+
return ctx.api.get(await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/activity`), { limit: ctx.input.limit });
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
var claim = {
|
|
1126
|
+
path: ["issues", "claim"],
|
|
1127
|
+
summary: "Take a lease on an issue so no other agent works on it meanwhile. Lapses on its own.",
|
|
1128
|
+
description: "The holder is your handle: --as, TRACKSIY_AGENT, else the token's label. Claiming what you hold extends it. Someone else's live claim is an error naming them, unless --force. Resolving or ignoring releases it.",
|
|
1129
|
+
context: "project",
|
|
1130
|
+
effect: "write",
|
|
1131
|
+
endpoint: { method: "POST", path: "/api/agent/v1/projects/{org}/{project}/issues/{issue_id}/claim" },
|
|
1132
|
+
args: [{ name: "id", required: true, description: "Issue id." }],
|
|
1133
|
+
flags: {
|
|
1134
|
+
for: { type: "string", value: "<30m|2h|1d>", description: "Lease length (60 s to 7 days). Default 2h." },
|
|
1135
|
+
force: { type: "boolean", description: "Take it from another live holder." }
|
|
1136
|
+
},
|
|
1137
|
+
examples: [{ command: "TRACKSIY_AGENT=logistivo-app tracksiy issues claim 4812 --for 2h", description: "Work on 4812 for two hours." }],
|
|
1138
|
+
output: "The issue with claimedBy, claimedUntil, changed, previousClaimedBy.",
|
|
1139
|
+
async run(ctx) {
|
|
1140
|
+
return ctx.api.send(
|
|
1141
|
+
"POST",
|
|
1142
|
+
await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/claim`),
|
|
1143
|
+
compact({ seconds: duration(ctx.input.for, "for"), force: ctx.input.force || void 0 })
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
var unclaim = {
|
|
1148
|
+
path: ["issues", "unclaim"],
|
|
1149
|
+
summary: "Give back your lease on an issue.",
|
|
1150
|
+
context: "project",
|
|
1151
|
+
effect: "write",
|
|
1152
|
+
idempotent: true,
|
|
1153
|
+
endpoint: { method: "DELETE", path: "/api/agent/v1/projects/{org}/{project}/issues/{issue_id}/claim" },
|
|
1154
|
+
args: [{ name: "id", required: true, description: "Issue id." }],
|
|
1155
|
+
flags: { force: { type: "boolean", description: "Release someone else's claim." } },
|
|
1156
|
+
examples: [{ command: "tracksiy issues unclaim 4812", description: "Done, or giving up: let others take it." }],
|
|
1157
|
+
output: "The issue with changed and previousClaimedBy.",
|
|
1158
|
+
async run(ctx) {
|
|
1159
|
+
const path = await projectPath(ctx, `issues/${encodeURIComponent(ctx.input.id)}/claim`);
|
|
1160
|
+
return (await ctx.api.request("DELETE", path, { query: ctx.input.force ? { force: true } : void 0 })).data;
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1021
1163
|
var bulk = {
|
|
1022
1164
|
path: ["issues", "bulk-status"],
|
|
1023
1165
|
summary: "Change the status of every issue matching a filter. Dry run unless --apply.",
|
|
@@ -1030,7 +1172,9 @@ var bulk = {
|
|
|
1030
1172
|
ids: { type: "list", value: "<id,...>", description: "Exactly these issues." },
|
|
1031
1173
|
...FILTERS,
|
|
1032
1174
|
all: { type: "boolean", description: "Every issue of the project (required when no other selector is given)." },
|
|
1033
|
-
apply: { type: "boolean", description: "Actually change them. Default is a dry run." }
|
|
1175
|
+
apply: { type: "boolean", description: "Actually change them. Default is a dry run." },
|
|
1176
|
+
...FIXED_IN,
|
|
1177
|
+
...WHY
|
|
1034
1178
|
},
|
|
1035
1179
|
examples: [
|
|
1036
1180
|
{ command: "tracksiy issues bulk-status --to resolved --until 30d", description: "Preview resolving everything not seen for 30 days." },
|
|
@@ -1045,12 +1189,13 @@ var bulk = {
|
|
|
1045
1189
|
ids: ctx.input.ids?.map(String),
|
|
1046
1190
|
filter: hasFilter ? filter : void 0,
|
|
1047
1191
|
all: ctx.input.all || void 0,
|
|
1048
|
-
dryRun: !ctx.input.apply
|
|
1192
|
+
dryRun: !ctx.input.apply,
|
|
1193
|
+
...whyBody(ctx.input)
|
|
1049
1194
|
});
|
|
1050
1195
|
return ctx.api.send("POST", await projectPath(ctx, "issues/status"), body);
|
|
1051
1196
|
}
|
|
1052
1197
|
};
|
|
1053
|
-
var issues_default = [list, view, resolve2, ignore, unresolve, bulk];
|
|
1198
|
+
var issues_default = [list, view, resolve2, ignore, unresolve, bulk, comment, activity, claim, unclaim];
|
|
1054
1199
|
|
|
1055
1200
|
// src/commands/projects.ts
|
|
1056
1201
|
var create = {
|
|
@@ -1466,7 +1611,7 @@ var releases_default = [list3, health2, declare];
|
|
|
1466
1611
|
|
|
1467
1612
|
// src/commands/artifacts.ts
|
|
1468
1613
|
import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
1469
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1614
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
1470
1615
|
|
|
1471
1616
|
// src/lib/sourcemaps.ts
|
|
1472
1617
|
import { createHash } from "node:crypto";
|
|
@@ -1841,7 +1986,7 @@ skipped, no source map: ${d.skipped.join(", ")}` : ""}`,
|
|
|
1841
1986
|
};
|
|
1842
1987
|
function uuidFromDebugMeta(path) {
|
|
1843
1988
|
if (!existsSync3(path)) throw usageError("bad_path", `No such file: ${path}`);
|
|
1844
|
-
const m = /ProguardUuids\s*[=:]\s*([0-9a-fA-F-]{36})/.exec(
|
|
1989
|
+
const m = /ProguardUuids\s*[=:]\s*([0-9a-fA-F-]{36})/.exec(readFileSync4(path, "utf8"));
|
|
1845
1990
|
if (!m) throw usageError("bad_value", `No ProguardUuids entry in ${path}`);
|
|
1846
1991
|
return m[1].toLowerCase();
|
|
1847
1992
|
}
|
|
@@ -2053,16 +2198,16 @@ var support = {
|
|
|
2053
2198
|
var support_default = [support];
|
|
2054
2199
|
|
|
2055
2200
|
// src/commands/init.ts
|
|
2056
|
-
import { existsSync as existsSync5, readFileSync as
|
|
2201
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
2057
2202
|
import { appendFile, writeFile as writeFile3 } from "node:fs/promises";
|
|
2058
2203
|
import { join as join4, resolve as resolve4 } from "node:path";
|
|
2059
2204
|
|
|
2060
2205
|
// src/lib/detect.ts
|
|
2061
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2206
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync } from "node:fs";
|
|
2062
2207
|
import { basename, join as join3 } from "node:path";
|
|
2063
2208
|
function readJson(path) {
|
|
2064
2209
|
try {
|
|
2065
|
-
return JSON.parse(
|
|
2210
|
+
return JSON.parse(readFileSync5(path, "utf8"));
|
|
2066
2211
|
} catch {
|
|
2067
2212
|
return void 0;
|
|
2068
2213
|
}
|
|
@@ -2134,7 +2279,7 @@ function detect(dir, guideBase) {
|
|
|
2134
2279
|
}
|
|
2135
2280
|
const py = has(dir, "pyproject.toml", "requirements.txt", "Pipfile", "manage.py");
|
|
2136
2281
|
if (py) {
|
|
2137
|
-
const text = ["pyproject.toml", "requirements.txt", "Pipfile"].map((f) => existsSync4(join3(dir, f)) ?
|
|
2282
|
+
const text = ["pyproject.toml", "requirements.txt", "Pipfile"].map((f) => existsSync4(join3(dir, f)) ? readFileSync5(join3(dir, f), "utf8") : "").join("\n").toLowerCase();
|
|
2138
2283
|
const fw = existsSync4(join3(dir, "manage.py")) || text.includes("django") ? "django" : text.includes("fastapi") ? "fastapi" : text.includes("flask") ? "flask" : "python";
|
|
2139
2284
|
evidence.push(`${py} (${fw})`);
|
|
2140
2285
|
return { platform: fw === "python" ? "python" : `python-${fw}`, framework: fw, surfaces: ["backend"], env: { backend: "SENTRY_DSN" }, guide: guide("python-sentry-sdk"), name, evidence };
|
|
@@ -2230,7 +2375,7 @@ ${d.steps.map((s, i) => `${i + 1}. ${s.detail}${s.run ? `
|
|
|
2230
2375
|
if (!existsSync5(envPath)) {
|
|
2231
2376
|
throw usageError("no_env_file", `${envPath} does not exist; nothing was written to it.`, "Create it (and make sure it is gitignored), or set the variables yourself from `keys` in the output.");
|
|
2232
2377
|
}
|
|
2233
|
-
const current =
|
|
2378
|
+
const current = readFileSync6(envPath, "utf8");
|
|
2234
2379
|
const added = [];
|
|
2235
2380
|
const kept = [];
|
|
2236
2381
|
let block = "";
|
|
@@ -2285,12 +2430,13 @@ function camel(name) {
|
|
|
2285
2430
|
}
|
|
2286
2431
|
|
|
2287
2432
|
// src/core/args.ts
|
|
2288
|
-
import { readFileSync as
|
|
2433
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
2289
2434
|
var GLOBAL_FLAGS = {
|
|
2290
2435
|
org: { type: "string", value: "<slug>", description: "Organization slug. Env: TRACKSIY_ORG. Default: .tracksiy.json, saved login, or your only organization." },
|
|
2291
2436
|
project: { type: "string", value: "<slug>", description: "Project slug, or org/project. Env: TRACKSIY_PROJECT. Default: .tracksiy.json." },
|
|
2292
2437
|
url: { type: "string", value: "<url>", description: "Server root. Env: TRACKSIY_URL. Default: https://tracksiy.com." },
|
|
2293
2438
|
token: { type: "string", value: "<token>", description: "API token. Env: TRACKSIY_TOKEN (preferred: flags show up in process lists)." },
|
|
2439
|
+
as: { type: "string", value: "<handle>", description: "Your agent handle, recorded on issue activity and claims (X-Tracksiy-Agent). Env: TRACKSIY_AGENT. Default: the token's label." },
|
|
2294
2440
|
json: { type: "boolean", description: "Print JSON. This is the default whenever stdout is not a terminal." },
|
|
2295
2441
|
format: { type: "string", enum: ["json", "text"], description: "Output format. Env: TRACKSIY_FORMAT." },
|
|
2296
2442
|
select: { type: "list", value: "<fields>", description: "Keep only these fields of `data`, comma-separated; dot paths reach into objects and lists (e.g. issues.id,issues.title)." },
|
|
@@ -2301,7 +2447,7 @@ var GLOBAL_FLAGS = {
|
|
|
2301
2447
|
};
|
|
2302
2448
|
var stdinCache;
|
|
2303
2449
|
function readStdin() {
|
|
2304
|
-
stdinCache ??=
|
|
2450
|
+
stdinCache ??= readFileSync7(0, "utf8");
|
|
2305
2451
|
return stdinCache;
|
|
2306
2452
|
}
|
|
2307
2453
|
function parseJsonValue(name, raw) {
|
|
@@ -2309,7 +2455,7 @@ function parseJsonValue(name, raw) {
|
|
|
2309
2455
|
if (raw === "-") text = readStdin();
|
|
2310
2456
|
else if (raw.startsWith("@")) {
|
|
2311
2457
|
try {
|
|
2312
|
-
text =
|
|
2458
|
+
text = readFileSync7(raw.slice(1), "utf8");
|
|
2313
2459
|
} catch (err) {
|
|
2314
2460
|
throw usageError("bad_value", `--${name}: cannot read ${raw.slice(1)}: ${err.message}`);
|
|
2315
2461
|
}
|
|
@@ -2649,7 +2795,7 @@ function makeContext(spec, input, globals, surface, io) {
|
|
|
2649
2795
|
if (!quiet || surface === "mcp" && config.verbose) io.stderr(`${message}
|
|
2650
2796
|
`);
|
|
2651
2797
|
};
|
|
2652
|
-
const api2 = new Api({ url: config.url, token: config.token, timeoutMs: config.timeoutMs, verbose: config.verbose, log, fetch: io.fetch });
|
|
2798
|
+
const api2 = new Api({ url: config.url, token: config.token, agent: config.agent, timeoutMs: config.timeoutMs, verbose: config.verbose, log, fetch: io.fetch });
|
|
2653
2799
|
let orgs2;
|
|
2654
2800
|
const visible = () => {
|
|
2655
2801
|
orgs2 ??= api2.get("organizations").catch((err) => {
|