@fdcn/sourcefed 0.2.2 → 0.2.3

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 (29) hide show
  1. package/README.md +2 -2
  2. package/dist/chunk-UOTCX4ME.js +3963 -0
  3. package/dist/cli.js +474 -361
  4. package/dist/data/core/SKILL.md +10 -0
  5. package/dist/data/core/references/commands.md +1 -0
  6. package/dist.dts/packages/cli/src/commands/daemon.d.ts +1 -0
  7. package/dist.dts/packages/cli/src/commands/mcp.d.ts +1 -0
  8. package/dist.dts/packages/cli/src/commands/monitor/ack.d.ts +2 -0
  9. package/dist.dts/packages/cli/src/commands/monitor/create.d.ts +2 -0
  10. package/dist.dts/packages/cli/src/commands/monitor/events.d.ts +2 -0
  11. package/dist.dts/packages/cli/src/commands/monitor/follow.d.ts +2 -0
  12. package/dist.dts/packages/cli/src/commands/monitor/index.d.ts +10 -0
  13. package/dist.dts/packages/cli/src/commands/monitor/list.d.ts +2 -0
  14. package/dist.dts/packages/cli/src/commands/monitor/logs.d.ts +2 -0
  15. package/dist.dts/packages/cli/src/commands/monitor/remove.d.ts +2 -0
  16. package/dist.dts/packages/cli/src/commands/monitor/sources.d.ts +2 -0
  17. package/dist.dts/packages/cli/src/commands/monitor/start.d.ts +2 -0
  18. package/dist.dts/packages/cli/src/commands/monitor/status.d.ts +2 -0
  19. package/dist.dts/packages/cli/src/commands/monitor/stop.d.ts +2 -0
  20. package/dist.dts/packages/cli/src/commands/monitor.d.ts +1 -0
  21. package/dist.dts/packages/cli/src/commands/setup.d.ts +1 -0
  22. package/dist.dts/packages/cli/src/follow.test.d.ts +1 -0
  23. package/dist.dts/packages/cli/src/utils/daemon.d.ts +2 -0
  24. package/dist.dts/packages/cli/src/utils/flags.d.ts +1 -0
  25. package/dist.dts/packages/cli/src/utils/format.d.ts +8 -0
  26. package/dist.dts/packages/cli/src/utils/net.d.ts +1 -0
  27. package/dist.dts/packages/cli/src/utils/setup.d.ts +2 -0
  28. package/dist.dts/packages/cli/src/utils/webhooks.d.ts +2 -0
  29. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,10 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // packages/cli/src/index.ts
4
- import os7 from "node:os";
5
4
  import { realpathSync } from "node:fs";
6
- import { fileURLToPath as fileURLToPath2 } from "node:url";
7
- import { serveStdio } from "@modelcontextprotocol/server/stdio";
5
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
8
6
 
9
7
  // packages/daemon/dist/index.js
10
8
  import { link, mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
@@ -116,9 +114,9 @@ function _isValidObjectKey(object$1, key) {
116
114
  }
117
115
  // @__NO_SIDE_EFFECTS__
118
116
  function _joinExpects(values$1, separator) {
119
- const list = [...new Set(values$1)];
120
- if (list.length > 1) return `(${list.join(` ${separator} `)})`;
121
- return list[0] ?? "never";
117
+ const list2 = [...new Set(values$1)];
118
+ if (list2.length > 1) return `(${list2.join(` ${separator} `)})`;
119
+ return list2[0] ?? "never";
122
120
  }
123
121
  var EMOJI_REGEX = new RegExp("^(?:[\\u{1F1E6}-\\u{1F1FF}]{2}|\\u{1F3F4}[\\u{E0061}-\\u{E007A}]{2}[\\u{E0030}-\\u{E0039}\\u{E0061}-\\u{E007A}]{1,3}\\u{E007F}|(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation})(?:\\u200D(?:\\p{Emoji}\\uFE0F\\u20E3?|\\p{Emoji_Modifier_Base}\\p{Emoji_Modifier}?|(?![\\p{Emoji_Modifier_Base}\\u{1F1E6}-\\u{1F1FF}])\\p{Emoji_Presentation}))*)+$", "u");
124
122
  // @__NO_SIDE_EFFECTS__
@@ -917,31 +915,31 @@ var JsonMonitorEventQueue = class {
917
915
  eventsFile;
918
916
  queue = Promise.resolve();
919
917
  async enqueue(input) {
920
- return this.transact((events) => {
918
+ return this.transact((events2) => {
921
919
  const queued = createQueuedMonitorEvent(input.monitor, input.event);
922
- const existing = events.find((event) => event.id === queued.id);
920
+ const existing = events2.find((event) => event.id === queued.id);
923
921
  if (existing) return existing;
924
- events.push(queued);
922
+ events2.push(queued);
925
923
  return queued;
926
924
  });
927
925
  }
928
926
  async read(target) {
929
- const events = await this.loadEvents();
930
- return events.filter((event) => sameTarget2(event.target, target));
927
+ const events2 = await this.loadEvents();
928
+ return events2.filter((event) => sameTarget2(event.target, target));
931
929
  }
932
930
  async acknowledge(target, eventIDs) {
933
- await this.transact((events) => {
931
+ await this.transact((events2) => {
934
932
  const ids = new Set(eventIDs);
935
- for (let index = events.length - 1; index >= 0; index -= 1) {
936
- if (sameTarget2(events[index].target, target) && ids.has(events[index].id)) events.splice(index, 1);
933
+ for (let index = events2.length - 1; index >= 0; index -= 1) {
934
+ if (sameTarget2(events2[index].target, target) && ids.has(events2[index].id)) events2.splice(index, 1);
937
935
  }
938
936
  });
939
937
  }
940
938
  async transact(operation) {
941
939
  const next = this.queue.then(async () => {
942
- const events = await this.loadEvents();
943
- const result = await operation(events);
944
- await this.saveEvents(events);
940
+ const events2 = await this.loadEvents();
941
+ const result = await operation(events2);
942
+ await this.saveEvents(events2);
945
943
  return result;
946
944
  });
947
945
  this.queue = next.then(() => void 0, () => void 0);
@@ -958,10 +956,10 @@ var JsonMonitorEventQueue = class {
958
956
  throw error2;
959
957
  }
960
958
  }
961
- async saveEvents(events) {
959
+ async saveEvents(events2) {
962
960
  await mkdir(this.stateDir, { recursive: true, mode: 448 });
963
961
  const temporary = `${this.eventsFile}.${process.pid}.tmp`;
964
- await writeFile(temporary, `${JSON.stringify(events, null, 2)}
962
+ await writeFile(temporary, `${JSON.stringify(events2, null, 2)}
965
963
  `, { mode: 384 });
966
964
  await rename(temporary, this.eventsFile);
967
965
  }
@@ -2153,7 +2151,7 @@ async function confirmStillFailing(repo, prNumber, failed) {
2153
2151
  return /FAIL|ERROR|CANCEL/i.test(state);
2154
2152
  });
2155
2153
  }
2156
- async function appendGithubCiEvent(repo, prNumber, cursor, events, rollup) {
2154
+ async function appendGithubCiEvent(repo, prNumber, cursor, events2, rollup) {
2157
2155
  const ciState = rollup.map((check) => check.conclusion ?? check.state ?? "PENDING").sort().join(",");
2158
2156
  if (!ciState || ciState === cursor.ciState) return;
2159
2157
  const previousCiState = cursor.ciState;
@@ -2164,7 +2162,7 @@ async function appendGithubCiEvent(repo, prNumber, cursor, events, rollup) {
2164
2162
  const failureState = failed.map((check) => `${check.name ?? check.context ?? "check"}:${check.conclusion ?? check.state ?? "FAILURE"}`).sort().join(",");
2165
2163
  if (failed.length > 0 && failureState !== cursor.ciFailureState) {
2166
2164
  const names = failed.map((check) => check.name ?? check.context ?? "check").join(", ");
2167
- events.push({
2165
+ events2.push({
2168
2166
  kind: "ci",
2169
2167
  id: `ci:${failureState}`,
2170
2168
  repo,
@@ -2177,13 +2175,13 @@ async function appendGithubCiEvent(repo, prNumber, cursor, events, rollup) {
2177
2175
  }
2178
2176
  cursor.ciFailureState = failureState;
2179
2177
  }
2180
- function appendGithubConflictEvent(repo, prNumber, cursor, events, mergeable) {
2178
+ function appendGithubConflictEvent(repo, prNumber, cursor, events2, mergeable) {
2181
2179
  if (mergeable === cursor.mergeable) return;
2182
2180
  const previousMergeable = cursor.mergeable;
2183
2181
  cursor.mergeable = mergeable;
2184
2182
  if (previousMergeable === void 0) return;
2185
2183
  if (mergeable !== "CONFLICTING" && mergeable !== "DIRTY") return;
2186
- events.push({
2184
+ events2.push({
2187
2185
  kind: "conflict",
2188
2186
  id: `conflict:${mergeable}`,
2189
2187
  repo,
@@ -2193,7 +2191,7 @@ function appendGithubConflictEvent(repo, prNumber, cursor, events, mergeable) {
2193
2191
  actionable: true
2194
2192
  });
2195
2193
  }
2196
- function appendGithubReviews(repo, prNumber, cursor, events, reviews, historyPrimed) {
2194
+ function appendGithubReviews(repo, prNumber, cursor, events2, reviews, historyPrimed) {
2197
2195
  const ref = `#${prNumber}`;
2198
2196
  for (const review of reviews) {
2199
2197
  const id = "rev:" + String(review.id ?? `${review.author?.login}-${review.submittedAt}`);
@@ -2204,7 +2202,7 @@ function appendGithubReviews(repo, prNumber, cursor, events, reviews, historyPri
2204
2202
  let actionable = false;
2205
2203
  if (review.state === "CHANGES_REQUESTED") actionable = true;
2206
2204
  if (review.state === "COMMENTED" && review.body?.trim()) actionable = true;
2207
- events.push({
2205
+ events2.push({
2208
2206
  kind: "review",
2209
2207
  id,
2210
2208
  repo,
@@ -2216,7 +2214,7 @@ function appendGithubReviews(repo, prNumber, cursor, events, reviews, historyPri
2216
2214
  });
2217
2215
  }
2218
2216
  }
2219
- function appendGithubStateEvent(repo, prNumber, cursor, events, prState) {
2217
+ function appendGithubStateEvent(repo, prNumber, cursor, events2, prState) {
2220
2218
  const isTerminal = prState === "MERGED" || prState === "CLOSED";
2221
2219
  if (prState === cursor.prState) return isTerminal;
2222
2220
  const previousPrState = cursor.prState;
@@ -2224,7 +2222,7 @@ function appendGithubStateEvent(repo, prNumber, cursor, events, prState) {
2224
2222
  if (previousPrState !== void 0 && isTerminal) {
2225
2223
  let summary = `PR #${prNumber} was CLOSED without merging`;
2226
2224
  if (prState === "MERGED") summary = `PR #${prNumber} was MERGED`;
2227
- events.push({
2225
+ events2.push({
2228
2226
  kind: "merged",
2229
2227
  id: `state:${prState}`,
2230
2228
  repo,
@@ -2237,14 +2235,14 @@ function appendGithubStateEvent(repo, prNumber, cursor, events, prState) {
2237
2235
  }
2238
2236
  return isTerminal;
2239
2237
  }
2240
- function appendComments(repo, prNumber, cursor, events, comments, idPrefix, historyPrimed, summary, body) {
2238
+ function appendComments(repo, prNumber, cursor, events2, comments, idPrefix, historyPrimed, summary, body) {
2241
2239
  for (const comment of comments) {
2242
2240
  const id = idPrefix + String(comment.id);
2243
2241
  if (cursor.commentIds.includes(id)) continue;
2244
2242
  cursor.commentIds.push(id);
2245
2243
  if (!historyPrimed) continue;
2246
2244
  const text = body(comment);
2247
- events.push({
2245
+ events2.push({
2248
2246
  kind: "comment",
2249
2247
  id,
2250
2248
  repo,
@@ -2274,22 +2272,22 @@ async function pollGithub(repo, prNumber, cursorRaw) {
2274
2272
  const parsedCursor = /* @__PURE__ */ safeParse22(GithubCursorSchema, cursorRaw);
2275
2273
  let cursor = emptyGithubCursor();
2276
2274
  if (parsedCursor.success) cursor = parsedCursor.output;
2277
- const events = [];
2275
+ const events2 = [];
2278
2276
  const ref = `#${prNumber}`;
2279
2277
  if (!githubToken()) {
2280
2278
  if (!warnedMissingToken) {
2281
2279
  warnedMissingToken = true;
2282
2280
  console.warn("[sourcefed] GH_TOKEN or GITHUB_TOKEN is not set; GitHub monitors will not poll");
2283
2281
  }
2284
- return { events, cursor, terminal: false };
2282
+ return { events: events2, cursor, terminal: false };
2285
2283
  }
2286
2284
  const data = await fetchPrData(repo, prNumber);
2287
- if (!data) return { events, cursor, terminal: false };
2285
+ if (!data) return { events: events2, cursor, terminal: false };
2288
2286
  let historyPrimed = cursor.historyPrimed;
2289
2287
  if (!historyPrimed) historyPrimed = Boolean(cursor.reviewIds.length || cursor.commentIds.length || cursor.ciState || cursor.mergeable || cursor.prState);
2290
2288
  const lineCommentsPrimed = cursor.lineCommentsPrimed;
2291
2289
  const conversationCommentsPrimed = cursor.conversationCommentsPrimed;
2292
- appendGithubReviews(repo, prNumber, cursor, events, data.reviews ?? [], historyPrimed);
2290
+ appendGithubReviews(repo, prNumber, cursor, events2, data.reviews ?? [], historyPrimed);
2293
2291
  if (!cursor.historyPrimed) cursor.historyPrimed = true;
2294
2292
  const lineComments = await fetchGithubComments(`repos/${repo}/pulls/${prNumber}/comments`);
2295
2293
  if (lineComments) {
@@ -2297,7 +2295,7 @@ async function pollGithub(repo, prNumber, cursorRaw) {
2297
2295
  repo,
2298
2296
  prNumber,
2299
2297
  cursor,
2300
- events,
2298
+ events2,
2301
2299
  lineComments,
2302
2300
  "c:",
2303
2301
  lineCommentsPrimed,
@@ -2316,7 +2314,7 @@ async function pollGithub(repo, prNumber, cursorRaw) {
2316
2314
  repo,
2317
2315
  prNumber,
2318
2316
  cursor,
2319
- events,
2317
+ events2,
2320
2318
  conversationComments,
2321
2319
  "issue:",
2322
2320
  conversationCommentsPrimed,
@@ -2325,12 +2323,12 @@ async function pollGithub(repo, prNumber, cursorRaw) {
2325
2323
  );
2326
2324
  cursor.conversationCommentsPrimed = true;
2327
2325
  }
2328
- await appendGithubCiEvent(repo, prNumber, cursor, events, data.statusCheckRollup ?? []);
2326
+ await appendGithubCiEvent(repo, prNumber, cursor, events2, data.statusCheckRollup ?? []);
2329
2327
  const mergeable = data.mergeable ?? data.mergeStateStatus ?? "UNKNOWN";
2330
- appendGithubConflictEvent(repo, prNumber, cursor, events, mergeable);
2328
+ appendGithubConflictEvent(repo, prNumber, cursor, events2, mergeable);
2331
2329
  const prState = data.state ?? "OPEN";
2332
- const terminal = appendGithubStateEvent(repo, prNumber, cursor, events, prState);
2333
- return { events, cursor, terminal };
2330
+ const terminal = appendGithubStateEvent(repo, prNumber, cursor, events2, prState);
2331
+ return { events: events2, cursor, terminal };
2334
2332
  }
2335
2333
  var githubPollMonitor = new PollMonitor({
2336
2334
  run: async (source, cursor) => {
@@ -3108,11 +3106,11 @@ function jiraChangeValue(item, side) {
3108
3106
  function formatJiraChangelogItem(item) {
3109
3107
  return `${item.field}: ${jiraChangeValue(item, "from")} \u2192 ${jiraChangeValue(item, "to")}`;
3110
3108
  }
3111
- function isTerminalStatus(status) {
3112
- if (typeof status !== "string") return false;
3109
+ function isTerminalStatus(status2) {
3110
+ if (typeof status2 !== "string") return false;
3113
3111
  const terminalStatus = process.env.SOURCEFED_JIRA_TERMINAL_STATUS;
3114
3112
  if (!terminalStatus) return false;
3115
- return status.trim().toLowerCase() === terminalStatus.trim().toLowerCase();
3113
+ return status2.trim().toLowerCase() === terminalStatus.trim().toLowerCase();
3116
3114
  }
3117
3115
  var TRACKED_CHANGE_FIELDS = /* @__PURE__ */ new Set(["summary", "status", "assignee", "priority", "labels"]);
3118
3116
  function meaningfulChangelogItems(items) {
@@ -3138,12 +3136,12 @@ function jiraChangelogEvent(issueKey, history) {
3138
3136
  terminal
3139
3137
  };
3140
3138
  }
3141
- function appendJiraChangelog(issueKey, cursor, events, histories) {
3139
+ function appendJiraChangelog(issueKey, cursor, events2, histories) {
3142
3140
  const total = histories.length;
3143
3141
  if (cursor.changelogCount !== 0 && total > cursor.changelogCount) {
3144
3142
  for (const history of histories.slice(cursor.changelogCount)) {
3145
3143
  const event = jiraChangelogEvent(issueKey, history);
3146
- if (event) events.push(event);
3144
+ if (event) events2.push(event);
3147
3145
  }
3148
3146
  }
3149
3147
  cursor.changelogCount = total;
@@ -3154,7 +3152,7 @@ function adfText(node) {
3154
3152
  if (Array.isArray(node.content)) return node.content.map(adfText).join("");
3155
3153
  return "";
3156
3154
  }
3157
- function appendJiraComments(issueKey, cursor, events, comments) {
3155
+ function appendJiraComments(issueKey, cursor, events2, comments) {
3158
3156
  const freshComments = [];
3159
3157
  for (const comment of comments) {
3160
3158
  const id = Number(comment.id);
@@ -3167,7 +3165,7 @@ function appendJiraComments(issueKey, cursor, events, comments) {
3167
3165
  }
3168
3166
  for (const comment of freshComments) {
3169
3167
  const body = adfText(comment.body);
3170
- events.push({
3168
+ events2.push({
3171
3169
  kind: "comment",
3172
3170
  id: `comment:${comment.id}`,
3173
3171
  issueKey,
@@ -3180,11 +3178,11 @@ function appendJiraComments(issueKey, cursor, events, comments) {
3180
3178
  cursor.commentIds.push(Number(comment.id));
3181
3179
  }
3182
3180
  }
3183
- function appendJiraDescription(issueKey, cursor, events, description) {
3181
+ function appendJiraDescription(issueKey, cursor, events2, description) {
3184
3182
  let descriptionVersion = "none";
3185
3183
  if (description) descriptionVersion = JSON.stringify(description).slice(0, 40);
3186
3184
  if (cursor.descriptionVersion !== void 0 && descriptionVersion !== cursor.descriptionVersion) {
3187
- events.push({
3185
+ events2.push({
3188
3186
  kind: "description",
3189
3187
  id: `description:${descriptionVersion}`,
3190
3188
  issueKey,
@@ -3218,15 +3216,15 @@ async function pollJira(issueKey, cursorRaw) {
3218
3216
  const parsedCursor = /* @__PURE__ */ safeParse23(JiraCursorSchema, cursorRaw);
3219
3217
  let cursor = emptyJiraCursor();
3220
3218
  if (parsedCursor.success) cursor = parsedCursor.output;
3221
- const events = [];
3219
+ const events2 = [];
3222
3220
  const comments = await jiraFetch(`/issue/${issueKey}/comment?maxResults=50&orderBy=-created`);
3223
- appendJiraComments(issueKey, cursor, events, comments.comments ?? []);
3221
+ appendJiraComments(issueKey, cursor, events2, comments.comments ?? []);
3224
3222
  const issue2 = await jiraFetch(`/issue/${issueKey}?expand=changelog&fields=status,description`);
3225
3223
  const statusName = issue2.fields?.status?.name;
3226
- appendJiraDescription(issueKey, cursor, events, issue2.fields?.description);
3224
+ appendJiraDescription(issueKey, cursor, events2, issue2.fields?.description);
3227
3225
  const changelog = issue2.changelog?.histories ?? [];
3228
- appendJiraChangelog(issueKey, cursor, events, changelog);
3229
- return { events, cursor, terminal: isTerminalStatus(statusName) };
3226
+ appendJiraChangelog(issueKey, cursor, events2, changelog);
3227
+ return { events: events2, cursor, terminal: isTerminalStatus(statusName) };
3230
3228
  }
3231
3229
  var jiraPollMonitor = new PollMonitor2({
3232
3230
  run: async (source, cursor) => {
@@ -4140,9 +4138,9 @@ function parseSlackReadResult(result, cursorRaw) {
4140
4138
  if (!cursor.latestTs) return true;
4141
4139
  return messageTimestamp(message.ts) > messageTimestamp(cursor.latestTs);
4142
4140
  });
4143
- let events = [];
4141
+ let events2 = [];
4144
4142
  if (cursor.primed) {
4145
- events = freshMessages.map((message) => {
4143
+ events2 = freshMessages.map((message) => {
4146
4144
  let text = "[message without text]";
4147
4145
  if (message.text?.trim()) text = message.text.trim();
4148
4146
  let author = users.get(message.user);
@@ -4165,7 +4163,7 @@ function parseSlackReadResult(result, cursorRaw) {
4165
4163
  if (messageTimestamp(message.ts) > messageTimestamp(latestTs)) latestTs = message.ts;
4166
4164
  }
4167
4165
  cursor.latestTs = latestTs;
4168
- return { events, cursor };
4166
+ return { events: events2, cursor };
4169
4167
  }
4170
4168
  var SLACK_API = "https://slack.com/api";
4171
4169
  var warnedMissingToken2 = false;
@@ -4356,8 +4354,8 @@ function sourceForInput(type, input) {
4356
4354
  return source.build(input);
4357
4355
  }
4358
4356
  function sourceForWebhookPath(pathname) {
4359
- const sources = Object.values(SOURCE_MAP);
4360
- for (const source of sources) {
4357
+ const sources2 = Object.values(SOURCE_MAP);
4358
+ for (const source of sources2) {
4361
4359
  if (source.webhook?.path === pathname) return source;
4362
4360
  }
4363
4361
  return void 0;
@@ -4671,10 +4669,10 @@ var SourcefedDaemon = class {
4671
4669
  this.observers.add(onEvent);
4672
4670
  return () => this.observers.delete(onEvent);
4673
4671
  }
4674
- appendLog(target, events) {
4672
+ appendLog(target, events2) {
4675
4673
  const key = targetKey(target);
4676
4674
  const existing = this.deliveryLog.get(key) ?? [];
4677
- const merged = [...events.reverse(), ...existing];
4675
+ const merged = [...events2.reverse(), ...existing];
4678
4676
  this.deliveryLog.set(key, merged.slice(0, DELIVERY_LOG_LIMIT));
4679
4677
  }
4680
4678
  emit(target) {
@@ -4834,16 +4832,16 @@ var HttpDaemonClient = class {
4834
4832
  });
4835
4833
  let buffered = [];
4836
4834
  let delivering = Promise.resolve();
4837
- const deliver = (events) => {
4838
- if (!callbacks?.has(onEvents) || events.length === 0) return Promise.resolve();
4839
- delivering = delivering.then(() => deliverWithRetry(events, 0)).catch((error2) => {
4835
+ const deliver = (events2) => {
4836
+ if (!callbacks?.has(onEvents) || events2.length === 0) return Promise.resolve();
4837
+ delivering = delivering.then(() => deliverWithRetry(events2, 0)).catch((error2) => {
4840
4838
  console.error(`[sourcefed] event delivery failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
4841
4839
  });
4842
4840
  return delivering;
4843
4841
  };
4844
- const deliverWithRetry = async (events, attempt) => {
4842
+ const deliverWithRetry = async (events2, attempt) => {
4845
4843
  try {
4846
- await onEvents(events);
4844
+ await onEvents(events2);
4847
4845
  } catch (error2) {
4848
4846
  if (attempt >= 2) {
4849
4847
  console.error(`[sourcefed] event delivery failed after retries; re-draining queue: ${error2 instanceof Error ? error2.message : String(error2)}`);
@@ -4852,7 +4850,7 @@ var HttpDaemonClient = class {
4852
4850
  return;
4853
4851
  }
4854
4852
  await sleep(250 * (attempt + 1));
4855
- await deliverWithRetry(events, attempt + 1);
4853
+ await deliverWithRetry(events2, attempt + 1);
4856
4854
  }
4857
4855
  };
4858
4856
  const performDrain = async () => {
@@ -5057,8 +5055,8 @@ function eventStream(daemon, target) {
5057
5055
  const stream = new ReadableStream({
5058
5056
  start(controller) {
5059
5057
  const encoder = new TextEncoder();
5060
- unsubscribe = daemon.subscribe(target, (events) => {
5061
- controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: "event", target, events })}
5058
+ unsubscribe = daemon.subscribe(target, (events2) => {
5059
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: "event", target, events: events2 })}
5062
5060
 
5063
5061
  `));
5064
5062
  });
@@ -5084,6 +5082,73 @@ function eventStream(daemon, target) {
5084
5082
  });
5085
5083
  }
5086
5084
 
5085
+ // packages/cli/src/utils/flags.ts
5086
+ function flag(args, name) {
5087
+ const index = args.indexOf(`--${name}`);
5088
+ return index >= 0 ? args[index + 1] : void 0;
5089
+ }
5090
+
5091
+ // packages/cli/src/utils/net.ts
5092
+ function isLoopback(hostname) {
5093
+ return hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost";
5094
+ }
5095
+
5096
+ // packages/cli/src/utils/daemon.ts
5097
+ function createDaemon() {
5098
+ return new SourcefedDaemon({
5099
+ stateDir: process.env.SOURCEFED_STATE_DIR
5100
+ });
5101
+ }
5102
+
5103
+ // packages/cli/src/utils/webhooks.ts
5104
+ async function startWebhookServer(runtime) {
5105
+ const enabled = Boolean(
5106
+ process.env.SOURCEFED_ENABLE_WEBHOOKS === "1" || process.env.SOURCEFED_WEBHOOK_PORT || process.env.SOURCEFED_GITHUB_WEBHOOK_SECRET || process.env.SOURCEFED_SLACK_SIGNING_SECRET
5107
+ );
5108
+ if (!enabled) return void 0;
5109
+ try {
5110
+ return await serveHttp({
5111
+ hostname: process.env.SOURCEFED_WEBHOOK_HOST ?? "127.0.0.1",
5112
+ port: Number(process.env.SOURCEFED_WEBHOOK_PORT ?? 8788),
5113
+ handler: (request) => runtime.webhook(request)
5114
+ });
5115
+ } catch (error2) {
5116
+ console.error(`[sourcefed] webhook listener unavailable; continuing with MCP and polling: ${error2 instanceof Error ? error2.message : String(error2)}`);
5117
+ return void 0;
5118
+ }
5119
+ }
5120
+
5121
+ // packages/cli/src/commands/daemon.ts
5122
+ async function runDaemon(args) {
5123
+ const port = Number(flag(args, "port") ?? process.env.SOURCEFED_DAEMON_PORT ?? 18787);
5124
+ const hostname = flag(args, "host") ?? process.env.SOURCEFED_DAEMON_HOST ?? "127.0.0.1";
5125
+ const token = process.env.SOURCEFED_DAEMON_TOKEN;
5126
+ if (!isLoopback(hostname) && !token) {
5127
+ throw new Error("binding the daemon to a non-loopback address requires SOURCEFED_DAEMON_TOKEN");
5128
+ }
5129
+ const daemon = createDaemon();
5130
+ await daemon.start();
5131
+ let server;
5132
+ try {
5133
+ server = await serveHttp({
5134
+ hostname,
5135
+ port,
5136
+ handler: (request) => handleDaemonHttpRequest(daemon, request, { token })
5137
+ });
5138
+ const webhookServer = await startWebhookServer(daemon.runtime);
5139
+ console.error(webhookServer ? `[sourcefed] webhook listener on http://${process.env.SOURCEFED_WEBHOOK_HOST ?? "127.0.0.1"}:${webhookServer.port}` : "[sourcefed] no separate webhook listener (webhooks served on daemon port)");
5140
+ } catch (error2) {
5141
+ await daemon.stop();
5142
+ throw error2;
5143
+ }
5144
+ console.error(`[sourcefed] daemon on http://${hostname}:${server.port} (rpc, events, webhooks)`);
5145
+ await new Promise(() => {
5146
+ });
5147
+ }
5148
+
5149
+ // packages/cli/src/commands/mcp.ts
5150
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
5151
+
5087
5152
  // packages/mcp/dist/index.js
5088
5153
  import process3 from "node:process";
5089
5154
  import { link as link5, mkdir as mkdir5, open as open5, readFile as readFile5, rename as rename5, rm as rm5, writeFile as writeFile5 } from "node:fs/promises";
@@ -5350,9 +5415,9 @@ function nullish(input) {
5350
5415
  return input === null || input === void 0;
5351
5416
  }
5352
5417
  function cleanRegex(source) {
5353
- const start = source.startsWith("^") ? 1 : 0;
5418
+ const start2 = source.startsWith("^") ? 1 : 0;
5354
5419
  const end = source.endsWith("$") ? source.length - 1 : source.length;
5355
- return source.slice(start, end);
5420
+ return source.slice(start2, end);
5356
5421
  }
5357
5422
  function floatSafeRemainder(val, step) {
5358
5423
  const ratio = val / step;
@@ -14888,19 +14953,19 @@ var MCP_NAME_HEADER_SOURCE = {
14888
14953
  "resources/read": "uri"
14889
14954
  };
14890
14955
  function stripHttpOws(value) {
14891
- let start = 0;
14892
- while (start < value.length) {
14893
- const code = value.codePointAt(start);
14956
+ let start2 = 0;
14957
+ while (start2 < value.length) {
14958
+ const code = value.codePointAt(start2);
14894
14959
  if (code !== 9 && code !== 32) break;
14895
- start += 1;
14960
+ start2 += 1;
14896
14961
  }
14897
14962
  let end = value.length;
14898
- while (end > start) {
14963
+ while (end > start2) {
14899
14964
  const code = value.codePointAt(end - 1);
14900
14965
  if (code !== 9 && code !== 32) break;
14901
14966
  end -= 1;
14902
14967
  }
14903
- return start === 0 && end === value.length ? value : value.slice(start, end);
14968
+ return start2 === 0 && end === value.length ? value : value.slice(start2, end);
14904
14969
  }
14905
14970
  function validateStandardRequestHeaders(request, route) {
14906
14971
  if (route.messageKind !== "request") return;
@@ -23659,11 +23724,11 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports, module) => {
23659
23724
  if (!f) throw new Error(`Unknown format "${name}"`);
23660
23725
  return f;
23661
23726
  };
23662
- function addFormats2(ajv, list, fs, exportName) {
23727
+ function addFormats2(ajv, list2, fs, exportName) {
23663
23728
  var _a3;
23664
23729
  var _b;
23665
23730
  (_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
23666
- for (const f of list) ajv.addFormat(f, fs[f]);
23731
+ for (const f of list2) ajv.addFormat(f, fs[f]);
23667
23732
  }
23668
23733
  module.exports = exports = formatsPlugin;
23669
23734
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -23931,11 +23996,11 @@ data: ${JSON.stringify({
23931
23996
  const readable = new ReadableStream({
23932
23997
  start(streamController) {
23933
23998
  controller = streamController;
23934
- const ack = stampSubscriptionId({
23999
+ const ack2 = stampSubscriptionId({
23935
24000
  method: "notifications/subscriptions/acknowledged",
23936
24001
  params: { notifications: honored }
23937
24002
  }, subscriptionId);
23938
- writeNotification(ack.method, ack.params);
24003
+ writeNotification(ack2.method, ack2.params);
23939
24004
  unsubscribe = bus.subscribe((event) => {
23940
24005
  if (closed || !listenFilterAccepts(honored, event)) return;
23941
24006
  const note = stampSubscriptionId(serverEventToNotification(event), subscriptionId);
@@ -25621,7 +25686,7 @@ var WebStandardStreamableHTTPServerTransport = class {
25621
25686
  /**
25622
25687
  * Helper to create a JSON error response
25623
25688
  */
25624
- createJsonErrorResponse(status, code, message, options) {
25689
+ createJsonErrorResponse(status2, code, message, options) {
25625
25690
  const error2 = {
25626
25691
  code,
25627
25692
  message
@@ -25632,7 +25697,7 @@ var WebStandardStreamableHTTPServerTransport = class {
25632
25697
  error: error2,
25633
25698
  id: null
25634
25699
  }, {
25635
- status,
25700
+ status: status2,
25636
25701
  headers: {
25637
25702
  "Content-Type": "application/json",
25638
25703
  ...options?.headers
@@ -27684,7 +27749,7 @@ async function confirmStillFailing2(repo, prNumber, failed) {
27684
27749
  return /FAIL|ERROR|CANCEL/i.test(state);
27685
27750
  });
27686
27751
  }
27687
- async function appendGithubCiEvent2(repo, prNumber, cursor, events, rollup) {
27752
+ async function appendGithubCiEvent2(repo, prNumber, cursor, events2, rollup) {
27688
27753
  const ciState = rollup.map((check) => check.conclusion ?? check.state ?? "PENDING").sort().join(",");
27689
27754
  if (!ciState || ciState === cursor.ciState) return;
27690
27755
  const previousCiState = cursor.ciState;
@@ -27695,7 +27760,7 @@ async function appendGithubCiEvent2(repo, prNumber, cursor, events, rollup) {
27695
27760
  const failureState = failed.map((check) => `${check.name ?? check.context ?? "check"}:${check.conclusion ?? check.state ?? "FAILURE"}`).sort().join(",");
27696
27761
  if (failed.length > 0 && failureState !== cursor.ciFailureState) {
27697
27762
  const names = failed.map((check) => check.name ?? check.context ?? "check").join(", ");
27698
- events.push({
27763
+ events2.push({
27699
27764
  kind: "ci",
27700
27765
  id: `ci:${failureState}`,
27701
27766
  repo,
@@ -27708,13 +27773,13 @@ async function appendGithubCiEvent2(repo, prNumber, cursor, events, rollup) {
27708
27773
  }
27709
27774
  cursor.ciFailureState = failureState;
27710
27775
  }
27711
- function appendGithubConflictEvent2(repo, prNumber, cursor, events, mergeable) {
27776
+ function appendGithubConflictEvent2(repo, prNumber, cursor, events2, mergeable) {
27712
27777
  if (mergeable === cursor.mergeable) return;
27713
27778
  const previousMergeable = cursor.mergeable;
27714
27779
  cursor.mergeable = mergeable;
27715
27780
  if (previousMergeable === void 0) return;
27716
27781
  if (mergeable !== "CONFLICTING" && mergeable !== "DIRTY") return;
27717
- events.push({
27782
+ events2.push({
27718
27783
  kind: "conflict",
27719
27784
  id: `conflict:${mergeable}`,
27720
27785
  repo,
@@ -27724,7 +27789,7 @@ function appendGithubConflictEvent2(repo, prNumber, cursor, events, mergeable) {
27724
27789
  actionable: true
27725
27790
  });
27726
27791
  }
27727
- function appendGithubReviews2(repo, prNumber, cursor, events, reviews, historyPrimed) {
27792
+ function appendGithubReviews2(repo, prNumber, cursor, events2, reviews, historyPrimed) {
27728
27793
  const ref = `#${prNumber}`;
27729
27794
  for (const review of reviews) {
27730
27795
  const id = "rev:" + String(review.id ?? `${review.author?.login}-${review.submittedAt}`);
@@ -27735,7 +27800,7 @@ function appendGithubReviews2(repo, prNumber, cursor, events, reviews, historyPr
27735
27800
  let actionable = false;
27736
27801
  if (review.state === "CHANGES_REQUESTED") actionable = true;
27737
27802
  if (review.state === "COMMENTED" && review.body?.trim()) actionable = true;
27738
- events.push({
27803
+ events2.push({
27739
27804
  kind: "review",
27740
27805
  id,
27741
27806
  repo,
@@ -27747,7 +27812,7 @@ function appendGithubReviews2(repo, prNumber, cursor, events, reviews, historyPr
27747
27812
  });
27748
27813
  }
27749
27814
  }
27750
- function appendGithubStateEvent2(repo, prNumber, cursor, events, prState) {
27815
+ function appendGithubStateEvent2(repo, prNumber, cursor, events2, prState) {
27751
27816
  const isTerminal = prState === "MERGED" || prState === "CLOSED";
27752
27817
  if (prState === cursor.prState) return isTerminal;
27753
27818
  const previousPrState = cursor.prState;
@@ -27755,7 +27820,7 @@ function appendGithubStateEvent2(repo, prNumber, cursor, events, prState) {
27755
27820
  if (previousPrState !== void 0 && isTerminal) {
27756
27821
  let summary = `PR #${prNumber} was CLOSED without merging`;
27757
27822
  if (prState === "MERGED") summary = `PR #${prNumber} was MERGED`;
27758
- events.push({
27823
+ events2.push({
27759
27824
  kind: "merged",
27760
27825
  id: `state:${prState}`,
27761
27826
  repo,
@@ -27768,14 +27833,14 @@ function appendGithubStateEvent2(repo, prNumber, cursor, events, prState) {
27768
27833
  }
27769
27834
  return isTerminal;
27770
27835
  }
27771
- function appendComments2(repo, prNumber, cursor, events, comments, idPrefix, historyPrimed, summary, body) {
27836
+ function appendComments2(repo, prNumber, cursor, events2, comments, idPrefix, historyPrimed, summary, body) {
27772
27837
  for (const comment of comments) {
27773
27838
  const id = idPrefix + String(comment.id);
27774
27839
  if (cursor.commentIds.includes(id)) continue;
27775
27840
  cursor.commentIds.push(id);
27776
27841
  if (!historyPrimed) continue;
27777
27842
  const text = body(comment);
27778
- events.push({
27843
+ events2.push({
27779
27844
  kind: "comment",
27780
27845
  id,
27781
27846
  repo,
@@ -27805,22 +27870,22 @@ async function pollGithub2(repo, prNumber, cursorRaw) {
27805
27870
  const parsedCursor = /* @__PURE__ */ safeParse2222(GithubCursorSchema2, cursorRaw);
27806
27871
  let cursor = emptyGithubCursor2();
27807
27872
  if (parsedCursor.success) cursor = parsedCursor.output;
27808
- const events = [];
27873
+ const events2 = [];
27809
27874
  const ref = `#${prNumber}`;
27810
27875
  if (!githubToken2()) {
27811
27876
  if (!warnedMissingToken3) {
27812
27877
  warnedMissingToken3 = true;
27813
27878
  console.warn("[sourcefed] GH_TOKEN or GITHUB_TOKEN is not set; GitHub monitors will not poll");
27814
27879
  }
27815
- return { events, cursor, terminal: false };
27880
+ return { events: events2, cursor, terminal: false };
27816
27881
  }
27817
27882
  const data = await fetchPrData2(repo, prNumber);
27818
- if (!data) return { events, cursor, terminal: false };
27883
+ if (!data) return { events: events2, cursor, terminal: false };
27819
27884
  let historyPrimed = cursor.historyPrimed;
27820
27885
  if (!historyPrimed) historyPrimed = Boolean(cursor.reviewIds.length || cursor.commentIds.length || cursor.ciState || cursor.mergeable || cursor.prState);
27821
27886
  const lineCommentsPrimed = cursor.lineCommentsPrimed;
27822
27887
  const conversationCommentsPrimed = cursor.conversationCommentsPrimed;
27823
- appendGithubReviews2(repo, prNumber, cursor, events, data.reviews ?? [], historyPrimed);
27888
+ appendGithubReviews2(repo, prNumber, cursor, events2, data.reviews ?? [], historyPrimed);
27824
27889
  if (!cursor.historyPrimed) cursor.historyPrimed = true;
27825
27890
  const lineComments = await fetchGithubComments2(`repos/${repo}/pulls/${prNumber}/comments`);
27826
27891
  if (lineComments) {
@@ -27828,7 +27893,7 @@ async function pollGithub2(repo, prNumber, cursorRaw) {
27828
27893
  repo,
27829
27894
  prNumber,
27830
27895
  cursor,
27831
- events,
27896
+ events2,
27832
27897
  lineComments,
27833
27898
  "c:",
27834
27899
  lineCommentsPrimed,
@@ -27847,7 +27912,7 @@ async function pollGithub2(repo, prNumber, cursorRaw) {
27847
27912
  repo,
27848
27913
  prNumber,
27849
27914
  cursor,
27850
- events,
27915
+ events2,
27851
27916
  conversationComments,
27852
27917
  "issue:",
27853
27918
  conversationCommentsPrimed,
@@ -27856,12 +27921,12 @@ async function pollGithub2(repo, prNumber, cursorRaw) {
27856
27921
  );
27857
27922
  cursor.conversationCommentsPrimed = true;
27858
27923
  }
27859
- await appendGithubCiEvent2(repo, prNumber, cursor, events, data.statusCheckRollup ?? []);
27924
+ await appendGithubCiEvent2(repo, prNumber, cursor, events2, data.statusCheckRollup ?? []);
27860
27925
  const mergeable = data.mergeable ?? data.mergeStateStatus ?? "UNKNOWN";
27861
- appendGithubConflictEvent2(repo, prNumber, cursor, events, mergeable);
27926
+ appendGithubConflictEvent2(repo, prNumber, cursor, events2, mergeable);
27862
27927
  const prState = data.state ?? "OPEN";
27863
- const terminal = appendGithubStateEvent2(repo, prNumber, cursor, events, prState);
27864
- return { events, cursor, terminal };
27928
+ const terminal = appendGithubStateEvent2(repo, prNumber, cursor, events2, prState);
27929
+ return { events: events2, cursor, terminal };
27865
27930
  }
27866
27931
  var githubPollMonitor2 = new PollMonitor4({
27867
27932
  run: async (source, cursor) => {
@@ -28639,11 +28704,11 @@ function jiraChangeValue2(item, side) {
28639
28704
  function formatJiraChangelogItem2(item) {
28640
28705
  return `${item.field}: ${jiraChangeValue2(item, "from")} \u2192 ${jiraChangeValue2(item, "to")}`;
28641
28706
  }
28642
- function isTerminalStatus2(status) {
28643
- if (typeof status !== "string") return false;
28707
+ function isTerminalStatus2(status2) {
28708
+ if (typeof status2 !== "string") return false;
28644
28709
  const terminalStatus = process.env.SOURCEFED_JIRA_TERMINAL_STATUS;
28645
28710
  if (!terminalStatus) return false;
28646
- return status.trim().toLowerCase() === terminalStatus.trim().toLowerCase();
28711
+ return status2.trim().toLowerCase() === terminalStatus.trim().toLowerCase();
28647
28712
  }
28648
28713
  var TRACKED_CHANGE_FIELDS2 = /* @__PURE__ */ new Set(["summary", "status", "assignee", "priority", "labels"]);
28649
28714
  function meaningfulChangelogItems2(items) {
@@ -28669,12 +28734,12 @@ function jiraChangelogEvent2(issueKey, history) {
28669
28734
  terminal
28670
28735
  };
28671
28736
  }
28672
- function appendJiraChangelog2(issueKey, cursor, events, histories) {
28737
+ function appendJiraChangelog2(issueKey, cursor, events2, histories) {
28673
28738
  const total = histories.length;
28674
28739
  if (cursor.changelogCount !== 0 && total > cursor.changelogCount) {
28675
28740
  for (const history of histories.slice(cursor.changelogCount)) {
28676
28741
  const event = jiraChangelogEvent2(issueKey, history);
28677
- if (event) events.push(event);
28742
+ if (event) events2.push(event);
28678
28743
  }
28679
28744
  }
28680
28745
  cursor.changelogCount = total;
@@ -28685,7 +28750,7 @@ function adfText2(node) {
28685
28750
  if (Array.isArray(node.content)) return node.content.map(adfText2).join("");
28686
28751
  return "";
28687
28752
  }
28688
- function appendJiraComments2(issueKey, cursor, events, comments) {
28753
+ function appendJiraComments2(issueKey, cursor, events2, comments) {
28689
28754
  const freshComments = [];
28690
28755
  for (const comment of comments) {
28691
28756
  const id = Number(comment.id);
@@ -28698,7 +28763,7 @@ function appendJiraComments2(issueKey, cursor, events, comments) {
28698
28763
  }
28699
28764
  for (const comment of freshComments) {
28700
28765
  const body = adfText2(comment.body);
28701
- events.push({
28766
+ events2.push({
28702
28767
  kind: "comment",
28703
28768
  id: `comment:${comment.id}`,
28704
28769
  issueKey,
@@ -28711,11 +28776,11 @@ function appendJiraComments2(issueKey, cursor, events, comments) {
28711
28776
  cursor.commentIds.push(Number(comment.id));
28712
28777
  }
28713
28778
  }
28714
- function appendJiraDescription2(issueKey, cursor, events, description) {
28779
+ function appendJiraDescription2(issueKey, cursor, events2, description) {
28715
28780
  let descriptionVersion = "none";
28716
28781
  if (description) descriptionVersion = JSON.stringify(description).slice(0, 40);
28717
28782
  if (cursor.descriptionVersion !== void 0 && descriptionVersion !== cursor.descriptionVersion) {
28718
- events.push({
28783
+ events2.push({
28719
28784
  kind: "description",
28720
28785
  id: `description:${descriptionVersion}`,
28721
28786
  issueKey,
@@ -28749,15 +28814,15 @@ async function pollJira2(issueKey, cursorRaw) {
28749
28814
  const parsedCursor = /* @__PURE__ */ safeParse232(JiraCursorSchema2, cursorRaw);
28750
28815
  let cursor = emptyJiraCursor2();
28751
28816
  if (parsedCursor.success) cursor = parsedCursor.output;
28752
- const events = [];
28817
+ const events2 = [];
28753
28818
  const comments = await jiraFetch2(`/issue/${issueKey}/comment?maxResults=50&orderBy=-created`);
28754
- appendJiraComments2(issueKey, cursor, events, comments.comments ?? []);
28819
+ appendJiraComments2(issueKey, cursor, events2, comments.comments ?? []);
28755
28820
  const issue2 = await jiraFetch2(`/issue/${issueKey}?expand=changelog&fields=status,description`);
28756
28821
  const statusName = issue2.fields?.status?.name;
28757
- appendJiraDescription2(issueKey, cursor, events, issue2.fields?.description);
28822
+ appendJiraDescription2(issueKey, cursor, events2, issue2.fields?.description);
28758
28823
  const changelog = issue2.changelog?.histories ?? [];
28759
- appendJiraChangelog2(issueKey, cursor, events, changelog);
28760
- return { events, cursor, terminal: isTerminalStatus2(statusName) };
28824
+ appendJiraChangelog2(issueKey, cursor, events2, changelog);
28825
+ return { events: events2, cursor, terminal: isTerminalStatus2(statusName) };
28761
28826
  }
28762
28827
  var jiraPollMonitor2 = new PollMonitor22({
28763
28828
  run: async (source, cursor) => {
@@ -29671,9 +29736,9 @@ function parseSlackReadResult2(result, cursorRaw) {
29671
29736
  if (!cursor.latestTs) return true;
29672
29737
  return messageTimestamp2(message.ts) > messageTimestamp2(cursor.latestTs);
29673
29738
  });
29674
- let events = [];
29739
+ let events2 = [];
29675
29740
  if (cursor.primed) {
29676
- events = freshMessages.map((message) => {
29741
+ events2 = freshMessages.map((message) => {
29677
29742
  let text = "[message without text]";
29678
29743
  if (message.text?.trim()) text = message.text.trim();
29679
29744
  let author = users.get(message.user);
@@ -29696,7 +29761,7 @@ function parseSlackReadResult2(result, cursorRaw) {
29696
29761
  if (messageTimestamp2(message.ts) > messageTimestamp2(latestTs)) latestTs = message.ts;
29697
29762
  }
29698
29763
  cursor.latestTs = latestTs;
29699
- return { events, cursor };
29764
+ return { events: events2, cursor };
29700
29765
  }
29701
29766
  var SLACK_API2 = "https://slack.com/api";
29702
29767
  var warnedMissingToken22 = false;
@@ -30038,9 +30103,9 @@ function buildServer(daemon) {
30038
30103
  async (uri, variables) => {
30039
30104
  const target = decodeTarget2(String(variables.targetId));
30040
30105
  if (!target) throw new Error("invalid Sourcefed target resource");
30041
- const events = await daemon.readEvents(target);
30106
+ const events2 = await daemon.readEvents(target);
30042
30107
  return {
30043
- contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify({ events }) }]
30108
+ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify({ events: events2 }) }]
30044
30109
  };
30045
30110
  }
30046
30111
  );
@@ -30053,6 +30118,275 @@ function errorResult(error2) {
30053
30118
  return { content: [{ type: "text", text: error2 }], isError: true };
30054
30119
  }
30055
30120
 
30121
+ // packages/cli/src/commands/mcp.ts
30122
+ async function runMcp(args) {
30123
+ const mode = args[0] ?? "--stdio";
30124
+ if (mode === "--stdio") {
30125
+ const daemon2 = createDaemon();
30126
+ const mcp2 = createSourcefedStdio(daemon2);
30127
+ await daemon2.start();
30128
+ const webhookServer = await startWebhookServer(mcp2.runtime);
30129
+ await serveStdio(mcp2.factory);
30130
+ await webhookServer?.stop();
30131
+ await mcp2.close();
30132
+ return;
30133
+ }
30134
+ if (mode !== "--http") {
30135
+ throw new Error("mcp expects --stdio or --http");
30136
+ }
30137
+ const port = Number(flag(args, "port") ?? process.env.SOURCEFED_MCP_PORT ?? 18788);
30138
+ const hostname = flag(args, "host") ?? process.env.SOURCEFED_MCP_HOST ?? "127.0.0.1";
30139
+ const token = process.env.SOURCEFED_DAEMON_TOKEN;
30140
+ if (!isLoopback(hostname) && !token) {
30141
+ throw new Error("binding the MCP server to a non-loopback address requires SOURCEFED_DAEMON_TOKEN");
30142
+ }
30143
+ const daemon = createDaemon();
30144
+ const mcp = createSourcefedMcp(daemon);
30145
+ await daemon.start();
30146
+ let server;
30147
+ try {
30148
+ server = await serveHttp({
30149
+ hostname,
30150
+ port,
30151
+ handler: (request) => {
30152
+ const url2 = new URL(request.url);
30153
+ if (url2.pathname === "/mcp" && token && request.headers.get("authorization") !== `Bearer ${token}`) {
30154
+ return new Response("unauthorized", { status: 401 });
30155
+ }
30156
+ return url2.pathname === "/mcp" ? mcp.handler.fetch(request) : daemon.webhook(request);
30157
+ }
30158
+ });
30159
+ } catch (error2) {
30160
+ await mcp.close();
30161
+ throw error2;
30162
+ }
30163
+ console.error(`[sourcefed] MCP server on http://${hostname}:${server.port}/mcp`);
30164
+ await new Promise(() => {
30165
+ });
30166
+ }
30167
+
30168
+ // packages/cli/src/commands/monitor/index.ts
30169
+ import os7 from "node:os";
30170
+
30171
+ // packages/cli/src/utils/format.ts
30172
+ function printMonitors(monitors) {
30173
+ if (monitors.length === 0) {
30174
+ console.log(dim("no monitors for this target"));
30175
+ return;
30176
+ }
30177
+ for (const monitor of monitors) {
30178
+ const status2 = monitor.enabled ? green("enabled") : red("stopped");
30179
+ const delivery = dim(monitor.delivery);
30180
+ console.log(`${monitor.icon} ${monitor.describe} ${status2} ${delivery}`);
30181
+ }
30182
+ }
30183
+ function printLogs(logs2) {
30184
+ if (logs2.length === 0) {
30185
+ console.log(dim("no notifications sent yet"));
30186
+ return;
30187
+ }
30188
+ for (const entry of logs2) {
30189
+ const time3 = dim(new Date(entry.at).toLocaleString());
30190
+ const marker = entry.actionable ? yellow("\u25B6") : dim("\xB7");
30191
+ console.log(`${marker} ${entry.icon} ${time3} ${entry.describe}`);
30192
+ console.log(` ${entry.summary}`);
30193
+ if (entry.body) console.log(dim(` ${entry.body}`));
30194
+ }
30195
+ }
30196
+ function printResult(result) {
30197
+ console.log(JSON.stringify(result, null, 2));
30198
+ }
30199
+ function green(text) {
30200
+ return `\x1B[32m${text}\x1B[0m`;
30201
+ }
30202
+ function red(text) {
30203
+ return `\x1B[31m${text}\x1B[0m`;
30204
+ }
30205
+ function yellow(text) {
30206
+ return `\x1B[33m${text}\x1B[0m`;
30207
+ }
30208
+ function dim(text) {
30209
+ return `\x1B[2m${text}\x1B[0m`;
30210
+ }
30211
+
30212
+ // packages/cli/src/commands/monitor/ack.ts
30213
+ async function ack(context) {
30214
+ const eventIDs = (flag(context.args, "event-id") ?? "").split(",").filter(Boolean);
30215
+ if (eventIDs.length === 0) throw new Error("ack requires --event-id <id>[,<id>...]");
30216
+ printResult(await context.client.request("monitor.ack", { target: context.target, eventIDs }));
30217
+ }
30218
+
30219
+ // packages/cli/src/commands/monitor/create.ts
30220
+ async function create(context) {
30221
+ const sourceType = flag(context.args, "source-type");
30222
+ const sourceTypes = await context.client.request("daemon.sourceTypes");
30223
+ if (!sourceType || !sourceTypes.sourceTypes?.includes(sourceType)) {
30224
+ throw new Error(`create requires --source-type ${sourceTypes.sourceTypes?.join("|") ?? "unknown"}`);
30225
+ }
30226
+ const input = {
30227
+ name: flag(context.args, "name") ?? sourceType,
30228
+ sourceType,
30229
+ target: context.target
30230
+ };
30231
+ const keyMap = {
30232
+ "issue-key": "issueKey",
30233
+ repo: "repo",
30234
+ "channel-id": "channelId",
30235
+ "thread-ts": "threadTs",
30236
+ "thread-url": "threadUrl"
30237
+ };
30238
+ for (const key of Object.keys(keyMap)) {
30239
+ const value = flag(context.args, key);
30240
+ if (value) input[keyMap[key]] = value;
30241
+ }
30242
+ const prNumber = flag(context.args, "pr-number");
30243
+ if (prNumber) input.prNumber = Number(prNumber);
30244
+ const pollIntervalSec = flag(context.args, "poll-interval-sec");
30245
+ if (pollIntervalSec) input.pollIntervalSec = Number(pollIntervalSec);
30246
+ printResult(await context.client.request("monitor.create", input));
30247
+ }
30248
+
30249
+ // packages/cli/src/commands/monitor/events.ts
30250
+ async function events(context) {
30251
+ printResult(await context.client.request("monitor.events", { target: context.target }));
30252
+ }
30253
+
30254
+ // packages/cli/src/commands/monitor/follow.ts
30255
+ async function follow(context) {
30256
+ console.error(`[sourcefed] following events for ${context.target.kind}:${context.target.id}; Ctrl+C to stop`);
30257
+ const listener = await context.client.subscribe(context.target, async (events2) => {
30258
+ for (const entry of events2) {
30259
+ const time3 = dim(new Date(entry.event.at).toLocaleString());
30260
+ const marker = entry.event.actionable ? yellow("\u25B6") : dim("\xB7");
30261
+ console.log(`${marker} ${time3} ${entry.event.summary}`);
30262
+ if (entry.event.body) console.log(dim(` ${entry.event.body}`));
30263
+ }
30264
+ });
30265
+ await new Promise(() => {
30266
+ });
30267
+ await listener.close();
30268
+ }
30269
+
30270
+ // packages/cli/src/commands/monitor/list.ts
30271
+ async function list(context) {
30272
+ const result = await context.client.request("monitor.list", { target: context.target });
30273
+ printMonitors(result?.monitors ?? []);
30274
+ }
30275
+
30276
+ // packages/cli/src/commands/monitor/logs.ts
30277
+ async function logs(context) {
30278
+ const result = await context.client.request("monitor.logs", { target: context.target });
30279
+ printLogs(result?.logs ?? []);
30280
+ }
30281
+
30282
+ // packages/cli/src/commands/monitor/remove.ts
30283
+ async function remove(context) {
30284
+ if (!context.args.includes("--yes")) throw new Error("remove is destructive: pass --yes to confirm");
30285
+ const id = flag(context.args, "id");
30286
+ if (!id) throw new Error("remove requires --id");
30287
+ printResult(await context.client.request("monitor.remove", { target: context.target, id }));
30288
+ }
30289
+
30290
+ // packages/cli/src/commands/monitor/sources.ts
30291
+ async function sources(context) {
30292
+ printResult(await context.client.request("daemon.sourceTypes"));
30293
+ }
30294
+
30295
+ // packages/cli/src/commands/monitor/start.ts
30296
+ async function start(context) {
30297
+ const id = flag(context.args, "id");
30298
+ if (!id) throw new Error("start requires --id");
30299
+ printResult(await context.client.request("monitor.start", { target: context.target, id }));
30300
+ }
30301
+
30302
+ // packages/cli/src/commands/monitor/status.ts
30303
+ async function status(context) {
30304
+ const id = flag(context.args, "id");
30305
+ if (!id) throw new Error("status requires --id");
30306
+ printResult(await context.client.request("monitor.status", { target: context.target, id }));
30307
+ }
30308
+
30309
+ // packages/cli/src/commands/monitor/stop.ts
30310
+ async function stop(context) {
30311
+ const id = flag(context.args, "id");
30312
+ if (!id) throw new Error("stop requires --id");
30313
+ printResult(await context.client.request("monitor.stop", { target: context.target, id }));
30314
+ }
30315
+
30316
+ // packages/cli/src/commands/monitor/index.ts
30317
+ var SUBCOMMANDS = {
30318
+ list,
30319
+ logs,
30320
+ sources,
30321
+ status,
30322
+ stop,
30323
+ start,
30324
+ remove,
30325
+ events,
30326
+ follow,
30327
+ ack,
30328
+ create
30329
+ };
30330
+ var SUBCOMMAND_NAMES = Object.keys(SUBCOMMANDS).join(", ");
30331
+ async function runMonitor(argv) {
30332
+ const [subcommand, ...args] = argv;
30333
+ const target = {
30334
+ kind: flag(args, "target-kind") ?? "cli",
30335
+ id: flag(args, "target-id") ?? process.env.SOURCEFED_TARGET_ID ?? os7.hostname()
30336
+ };
30337
+ const client = await connectDaemonClient({
30338
+ name: "sourcefed-cli",
30339
+ url: process.env.SOURCEFED_DAEMON_URL ?? defaultDaemonUrl()
30340
+ });
30341
+ const run = subcommand ? SUBCOMMANDS[subcommand] : void 0;
30342
+ if (!run) {
30343
+ await client.close();
30344
+ throw new Error(`monitor expects ${SUBCOMMAND_NAMES}`);
30345
+ }
30346
+ try {
30347
+ await run({ client, target, args });
30348
+ } finally {
30349
+ await client.close();
30350
+ }
30351
+ }
30352
+
30353
+ // packages/cli/src/utils/setup.ts
30354
+ import { execFile } from "node:child_process";
30355
+ import { readFileSync } from "node:fs";
30356
+ import { fileURLToPath } from "node:url";
30357
+ function setupSpec() {
30358
+ const manifest = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
30359
+ if (manifest.name === "@fdcn/sourcefed" && manifest.version) return `@fdcn/sourcefed@${manifest.version}`;
30360
+ return "@fdcn/sourcefed@latest";
30361
+ }
30362
+ async function setupHost(name, args, spec) {
30363
+ console.error(`[sourcefed] installing ${spec} into ${name}`);
30364
+ try {
30365
+ await execFile(name, args);
30366
+ console.error(`[sourcefed] ${name} configured \u2014 restart ${name} to load the plugin`);
30367
+ } catch (error2) {
30368
+ const code = error2 && typeof error2 === "object" && "code" in error2 ? String(error2.code) : "";
30369
+ if (code === "ENOENT") {
30370
+ console.error(`[sourcefed] ${name} is not on PATH \u2014 install it manually:`);
30371
+ console.error(name === "opencode" ? ` add "${spec}" to the plugin array in ~/.config/opencode/opencode.jsonc and tui.json` : ` run: pi install ${spec}`);
30372
+ return;
30373
+ }
30374
+ throw error2;
30375
+ }
30376
+ }
30377
+
30378
+ // packages/cli/src/commands/setup.ts
30379
+ async function runSetup(args) {
30380
+ const spec = setupSpec();
30381
+ const requested = args[0] ?? "all";
30382
+ if (requested === "opencode" || requested === "all") {
30383
+ await setupHost("opencode", ["plugin", spec, "--global"], spec);
30384
+ }
30385
+ if (requested === "pi" || requested === "all") {
30386
+ await setupHost("pi", ["install", `npm:${spec}`], `npm:${spec}`);
30387
+ }
30388
+ }
30389
+
30056
30390
  // packages/cli/src/skills/command.ts
30057
30391
  import { readFile as readFile7, readdir as readdir2, stat as stat2 } from "node:fs/promises";
30058
30392
  import path9 from "node:path";
@@ -30061,7 +30395,7 @@ import path9 from "node:path";
30061
30395
  import { statSync } from "node:fs";
30062
30396
  import { readdir, readFile as readFile6, stat } from "node:fs/promises";
30063
30397
  import path8 from "node:path";
30064
- import { fileURLToPath } from "node:url";
30398
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
30065
30399
 
30066
30400
  // packages/cli/src/skills/frontmatter.ts
30067
30401
  function parseFrontmatter(content) {
@@ -30141,7 +30475,7 @@ async function discoverSkills() {
30141
30475
  return skills;
30142
30476
  }
30143
30477
  function skillRoot() {
30144
- let dir = path8.dirname(fileURLToPath(import.meta.url));
30478
+ let dir = path8.dirname(fileURLToPath2(import.meta.url));
30145
30479
  for (let depth = 0; depth < 6; depth++) {
30146
30480
  const marker = path8.join(dir, "data");
30147
30481
  if (statSync(marker, { throwIfNoEntry: false })?.isDirectory()) return dir;
@@ -30149,7 +30483,7 @@ function skillRoot() {
30149
30483
  if (parent === dir) break;
30150
30484
  dir = parent;
30151
30485
  }
30152
- return path8.dirname(fileURLToPath(import.meta.url));
30486
+ return path8.dirname(fileURLToPath2(import.meta.url));
30153
30487
  }
30154
30488
 
30155
30489
  // packages/cli/src/skills/command.ts
@@ -30245,252 +30579,31 @@ ${content}
30245
30579
  }
30246
30580
 
30247
30581
  // packages/cli/src/index.ts
30582
+ var COMMANDS = {
30583
+ daemon: runDaemon,
30584
+ mcp: runMcp,
30585
+ monitor: runMonitor,
30586
+ skills: runSkillsCommand,
30587
+ setup: runSetup
30588
+ };
30248
30589
  async function main(argv = process.argv.slice(2)) {
30249
- const [command, subcommand, ...rest] = argv;
30250
- if (command === "daemon") {
30251
- await runDaemon([subcommand, ...rest].filter((value) => value !== void 0));
30252
- return;
30253
- }
30254
- if (command === "mcp") {
30255
- await runMcp([subcommand, ...rest].filter((value) => value !== void 0));
30256
- return;
30257
- }
30258
- if (command === "monitor") {
30259
- await runMonitor(subcommand, rest);
30260
- return;
30261
- }
30262
- if (command === "skills") {
30263
- await runSkillsCommand([subcommand, ...rest].filter((value) => value !== void 0));
30264
- return;
30265
- }
30266
- printUsage();
30267
- process.exitCode = 1;
30268
- }
30269
- function createDaemon() {
30270
- return new SourcefedDaemon({
30271
- stateDir: process.env.SOURCEFED_STATE_DIR
30272
- });
30273
- }
30274
- async function runDaemon(args) {
30275
- const port = Number(flag(args, "port") ?? process.env.SOURCEFED_DAEMON_PORT ?? 18787);
30276
- const hostname = flag(args, "host") ?? process.env.SOURCEFED_DAEMON_HOST ?? "127.0.0.1";
30277
- const token = process.env.SOURCEFED_DAEMON_TOKEN;
30278
- if (!isLoopback(hostname) && !token) {
30279
- throw new Error("binding the daemon to a non-loopback address requires SOURCEFED_DAEMON_TOKEN");
30280
- }
30281
- const daemon = createDaemon();
30282
- await daemon.start();
30283
- let server;
30284
- try {
30285
- server = await serveHttp({
30286
- hostname,
30287
- port,
30288
- handler: (request) => handleDaemonHttpRequest(daemon, request, { token })
30289
- });
30290
- const webhookServer = await startWebhookServer(daemon.runtime);
30291
- console.error(webhookServer ? `[sourcefed] webhook listener on http://${process.env.SOURCEFED_WEBHOOK_HOST ?? "127.0.0.1"}:${webhookServer.port}` : "[sourcefed] no separate webhook listener (webhooks served on daemon port)");
30292
- } catch (error2) {
30293
- await daemon.stop();
30294
- throw error2;
30295
- }
30296
- console.error(`[sourcefed] daemon on http://${hostname}:${server.port} (rpc, events, webhooks)`);
30297
- await new Promise(() => {
30298
- });
30299
- }
30300
- function isLoopback(hostname) {
30301
- return hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost";
30302
- }
30303
- async function runMcp(args) {
30304
- const mode = args[0] ?? "--stdio";
30305
- if (mode === "--stdio") {
30306
- const daemon2 = createDaemon();
30307
- const mcp2 = createSourcefedStdio(daemon2);
30308
- await daemon2.start();
30309
- const webhookServer = await startWebhookServer(mcp2.runtime);
30310
- await serveStdio(mcp2.factory);
30311
- await webhookServer?.stop();
30312
- await mcp2.close();
30313
- return;
30314
- }
30315
- if (mode !== "--http") {
30316
- throw new Error("mcp expects --stdio or --http");
30317
- }
30318
- const port = Number(flag(args, "port") ?? process.env.SOURCEFED_MCP_PORT ?? 18788);
30319
- const hostname = flag(args, "host") ?? process.env.SOURCEFED_MCP_HOST ?? "127.0.0.1";
30320
- const token = process.env.SOURCEFED_DAEMON_TOKEN;
30321
- if (!isLoopback(hostname) && !token) {
30322
- throw new Error("binding the MCP server to a non-loopback address requires SOURCEFED_DAEMON_TOKEN");
30323
- }
30324
- const daemon = createDaemon();
30325
- const mcp = createSourcefedMcp(daemon);
30326
- await daemon.start();
30327
- let server;
30328
- try {
30329
- server = await serveHttp({
30330
- hostname,
30331
- port,
30332
- handler: (request) => {
30333
- const url2 = new URL(request.url);
30334
- if (url2.pathname === "/mcp" && token && request.headers.get("authorization") !== `Bearer ${token}`) {
30335
- return new Response("unauthorized", { status: 401 });
30336
- }
30337
- return url2.pathname === "/mcp" ? mcp.handler.fetch(request) : daemon.webhook(request);
30338
- }
30339
- });
30340
- } catch (error2) {
30341
- await mcp.close();
30342
- throw error2;
30343
- }
30344
- console.error(`[sourcefed] MCP server on http://${hostname}:${server.port}/mcp`);
30345
- await new Promise(() => {
30346
- });
30347
- }
30348
- async function startWebhookServer(runtime) {
30349
- const enabled = Boolean(
30350
- process.env.SOURCEFED_ENABLE_WEBHOOKS === "1" || process.env.SOURCEFED_WEBHOOK_PORT || process.env.SOURCEFED_GITHUB_WEBHOOK_SECRET || process.env.SOURCEFED_SLACK_SIGNING_SECRET
30351
- );
30352
- if (!enabled) return void 0;
30353
- try {
30354
- return await serveHttp({
30355
- hostname: process.env.SOURCEFED_WEBHOOK_HOST ?? "127.0.0.1",
30356
- port: Number(process.env.SOURCEFED_WEBHOOK_PORT ?? 8788),
30357
- handler: (request) => runtime.webhook(request)
30358
- });
30359
- } catch (error2) {
30360
- console.error(`[sourcefed] webhook listener unavailable; continuing with MCP and polling: ${error2 instanceof Error ? error2.message : String(error2)}`);
30361
- return void 0;
30362
- }
30363
- }
30364
- async function runMonitor(subcommand, args) {
30365
- const target = {
30366
- kind: flag(args, "target-kind") ?? "cli",
30367
- id: flag(args, "target-id") ?? process.env.SOURCEFED_TARGET_ID ?? os7.hostname()
30368
- };
30369
- const clientOptions = {
30370
- name: "sourcefed-cli",
30371
- url: process.env.SOURCEFED_DAEMON_URL ?? defaultDaemonUrl()
30372
- };
30373
- const client = await connectDaemonClient(clientOptions);
30374
- try {
30375
- if (subcommand === "list") {
30376
- const result = await client.request("monitor.list", { target });
30377
- printMonitors(result?.monitors ?? []);
30378
- return;
30379
- }
30380
- if (subcommand === "logs") {
30381
- const result = await client.request("monitor.logs", { target });
30382
- printLogs(result?.logs ?? []);
30383
- return;
30384
- }
30385
- if (subcommand === "sources") {
30386
- printResult(await client.request("daemon.sourceTypes"));
30387
- return;
30388
- }
30389
- const id = flag(args, "id");
30390
- if ((subcommand === "status" || subcommand === "stop" || subcommand === "start" || subcommand === "remove") && !id) throw new Error(`${subcommand} requires --id`);
30391
- if (subcommand === "status" || subcommand === "stop" || subcommand === "start") {
30392
- printResult(await client.request(`monitor.${subcommand}`, { target, id }));
30393
- return;
30394
- }
30395
- if (subcommand === "remove") {
30396
- if (!args.includes("--yes")) throw new Error("remove is destructive: pass --yes to confirm");
30397
- printResult(await client.request("monitor.remove", { target, id }));
30398
- return;
30399
- }
30400
- if (subcommand === "events") {
30401
- printResult(await client.request("monitor.events", { target }));
30402
- return;
30403
- }
30404
- if (subcommand === "ack") {
30405
- const eventIDs = (flag(args, "event-id") ?? "").split(",").filter(Boolean);
30406
- if (eventIDs.length === 0) throw new Error("ack requires --event-id <id>[,<id>...]");
30407
- printResult(await client.request("monitor.ack", { target, eventIDs }));
30408
- return;
30409
- }
30410
- if (subcommand === "create") {
30411
- const sourceType = flag(args, "source-type");
30412
- const sourceTypes = await client.request("daemon.sourceTypes");
30413
- if (!sourceType || !sourceTypes.sourceTypes?.includes(sourceType)) {
30414
- throw new Error(`create requires --source-type ${sourceTypes.sourceTypes?.join("|") ?? "unknown"}`);
30415
- }
30416
- const input = {
30417
- name: flag(args, "name") ?? sourceType,
30418
- sourceType,
30419
- target
30420
- };
30421
- const keyMap = {
30422
- "issue-key": "issueKey",
30423
- repo: "repo",
30424
- "channel-id": "channelId",
30425
- "thread-ts": "threadTs",
30426
- "thread-url": "threadUrl"
30427
- };
30428
- for (const key of Object.keys(keyMap)) {
30429
- const value = flag(args, key);
30430
- if (value) input[keyMap[key]] = value;
30431
- }
30432
- const prNumber = flag(args, "pr-number");
30433
- if (prNumber) input.prNumber = Number(prNumber);
30434
- const pollIntervalSec = flag(args, "poll-interval-sec");
30435
- if (pollIntervalSec) input.pollIntervalSec = Number(pollIntervalSec);
30436
- printResult(await client.request("monitor.create", input));
30437
- return;
30438
- }
30439
- } finally {
30440
- await client.close();
30441
- }
30442
- throw new Error("monitor expects create, list, status, stop, start, remove, events, ack, logs, or sources");
30443
- }
30444
- function flag(args, name) {
30445
- const index = args.indexOf(`--${name}`);
30446
- return index >= 0 ? args[index + 1] : void 0;
30447
- }
30448
- function printMonitors(monitors) {
30449
- if (monitors.length === 0) {
30450
- console.log(dim("no monitors for this target"));
30451
- return;
30452
- }
30453
- for (const monitor of monitors) {
30454
- const status = monitor.enabled ? green("enabled") : red("stopped");
30455
- const delivery = dim(monitor.delivery);
30456
- console.log(`${monitor.icon} ${monitor.describe} ${status} ${delivery}`);
30457
- }
30458
- }
30459
- function printLogs(logs) {
30460
- if (logs.length === 0) {
30461
- console.log(dim("no notifications sent yet"));
30590
+ const [command, ...args] = argv;
30591
+ const run = command ? COMMANDS[command] : void 0;
30592
+ if (!run) {
30593
+ printUsage();
30594
+ process.exitCode = 1;
30462
30595
  return;
30463
30596
  }
30464
- for (const entry of logs) {
30465
- const time3 = dim(new Date(entry.at).toLocaleString());
30466
- const marker = entry.actionable ? yellow("\u25B6") : dim("\xB7");
30467
- console.log(`${marker} ${entry.icon} ${time3} ${entry.describe}`);
30468
- console.log(` ${entry.summary}`);
30469
- if (entry.body) console.log(dim(` ${entry.body}`));
30470
- }
30471
- }
30472
- function green(text) {
30473
- return `\x1B[32m${text}\x1B[0m`;
30474
- }
30475
- function red(text) {
30476
- return `\x1B[31m${text}\x1B[0m`;
30477
- }
30478
- function yellow(text) {
30479
- return `\x1B[33m${text}\x1B[0m`;
30480
- }
30481
- function dim(text) {
30482
- return `\x1B[2m${text}\x1B[0m`;
30483
- }
30484
- function printResult(result) {
30485
- console.log(JSON.stringify(result, null, 2));
30597
+ await run(args);
30486
30598
  }
30487
30599
  function printUsage() {
30488
30600
  console.error("sourcefed daemon [--port PORT] [--host HOST]");
30489
30601
  console.error("sourcefed mcp --stdio|--http [--port PORT]");
30490
- console.error("sourcefed monitor create|list|status|stop|start|remove|events|ack|logs|sources [options]");
30602
+ console.error("sourcefed monitor create|list|status|stop|start|remove|events|follow|ack|logs|sources [options]");
30491
30603
  console.error("sourcefed skills [list|get <name>|path [name]]");
30604
+ console.error("sourcefed setup [opencode|pi]");
30492
30605
  }
30493
- if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath2(import.meta.url)) {
30606
+ if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath3(import.meta.url)) {
30494
30607
  main().catch((error2) => {
30495
30608
  console.error(error2 instanceof Error ? error2.message : String(error2));
30496
30609
  process.exitCode = 1;