@mgsoftwarebv/mg-dashboard-mcp 7.4.23 → 7.4.25

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.
Files changed (2) hide show
  1. package/dist/index.js +489 -103
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -22,7 +22,7 @@ import { sql, or, eq, and, inArray, like, desc } from 'drizzle-orm';
22
22
  import { once } from 'events';
23
23
  import { lookup } from 'dns/promises';
24
24
  import { connect } from 'tls';
25
- import { pgEnum, pgTable, timestamp, text, jsonb, integer, uuid, uniqueIndex, index, bigint, boolean, customType, date, real, check, doublePrecision, primaryKey, foreignKey } from 'drizzle-orm/pg-core';
25
+ import { pgEnum, pgTable, timestamp, text, jsonb, integer, uuid, uniqueIndex, index, bigint, boolean, customType, date, real, check, numeric, bigserial, doublePrecision, primaryKey, foreignKey } from 'drizzle-orm/pg-core';
26
26
  import { HeadObjectCommand, ListObjectsV2Command, DeleteObjectsCommand, S3Client, DeleteObjectCommand, CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand, AbortMultipartUploadCommand, PutObjectCommand, GetObjectCommand, CopyObjectCommand } from '@aws-sdk/client-s3';
27
27
 
28
28
  var __defProp = Object.defineProperty;
@@ -724,10 +724,10 @@ var TRIGGER_TOOL_MODULE_MAP = {
724
724
  "trigger-env": "settings"
725
725
  };
726
726
  async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
727
- const sql33 = `SELECT re.\\"apiKey\\" || '~~' || p.\\"externalRef\\" FROM \\"RuntimeEnvironment\\" re JOIN \\"Project\\" p ON re.\\"projectId\\" = p.id WHERE p.slug='${projectSlug}' AND re.slug='prod' LIMIT 1`;
727
+ const sql36 = `SELECT re.\\"apiKey\\" || '~~' || p.\\"externalRef\\" FROM \\"RuntimeEnvironment\\" re JOIN \\"Project\\" p ON re.\\"projectId\\" = p.id WHERE p.slug='${projectSlug}' AND re.slug='prod' LIMIT 1`;
728
728
  const cmd = [
729
729
  `PORT=$(docker port "${WA_CONTAINER}" 3000/tcp 2>/dev/null | head -1 | sed 's/.*://')`,
730
- `ROW=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql33}" 2>/dev/null | tr -d '[:space:]')`,
730
+ `ROW=$(docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql36}" 2>/dev/null | tr -d '[:space:]')`,
731
731
  'echo "$PORT|$ROW"'
732
732
  ].join(" && ");
733
733
  const result = await sshExec2(conn, cmd, proxy);
@@ -749,8 +749,8 @@ async function discoverInstance(projectSlug, conn, proxy, sshExec2) {
749
749
  return { port, apiKey: apiKey2, projectRef: projectRef || "" };
750
750
  }
751
751
  async function fetchRunLogs(runId, conn, proxy, sshExec2) {
752
- const sql33 = `SELECT level, message, \\"isError\\", \\"createdAt\\" FROM \\"TaskEvent\\" WHERE \\"runId\\" = '${runId}' AND level IN ('INFO','WARN','ERROR','DEBUG','LOG','TRACE') ORDER BY \\"startTime\\" ASC LIMIT 200`;
753
- const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql33}" 2>/dev/null`;
752
+ const sql36 = `SELECT level, message, \\"isError\\", \\"createdAt\\" FROM \\"TaskEvent\\" WHERE \\"runId\\" = '${runId}' AND level IN ('INFO','WARN','ERROR','DEBUG','LOG','TRACE') ORDER BY \\"startTime\\" ASC LIMIT 200`;
753
+ const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql36}" 2>/dev/null`;
754
754
  const result = await sshExec2(conn, cmd, proxy);
755
755
  const output = result.stdout.trim();
756
756
  if (!output) return "";
@@ -832,8 +832,8 @@ async function handleTriggerTool(name, args2, deps) {
832
832
  switch (name) {
833
833
  // -----------------------------------------------------------------
834
834
  case "trigger-list": {
835
- const sql33 = 'SELECT slug, name FROM \\"Project\\" ORDER BY name';
836
- const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql33}" 2>/dev/null`;
835
+ const sql36 = 'SELECT slug, name FROM \\"Project\\" ORDER BY name';
836
+ const cmd = `docker exec "${PG_CONTAINER}" psql -U postgres -d main -t -A -c "${sql36}" 2>/dev/null`;
837
837
  const result = await sshExec2(conn, cmd, proxy);
838
838
  const output = result.stdout.trim();
839
839
  if (!output) {
@@ -1011,9 +1011,9 @@ ${raw2.substring(0, 500)}` }] };
1011
1011
  return { content: [{ type: "text", text: `No environment variables for ${project}/${env}.` }] };
1012
1012
  }
1013
1013
  const lines = vars.map((v) => `${v.isSecret ? "[secret]" : " "} ${v.name}`).sort();
1014
- const text22 = `Env vars for ${project}/${env} (${vars.length}) \u2014 values hidden, use action="get" with a key:
1014
+ const text23 = `Env vars for ${project}/${env} (${vars.length}) \u2014 values hidden, use action="get" with a key:
1015
1015
  ` + "-".repeat(50) + "\n" + lines.join("\n");
1016
- return { content: [{ type: "text", text: text22 }] };
1016
+ return { content: [{ type: "text", text: text23 }] };
1017
1017
  }
1018
1018
  if (action === "get") {
1019
1019
  const key2 = String(args2.key ?? "");
@@ -1100,9 +1100,9 @@ async function fetchAndFormatRun(conn, proxy, sshExec2, instance, runId) {
1100
1100
  return { content: [{ type: "text", text: `Invalid API response:
1101
1101
  ${rawJson.substring(0, 500)}` }] };
1102
1102
  }
1103
- let text22 = formatRunDetail(run);
1104
- if (logs) text22 += "\n\n--- Logs ---\n" + logs;
1105
- return { content: [{ type: "text", text: text22 }] };
1103
+ let text23 = formatRunDetail(run);
1104
+ if (logs) text23 += "\n\n--- Logs ---\n" + logs;
1105
+ return { content: [{ type: "text", text: text23 }] };
1106
1106
  }
1107
1107
  async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSeconds) {
1108
1108
  const pollInterval = 3e3;
@@ -1124,10 +1124,10 @@ async function waitForCompletion(conn, proxy, sshExec2, instance, runId, waitSec
1124
1124
  continue;
1125
1125
  }
1126
1126
  if (TERMINAL_STATUSES.has(run.status)) {
1127
- let text22 = formatRunDetail(run);
1127
+ let text23 = formatRunDetail(run);
1128
1128
  const logs = await fetchRunLogs(runId, conn, proxy, sshExec2);
1129
- if (logs) text22 += "\n\n--- Logs ---\n" + logs;
1130
- return { content: [{ type: "text", text: text22 }] };
1129
+ if (logs) text23 += "\n\n--- Logs ---\n" + logs;
1130
+ return { content: [{ type: "text", text: text23 }] };
1131
1131
  }
1132
1132
  }
1133
1133
  return {
@@ -1317,6 +1317,56 @@ async function proxyJson(ctx, route, body) {
1317
1317
  };
1318
1318
  }
1319
1319
 
1320
+ // src/git-credential-tools.ts
1321
+ var GIT_CREDENTIAL_TOOL_NAME = "git-credential";
1322
+ var GIT_CREDENTIAL_TOOLS = [
1323
+ {
1324
+ name: GIT_CREDENTIAL_TOOL_NAME,
1325
+ description: "Issue a 15-minute mggit access token for git.mgsoftware.nl (username=token, Basic password). Bound to owner/name + write bit. Intended for git credential helpers and agents that need to clone/fetch/push. Not a git runner \u2014 do not use this to execute git commands. Requires module git_hosting.",
1326
+ inputSchema: {
1327
+ type: "object",
1328
+ properties: {
1329
+ repo: {
1330
+ type: "string",
1331
+ description: "Repository as owner/name (e.g. MGSoftwareBV/mg-dashboard)."
1332
+ },
1333
+ write: {
1334
+ type: "boolean",
1335
+ description: "Request push rights (default true). Fetch-only: false."
1336
+ }
1337
+ },
1338
+ required: ["repo"]
1339
+ }
1340
+ }
1341
+ ];
1342
+ async function handleGitCredentialTool(args2, ctx) {
1343
+ const repo = typeof args2.repo === "string" ? args2.repo.trim() : "";
1344
+ if (!repo) {
1345
+ return { content: [{ type: "text", text: "Error: repo is required (owner/name)" }] };
1346
+ }
1347
+ const write = args2.write !== false;
1348
+ const res = await fetch(`${ctx.dashboardBaseUrl.replace(/\/$/, "")}/api/git/credential`, {
1349
+ method: "POST",
1350
+ headers: {
1351
+ "content-type": "application/json",
1352
+ authorization: `Bearer ${ctx.apiKey}`
1353
+ },
1354
+ body: JSON.stringify({ repo, write })
1355
+ });
1356
+ const text23 = await res.text().catch(() => "");
1357
+ if (!res.ok) {
1358
+ return {
1359
+ content: [
1360
+ {
1361
+ type: "text",
1362
+ text: `Error: git-credential failed (${res.status}). ${text23.slice(0, 300)}`
1363
+ }
1364
+ ]
1365
+ };
1366
+ }
1367
+ return { content: [{ type: "text", text: text23 || "{}" }] };
1368
+ }
1369
+
1320
1370
  // src/mailserver-tools.ts
1321
1371
  var MAILSERVER_TOOL_NAME = "mailserver";
1322
1372
  var MAILSERVER_ACTIONS = [
@@ -1391,11 +1441,11 @@ function tokenFromDotenv(content) {
1391
1441
  for (const line of content.split(/\r?\n/)) {
1392
1442
  const trimmed = line.trim();
1393
1443
  if (!trimmed || trimmed.startsWith("#")) continue;
1394
- const eq5 = trimmed.indexOf("=");
1395
- if (eq5 < 1) continue;
1396
- const key = trimmed.slice(0, eq5).trim();
1444
+ const eq6 = trimmed.indexOf("=");
1445
+ if (eq6 < 1) continue;
1446
+ const key = trimmed.slice(0, eq6).trim();
1397
1447
  if (key !== "MAILSERVER_MCP_TOKEN") continue;
1398
- let value = trimmed.slice(eq5 + 1).trim();
1448
+ let value = trimmed.slice(eq6 + 1).trim();
1399
1449
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1400
1450
  value = value.slice(1, -1);
1401
1451
  }
@@ -1500,12 +1550,12 @@ async function mailserverFetch(deps, plan) {
1500
1550
  },
1501
1551
  body: plan.body ? JSON.stringify(plan.body) : void 0
1502
1552
  });
1503
- const text22 = await res.text();
1504
- let json = text22;
1553
+ const text23 = await res.text();
1554
+ let json = text23;
1505
1555
  try {
1506
- json = text22 ? JSON.parse(text22) : null;
1556
+ json = text23 ? JSON.parse(text23) : null;
1507
1557
  } catch {
1508
- json = { error: text22.slice(0, 500) };
1558
+ json = { error: text23.slice(0, 500) };
1509
1559
  }
1510
1560
  return { status: res.status, json };
1511
1561
  }
@@ -4789,10 +4839,10 @@ var ZodObject = class _ZodObject extends ZodType {
4789
4839
  // }) as any;
4790
4840
  // return merged;
4791
4841
  // }
4792
- catchall(index19) {
4842
+ catchall(index20) {
4793
4843
  return new _ZodObject({
4794
4844
  ...this._def,
4795
- catchall: index19
4845
+ catchall: index20
4796
4846
  });
4797
4847
  }
4798
4848
  pick(mask) {
@@ -5110,9 +5160,9 @@ function mergeValues(a, b) {
5110
5160
  return { valid: false };
5111
5161
  }
5112
5162
  const newArray = [];
5113
- for (let index19 = 0; index19 < a.length; index19++) {
5114
- const itemA = a[index19];
5115
- const itemB = b[index19];
5163
+ for (let index20 = 0; index20 < a.length; index20++) {
5164
+ const itemA = a[index20];
5165
+ const itemB = b[index20];
5116
5166
  const sharedValue = mergeValues(itemA, itemB);
5117
5167
  if (!sharedValue.valid) {
5118
5168
  return { valid: false };
@@ -5318,10 +5368,10 @@ var ZodMap = class extends ZodType {
5318
5368
  }
5319
5369
  const keyType = this._def.keyType;
5320
5370
  const valueType = this._def.valueType;
5321
- const pairs = [...ctx.data.entries()].map(([key, value], index19) => {
5371
+ const pairs = [...ctx.data.entries()].map(([key, value], index20) => {
5322
5372
  return {
5323
- key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index19, "key"])),
5324
- value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index19, "value"]))
5373
+ key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index20, "key"])),
5374
+ value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index20, "value"]))
5325
5375
  };
5326
5376
  });
5327
5377
  if (ctx.common.async) {
@@ -6336,6 +6386,8 @@ pgTable(
6336
6386
  verifyNote: text("verify_note"),
6337
6387
  dirty: boolean("dirty").notNull().default(false),
6338
6388
  vaultSyncedAt: timestamp("vault_synced_at", { withTimezone: true }),
6389
+ /** Flattened frontmatter aliases for tsv weight A. */
6390
+ aliasesText: text("aliases_text").notNull().default(""),
6339
6391
  /** Reviewer / itemized-phase lease holder. */
6340
6392
  claimedBy: text("claimed_by"),
6341
6393
  claimedAt: timestamp("claimed_at", { withTimezone: true }),
@@ -6532,6 +6584,7 @@ pgTable(
6532
6584
  topId: text("top_id"),
6533
6585
  caller: text("caller"),
6534
6586
  repo: text("repo"),
6587
+ confident: boolean("confident"),
6535
6588
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6536
6589
  },
6537
6590
  (table) => [index("idx_team_memory_search_log_created").on(table.createdAt)]
@@ -6558,6 +6611,9 @@ pgTable(
6558
6611
  reviewed: boolean("reviewed").notNull().default(false),
6559
6612
  reviewNotes: text("review_notes"),
6560
6613
  reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
6614
+ inputBytes: integer("input_bytes"),
6615
+ inputTokens: integer("input_tokens"),
6616
+ outputTokens: integer("output_tokens"),
6561
6617
  startedAt: timestamp("started_at", { withTimezone: true }),
6562
6618
  finishedAt: timestamp("finished_at", { withTimezone: true }).notNull().defaultNow(),
6563
6619
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
@@ -6588,6 +6644,7 @@ pgTable(
6588
6644
  evidenceRefs: text("evidence_refs").array().notNull().default([]),
6589
6645
  dedupeKey: text("dedupe_key").notNull(),
6590
6646
  source: text("source").notNull().default("auto"),
6647
+ ledgerId: uuid("ledger_id"),
6591
6648
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6592
6649
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
6593
6650
  },
@@ -6599,6 +6656,138 @@ pgTable(
6599
6656
  )
6600
6657
  ]
6601
6658
  );
6659
+ pgTable(
6660
+ "wiki_page_chunk",
6661
+ {
6662
+ id: uuid("id").primaryKey().defaultRandom(),
6663
+ pageId: uuid("page_id").notNull(),
6664
+ slug: text("slug").notNull(),
6665
+ headingPath: text("heading_path").notNull().default(""),
6666
+ chunkIndex: integer("chunk_index").notNull().default(0),
6667
+ content: text("content").notNull().default(""),
6668
+ embeddingVec: vector("embedding_vec", {
6669
+ dimensions: WIKI_EMBEDDING_DIMENSIONS
6670
+ }),
6671
+ embeddingModel: text("embedding_model"),
6672
+ embeddedAt: timestamp("embedded_at", { withTimezone: true }),
6673
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6674
+ },
6675
+ (table) => [
6676
+ uniqueIndex("wiki_page_chunk_page_id_chunk_index_key").on(
6677
+ table.pageId,
6678
+ table.chunkIndex
6679
+ ),
6680
+ index("idx_wiki_page_chunk_slug").on(table.slug)
6681
+ ]
6682
+ );
6683
+ pgTable("brain_config", {
6684
+ key: text("key").primaryKey(),
6685
+ value: jsonb("value").$type().notNull(),
6686
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
6687
+ updatedBy: text("updated_by")
6688
+ });
6689
+ pgTable(
6690
+ "brain_metrics_daily",
6691
+ {
6692
+ id: uuid("id").primaryKey().defaultRandom(),
6693
+ metricDate: date("metric_date").notNull(),
6694
+ callerClass: text("caller_class").notNull(),
6695
+ wikiSearches: integer("wiki_searches").notNull().default(0),
6696
+ confidentHits: integer("confident_hits").notNull().default(0),
6697
+ misses: integer("misses").notNull().default(0),
6698
+ hitAt1: real("hit_at_1"),
6699
+ hitAt3: real("hit_at_3"),
6700
+ searchToRead: real("search_to_read"),
6701
+ tmFallbacks: integer("tm_fallbacks").notNull().default(0),
6702
+ reviewerRuns: integer("reviewer_runs").notNull().default(0),
6703
+ reviewerAvgInputBytes: integer("reviewer_avg_input_bytes"),
6704
+ tokensPerPhase: jsonb("tokens_per_phase").$type().notNull().default({}),
6705
+ pagesPromoted: integer("pages_promoted").notNull().default(0),
6706
+ pagesDemoted: integer("pages_demoted").notNull().default(0),
6707
+ thrashCount: integer("thrash_count").notNull().default(0),
6708
+ flagsPer100Reads: real("flags_per_100_reads"),
6709
+ verifierDisagreeRate: real("verifier_disagree_rate"),
6710
+ verifyGateIdle: boolean("verify_gate_idle").notNull().default(false),
6711
+ verifyGateActing: boolean("verify_gate_acting"),
6712
+ draftsOlder14d: integer("drafts_older_14d").notNull().default(0),
6713
+ developersHarvested24h: integer("developers_harvested_24h").notNull().default(0),
6714
+ teamMemoryCited: integer("team_memory_cited").notNull().default(0),
6715
+ extras: jsonb("extras").$type().notNull().default({}),
6716
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6717
+ },
6718
+ (table) => [
6719
+ uniqueIndex("brain_metrics_daily_metric_date_caller_class_key").on(
6720
+ table.metricDate,
6721
+ table.callerClass
6722
+ )
6723
+ ]
6724
+ );
6725
+ pgTable(
6726
+ "brain_change_ledger",
6727
+ {
6728
+ id: uuid("id").primaryKey().defaultRandom(),
6729
+ scope: text("scope").notNull(),
6730
+ tier: text("tier").notNull(),
6731
+ kind: text("kind").notNull(),
6732
+ hypothesis: text("hypothesis").notNull().default(""),
6733
+ target: text("target"),
6734
+ beforeValue: jsonb("before_value"),
6735
+ afterValue: jsonb("after_value"),
6736
+ baselineMetrics: jsonb("baseline_metrics"),
6737
+ outcomeMetrics: jsonb("outcome_metrics"),
6738
+ verdict: text("verdict").notNull().default("pending"),
6739
+ evidenceRefs: text("evidence_refs").array().notNull().default([]),
6740
+ createdBy: text("created_by"),
6741
+ suggestionId: uuid("suggestion_id"),
6742
+ ticketNumber: text("ticket_number"),
6743
+ ticketId: text("ticket_id"),
6744
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
6745
+ verdictAt: timestamp("verdict_at", { withTimezone: true }),
6746
+ revertedAt: timestamp("reverted_at", { withTimezone: true })
6747
+ },
6748
+ (table) => [
6749
+ index("idx_brain_change_ledger_verdict").on(table.verdict, table.createdAt),
6750
+ index("idx_brain_change_ledger_scope").on(table.scope, table.createdAt)
6751
+ ]
6752
+ );
6753
+ pgTable(
6754
+ "wiki_search_shadow",
6755
+ {
6756
+ id: uuid("id").primaryKey().defaultRandom(),
6757
+ query: text("query").notNull(),
6758
+ caller: text("caller"),
6759
+ servedSlug: text("served_slug"),
6760
+ servedScore: real("served_score"),
6761
+ shadowSlug: text("shadow_slug"),
6762
+ shadowScore: real("shadow_score"),
6763
+ ledgerId: uuid("ledger_id"),
6764
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6765
+ },
6766
+ (table) => [index("idx_wiki_search_shadow_created").on(table.createdAt)]
6767
+ );
6768
+ pgTable("wiki_eval_snapshot", {
6769
+ id: uuid("id").primaryKey().defaultRandom(),
6770
+ name: text("name").notNull(),
6771
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6772
+ });
6773
+ pgTable(
6774
+ "wiki_eval_label",
6775
+ {
6776
+ id: uuid("id").primaryKey().defaultRandom(),
6777
+ snapshotId: uuid("snapshot_id").notNull(),
6778
+ query: text("query").notNull(),
6779
+ caller: text("caller"),
6780
+ positiveSlug: text("positive_slug"),
6781
+ isNegative: boolean("is_negative").notNull().default(false),
6782
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
6783
+ },
6784
+ (table) => [index("idx_wiki_eval_label_snapshot").on(table.snapshotId)]
6785
+ );
6786
+ pgTable("wiki_status_dirty", {
6787
+ slug: text("slug").primaryKey(),
6788
+ markedAt: timestamp("marked_at", { withTimezone: true }).notNull().defaultNow(),
6789
+ reason: text("reason")
6790
+ });
6602
6791
 
6603
6792
  // ../db/src/schema/agent-memory.ts
6604
6793
  pgTable(
@@ -6639,7 +6828,8 @@ pgTable(
6639
6828
  ),
6640
6829
  index("idx_agent_memory_repo").on(table.gitRepository),
6641
6830
  index("idx_agent_memory_user").on(table.userId),
6642
- index("idx_agent_memory_kind").on(table.kind)
6831
+ index("idx_agent_memory_kind").on(table.kind),
6832
+ index("idx_agent_memory_automated_created").on(table.createdAt).where(sql`kind = 'automated'`)
6643
6833
  ]
6644
6834
  );
6645
6835
  var contentSourceType = pgEnum("content_source_type", [
@@ -7084,6 +7274,133 @@ pgTable(
7084
7274
  index("dns_migration_mailbox_migration_idx").on(table.migrationId)
7085
7275
  ]
7086
7276
  );
7277
+ pgTable(
7278
+ "git_repo",
7279
+ {
7280
+ id: uuid("id").primaryKey().defaultRandom(),
7281
+ /** `owner/name`, the canonical id on mggit and GitHub. */
7282
+ fullName: text("full_name").notNull(),
7283
+ owner: text("owner").notNull(),
7284
+ name: text("name").notNull(),
7285
+ source: text("source").$type().notNull().default("mirror"),
7286
+ /** GitHub clone URL the repo follows while it is a mirror. */
7287
+ upstreamUrl: text("upstream_url"),
7288
+ /** Full ref names mggit follows (`refs/heads/main`, `refs/tags/v1`). */
7289
+ followRefs: jsonb("follow_refs").$type().notNull().default([]),
7290
+ /** Sync job keeps the follow list current and the repo present on mggit. */
7291
+ mirrorEnabled: boolean("mirror_enabled").notNull().default(true),
7292
+ /** Present on mggit (repo created). */
7293
+ existsOnMggit: boolean("exists_on_mggit").notNull().default(false),
7294
+ githubArchived: boolean("github_archived").notNull().default(false),
7295
+ githubPrivate: boolean("github_private").notNull().default(true),
7296
+ githubDefaultBranch: text("github_default_branch"),
7297
+ githubPushedAt: timestamp("github_pushed_at", { withTimezone: true }),
7298
+ branchCount: integer("branch_count").notNull().default(0),
7299
+ tagCount: integer("tag_count").notNull().default(0),
7300
+ /** Latest WAL seq seen in an event for this repo. */
7301
+ lastSeq: bigint("last_seq", { mode: "number" }).notNull().default(0),
7302
+ lastEventAt: timestamp("last_event_at", { withTimezone: true }),
7303
+ followState: jsonb("follow_state").$type().notNull().default({}),
7304
+ /** Refs where GitHub and mggit disagree after the last sync (name → {github, mggit}). */
7305
+ behindRefs: jsonb("behind_refs").$type().notNull().default({}),
7306
+ lastSyncAt: timestamp("last_sync_at", { withTimezone: true }),
7307
+ lastSyncError: text("last_sync_error"),
7308
+ notes: text("notes"),
7309
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
7310
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
7311
+ },
7312
+ (table) => [
7313
+ uniqueIndex("git_repo_full_name_uidx").on(table.fullName),
7314
+ index("git_repo_source_idx").on(table.source, table.mirrorEnabled),
7315
+ index("git_repo_last_event_idx").on(table.lastEventAt)
7316
+ ]
7317
+ );
7318
+ pgTable(
7319
+ "git_ref_event",
7320
+ {
7321
+ id: uuid("id").primaryKey().defaultRandom(),
7322
+ repoFullName: text("repo_full_name").notNull(),
7323
+ /** `_walgit.seq` (uint64 as string on the wire). */
7324
+ seq: bigint("seq", { mode: "number" }).notNull(),
7325
+ refName: text("ref_name").notNull(),
7326
+ /** `branch` | `tag` | `` */
7327
+ refType: text("ref_type").notNull().default(""),
7328
+ /** `create` | `update` | `delete` */
7329
+ action: text("action").notNull(),
7330
+ oldOid: text("old_oid").notNull(),
7331
+ newOid: text("new_oid").notNull(),
7332
+ /** Authenticated principal (`upstream` for mirror follows). */
7333
+ pusher: text("pusher").notNull().default(""),
7334
+ correlationId: text("correlation_id"),
7335
+ entryKind: text("entry_kind"),
7336
+ /** `X-Walgit-Delivery` of the batch that carried it. */
7337
+ deliveryId: text("delivery_id"),
7338
+ receivedAt: timestamp("received_at", { withTimezone: true }).notNull().defaultNow()
7339
+ },
7340
+ (table) => [
7341
+ uniqueIndex("git_ref_event_dedup_uidx").on(
7342
+ table.repoFullName,
7343
+ table.seq,
7344
+ table.refName
7345
+ ),
7346
+ index("git_ref_event_repo_received_idx").on(
7347
+ table.repoFullName,
7348
+ table.receivedAt
7349
+ ),
7350
+ index("git_ref_event_received_idx").on(table.receivedAt),
7351
+ index("git_ref_event_pusher_idx").on(table.pusher)
7352
+ ]
7353
+ );
7354
+ pgTable(
7355
+ "git_access_log",
7356
+ {
7357
+ id: bigserial("id", { mode: "number" }).primaryKey(),
7358
+ ts: timestamp("ts", { withTimezone: true }).notNull(),
7359
+ /** nginx `$request_id`; dedup key for re-shipped batches. */
7360
+ requestId: text("request_id").notNull(),
7361
+ ip: text("ip").notNull().default(""),
7362
+ principal: text("principal").notNull().default(""),
7363
+ repo: text("repo").notNull().default(""),
7364
+ /** `advertise` | `fetch` | `push` | `api` | `` */
7365
+ op: text("op").notNull().default(""),
7366
+ method: text("method").notNull().default(""),
7367
+ uri: text("uri").notNull().default(""),
7368
+ status: integer("status").notNull().default(0),
7369
+ bytesIn: bigint("bytes_in", { mode: "number" }).notNull().default(0),
7370
+ bytesOut: bigint("bytes_out", { mode: "number" }).notNull().default(0),
7371
+ durationMs: numeric("duration_ms", { precision: 12, scale: 3 }).notNull().default("0"),
7372
+ userAgent: text("user_agent").notNull().default(""),
7373
+ upstreamStatus: text("upstream_status").notNull().default(""),
7374
+ receivedAt: timestamp("received_at", { withTimezone: true }).notNull().defaultNow()
7375
+ },
7376
+ (table) => [
7377
+ uniqueIndex("git_access_log_request_id_uidx").on(table.requestId),
7378
+ index("git_access_log_ts_idx").on(table.ts),
7379
+ index("git_access_log_repo_ts_idx").on(table.repo, table.ts),
7380
+ index("git_access_log_principal_ts_idx").on(table.principal, table.ts),
7381
+ index("git_access_log_op_status_idx").on(table.op, table.status)
7382
+ ]
7383
+ );
7384
+ pgTable(
7385
+ "git_credential_grant",
7386
+ {
7387
+ id: uuid("id").primaryKey().defaultRandom(),
7388
+ tokenHash: text("token_hash").notNull(),
7389
+ userId: uuid("user_id").notNull(),
7390
+ apiKeyId: uuid("api_key_id").notNull(),
7391
+ repo: text("repo").notNull(),
7392
+ write: boolean("write").notNull().default(true),
7393
+ principal: text("principal").notNull(),
7394
+ expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
7395
+ issuedIp: text("issued_ip").notNull().default(""),
7396
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
7397
+ },
7398
+ (table) => [
7399
+ uniqueIndex("git_credential_grant_token_hash_uidx").on(table.tokenHash),
7400
+ index("git_credential_grant_expires_idx").on(table.expiresAt),
7401
+ index("git_credential_grant_user_idx").on(table.userId, table.expiresAt)
7402
+ ]
7403
+ );
7087
7404
  var directoryLinkType = pgEnum("directory_link_type", [
7088
7405
  "dofollow",
7089
7406
  "nofollow",
@@ -7888,6 +8205,22 @@ pgTable(
7888
8205
  )
7889
8206
  ]
7890
8207
  );
8208
+
8209
+ // ../platform/dist/utils/permissions.js
8210
+ var MODULE_KEYS = [
8211
+ "users",
8212
+ "ssh_servers",
8213
+ "wiki",
8214
+ "ci_cd",
8215
+ "settings",
8216
+ "cursor_remote",
8217
+ "code_battle",
8218
+ "git_hosting"
8219
+ ];
8220
+ ({
8221
+ modules: Object.fromEntries(MODULE_KEYS.map((k) => [k, false]))});
8222
+ ({
8223
+ modules: Object.fromEntries(MODULE_KEYS.map((k) => [k, true]))});
7891
8224
  var SSH_POOL_IDLE_MS = 6e4;
7892
8225
  var sshPool = /* @__PURE__ */ new Map();
7893
8226
  function poolKey(options) {
@@ -9140,6 +9473,24 @@ function hourBucket(now = /* @__PURE__ */ new Date()) {
9140
9473
  )
9141
9474
  );
9142
9475
  }
9476
+ var GITHUB_API_STAT_FLUSH_EVERY = 50;
9477
+ var pendingGithubApiStats = /* @__PURE__ */ new Map();
9478
+ var pendingGithubApiStatIncrements = 0;
9479
+ function githubApiStatMapKey(tokenKey, jobId, bucket) {
9480
+ return `${tokenKey} ${jobId} ${bucket}`;
9481
+ }
9482
+ function mergeGithubApiStatDelta(current, add) {
9483
+ if (!current) return { ...add };
9484
+ return {
9485
+ tokenKey: add.tokenKey,
9486
+ jobId: add.jobId,
9487
+ hourBucket: add.hourBucket,
9488
+ requestCount: current.requestCount + add.requestCount,
9489
+ errorCount: current.errorCount + add.errorCount,
9490
+ cacheHitCount: current.cacheHitCount + add.cacheHitCount,
9491
+ breakerHitCount: current.breakerHitCount + add.breakerHitCount
9492
+ };
9493
+ }
9143
9494
  function cacheKeyFor(url, tokenKey) {
9144
9495
  return createHash("sha256").update(`${tokenKey}:${url}`).digest("hex");
9145
9496
  }
@@ -9336,28 +9687,54 @@ async function forceConsumeBudget(tokenKey) {
9336
9687
  }
9337
9688
  async function recordStat(input) {
9338
9689
  const bucket = hourBucket().toISOString();
9339
- await getDb().execute(sql`
9340
- INSERT INTO github_api_stat (
9341
- token_key, job_id, hour_bucket, request_count, error_count,
9342
- cache_hit_count, breaker_hit_count, updated_at
9343
- )
9344
- VALUES (
9345
- ${input.tokenKey},
9346
- ${input.jobId},
9347
- ${bucket}::timestamptz,
9348
- ${input.breakerHit ? 0 : 1},
9349
- ${input.error ? 1 : 0},
9350
- ${input.cacheHit ? 1 : 0},
9351
- ${input.breakerHit ? 1 : 0},
9352
- now()
9353
- )
9354
- ON CONFLICT (token_key, job_id, hour_bucket) DO UPDATE SET
9355
- request_count = github_api_stat.request_count + ${input.breakerHit ? 0 : 1},
9356
- error_count = github_api_stat.error_count + ${input.error ? 1 : 0},
9357
- cache_hit_count = github_api_stat.cache_hit_count + ${input.cacheHit ? 1 : 0},
9358
- breaker_hit_count = github_api_stat.breaker_hit_count + ${input.breakerHit ? 1 : 0},
9359
- updated_at = now()
9360
- `);
9690
+ const key = githubApiStatMapKey(input.tokenKey, input.jobId, bucket);
9691
+ const add = {
9692
+ tokenKey: input.tokenKey,
9693
+ jobId: input.jobId,
9694
+ hourBucket: bucket,
9695
+ requestCount: input.breakerHit ? 0 : 1,
9696
+ errorCount: input.error ? 1 : 0,
9697
+ cacheHitCount: input.cacheHit ? 1 : 0,
9698
+ breakerHitCount: input.breakerHit ? 1 : 0
9699
+ };
9700
+ pendingGithubApiStats.set(
9701
+ key,
9702
+ mergeGithubApiStatDelta(pendingGithubApiStats.get(key), add)
9703
+ );
9704
+ pendingGithubApiStatIncrements += 1;
9705
+ if (pendingGithubApiStatIncrements >= GITHUB_API_STAT_FLUSH_EVERY) {
9706
+ await flushPendingGithubApiStats();
9707
+ }
9708
+ }
9709
+ async function flushPendingGithubApiStats() {
9710
+ const rows = [...pendingGithubApiStats.values()];
9711
+ pendingGithubApiStats.clear();
9712
+ pendingGithubApiStatIncrements = 0;
9713
+ for (const row of rows) {
9714
+ await getDb().execute(sql`
9715
+ INSERT INTO github_api_stat (
9716
+ token_key, job_id, hour_bucket, request_count, error_count,
9717
+ cache_hit_count, breaker_hit_count, updated_at
9718
+ )
9719
+ VALUES (
9720
+ ${row.tokenKey},
9721
+ ${row.jobId},
9722
+ ${row.hourBucket}::timestamptz,
9723
+ ${row.requestCount},
9724
+ ${row.errorCount},
9725
+ ${row.cacheHitCount},
9726
+ ${row.breakerHitCount},
9727
+ now()
9728
+ )
9729
+ ON CONFLICT (token_key, job_id, hour_bucket) DO UPDATE SET
9730
+ request_count = github_api_stat.request_count + ${row.requestCount},
9731
+ error_count = github_api_stat.error_count + ${row.errorCount},
9732
+ cache_hit_count = github_api_stat.cache_hit_count + ${row.cacheHitCount},
9733
+ breaker_hit_count = github_api_stat.breaker_hit_count + ${row.breakerHitCount},
9734
+ updated_at = now()
9735
+ `);
9736
+ }
9737
+ return rows.length;
9361
9738
  }
9362
9739
  async function recordBreakerHit(input) {
9363
9740
  await recordStat({
@@ -10397,7 +10774,7 @@ async function writeAuditLog(entry) {
10397
10774
  );
10398
10775
  }
10399
10776
  }
10400
- var MODULE_KEYS = [
10777
+ var MODULE_KEYS2 = [
10401
10778
  "users",
10402
10779
  "ssh_servers",
10403
10780
  "wiki",
@@ -10405,11 +10782,12 @@ var MODULE_KEYS = [
10405
10782
  "domains",
10406
10783
  "settings",
10407
10784
  "cursor_remote",
10408
- "code_battle"
10785
+ "code_battle",
10786
+ "git_hosting"
10409
10787
  ];
10410
- var FULL_PERMISSIONS = {
10788
+ var FULL_PERMISSIONS2 = {
10411
10789
  modules: Object.fromEntries(
10412
- MODULE_KEYS.map((k) => [k, true])
10790
+ MODULE_KEYS2.map((k) => [k, true])
10413
10791
  ),
10414
10792
  resources: { ssh_servers: ["*"] }
10415
10793
  };
@@ -10417,12 +10795,12 @@ function parsePermissions(raw) {
10417
10795
  if (!raw || typeof raw !== "object") return null;
10418
10796
  return raw;
10419
10797
  }
10420
- function resolvePermissions(roleName, roleDefaults, userOverrides) {
10421
- if (roleName === "superadmin") return FULL_PERMISSIONS;
10798
+ function resolvePermissions2(roleName, roleDefaults, userOverrides) {
10799
+ if (roleName === "superadmin") return FULL_PERMISSIONS2;
10422
10800
  const base = parsePermissions(roleDefaults);
10423
10801
  const overrides = parsePermissions(userOverrides);
10424
10802
  const modules = {};
10425
- for (const key of MODULE_KEYS) {
10803
+ for (const key of MODULE_KEYS2) {
10426
10804
  const userVal = overrides?.modules?.[key];
10427
10805
  const roleVal = base?.modules?.[key];
10428
10806
  modules[key] = userVal !== void 0 ? userVal : roleVal !== void 0 ? roleVal : false;
@@ -10471,6 +10849,7 @@ var TOOL_MODULE_MAP = {
10471
10849
  "cursor-remote-list": "cursor_remote",
10472
10850
  "cursor-remote-run": "cursor_remote",
10473
10851
  "ai-company": "ssh_servers",
10852
+ "git-credential": "git_hosting",
10474
10853
  // cursor-skill intentionally unmapped — any valid API key may pull shared skills
10475
10854
  ...TRIGGER_TOOL_MODULE_MAP
10476
10855
  };
@@ -10614,7 +10993,7 @@ async function validateApiKey(key) {
10614
10993
  const roleName = userData.role_name || "user";
10615
10994
  const roleDefaults = userData.role_default_permissions ?? {};
10616
10995
  const userOverrides = userData.permissions ?? null;
10617
- const permissions = resolvePermissions(roleName, roleDefaults, userOverrides);
10996
+ const permissions = resolvePermissions2(roleName, roleDefaults, userOverrides);
10618
10997
  const allowedServerIds = intersectServerAccess(
10619
10998
  data.allowed_server_ids,
10620
10999
  permissions.resources.ssh_servers
@@ -10625,9 +11004,9 @@ async function validateApiKey(key) {
10625
11004
  WHERE id = ${data.id}
10626
11005
  AND (last_used_at IS NULL OR last_used_at < now() - interval '60 seconds')
10627
11006
  `);
10628
- const moduleCount = MODULE_KEYS.filter((k) => permissions.modules[k]).length;
11007
+ const moduleCount = MODULE_KEYS2.filter((k) => permissions.modules[k]).length;
10629
11008
  console.error(
10630
- `Authenticated as user ${data.created_by} (role: ${roleName}, modules: ${moduleCount}/${MODULE_KEYS.length})`
11009
+ `Authenticated as user ${data.created_by} (role: ${roleName}, modules: ${moduleCount}/${MODULE_KEYS2.length})`
10631
11010
  );
10632
11011
  return {
10633
11012
  apiKeyId: data.id,
@@ -10861,7 +11240,7 @@ async function validateSshKey(pubkeyPathInput, expectedApiKeyId) {
10861
11240
  const roleName = userData.role_name || "user";
10862
11241
  const roleDefaults = userData.role_default_permissions ?? {};
10863
11242
  const userOverrides = userData.permissions ?? null;
10864
- const permissions = resolvePermissions(roleName, roleDefaults, userOverrides);
11243
+ const permissions = resolvePermissions2(roleName, roleDefaults, userOverrides);
10865
11244
  const allowedServerIds = intersectServerAccess(
10866
11245
  apiRow.allowed_server_ids,
10867
11246
  permissions.resources.ssh_servers
@@ -10878,9 +11257,9 @@ async function validateSshKey(pubkeyPathInput, expectedApiKeyId) {
10878
11257
  AND (last_used_at IS NULL OR last_used_at < now() - interval '60 seconds')`
10879
11258
  )
10880
11259
  ]);
10881
- const moduleCount = MODULE_KEYS.filter((k) => permissions.modules[k]).length;
11260
+ const moduleCount = MODULE_KEYS2.filter((k) => permissions.modules[k]).length;
10882
11261
  console.error(
10883
- `Authenticated via SSH key "${keyRow.name}" (fp ${fingerprint.slice(0, 24)}...) as user ${apiRow.created_by} (role: ${roleName}, modules: ${moduleCount}/${MODULE_KEYS.length})`
11262
+ `Authenticated via SSH key "${keyRow.name}" (fp ${fingerprint.slice(0, 24)}...) as user ${apiRow.created_by} (role: ${roleName}, modules: ${moduleCount}/${MODULE_KEYS2.length})`
10884
11263
  );
10885
11264
  return {
10886
11265
  apiKeyId: apiRow.id,
@@ -11044,7 +11423,7 @@ function getEncryptionKey() {
11044
11423
  throw new Error("ENCRYPTION_KEY must be a 64-character hex string");
11045
11424
  return buf;
11046
11425
  }
11047
- function encrypt(text22) {
11426
+ function encrypt(text23) {
11048
11427
  const key = getEncryptionKey();
11049
11428
  const iv = randomBytes(ENC_IV_LENGTH);
11050
11429
  const cipher = createCipheriv(
@@ -11052,7 +11431,7 @@ function encrypt(text22) {
11052
11431
  new Uint8Array(key),
11053
11432
  new Uint8Array(iv)
11054
11433
  );
11055
- let encrypted = cipher.update(text22, "utf8", "hex");
11434
+ let encrypted = cipher.update(text23, "utf8", "hex");
11056
11435
  encrypted += cipher.final("hex");
11057
11436
  const authTag = cipher.getAuthTag();
11058
11437
  return Buffer.concat([
@@ -11789,10 +12168,10 @@ async function r2GetObjectRange(bucket, key, range) {
11789
12168
  const body = result.Body;
11790
12169
  if (!body?.transformToString)
11791
12170
  throw new Error("R2 returned no readable body");
11792
- const text22 = await body.transformToString();
12171
+ const text23 = await body.transformToString();
11793
12172
  const header = `# range: bytes ${range.offset}-${end} of ${size} (${effectiveLen} bytes)`;
11794
12173
  return `${header}
11795
- ${text22}`;
12174
+ ${text23}`;
11796
12175
  } catch (e) {
11797
12176
  throw r2WrapError(bucket, key, e);
11798
12177
  }
@@ -12155,15 +12534,15 @@ async function sftpRead(opts, filePath, proxy, options) {
12155
12534
  clearTimeout(timer);
12156
12535
  cleanup?.();
12157
12536
  cleanup = void 0;
12158
- const text22 = Buffer.concat(
12537
+ const text23 = Buffer.concat(
12159
12538
  chunks.map((ch) => new Uint8Array(ch))
12160
12539
  ).toString("utf-8");
12161
12540
  if (!isWholeFileRequest) {
12162
12541
  const header = `# range: bytes ${offset}-${offset + effectiveLen - 1} of ${total} (${effectiveLen} bytes)`;
12163
12542
  resolve(`${header}
12164
- ${text22}`);
12543
+ ${text23}`);
12165
12544
  } else {
12166
- resolve(text22);
12545
+ resolve(text23);
12167
12546
  }
12168
12547
  });
12169
12548
  rs.on("error", (e) => {
@@ -12319,11 +12698,11 @@ function getKnownContainers(serverId, maxAgeMs = 5 * 6e4) {
12319
12698
  if (Date.now() - e.capturedAt > maxAgeMs) return void 0;
12320
12699
  return e.names;
12321
12700
  }
12322
- function truncateForLLM(text22, maxBytes) {
12323
- const totalBytes = Buffer.byteLength(text22, "utf8");
12701
+ function truncateForLLM(text23, maxBytes) {
12702
+ const totalBytes = Buffer.byteLength(text23, "utf8");
12324
12703
  if (totalBytes <= maxBytes)
12325
- return { text: text22, truncated: false, totalBytes, shownBytes: totalBytes };
12326
- const buf = Buffer.from(text22, "utf8");
12704
+ return { text: text23, truncated: false, totalBytes, shownBytes: totalBytes };
12705
+ const buf = Buffer.from(text23, "utf8");
12327
12706
  let cut = maxBytes;
12328
12707
  while (cut > 0 && (buf[cut] & 192) === 128) cut--;
12329
12708
  const head = buf.subarray(0, cut).toString("utf8");
@@ -12354,10 +12733,10 @@ function postprocessResult(result, meta) {
12354
12733
  if (!result.content?.length) return result;
12355
12734
  if (RAW_JSON_TOOLS.has(meta.toolName)) return result;
12356
12735
  const block = result.content[0];
12357
- let text22 = String(block.text ?? "");
12358
- const trunc = truncateForLLM(text22, RESPONSE_MAX_BYTES);
12736
+ let text23 = String(block.text ?? "");
12737
+ const trunc = truncateForLLM(text23, RESPONSE_MAX_BYTES);
12359
12738
  if (trunc.truncated) {
12360
- text22 = trunc.text + "\n\n... " + buildTruncationHint(
12739
+ text23 = trunc.text + "\n\n... " + buildTruncationHint(
12361
12740
  meta.toolName,
12362
12741
  meta.args,
12363
12742
  trunc.totalBytes,
@@ -12371,11 +12750,11 @@ function postprocessResult(result, meta) {
12371
12750
  const parts = [`took ${tookStr}`, sizeStr];
12372
12751
  if (meta.serverIdLabel) parts.push(`server: ${meta.serverIdLabel}`);
12373
12752
  if (meta.cached) parts.push("cached");
12374
- text22 = `${text22}
12753
+ text23 = `${text23}
12375
12754
 
12376
12755
  [${parts.join(", ")}]`;
12377
12756
  }
12378
- return { ...result, content: [{ ...block, text: text22 }] };
12757
+ return { ...result, content: [{ ...block, text: text23 }] };
12379
12758
  }
12380
12759
  function buildPipelineScript(commands, shell, marker, stopOnError) {
12381
12760
  if (shell === "powershell") {
@@ -13161,11 +13540,11 @@ CREATE TABLE IF NOT EXISTS _mcp_migrations (
13161
13540
  applied_by TEXT
13162
13541
  );
13163
13542
  `.trim();
13164
- function normaliseMigrationSql(sql33) {
13165
- return sql33.replace(/\r\n/g, "\n").trim() + "\n";
13543
+ function normaliseMigrationSql(sql36) {
13544
+ return sql36.replace(/\r\n/g, "\n").trim() + "\n";
13166
13545
  }
13167
- function migrationSha256(sql33) {
13168
- return createHash("sha256").update(sql33.replace(/\r\n/g, "\n"), "utf8").digest("hex");
13546
+ function migrationSha256(sql36) {
13547
+ return createHash("sha256").update(sql36.replace(/\r\n/g, "\n"), "utf8").digest("hex");
13169
13548
  }
13170
13549
  function dollarQuoteTag(value) {
13171
13550
  let tag = "_mcp";
@@ -15177,6 +15556,7 @@ var TOOLS = [
15177
15556
  }
15178
15557
  },
15179
15558
  ...MAILSERVER_TOOLS,
15559
+ ...GIT_CREDENTIAL_TOOLS,
15180
15560
  ...HERMES_COMPANY_TOOLS,
15181
15561
  // ----- Trigger.dev -----
15182
15562
  ...TRIGGER_TOOLS
@@ -15498,7 +15878,7 @@ async function executeToolCall(name, a, _serverId) {
15498
15878
  ]
15499
15879
  };
15500
15880
  }
15501
- const lines = data.hits.map((hit, index19) => {
15881
+ const lines = data.hits.map((hit, index20) => {
15502
15882
  const when = hit.lastMessageAt ? new Date(hit.lastMessageAt).toISOString().slice(0, 10) : "unknown date";
15503
15883
  const repo = hit.repo ?? "unknown repo";
15504
15884
  const sim = hit.similarity !== null ? ` \xB7 ${(hit.similarity * 100).toFixed(0)}% match` : "";
@@ -15508,7 +15888,7 @@ async function executeToolCall(name, a, _serverId) {
15508
15888
  ...Array.isArray(hit.tech) ? hit.tech.slice(0, 4) : []
15509
15889
  ].filter(Boolean);
15510
15890
  const tags = facets.length > 0 ? ` \xB7 ${facets.join(", ")}` : "";
15511
- return `${index19 + 1}. [${repo} \xB7 ${when}${sim}${tags}] ${hit.title}
15891
+ return `${index20 + 1}. [${repo} \xB7 ${when}${sim}${tags}] ${hit.title}
15512
15892
  id: ${hit.id}
15513
15893
  ${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 500)}`;
15514
15894
  });
@@ -15715,12 +16095,12 @@ Searchable via search-team-memory once indexing completes (usually immediate).`
15715
16095
  ]
15716
16096
  };
15717
16097
  }
15718
- const lines = data.hits.map((hit, index19) => {
16098
+ const lines = data.hits.map((hit, index20) => {
15719
16099
  const sim = hit.similarity !== null ? ` \xB7 ${Math.round(hit.similarity * 100)}% match` : "";
15720
16100
  const tags = hit.tags.length ? ` \xB7 ${hit.tags.slice(0, 5).join(", ")}` : "";
15721
16101
  const sources = hit.sources.length ? `
15722
16102
  sources: ${hit.sources.slice(0, 6).map((s) => `${s.type}:${s.ref}`).join(", ")}` : "";
15723
- return `${index19 + 1}. [${hit.status} \xB7 updated ${hit.pageUpdatedOn ?? "unknown"}${sim}${tags}] ${hit.title}
16103
+ return `${index20 + 1}. [${hit.status} \xB7 updated ${hit.pageUpdatedOn ?? "unknown"}${sim}${tags}] ${hit.title}
15724
16104
  slug: ${hit.slug}${sources}
15725
16105
  ${(hit.summary ?? hit.snippet).replace(/\s+/g, " ").slice(0, 400)}`;
15726
16106
  });
@@ -15898,7 +16278,7 @@ Archived sources (${archiveOnly.length}):
15898
16278
  let formatItems2 = function(items) {
15899
16279
  if (items.length === 0) return "(none)";
15900
16280
  return items.map(
15901
- (item, index19) => `${index19 + 1}. [${item.status} \xB7 ${item.folder} \xB7 updated ${item.updated ?? "unknown"}] ${item.title}
16281
+ (item, index20) => `${index20 + 1}. [${item.status} \xB7 ${item.folder} \xB7 updated ${item.updated ?? "unknown"}] ${item.title}
15902
16282
  slug: ${item.slug}`
15903
16283
  ).join("\n");
15904
16284
  };
@@ -15924,10 +16304,10 @@ Archived sources (${archiveOnly.length}):
15924
16304
  };
15925
16305
  }
15926
16306
  const data = await res.json();
15927
- const countLine = Object.entries(data.counts).sort(([left], [right]) => left.localeCompare(right)).map(([status, count]) => `${status} ${count}`).join(" \xB7 ");
15928
- const flagLines = data.flags.length === 0 ? "(none)" : data.flags.map((flag, index19) => {
16307
+ const countLine = Object.entries(data.counts).sort(([left], [right]) => left.localeCompare(right)).map(([status, count2]) => `${status} ${count2}`).join(" \xB7 ");
16308
+ const flagLines = data.flags.length === 0 ? "(none)" : data.flags.map((flag, index20) => {
15929
16309
  const detail = flag.detail.replace(/\s+/g, " ").slice(0, 240);
15930
- return `${index19 + 1}. [${flag.kind} \xB7 ${flag.folder}${flag.status ? ` \xB7 page ${flag.status}` : ""}] ${flag.title}
16310
+ return `${index20 + 1}. [${flag.kind} \xB7 ${flag.folder}${flag.status ? ` \xB7 page ${flag.status}` : ""}] ${flag.title}
15931
16311
  slug: ${flag.slug}
15932
16312
  ${detail}`;
15933
16313
  }).join("\n");
@@ -16834,14 +17214,14 @@ ${renderOne(r)}`);
16834
17214
  const dryRun = a.dryRun === true;
16835
17215
  const confirmAbove = a.confirmAbove !== void 0 ? Math.max(0, Number(a.confirmAbove)) : void 0;
16836
17216
  const confirmCount = a.confirmCount !== void 0 ? Number(a.confirmCount) : void 0;
16837
- const guard = (count, sizeBytes, where) => {
16838
- if (confirmAbove !== void 0 && count > confirmAbove) {
16839
- if (confirmCount === count) return null;
17217
+ const guard = (count2, sizeBytes, where) => {
17218
+ if (confirmAbove !== void 0 && count2 > confirmAbove) {
17219
+ if (confirmCount === count2) return null;
16840
17220
  return {
16841
17221
  content: [
16842
17222
  {
16843
17223
  type: "text",
16844
- text: `Refusing to delete ${count} item(s) (${formatBytes(sizeBytes)}) under ${where}: exceeds confirmAbove=${confirmAbove}. To proceed, re-issue the call with confirmCount: ${count}.`
17224
+ text: `Refusing to delete ${count2} item(s) (${formatBytes(sizeBytes)}) under ${where}: exceeds confirmAbove=${confirmAbove}. To proceed, re-issue the call with confirmCount: ${count2}.`
16845
17225
  }
16846
17226
  ]
16847
17227
  };
@@ -17093,8 +17473,8 @@ ${sample.join("\n")}${files.length > 5 ? `
17093
17473
  };
17094
17474
  const filtered = sortRows(applyFilter(only.rows));
17095
17475
  if (format === "json") {
17096
- const text23 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
17097
- return { content: [{ type: "text", text: text23 }] };
17476
+ const text24 = filtered.map((c) => JSON.stringify(c)).join("\n") || "(no containers)";
17477
+ return { content: [{ type: "text", text: text24 }] };
17098
17478
  }
17099
17479
  if (groupByProject) {
17100
17480
  const groups = /* @__PURE__ */ new Map();
@@ -17121,8 +17501,8 @@ ${sample.join("\n")}${files.length > 5 ? `
17121
17501
  }
17122
17502
  const header = `${"NAMES".padEnd(36)} ${"IMAGE".padEnd(40)} ${"STATUS".padEnd(24)} ${"HEALTH".padEnd(10)} ${"PORTS".padEnd(40)} PROJECT`;
17123
17503
  const body = filtered.map((r) => `${fmtRow(r)} ${r.Project || ""}`);
17124
- const text22 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
17125
- return { content: [{ type: "text", text: text22 }] };
17504
+ const text23 = filtered.length === 0 ? "(no containers match)" : [header, ...body].join("\n");
17505
+ return { content: [{ type: "text", text: text23 }] };
17126
17506
  }
17127
17507
  if (format === "json") {
17128
17508
  const lines = [];
@@ -18945,6 +19325,12 @@ Install: GET https://dashboard.mgsoftware.nl/api/downloads/versions.txt then \u2
18945
19325
  token
18946
19326
  });
18947
19327
  }
19328
+ case GIT_CREDENTIAL_TOOL_NAME: {
19329
+ return handleGitCredentialTool(a, {
19330
+ dashboardBaseUrl,
19331
+ apiKey: apiKey ?? ""
19332
+ });
19333
+ }
18948
19334
  case HERMES_COMPANY_TOOL_NAME: {
18949
19335
  return handleHermesCompanyTool(a, {
18950
19336
  userId: ctx.userId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgsoftwarebv/mg-dashboard-mcp",
3
- "version": "7.4.23",
3
+ "version": "7.4.25",
4
4
  "description": "MCP Server for MG Dashboard - SSH, SFTP, Docker, domains, DNS, and environment config tools for Cursor",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",