standup-mr 0.6.1 → 0.7.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.
@@ -24,21 +24,15 @@ function findPackageRoot(startDir) {
24
24
  }
25
25
  function packageVersion(moduleUrl) {
26
26
  const root = findPackageRoot(dirname(fileURLToPath(moduleUrl)));
27
- const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
27
+ const manifest = JSON.parse(
28
+ readFileSync(join(root, "package.json"), "utf8")
29
+ );
28
30
  if (!manifest.version) {
29
31
  throw new Error(`package.json at ${root} has no version`);
30
32
  }
31
33
  return manifest.version;
32
34
  }
33
35
 
34
- // src/notify/notify.constants.ts
35
- var PAYLOAD_FIELD = {
36
- slack: "text",
37
- discord: "content",
38
- "google-chat": "text"
39
- };
40
- var WEBHOOK_KINDS = Object.keys(PAYLOAD_FIELD);
41
-
42
36
  // src/render/chat.ts
43
37
  var MARKDOWN_NATIVE = ["discord"];
44
38
  var FENCE = /^```/;
@@ -59,6 +53,14 @@ function toChatText(markdown, kind) {
59
53
  }).join("\n");
60
54
  }
61
55
 
56
+ // src/notify/notify.constants.ts
57
+ var PAYLOAD_FIELD = {
58
+ "slack": "text",
59
+ "discord": "content",
60
+ "google-chat": "text"
61
+ };
62
+ var WEBHOOK_KINDS = Object.keys(PAYLOAD_FIELD);
63
+
62
64
  // src/notify/notify.ts
63
65
  async function postWebhook(url, text2, kind = "slack", fetchImpl = fetch) {
64
66
  const field = PAYLOAD_FIELD[kind];
@@ -73,7 +75,9 @@ async function postWebhook(url, text2, kind = "slack", fetchImpl = fetch) {
73
75
  body: JSON.stringify({ [field]: toChatText(text2, kind) })
74
76
  });
75
77
  if (!response.ok) {
76
- throw new Error(`Webhook rejected the message: HTTP ${response.status}.`);
78
+ throw new Error(
79
+ `Webhook rejected the message: HTTP ${response.status}.`
80
+ );
77
81
  }
78
82
  }
79
83
  var WEBHOOK_HOSTS = {
@@ -168,7 +172,9 @@ function cliStatus(cli) {
168
172
  }
169
173
  }
170
174
  function parseLoggedInHosts(statusOutput) {
171
- const hosts = [...statusOutput.matchAll(/Logged in to (\S+)/g)].map((match) => match[1]);
175
+ const hosts = [...statusOutput.matchAll(/Logged in to (\S+)/g)].map(
176
+ (match) => match[1]
177
+ );
172
178
  return [...new Set(hosts)].sort();
173
179
  }
174
180
  function glabHosts() {
@@ -184,13 +190,26 @@ function ghToken(host) {
184
190
  return cliCapture("gh", ["auth", "token", "--hostname", host]);
185
191
  }
186
192
 
187
- // src/buckets/buckets.constants.ts
188
- var STALE_DAYS = 14;
189
-
190
193
  // src/dates/dates.constants.ts
191
194
  var DAYS = {
192
- en: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
193
- tr: ["Pazar", "Pazartesi", "Sal\u0131", "\xC7ar\u015Famba", "Per\u015Fembe", "Cuma", "Cumartesi"]
195
+ en: [
196
+ "Sunday",
197
+ "Monday",
198
+ "Tuesday",
199
+ "Wednesday",
200
+ "Thursday",
201
+ "Friday",
202
+ "Saturday"
203
+ ],
204
+ tr: [
205
+ "Pazar",
206
+ "Pazartesi",
207
+ "Sal\u0131",
208
+ "\xC7ar\u015Famba",
209
+ "Per\u015Fembe",
210
+ "Cuma",
211
+ "Cumartesi"
212
+ ]
194
213
  };
195
214
  var MONTHS = {
196
215
  en: [
@@ -261,10 +280,14 @@ function previousActiveDays(eventDates, today) {
261
280
  }));
262
281
  }
263
282
 
283
+ // src/buckets/buckets.constants.ts
284
+ var STALE_DAYS = 14;
285
+
264
286
  // src/buckets/buckets.ts
265
287
  function classify(mr, today, staleDays = STALE_DAYS) {
266
288
  if (mr.draft) return "draft";
267
- if (mr.pipeline === "failed" || mr.pipeline === "canceled" || mr.unresolved > 0) return "blocked";
289
+ if (mr.pipeline === "failed" || mr.pipeline === "canceled" || mr.unresolved > 0)
290
+ return "blocked";
268
291
  const age = Math.round(
269
292
  (Date.parse(`${isoDay(today)}T00:00:00Z`) - Date.parse(`${mr.updated}T00:00:00Z`)) / MS_PER_DAY
270
293
  );
@@ -350,17 +373,25 @@ function retryAfter(response) {
350
373
  const value = response.headers.get("retry-after");
351
374
  if (!value) return "";
352
375
  const seconds = Number(value);
353
- if (!Number.isFinite(seconds) || seconds <= 0) return `; retry after ${value}`;
376
+ if (!Number.isFinite(seconds) || seconds <= 0)
377
+ return `; retry after ${value}`;
354
378
  return `; retry after ${seconds}s`;
355
379
  }
356
380
  function assertUsable(response, host) {
357
381
  if (response.ok || response.status === 404) return;
358
382
  if (response.status === 403 || response.status === 429) {
359
383
  if (remaining(response) === "0") {
360
- throw new ApiError(`${host} rate limit reached${resetAt(response)}.`, response.status);
384
+ throw new ApiError(
385
+ `${host} rate limit reached${resetAt(response)}.`,
386
+ response.status
387
+ );
361
388
  }
362
389
  const retry = retryAfter(response);
363
- if (retry) throw new ApiError(`${host} rate limit reached${retry}.`, response.status);
390
+ if (retry)
391
+ throw new ApiError(
392
+ `${host} rate limit reached${retry}.`,
393
+ response.status
394
+ );
364
395
  }
365
396
  if (response.status === 401) {
366
397
  throw new ApiError(
@@ -520,8 +551,8 @@ var GitHubProvider = class {
520
551
  }
521
552
  headers() {
522
553
  return {
523
- authorization: `Bearer ${this.token}`,
524
- accept: "application/vnd.github+json",
554
+ "authorization": `Bearer ${this.token}`,
555
+ "accept": "application/vnd.github+json",
525
556
  "x-github-api-version": API_VERSION
526
557
  };
527
558
  }
@@ -573,11 +604,14 @@ var GitHubProvider = class {
573
604
  return rows;
574
605
  }
575
606
  async getLogText(path) {
576
- const first = await this.send(buildUrl(this.api, path), { redirect: "manual" });
607
+ const first = await this.send(buildUrl(this.api, path), {
608
+ redirect: "manual"
609
+ });
577
610
  if (first.status === 404) return "";
578
611
  const location = first.headers.get("location");
579
612
  if (!location) {
580
- if (first.status === 0 || first.status >= 300 && first.status < 400) return "";
613
+ if (first.status === 0 || first.status >= 300 && first.status < 400)
614
+ return "";
581
615
  assertUsable(first, this.host);
582
616
  return first.ok ? await first.text() : "";
583
617
  }
@@ -620,7 +654,9 @@ var GitHubProvider = class {
620
654
  async shapePr(item) {
621
655
  const project = repoFromUrl(item.repository_url);
622
656
  const iid = item.number;
623
- const pull = await this.getJson(`repos/${project}/pulls/${iid}`);
657
+ const pull = await this.getJson(
658
+ `repos/${project}/pulls/${iid}`
659
+ );
624
660
  if (!pull) return null;
625
661
  const sha = pull.head?.sha ?? "";
626
662
  const [checks, reviews] = await Promise.all([
@@ -628,9 +664,15 @@ var GitHubProvider = class {
628
664
  `repos/${project}/commits/${sha}/check-runs`,
629
665
  { per_page: PAGE_SIZE }
630
666
  ) : Promise.resolve(null),
631
- this.getPaged(`repos/${project}/pulls/${iid}/reviews`, {}, 2)
667
+ this.getPaged(
668
+ `repos/${project}/pulls/${iid}/reviews`,
669
+ {},
670
+ 2
671
+ )
632
672
  ]);
633
- const { pipeline, pipelineId } = normalizeChecks(checks?.check_runs ?? []);
673
+ const { pipeline, pipelineId } = normalizeChecks(
674
+ checks?.check_runs ?? []
675
+ );
634
676
  return {
635
677
  provider: "github",
636
678
  project,
@@ -669,7 +711,9 @@ var GitHubProvider = class {
669
711
  `is:pr is:open review-requested:${identity.username}`,
670
712
  SEARCH_CAP
671
713
  );
672
- const cutoff = isoDay(new Date(today.getTime() - FRESH_REVIEW_DAYS * MS_PER_DAY));
714
+ const cutoff = isoDay(
715
+ new Date(today.getTime() - FRESH_REVIEW_DAYS * MS_PER_DAY)
716
+ );
673
717
  const rows = await Promise.all(
674
718
  items.map(async (item) => {
675
719
  const project = repoFromUrl(item.repository_url);
@@ -735,7 +779,10 @@ var GitHubProvider = class {
735
779
  (run) => run.conclusion !== null && FAILED_CONCLUSIONS.has(run.conclusion)
736
780
  );
737
781
  if (!failed) return null;
738
- const summary = [failed.output?.summary ?? "", failed.output?.text ?? ""].filter(Boolean).join("\n");
782
+ const summary = [
783
+ failed.output?.summary ?? "",
784
+ failed.output?.text ?? ""
785
+ ].filter(Boolean).join("\n");
739
786
  const errors = extractErrors(summary);
740
787
  if (errors.length === 0) return null;
741
788
  return {
@@ -848,7 +895,9 @@ var GitLabProvider = class {
848
895
  const raw = await this.getPaged("events", {
849
896
  after: isoDay(since)
850
897
  });
851
- const ids = [...new Set(raw.map((e) => e.project_id).filter(Boolean))];
898
+ const ids = [
899
+ ...new Set(raw.map((e) => e.project_id).filter(Boolean))
900
+ ];
852
901
  const paths = new Map(
853
902
  await Promise.all(
854
903
  ids.map(async (id) => [id, await this.projectPath(id)])
@@ -859,7 +908,7 @@ var GitLabProvider = class {
859
908
  return {
860
909
  at: String(event.created_at).slice(0, 16),
861
910
  action: event.action_name,
862
- project: paths.get(event.project_id) ?? "",
911
+ project: paths.get(event.project_id ?? -1) ?? "",
863
912
  targetType: event.target_type ?? "",
864
913
  title: event.target_title ?? "",
865
914
  branch: push.ref ?? "",
@@ -871,7 +920,9 @@ var GitLabProvider = class {
871
920
  async countUnresolved(projectId, iid) {
872
921
  const discussions = await this.getJson(
873
922
  `projects/${projectId}/merge_requests/${iid}/discussions`,
874
- { per_page: PAGE_SIZE2 }
923
+ {
924
+ per_page: PAGE_SIZE2
925
+ }
875
926
  );
876
927
  if (!discussions) return 0;
877
928
  return discussions.filter((discussion) => {
@@ -924,7 +975,9 @@ var GitLabProvider = class {
924
975
  async approvedByMe(projectId, iid, uid) {
925
976
  const approvals = await this.getJson(`projects/${projectId}/merge_requests/${iid}/approvals`);
926
977
  if (!approvals) return false;
927
- return (approvals.approved_by ?? []).some((entry2) => entry2.user?.id === uid);
978
+ return (approvals.approved_by ?? []).some(
979
+ (entry2) => entry2.user?.id === uid
980
+ );
928
981
  }
929
982
  async getReviews(identity, today) {
930
983
  const raw = await this.getPaged("merge_requests", {
@@ -932,7 +985,9 @@ var GitLabProvider = class {
932
985
  state: "opened",
933
986
  reviewer_id: identity.id
934
987
  });
935
- const cutoff = isoDay(new Date(today.getTime() - FRESH_REVIEW_DAYS2 * MS_PER_DAY));
988
+ const cutoff = isoDay(
989
+ new Date(today.getTime() - FRESH_REVIEW_DAYS2 * MS_PER_DAY)
990
+ );
936
991
  const rows = await Promise.all(
937
992
  raw.map(async (mr) => ({
938
993
  provider: "gitlab",
@@ -944,7 +999,11 @@ var GitLabProvider = class {
944
999
  draft: mr.draft,
945
1000
  url: mr.web_url,
946
1001
  fresh: String(mr.updated_at).slice(0, 10) >= cutoff,
947
- approvedByMe: await this.approvedByMe(mr.project_id, mr.iid, identity.id)
1002
+ approvedByMe: await this.approvedByMe(
1003
+ mr.project_id,
1004
+ mr.iid,
1005
+ identity.id
1006
+ )
948
1007
  }))
949
1008
  );
950
1009
  return rows.sort((a, b) => b.updated.localeCompare(a.updated));
@@ -956,7 +1015,9 @@ var GitLabProvider = class {
956
1015
  ) ?? [];
957
1016
  const job = jobs.find((j) => j.status === "failed");
958
1017
  if (!job) return null;
959
- const trace = await this.getText(`projects/${mr.projectId}/jobs/${job.id}/trace`);
1018
+ const trace = await this.getText(
1019
+ `projects/${mr.projectId}/jobs/${job.id}/trace`
1020
+ );
960
1021
  return {
961
1022
  provider: "gitlab",
962
1023
  project: mr.project,
@@ -989,7 +1050,12 @@ function hostFrom(cliHost, envHost, probe, fallback, labels) {
989
1050
  if (cliHost) return cliHost;
990
1051
  if (envHost) return envHost;
991
1052
  const found = probe();
992
- return resolveHost(void 0, void 0, found.length > 0 ? found : fallback, labels);
1053
+ return resolveHost(
1054
+ void 0,
1055
+ void 0,
1056
+ found.length > 0 ? found : fallback,
1057
+ labels
1058
+ );
993
1059
  }
994
1060
  function chooseKind(options = {}) {
995
1061
  const env = options.env ?? process.env;
@@ -1042,7 +1108,13 @@ function connect(options = {}) {
1042
1108
  const env = options.env ?? process.env;
1043
1109
  const probe = options.probe ?? DEFAULT_PROBE;
1044
1110
  if (chooseKind(options) === "github") {
1045
- const host2 = hostFrom(options.host, env.GITHUB_HOST, probe.github, [DOT_COM], GITHUB_LABELS);
1111
+ const host2 = hostFrom(
1112
+ options.host,
1113
+ env.GITHUB_HOST,
1114
+ probe.github,
1115
+ [DOT_COM],
1116
+ GITHUB_LABELS
1117
+ );
1046
1118
  const token2 = resolveToken(
1047
1119
  host2,
1048
1120
  options.token,
@@ -1052,7 +1124,13 @@ function connect(options = {}) {
1052
1124
  );
1053
1125
  return new GitHubProvider(host2, token2);
1054
1126
  }
1055
- const host = hostFrom(options.host, env.GITLAB_HOST, probe.gitlab, [], GITLAB_LABELS);
1127
+ const host = hostFrom(
1128
+ options.host,
1129
+ env.GITLAB_HOST,
1130
+ probe.gitlab,
1131
+ [],
1132
+ GITLAB_LABELS
1133
+ );
1056
1134
  const token = resolveToken(
1057
1135
  host,
1058
1136
  options.token,
@@ -1063,21 +1141,6 @@ function connect(options = {}) {
1063
1141
  return new GitLabProvider(host, token);
1064
1142
  }
1065
1143
 
1066
- // src/skill/skill.ts
1067
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1068
- import { dirname as dirname2, join as join2 } from "path";
1069
- import { fileURLToPath as fileURLToPath2 } from "url";
1070
- function readStandupSkillBody() {
1071
- const moduleDir = dirname2(fileURLToPath2(import.meta.url));
1072
- const skillPath = join2(findPackageRoot(moduleDir), "skills", "standup", "SKILL.md");
1073
- if (!existsSync2(skillPath)) {
1074
- throw new Error(`Cannot find the standup skill file at ${skillPath}`);
1075
- }
1076
- const content = readFileSync2(skillPath, "utf8");
1077
- const withoutFrontmatter = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, "");
1078
- return withoutFrontmatter.replace(/^\n+/, "");
1079
- }
1080
-
1081
1144
  // src/report/report.constants.ts
1082
1145
  var LOOKBACK_DAYS = 21;
1083
1146
 
@@ -1115,9 +1178,36 @@ async function buildReport(provider, today, lang = "en", lookbackDays = LOOKBACK
1115
1178
  };
1116
1179
  }
1117
1180
 
1181
+ // src/skill/skill.ts
1182
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
1183
+ import { dirname as dirname2, join as join2 } from "path";
1184
+ import { fileURLToPath as fileURLToPath2 } from "url";
1185
+ function readStandupSkillBody() {
1186
+ const moduleDir = dirname2(fileURLToPath2(import.meta.url));
1187
+ const skillPath = join2(
1188
+ findPackageRoot(moduleDir),
1189
+ "skills",
1190
+ "standup",
1191
+ "SKILL.md"
1192
+ );
1193
+ if (!existsSync2(skillPath)) {
1194
+ throw new Error(`Cannot find the standup skill file at ${skillPath}`);
1195
+ }
1196
+ const content = readFileSync2(skillPath, "utf8");
1197
+ const withoutFrontmatter = content.replace(
1198
+ /^---\r?\n[\s\S]*?\r?\n---\r?\n/,
1199
+ ""
1200
+ );
1201
+ return withoutFrontmatter.replace(/^\n+/, "");
1202
+ }
1203
+
1118
1204
  // mcp/server.ts
1119
1205
  async function collect(options = {}) {
1120
- const provider = options.providerImpl ?? connect({ provider: options.provider, host: options.host, token: options.token });
1206
+ const provider = options.providerImpl ?? connect({
1207
+ provider: options.provider,
1208
+ host: options.host,
1209
+ token: options.token
1210
+ });
1121
1211
  return buildReport(provider, /* @__PURE__ */ new Date(), options.lang ?? "en");
1122
1212
  }
1123
1213
  var STANDUP_TOOL_DESCRIPTION = 'Collect merge-request-based standup data from GitLab or GitHub. Returns one JSON object: `today`, `previousDays[]` with the events of each, `todayEvents[]`, `myMrs[]` bucketed ready / blocked / draft / stale, `reviews[]` waiting on you, and `blockers[]` carrying the error lines read out of each failed pipeline job log.\n\nCall it once at the start of a working day, to write a standup note. It is a snapshot, not a search API: it cannot fetch one named merge request, reach further back than the previous working day, or filter by project.\n\nRead-only, and credentials never come from an argument \u2014 they come from the environment or a logged-in gh / glab session. A rejected token, a refused resource or a rate limit fails the call with the host\'s own message, after two retries on transient server errors. A blocker whose diagnosis could not be fetched is still returned, with `job: "unknown"`, so a red pipeline is never silently dropped.';
@@ -1138,7 +1228,9 @@ async function runStandupTool(args, collector = collect) {
1138
1228
  host: args.host,
1139
1229
  lang: args.lang
1140
1230
  });
1141
- return { content: [{ type: "text", text: JSON.stringify(report) }] };
1231
+ return {
1232
+ content: [{ type: "text", text: JSON.stringify(report) }]
1233
+ };
1142
1234
  }
1143
1235
  var WEBHOOK_URL_ENV = "STANDUP_WEBHOOK_URL";
1144
1236
  var POST_TOOL_DESCRIPTION = `Posts a finished standup note to a chat webhook. This one has a side effect: it sends a message other people will see, so only call it on a note the user has agreed to send.
@@ -1185,27 +1277,56 @@ async function runPostTool(args, deps = {}) {
1185
1277
  async function runInstructionsTool() {
1186
1278
  return text(readStandupSkillBody());
1187
1279
  }
1188
- async function main() {
1189
- const server = new McpServer({ name: "standup-mr", version: packageVersion(import.meta.url) });
1190
- server.tool(
1280
+ function createServer() {
1281
+ const server = new McpServer({
1282
+ name: "standup-mr",
1283
+ version: packageVersion(import.meta.url)
1284
+ });
1285
+ server.registerTool(
1191
1286
  "get_standup_data",
1192
- STANDUP_TOOL_DESCRIPTION,
1193
- STANDUP_TOOL_SCHEMA,
1287
+ {
1288
+ description: STANDUP_TOOL_DESCRIPTION,
1289
+ inputSchema: STANDUP_TOOL_SCHEMA,
1290
+ annotations: {
1291
+ readOnlyHint: true,
1292
+ destructiveHint: false,
1293
+ idempotentHint: true,
1294
+ openWorldHint: true
1295
+ }
1296
+ },
1194
1297
  async (args) => await runStandupTool(args)
1195
1298
  );
1196
- server.tool(
1299
+ server.registerTool(
1197
1300
  "post_standup_note",
1198
- POST_TOOL_DESCRIPTION,
1199
- POST_TOOL_SCHEMA,
1301
+ {
1302
+ description: POST_TOOL_DESCRIPTION,
1303
+ inputSchema: POST_TOOL_SCHEMA,
1304
+ annotations: {
1305
+ readOnlyHint: false,
1306
+ destructiveHint: false,
1307
+ idempotentHint: false,
1308
+ openWorldHint: true
1309
+ }
1310
+ },
1200
1311
  async (args) => await runPostTool(args)
1201
1312
  );
1202
- server.tool(
1313
+ server.registerTool(
1203
1314
  "get_note_instructions",
1204
- INSTRUCTIONS_TOOL_DESCRIPTION,
1205
- {},
1315
+ {
1316
+ description: INSTRUCTIONS_TOOL_DESCRIPTION,
1317
+ annotations: {
1318
+ readOnlyHint: true,
1319
+ destructiveHint: false,
1320
+ idempotentHint: true,
1321
+ openWorldHint: false
1322
+ }
1323
+ },
1206
1324
  async () => await runInstructionsTool()
1207
1325
  );
1208
- await server.connect(new StdioServerTransport());
1326
+ return server;
1327
+ }
1328
+ async function main() {
1329
+ await createServer().connect(new StdioServerTransport());
1209
1330
  }
1210
1331
  var entry = process.argv[1];
1211
1332
  var entryUrl;
@@ -1216,8 +1337,10 @@ try {
1216
1337
  }
1217
1338
  if (entryUrl && import.meta.url === entryUrl) {
1218
1339
  main().catch((error) => {
1219
- process.stderr.write(`${error instanceof Error ? error.message : String(error)}
1220
- `);
1340
+ process.stderr.write(
1341
+ `${error instanceof Error ? error.message : String(error)}
1342
+ `
1343
+ );
1221
1344
  process.exitCode = 1;
1222
1345
  });
1223
1346
  }
@@ -1229,6 +1352,7 @@ export {
1229
1352
  STANDUP_TOOL_SCHEMA,
1230
1353
  WEBHOOK_URL_ENV,
1231
1354
  collect,
1355
+ createServer,
1232
1356
  main,
1233
1357
  runInstructionsTool,
1234
1358
  runPostTool,
package/llms-install.md CHANGED
@@ -18,13 +18,13 @@ Add this to the MCP settings file (for Cline,
18
18
 
19
19
  ```json
20
20
  {
21
- "mcpServers": {
22
- "standup": {
23
- "command": "npx",
24
- "args": ["-y", "standup-mr", "mcp"],
25
- "env": {}
21
+ "mcpServers": {
22
+ "standup": {
23
+ "command": "npx",
24
+ "args": ["-y", "standup-mr", "mcp"],
25
+ "env": {}
26
+ }
26
27
  }
27
- }
28
28
  }
29
29
  ```
30
30
 
@@ -35,12 +35,12 @@ That is the whole install when the user already has `gh` or `glab` logged in
35
35
 
36
36
  Add only the pair the user actually needs.
37
37
 
38
- | Variable | When |
39
- |---|---|
40
- | `GITHUB_TOKEN` | GitHub, no `gh` session |
41
- | `GITHUB_HOST` | GitHub Enterprise only; defaults to `github.com` |
42
- | `GITLAB_TOKEN` | GitLab, no `glab` session |
43
- | `GITLAB_HOST` | **Required for GitLab** — there is no default host |
38
+ | Variable | When |
39
+ | ------------------ | -------------------------------------------------------------------------- |
40
+ | `GITHUB_TOKEN` | GitHub, no `gh` session |
41
+ | `GITHUB_HOST` | GitHub Enterprise only; defaults to `github.com` |
42
+ | `GITLAB_TOKEN` | GitLab, no `glab` session |
43
+ | `GITLAB_HOST` | **Required for GitLab** — there is no default host |
44
44
  | `STANDUP_PROVIDER` | `github` or `gitlab`, when both are configured and the choice is ambiguous |
45
45
 
46
46
  Ask the user for a token; do not invent one, and do not put a token anywhere
package/mcp/README.md CHANGED
@@ -9,16 +9,16 @@ GitLab:
9
9
 
10
10
  ```json
11
11
  {
12
- "mcpServers": {
13
- "standup": {
14
- "command": "npx",
15
- "args": ["-y", "standup-mr", "mcp"],
16
- "env": {
17
- "GITLAB_HOST": "gitlab.example.com",
18
- "GITLAB_TOKEN": "glpat-..."
19
- }
12
+ "mcpServers": {
13
+ "standup": {
14
+ "command": "npx",
15
+ "args": ["-y", "standup-mr", "mcp"],
16
+ "env": {
17
+ "GITLAB_HOST": "gitlab.example.com",
18
+ "GITLAB_TOKEN": "glpat-..."
19
+ }
20
+ }
20
21
  }
21
- }
22
22
  }
23
23
  ```
24
24
 
@@ -26,16 +26,16 @@ GitHub:
26
26
 
27
27
  ```json
28
28
  {
29
- "mcpServers": {
30
- "standup": {
31
- "command": "npx",
32
- "args": ["-y", "standup-mr", "mcp"],
33
- "env": {
34
- "GITHUB_HOST": "github.com",
35
- "GITHUB_TOKEN": "ghp_..."
36
- }
29
+ "mcpServers": {
30
+ "standup": {
31
+ "command": "npx",
32
+ "args": ["-y", "standup-mr", "mcp"],
33
+ "env": {
34
+ "GITHUB_HOST": "github.com",
35
+ "GITHUB_TOKEN": "ghp_..."
36
+ }
37
+ }
37
38
  }
38
- }
39
39
  }
40
40
  ```
41
41
 
@@ -46,16 +46,16 @@ If you're working from a local clone instead of the published package, point
46
46
 
47
47
  ```json
48
48
  {
49
- "mcpServers": {
50
- "standup": {
51
- "command": "node",
52
- "args": ["/absolute/path/to/standup-mr/dist/mcp/server.js"],
53
- "env": {
54
- "GITHUB_HOST": "github.com",
55
- "GITHUB_TOKEN": "ghp_..."
56
- }
49
+ "mcpServers": {
50
+ "standup": {
51
+ "command": "node",
52
+ "args": ["/absolute/path/to/standup-mr/dist/mcp/server.js"],
53
+ "env": {
54
+ "GITHUB_HOST": "github.com",
55
+ "GITHUB_TOKEN": "ghp_..."
56
+ }
57
+ }
57
58
  }
58
- }
59
59
  }
60
60
  ```
61
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "standup-mr",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "mcpName": "io.github.Jubstaaa/standup-mr",
5
5
  "description": "Standup notes from merge request state, not commit logs.",
6
6
  "keywords": [
@@ -55,19 +55,31 @@
55
55
  ],
56
56
  "scripts": {
57
57
  "build": "tsup",
58
+ "lint": "eslint .",
59
+ "format": "prettier . --write",
60
+ "format:check": "prettier . --check",
58
61
  "test": "bun test",
59
62
  "test:tz": "TZ=Europe/Istanbul bun test ./src/dates/dates.tz.istanbul.cases.ts && TZ=America/Los_Angeles bun test ./src/dates/dates.tz.los-angeles.cases.ts",
60
63
  "typecheck": "tsc --noEmit",
61
64
  "prepublishOnly": "tsup"
62
65
  },
63
66
  "dependencies": {
64
- "@modelcontextprotocol/sdk": "^1.0.0",
67
+ "@modelcontextprotocol/sdk": "^1.30.0",
65
68
  "zod": "^4.4.3"
66
69
  },
67
70
  "devDependencies": {
71
+ "@eslint/js": "^9.39.1",
68
72
  "@types/bun": "^1.0.0",
69
73
  "@types/node": "^22.0.0",
74
+ "eslint": "^9.39.1",
75
+ "eslint-config-prettier": "^10.1.8",
76
+ "eslint-plugin-import": "^2.32.0",
77
+ "eslint-plugin-perfectionist": "^5.5.0",
78
+ "eslint-plugin-prettier": "^5.5.6",
79
+ "globals": "^17.12.0",
80
+ "prettier": "^3.9.6",
70
81
  "tsup": "^8.0.0",
71
- "typescript": "^5.5.0"
82
+ "typescript": "^5.5.0",
83
+ "typescript-eslint": "^8.44.0"
72
84
  }
73
85
  }