@fdcn/sourcefed 0.2.1 → 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.
- package/README.md +2 -2
- package/dist/chunk-7KHZCWGQ.js +5174 -0
- package/dist/chunk-EVSHVYPF.js +5167 -0
- package/dist/chunk-UOTCX4ME.js +3963 -0
- package/dist/cli.js +494 -362
- package/dist/core.js +5 -0
- package/dist/daemon.js +20 -1
- package/dist/data/core/SKILL.md +10 -0
- package/dist/data/core/references/commands.md +1 -0
- package/dist/mcp.js +1 -1
- package/dist/opencode.js +1 -1
- package/dist/pi.js +22 -3
- package/dist/tui-compiled.mjs +146 -101
- package/dist.dts/packages/cli/src/commands/daemon.d.ts +1 -0
- package/dist.dts/packages/cli/src/commands/mcp.d.ts +1 -0
- package/dist.dts/packages/cli/src/commands/monitor/ack.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/create.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/events.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/follow.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/index.d.ts +10 -0
- package/dist.dts/packages/cli/src/commands/monitor/list.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/logs.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/remove.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/sources.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/start.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/status.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor/stop.d.ts +2 -0
- package/dist.dts/packages/cli/src/commands/monitor.d.ts +1 -0
- package/dist.dts/packages/cli/src/commands/setup.d.ts +1 -0
- package/dist.dts/packages/cli/src/follow.test.d.ts +1 -0
- package/dist.dts/packages/cli/src/utils/daemon.d.ts +2 -0
- package/dist.dts/packages/cli/src/utils/flags.d.ts +1 -0
- package/dist.dts/packages/cli/src/utils/format.d.ts +8 -0
- package/dist.dts/packages/cli/src/utils/net.d.ts +1 -0
- package/dist.dts/packages/cli/src/utils/setup.d.ts +2 -0
- package/dist.dts/packages/cli/src/utils/webhooks.d.ts +2 -0
- package/dist.dts/packages/daemon/src/types/daemon.d.ts +2 -0
- 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
|
|
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
|
|
120
|
-
if (
|
|
121
|
-
return
|
|
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__
|
|
@@ -566,6 +564,11 @@ var MonitorService = class {
|
|
|
566
564
|
return this.transact((registry2) => {
|
|
567
565
|
const monitor = registry2.monitors.find((entry) => entry.id === id);
|
|
568
566
|
if (!monitor) return { error: `monitor ${id} was not found` };
|
|
567
|
+
const identity = monitorIdentity(monitor.target, monitor.source);
|
|
568
|
+
const duplicate = registry2.monitors.find(
|
|
569
|
+
(entry) => entry.id !== id && entry.enabled && monitorIdentity(entry.target, entry.source) === identity
|
|
570
|
+
);
|
|
571
|
+
if (duplicate) return { error: `monitor ${id} duplicates active monitor ${duplicate.id} for the same source` };
|
|
569
572
|
monitor.enabled = true;
|
|
570
573
|
monitor.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
571
574
|
return monitor;
|
|
@@ -912,31 +915,31 @@ var JsonMonitorEventQueue = class {
|
|
|
912
915
|
eventsFile;
|
|
913
916
|
queue = Promise.resolve();
|
|
914
917
|
async enqueue(input) {
|
|
915
|
-
return this.transact((
|
|
918
|
+
return this.transact((events2) => {
|
|
916
919
|
const queued = createQueuedMonitorEvent(input.monitor, input.event);
|
|
917
|
-
const existing =
|
|
920
|
+
const existing = events2.find((event) => event.id === queued.id);
|
|
918
921
|
if (existing) return existing;
|
|
919
|
-
|
|
922
|
+
events2.push(queued);
|
|
920
923
|
return queued;
|
|
921
924
|
});
|
|
922
925
|
}
|
|
923
926
|
async read(target) {
|
|
924
|
-
const
|
|
925
|
-
return
|
|
927
|
+
const events2 = await this.loadEvents();
|
|
928
|
+
return events2.filter((event) => sameTarget2(event.target, target));
|
|
926
929
|
}
|
|
927
930
|
async acknowledge(target, eventIDs) {
|
|
928
|
-
await this.transact((
|
|
931
|
+
await this.transact((events2) => {
|
|
929
932
|
const ids = new Set(eventIDs);
|
|
930
|
-
for (let index =
|
|
931
|
-
if (sameTarget2(
|
|
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);
|
|
932
935
|
}
|
|
933
936
|
});
|
|
934
937
|
}
|
|
935
938
|
async transact(operation) {
|
|
936
939
|
const next = this.queue.then(async () => {
|
|
937
|
-
const
|
|
938
|
-
const result = await operation(
|
|
939
|
-
await this.saveEvents(
|
|
940
|
+
const events2 = await this.loadEvents();
|
|
941
|
+
const result = await operation(events2);
|
|
942
|
+
await this.saveEvents(events2);
|
|
940
943
|
return result;
|
|
941
944
|
});
|
|
942
945
|
this.queue = next.then(() => void 0, () => void 0);
|
|
@@ -953,10 +956,10 @@ var JsonMonitorEventQueue = class {
|
|
|
953
956
|
throw error2;
|
|
954
957
|
}
|
|
955
958
|
}
|
|
956
|
-
async saveEvents(
|
|
959
|
+
async saveEvents(events2) {
|
|
957
960
|
await mkdir(this.stateDir, { recursive: true, mode: 448 });
|
|
958
961
|
const temporary = `${this.eventsFile}.${process.pid}.tmp`;
|
|
959
|
-
await writeFile(temporary, `${JSON.stringify(
|
|
962
|
+
await writeFile(temporary, `${JSON.stringify(events2, null, 2)}
|
|
960
963
|
`, { mode: 384 });
|
|
961
964
|
await rename(temporary, this.eventsFile);
|
|
962
965
|
}
|
|
@@ -2148,7 +2151,7 @@ async function confirmStillFailing(repo, prNumber, failed) {
|
|
|
2148
2151
|
return /FAIL|ERROR|CANCEL/i.test(state);
|
|
2149
2152
|
});
|
|
2150
2153
|
}
|
|
2151
|
-
async function appendGithubCiEvent(repo, prNumber, cursor,
|
|
2154
|
+
async function appendGithubCiEvent(repo, prNumber, cursor, events2, rollup) {
|
|
2152
2155
|
const ciState = rollup.map((check) => check.conclusion ?? check.state ?? "PENDING").sort().join(",");
|
|
2153
2156
|
if (!ciState || ciState === cursor.ciState) return;
|
|
2154
2157
|
const previousCiState = cursor.ciState;
|
|
@@ -2159,7 +2162,7 @@ async function appendGithubCiEvent(repo, prNumber, cursor, events, rollup) {
|
|
|
2159
2162
|
const failureState = failed.map((check) => `${check.name ?? check.context ?? "check"}:${check.conclusion ?? check.state ?? "FAILURE"}`).sort().join(",");
|
|
2160
2163
|
if (failed.length > 0 && failureState !== cursor.ciFailureState) {
|
|
2161
2164
|
const names = failed.map((check) => check.name ?? check.context ?? "check").join(", ");
|
|
2162
|
-
|
|
2165
|
+
events2.push({
|
|
2163
2166
|
kind: "ci",
|
|
2164
2167
|
id: `ci:${failureState}`,
|
|
2165
2168
|
repo,
|
|
@@ -2172,13 +2175,13 @@ async function appendGithubCiEvent(repo, prNumber, cursor, events, rollup) {
|
|
|
2172
2175
|
}
|
|
2173
2176
|
cursor.ciFailureState = failureState;
|
|
2174
2177
|
}
|
|
2175
|
-
function appendGithubConflictEvent(repo, prNumber, cursor,
|
|
2178
|
+
function appendGithubConflictEvent(repo, prNumber, cursor, events2, mergeable) {
|
|
2176
2179
|
if (mergeable === cursor.mergeable) return;
|
|
2177
2180
|
const previousMergeable = cursor.mergeable;
|
|
2178
2181
|
cursor.mergeable = mergeable;
|
|
2179
2182
|
if (previousMergeable === void 0) return;
|
|
2180
2183
|
if (mergeable !== "CONFLICTING" && mergeable !== "DIRTY") return;
|
|
2181
|
-
|
|
2184
|
+
events2.push({
|
|
2182
2185
|
kind: "conflict",
|
|
2183
2186
|
id: `conflict:${mergeable}`,
|
|
2184
2187
|
repo,
|
|
@@ -2188,7 +2191,7 @@ function appendGithubConflictEvent(repo, prNumber, cursor, events, mergeable) {
|
|
|
2188
2191
|
actionable: true
|
|
2189
2192
|
});
|
|
2190
2193
|
}
|
|
2191
|
-
function appendGithubReviews(repo, prNumber, cursor,
|
|
2194
|
+
function appendGithubReviews(repo, prNumber, cursor, events2, reviews, historyPrimed) {
|
|
2192
2195
|
const ref = `#${prNumber}`;
|
|
2193
2196
|
for (const review of reviews) {
|
|
2194
2197
|
const id = "rev:" + String(review.id ?? `${review.author?.login}-${review.submittedAt}`);
|
|
@@ -2199,7 +2202,7 @@ function appendGithubReviews(repo, prNumber, cursor, events, reviews, historyPri
|
|
|
2199
2202
|
let actionable = false;
|
|
2200
2203
|
if (review.state === "CHANGES_REQUESTED") actionable = true;
|
|
2201
2204
|
if (review.state === "COMMENTED" && review.body?.trim()) actionable = true;
|
|
2202
|
-
|
|
2205
|
+
events2.push({
|
|
2203
2206
|
kind: "review",
|
|
2204
2207
|
id,
|
|
2205
2208
|
repo,
|
|
@@ -2211,7 +2214,7 @@ function appendGithubReviews(repo, prNumber, cursor, events, reviews, historyPri
|
|
|
2211
2214
|
});
|
|
2212
2215
|
}
|
|
2213
2216
|
}
|
|
2214
|
-
function appendGithubStateEvent(repo, prNumber, cursor,
|
|
2217
|
+
function appendGithubStateEvent(repo, prNumber, cursor, events2, prState) {
|
|
2215
2218
|
const isTerminal = prState === "MERGED" || prState === "CLOSED";
|
|
2216
2219
|
if (prState === cursor.prState) return isTerminal;
|
|
2217
2220
|
const previousPrState = cursor.prState;
|
|
@@ -2219,7 +2222,7 @@ function appendGithubStateEvent(repo, prNumber, cursor, events, prState) {
|
|
|
2219
2222
|
if (previousPrState !== void 0 && isTerminal) {
|
|
2220
2223
|
let summary = `PR #${prNumber} was CLOSED without merging`;
|
|
2221
2224
|
if (prState === "MERGED") summary = `PR #${prNumber} was MERGED`;
|
|
2222
|
-
|
|
2225
|
+
events2.push({
|
|
2223
2226
|
kind: "merged",
|
|
2224
2227
|
id: `state:${prState}`,
|
|
2225
2228
|
repo,
|
|
@@ -2232,14 +2235,14 @@ function appendGithubStateEvent(repo, prNumber, cursor, events, prState) {
|
|
|
2232
2235
|
}
|
|
2233
2236
|
return isTerminal;
|
|
2234
2237
|
}
|
|
2235
|
-
function appendComments(repo, prNumber, cursor,
|
|
2238
|
+
function appendComments(repo, prNumber, cursor, events2, comments, idPrefix, historyPrimed, summary, body) {
|
|
2236
2239
|
for (const comment of comments) {
|
|
2237
2240
|
const id = idPrefix + String(comment.id);
|
|
2238
2241
|
if (cursor.commentIds.includes(id)) continue;
|
|
2239
2242
|
cursor.commentIds.push(id);
|
|
2240
2243
|
if (!historyPrimed) continue;
|
|
2241
2244
|
const text = body(comment);
|
|
2242
|
-
|
|
2245
|
+
events2.push({
|
|
2243
2246
|
kind: "comment",
|
|
2244
2247
|
id,
|
|
2245
2248
|
repo,
|
|
@@ -2269,22 +2272,22 @@ async function pollGithub(repo, prNumber, cursorRaw) {
|
|
|
2269
2272
|
const parsedCursor = /* @__PURE__ */ safeParse22(GithubCursorSchema, cursorRaw);
|
|
2270
2273
|
let cursor = emptyGithubCursor();
|
|
2271
2274
|
if (parsedCursor.success) cursor = parsedCursor.output;
|
|
2272
|
-
const
|
|
2275
|
+
const events2 = [];
|
|
2273
2276
|
const ref = `#${prNumber}`;
|
|
2274
2277
|
if (!githubToken()) {
|
|
2275
2278
|
if (!warnedMissingToken) {
|
|
2276
2279
|
warnedMissingToken = true;
|
|
2277
2280
|
console.warn("[sourcefed] GH_TOKEN or GITHUB_TOKEN is not set; GitHub monitors will not poll");
|
|
2278
2281
|
}
|
|
2279
|
-
return { events, cursor, terminal: false };
|
|
2282
|
+
return { events: events2, cursor, terminal: false };
|
|
2280
2283
|
}
|
|
2281
2284
|
const data = await fetchPrData(repo, prNumber);
|
|
2282
|
-
if (!data) return { events, cursor, terminal: false };
|
|
2285
|
+
if (!data) return { events: events2, cursor, terminal: false };
|
|
2283
2286
|
let historyPrimed = cursor.historyPrimed;
|
|
2284
2287
|
if (!historyPrimed) historyPrimed = Boolean(cursor.reviewIds.length || cursor.commentIds.length || cursor.ciState || cursor.mergeable || cursor.prState);
|
|
2285
2288
|
const lineCommentsPrimed = cursor.lineCommentsPrimed;
|
|
2286
2289
|
const conversationCommentsPrimed = cursor.conversationCommentsPrimed;
|
|
2287
|
-
appendGithubReviews(repo, prNumber, cursor,
|
|
2290
|
+
appendGithubReviews(repo, prNumber, cursor, events2, data.reviews ?? [], historyPrimed);
|
|
2288
2291
|
if (!cursor.historyPrimed) cursor.historyPrimed = true;
|
|
2289
2292
|
const lineComments = await fetchGithubComments(`repos/${repo}/pulls/${prNumber}/comments`);
|
|
2290
2293
|
if (lineComments) {
|
|
@@ -2292,7 +2295,7 @@ async function pollGithub(repo, prNumber, cursorRaw) {
|
|
|
2292
2295
|
repo,
|
|
2293
2296
|
prNumber,
|
|
2294
2297
|
cursor,
|
|
2295
|
-
|
|
2298
|
+
events2,
|
|
2296
2299
|
lineComments,
|
|
2297
2300
|
"c:",
|
|
2298
2301
|
lineCommentsPrimed,
|
|
@@ -2311,7 +2314,7 @@ async function pollGithub(repo, prNumber, cursorRaw) {
|
|
|
2311
2314
|
repo,
|
|
2312
2315
|
prNumber,
|
|
2313
2316
|
cursor,
|
|
2314
|
-
|
|
2317
|
+
events2,
|
|
2315
2318
|
conversationComments,
|
|
2316
2319
|
"issue:",
|
|
2317
2320
|
conversationCommentsPrimed,
|
|
@@ -2320,12 +2323,12 @@ async function pollGithub(repo, prNumber, cursorRaw) {
|
|
|
2320
2323
|
);
|
|
2321
2324
|
cursor.conversationCommentsPrimed = true;
|
|
2322
2325
|
}
|
|
2323
|
-
await appendGithubCiEvent(repo, prNumber, cursor,
|
|
2326
|
+
await appendGithubCiEvent(repo, prNumber, cursor, events2, data.statusCheckRollup ?? []);
|
|
2324
2327
|
const mergeable = data.mergeable ?? data.mergeStateStatus ?? "UNKNOWN";
|
|
2325
|
-
appendGithubConflictEvent(repo, prNumber, cursor,
|
|
2328
|
+
appendGithubConflictEvent(repo, prNumber, cursor, events2, mergeable);
|
|
2326
2329
|
const prState = data.state ?? "OPEN";
|
|
2327
|
-
const terminal = appendGithubStateEvent(repo, prNumber, cursor,
|
|
2328
|
-
return { events, cursor, terminal };
|
|
2330
|
+
const terminal = appendGithubStateEvent(repo, prNumber, cursor, events2, prState);
|
|
2331
|
+
return { events: events2, cursor, terminal };
|
|
2329
2332
|
}
|
|
2330
2333
|
var githubPollMonitor = new PollMonitor({
|
|
2331
2334
|
run: async (source, cursor) => {
|
|
@@ -3103,11 +3106,11 @@ function jiraChangeValue(item, side) {
|
|
|
3103
3106
|
function formatJiraChangelogItem(item) {
|
|
3104
3107
|
return `${item.field}: ${jiraChangeValue(item, "from")} \u2192 ${jiraChangeValue(item, "to")}`;
|
|
3105
3108
|
}
|
|
3106
|
-
function isTerminalStatus(
|
|
3107
|
-
if (typeof
|
|
3109
|
+
function isTerminalStatus(status2) {
|
|
3110
|
+
if (typeof status2 !== "string") return false;
|
|
3108
3111
|
const terminalStatus = process.env.SOURCEFED_JIRA_TERMINAL_STATUS;
|
|
3109
3112
|
if (!terminalStatus) return false;
|
|
3110
|
-
return
|
|
3113
|
+
return status2.trim().toLowerCase() === terminalStatus.trim().toLowerCase();
|
|
3111
3114
|
}
|
|
3112
3115
|
var TRACKED_CHANGE_FIELDS = /* @__PURE__ */ new Set(["summary", "status", "assignee", "priority", "labels"]);
|
|
3113
3116
|
function meaningfulChangelogItems(items) {
|
|
@@ -3133,12 +3136,12 @@ function jiraChangelogEvent(issueKey, history) {
|
|
|
3133
3136
|
terminal
|
|
3134
3137
|
};
|
|
3135
3138
|
}
|
|
3136
|
-
function appendJiraChangelog(issueKey, cursor,
|
|
3139
|
+
function appendJiraChangelog(issueKey, cursor, events2, histories) {
|
|
3137
3140
|
const total = histories.length;
|
|
3138
3141
|
if (cursor.changelogCount !== 0 && total > cursor.changelogCount) {
|
|
3139
3142
|
for (const history of histories.slice(cursor.changelogCount)) {
|
|
3140
3143
|
const event = jiraChangelogEvent(issueKey, history);
|
|
3141
|
-
if (event)
|
|
3144
|
+
if (event) events2.push(event);
|
|
3142
3145
|
}
|
|
3143
3146
|
}
|
|
3144
3147
|
cursor.changelogCount = total;
|
|
@@ -3149,7 +3152,7 @@ function adfText(node) {
|
|
|
3149
3152
|
if (Array.isArray(node.content)) return node.content.map(adfText).join("");
|
|
3150
3153
|
return "";
|
|
3151
3154
|
}
|
|
3152
|
-
function appendJiraComments(issueKey, cursor,
|
|
3155
|
+
function appendJiraComments(issueKey, cursor, events2, comments) {
|
|
3153
3156
|
const freshComments = [];
|
|
3154
3157
|
for (const comment of comments) {
|
|
3155
3158
|
const id = Number(comment.id);
|
|
@@ -3162,7 +3165,7 @@ function appendJiraComments(issueKey, cursor, events, comments) {
|
|
|
3162
3165
|
}
|
|
3163
3166
|
for (const comment of freshComments) {
|
|
3164
3167
|
const body = adfText(comment.body);
|
|
3165
|
-
|
|
3168
|
+
events2.push({
|
|
3166
3169
|
kind: "comment",
|
|
3167
3170
|
id: `comment:${comment.id}`,
|
|
3168
3171
|
issueKey,
|
|
@@ -3175,11 +3178,11 @@ function appendJiraComments(issueKey, cursor, events, comments) {
|
|
|
3175
3178
|
cursor.commentIds.push(Number(comment.id));
|
|
3176
3179
|
}
|
|
3177
3180
|
}
|
|
3178
|
-
function appendJiraDescription(issueKey, cursor,
|
|
3181
|
+
function appendJiraDescription(issueKey, cursor, events2, description) {
|
|
3179
3182
|
let descriptionVersion = "none";
|
|
3180
3183
|
if (description) descriptionVersion = JSON.stringify(description).slice(0, 40);
|
|
3181
3184
|
if (cursor.descriptionVersion !== void 0 && descriptionVersion !== cursor.descriptionVersion) {
|
|
3182
|
-
|
|
3185
|
+
events2.push({
|
|
3183
3186
|
kind: "description",
|
|
3184
3187
|
id: `description:${descriptionVersion}`,
|
|
3185
3188
|
issueKey,
|
|
@@ -3213,15 +3216,15 @@ async function pollJira(issueKey, cursorRaw) {
|
|
|
3213
3216
|
const parsedCursor = /* @__PURE__ */ safeParse23(JiraCursorSchema, cursorRaw);
|
|
3214
3217
|
let cursor = emptyJiraCursor();
|
|
3215
3218
|
if (parsedCursor.success) cursor = parsedCursor.output;
|
|
3216
|
-
const
|
|
3219
|
+
const events2 = [];
|
|
3217
3220
|
const comments = await jiraFetch(`/issue/${issueKey}/comment?maxResults=50&orderBy=-created`);
|
|
3218
|
-
appendJiraComments(issueKey, cursor,
|
|
3221
|
+
appendJiraComments(issueKey, cursor, events2, comments.comments ?? []);
|
|
3219
3222
|
const issue2 = await jiraFetch(`/issue/${issueKey}?expand=changelog&fields=status,description`);
|
|
3220
3223
|
const statusName = issue2.fields?.status?.name;
|
|
3221
|
-
appendJiraDescription(issueKey, cursor,
|
|
3224
|
+
appendJiraDescription(issueKey, cursor, events2, issue2.fields?.description);
|
|
3222
3225
|
const changelog = issue2.changelog?.histories ?? [];
|
|
3223
|
-
appendJiraChangelog(issueKey, cursor,
|
|
3224
|
-
return { events, cursor, terminal: isTerminalStatus(statusName) };
|
|
3226
|
+
appendJiraChangelog(issueKey, cursor, events2, changelog);
|
|
3227
|
+
return { events: events2, cursor, terminal: isTerminalStatus(statusName) };
|
|
3225
3228
|
}
|
|
3226
3229
|
var jiraPollMonitor = new PollMonitor2({
|
|
3227
3230
|
run: async (source, cursor) => {
|
|
@@ -4135,9 +4138,9 @@ function parseSlackReadResult(result, cursorRaw) {
|
|
|
4135
4138
|
if (!cursor.latestTs) return true;
|
|
4136
4139
|
return messageTimestamp(message.ts) > messageTimestamp(cursor.latestTs);
|
|
4137
4140
|
});
|
|
4138
|
-
let
|
|
4141
|
+
let events2 = [];
|
|
4139
4142
|
if (cursor.primed) {
|
|
4140
|
-
|
|
4143
|
+
events2 = freshMessages.map((message) => {
|
|
4141
4144
|
let text = "[message without text]";
|
|
4142
4145
|
if (message.text?.trim()) text = message.text.trim();
|
|
4143
4146
|
let author = users.get(message.user);
|
|
@@ -4160,7 +4163,7 @@ function parseSlackReadResult(result, cursorRaw) {
|
|
|
4160
4163
|
if (messageTimestamp(message.ts) > messageTimestamp(latestTs)) latestTs = message.ts;
|
|
4161
4164
|
}
|
|
4162
4165
|
cursor.latestTs = latestTs;
|
|
4163
|
-
return { events, cursor };
|
|
4166
|
+
return { events: events2, cursor };
|
|
4164
4167
|
}
|
|
4165
4168
|
var SLACK_API = "https://slack.com/api";
|
|
4166
4169
|
var warnedMissingToken2 = false;
|
|
@@ -4351,14 +4354,17 @@ function sourceForInput(type, input) {
|
|
|
4351
4354
|
return source.build(input);
|
|
4352
4355
|
}
|
|
4353
4356
|
function sourceForWebhookPath(pathname) {
|
|
4354
|
-
const
|
|
4355
|
-
for (const source of
|
|
4357
|
+
const sources2 = Object.values(SOURCE_MAP);
|
|
4358
|
+
for (const source of sources2) {
|
|
4356
4359
|
if (source.webhook?.path === pathname) return source;
|
|
4357
4360
|
}
|
|
4358
4361
|
return void 0;
|
|
4359
4362
|
}
|
|
4360
4363
|
async function serveHttp(options) {
|
|
4364
|
+
const active = /* @__PURE__ */ new Set();
|
|
4361
4365
|
const server = createServer((incoming, outgoing) => {
|
|
4366
|
+
active.add(outgoing);
|
|
4367
|
+
outgoing.on("close", () => active.delete(outgoing));
|
|
4362
4368
|
void handle(incoming, outgoing, options.handler);
|
|
4363
4369
|
});
|
|
4364
4370
|
server.requestTimeout = 0;
|
|
@@ -4377,6 +4383,10 @@ async function serveHttp(options) {
|
|
|
4377
4383
|
return {
|
|
4378
4384
|
port,
|
|
4379
4385
|
async stop() {
|
|
4386
|
+
for (const outgoing of active) {
|
|
4387
|
+
outgoing.destroy();
|
|
4388
|
+
}
|
|
4389
|
+
active.clear();
|
|
4380
4390
|
server.closeIdleConnections?.();
|
|
4381
4391
|
await new Promise((resolve) => {
|
|
4382
4392
|
server.close(() => resolve());
|
|
@@ -4659,10 +4669,10 @@ var SourcefedDaemon = class {
|
|
|
4659
4669
|
this.observers.add(onEvent);
|
|
4660
4670
|
return () => this.observers.delete(onEvent);
|
|
4661
4671
|
}
|
|
4662
|
-
appendLog(target,
|
|
4672
|
+
appendLog(target, events2) {
|
|
4663
4673
|
const key = targetKey(target);
|
|
4664
4674
|
const existing = this.deliveryLog.get(key) ?? [];
|
|
4665
|
-
const merged = [...
|
|
4675
|
+
const merged = [...events2.reverse(), ...existing];
|
|
4666
4676
|
this.deliveryLog.set(key, merged.slice(0, DELIVERY_LOG_LIMIT));
|
|
4667
4677
|
}
|
|
4668
4678
|
emit(target) {
|
|
@@ -4722,9 +4732,16 @@ function monitorView(monitor) {
|
|
|
4722
4732
|
label: definition.label(monitor.source),
|
|
4723
4733
|
detail: definition.detail(monitor.source),
|
|
4724
4734
|
describe: definition.describe(monitor.source),
|
|
4725
|
-
unresponsive: definition.unresponsive(monitor)
|
|
4735
|
+
unresponsive: definition.unresponsive(monitor),
|
|
4736
|
+
lastPolledAt: cursorTime(monitor.cursors.__lastPolledAt ?? monitor.cursors.__lastAttemptAt),
|
|
4737
|
+
webhookHeartbeatAt: cursorTime(monitor.cursors.__webhookHeartbeatAt)
|
|
4726
4738
|
};
|
|
4727
4739
|
}
|
|
4740
|
+
function cursorTime(value) {
|
|
4741
|
+
const numeric = Number(value);
|
|
4742
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return void 0;
|
|
4743
|
+
return new Date(numeric).toISOString();
|
|
4744
|
+
}
|
|
4728
4745
|
function logEntryView(queued) {
|
|
4729
4746
|
const definition = sourceDefinition(queued.event.source);
|
|
4730
4747
|
return {
|
|
@@ -4815,16 +4832,16 @@ var HttpDaemonClient = class {
|
|
|
4815
4832
|
});
|
|
4816
4833
|
let buffered = [];
|
|
4817
4834
|
let delivering = Promise.resolve();
|
|
4818
|
-
const deliver = (
|
|
4819
|
-
if (!callbacks?.has(onEvents) ||
|
|
4820
|
-
delivering = delivering.then(() => deliverWithRetry(
|
|
4835
|
+
const deliver = (events2) => {
|
|
4836
|
+
if (!callbacks?.has(onEvents) || events2.length === 0) return Promise.resolve();
|
|
4837
|
+
delivering = delivering.then(() => deliverWithRetry(events2, 0)).catch((error2) => {
|
|
4821
4838
|
console.error(`[sourcefed] event delivery failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
4822
4839
|
});
|
|
4823
4840
|
return delivering;
|
|
4824
4841
|
};
|
|
4825
|
-
const deliverWithRetry = async (
|
|
4842
|
+
const deliverWithRetry = async (events2, attempt) => {
|
|
4826
4843
|
try {
|
|
4827
|
-
await onEvents(
|
|
4844
|
+
await onEvents(events2);
|
|
4828
4845
|
} catch (error2) {
|
|
4829
4846
|
if (attempt >= 2) {
|
|
4830
4847
|
console.error(`[sourcefed] event delivery failed after retries; re-draining queue: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
@@ -4833,7 +4850,7 @@ var HttpDaemonClient = class {
|
|
|
4833
4850
|
return;
|
|
4834
4851
|
}
|
|
4835
4852
|
await sleep(250 * (attempt + 1));
|
|
4836
|
-
await deliverWithRetry(
|
|
4853
|
+
await deliverWithRetry(events2, attempt + 1);
|
|
4837
4854
|
}
|
|
4838
4855
|
};
|
|
4839
4856
|
const performDrain = async () => {
|
|
@@ -5038,8 +5055,8 @@ function eventStream(daemon, target) {
|
|
|
5038
5055
|
const stream = new ReadableStream({
|
|
5039
5056
|
start(controller) {
|
|
5040
5057
|
const encoder = new TextEncoder();
|
|
5041
|
-
unsubscribe = daemon.subscribe(target, (
|
|
5042
|
-
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 })}
|
|
5043
5060
|
|
|
5044
5061
|
`));
|
|
5045
5062
|
});
|
|
@@ -5065,6 +5082,73 @@ function eventStream(daemon, target) {
|
|
|
5065
5082
|
});
|
|
5066
5083
|
}
|
|
5067
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
|
+
|
|
5068
5152
|
// packages/mcp/dist/index.js
|
|
5069
5153
|
import process3 from "node:process";
|
|
5070
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";
|
|
@@ -5331,9 +5415,9 @@ function nullish(input) {
|
|
|
5331
5415
|
return input === null || input === void 0;
|
|
5332
5416
|
}
|
|
5333
5417
|
function cleanRegex(source) {
|
|
5334
|
-
const
|
|
5418
|
+
const start2 = source.startsWith("^") ? 1 : 0;
|
|
5335
5419
|
const end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
5336
|
-
return source.slice(
|
|
5420
|
+
return source.slice(start2, end);
|
|
5337
5421
|
}
|
|
5338
5422
|
function floatSafeRemainder(val, step) {
|
|
5339
5423
|
const ratio = val / step;
|
|
@@ -14869,19 +14953,19 @@ var MCP_NAME_HEADER_SOURCE = {
|
|
|
14869
14953
|
"resources/read": "uri"
|
|
14870
14954
|
};
|
|
14871
14955
|
function stripHttpOws(value) {
|
|
14872
|
-
let
|
|
14873
|
-
while (
|
|
14874
|
-
const code = value.codePointAt(
|
|
14956
|
+
let start2 = 0;
|
|
14957
|
+
while (start2 < value.length) {
|
|
14958
|
+
const code = value.codePointAt(start2);
|
|
14875
14959
|
if (code !== 9 && code !== 32) break;
|
|
14876
|
-
|
|
14960
|
+
start2 += 1;
|
|
14877
14961
|
}
|
|
14878
14962
|
let end = value.length;
|
|
14879
|
-
while (end >
|
|
14963
|
+
while (end > start2) {
|
|
14880
14964
|
const code = value.codePointAt(end - 1);
|
|
14881
14965
|
if (code !== 9 && code !== 32) break;
|
|
14882
14966
|
end -= 1;
|
|
14883
14967
|
}
|
|
14884
|
-
return
|
|
14968
|
+
return start2 === 0 && end === value.length ? value : value.slice(start2, end);
|
|
14885
14969
|
}
|
|
14886
14970
|
function validateStandardRequestHeaders(request, route) {
|
|
14887
14971
|
if (route.messageKind !== "request") return;
|
|
@@ -23640,11 +23724,11 @@ var require_dist = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
23640
23724
|
if (!f) throw new Error(`Unknown format "${name}"`);
|
|
23641
23725
|
return f;
|
|
23642
23726
|
};
|
|
23643
|
-
function addFormats2(ajv,
|
|
23727
|
+
function addFormats2(ajv, list2, fs, exportName) {
|
|
23644
23728
|
var _a3;
|
|
23645
23729
|
var _b;
|
|
23646
23730
|
(_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
|
|
23647
|
-
for (const f of
|
|
23731
|
+
for (const f of list2) ajv.addFormat(f, fs[f]);
|
|
23648
23732
|
}
|
|
23649
23733
|
module.exports = exports = formatsPlugin;
|
|
23650
23734
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -23912,11 +23996,11 @@ data: ${JSON.stringify({
|
|
|
23912
23996
|
const readable = new ReadableStream({
|
|
23913
23997
|
start(streamController) {
|
|
23914
23998
|
controller = streamController;
|
|
23915
|
-
const
|
|
23999
|
+
const ack2 = stampSubscriptionId({
|
|
23916
24000
|
method: "notifications/subscriptions/acknowledged",
|
|
23917
24001
|
params: { notifications: honored }
|
|
23918
24002
|
}, subscriptionId);
|
|
23919
|
-
writeNotification(
|
|
24003
|
+
writeNotification(ack2.method, ack2.params);
|
|
23920
24004
|
unsubscribe = bus.subscribe((event) => {
|
|
23921
24005
|
if (closed || !listenFilterAccepts(honored, event)) return;
|
|
23922
24006
|
const note = stampSubscriptionId(serverEventToNotification(event), subscriptionId);
|
|
@@ -25602,7 +25686,7 @@ var WebStandardStreamableHTTPServerTransport = class {
|
|
|
25602
25686
|
/**
|
|
25603
25687
|
* Helper to create a JSON error response
|
|
25604
25688
|
*/
|
|
25605
|
-
createJsonErrorResponse(
|
|
25689
|
+
createJsonErrorResponse(status2, code, message, options) {
|
|
25606
25690
|
const error2 = {
|
|
25607
25691
|
code,
|
|
25608
25692
|
message
|
|
@@ -25613,7 +25697,7 @@ var WebStandardStreamableHTTPServerTransport = class {
|
|
|
25613
25697
|
error: error2,
|
|
25614
25698
|
id: null
|
|
25615
25699
|
}, {
|
|
25616
|
-
status,
|
|
25700
|
+
status: status2,
|
|
25617
25701
|
headers: {
|
|
25618
25702
|
"Content-Type": "application/json",
|
|
25619
25703
|
...options?.headers
|
|
@@ -27665,7 +27749,7 @@ async function confirmStillFailing2(repo, prNumber, failed) {
|
|
|
27665
27749
|
return /FAIL|ERROR|CANCEL/i.test(state);
|
|
27666
27750
|
});
|
|
27667
27751
|
}
|
|
27668
|
-
async function appendGithubCiEvent2(repo, prNumber, cursor,
|
|
27752
|
+
async function appendGithubCiEvent2(repo, prNumber, cursor, events2, rollup) {
|
|
27669
27753
|
const ciState = rollup.map((check) => check.conclusion ?? check.state ?? "PENDING").sort().join(",");
|
|
27670
27754
|
if (!ciState || ciState === cursor.ciState) return;
|
|
27671
27755
|
const previousCiState = cursor.ciState;
|
|
@@ -27676,7 +27760,7 @@ async function appendGithubCiEvent2(repo, prNumber, cursor, events, rollup) {
|
|
|
27676
27760
|
const failureState = failed.map((check) => `${check.name ?? check.context ?? "check"}:${check.conclusion ?? check.state ?? "FAILURE"}`).sort().join(",");
|
|
27677
27761
|
if (failed.length > 0 && failureState !== cursor.ciFailureState) {
|
|
27678
27762
|
const names = failed.map((check) => check.name ?? check.context ?? "check").join(", ");
|
|
27679
|
-
|
|
27763
|
+
events2.push({
|
|
27680
27764
|
kind: "ci",
|
|
27681
27765
|
id: `ci:${failureState}`,
|
|
27682
27766
|
repo,
|
|
@@ -27689,13 +27773,13 @@ async function appendGithubCiEvent2(repo, prNumber, cursor, events, rollup) {
|
|
|
27689
27773
|
}
|
|
27690
27774
|
cursor.ciFailureState = failureState;
|
|
27691
27775
|
}
|
|
27692
|
-
function appendGithubConflictEvent2(repo, prNumber, cursor,
|
|
27776
|
+
function appendGithubConflictEvent2(repo, prNumber, cursor, events2, mergeable) {
|
|
27693
27777
|
if (mergeable === cursor.mergeable) return;
|
|
27694
27778
|
const previousMergeable = cursor.mergeable;
|
|
27695
27779
|
cursor.mergeable = mergeable;
|
|
27696
27780
|
if (previousMergeable === void 0) return;
|
|
27697
27781
|
if (mergeable !== "CONFLICTING" && mergeable !== "DIRTY") return;
|
|
27698
|
-
|
|
27782
|
+
events2.push({
|
|
27699
27783
|
kind: "conflict",
|
|
27700
27784
|
id: `conflict:${mergeable}`,
|
|
27701
27785
|
repo,
|
|
@@ -27705,7 +27789,7 @@ function appendGithubConflictEvent2(repo, prNumber, cursor, events, mergeable) {
|
|
|
27705
27789
|
actionable: true
|
|
27706
27790
|
});
|
|
27707
27791
|
}
|
|
27708
|
-
function appendGithubReviews2(repo, prNumber, cursor,
|
|
27792
|
+
function appendGithubReviews2(repo, prNumber, cursor, events2, reviews, historyPrimed) {
|
|
27709
27793
|
const ref = `#${prNumber}`;
|
|
27710
27794
|
for (const review of reviews) {
|
|
27711
27795
|
const id = "rev:" + String(review.id ?? `${review.author?.login}-${review.submittedAt}`);
|
|
@@ -27716,7 +27800,7 @@ function appendGithubReviews2(repo, prNumber, cursor, events, reviews, historyPr
|
|
|
27716
27800
|
let actionable = false;
|
|
27717
27801
|
if (review.state === "CHANGES_REQUESTED") actionable = true;
|
|
27718
27802
|
if (review.state === "COMMENTED" && review.body?.trim()) actionable = true;
|
|
27719
|
-
|
|
27803
|
+
events2.push({
|
|
27720
27804
|
kind: "review",
|
|
27721
27805
|
id,
|
|
27722
27806
|
repo,
|
|
@@ -27728,7 +27812,7 @@ function appendGithubReviews2(repo, prNumber, cursor, events, reviews, historyPr
|
|
|
27728
27812
|
});
|
|
27729
27813
|
}
|
|
27730
27814
|
}
|
|
27731
|
-
function appendGithubStateEvent2(repo, prNumber, cursor,
|
|
27815
|
+
function appendGithubStateEvent2(repo, prNumber, cursor, events2, prState) {
|
|
27732
27816
|
const isTerminal = prState === "MERGED" || prState === "CLOSED";
|
|
27733
27817
|
if (prState === cursor.prState) return isTerminal;
|
|
27734
27818
|
const previousPrState = cursor.prState;
|
|
@@ -27736,7 +27820,7 @@ function appendGithubStateEvent2(repo, prNumber, cursor, events, prState) {
|
|
|
27736
27820
|
if (previousPrState !== void 0 && isTerminal) {
|
|
27737
27821
|
let summary = `PR #${prNumber} was CLOSED without merging`;
|
|
27738
27822
|
if (prState === "MERGED") summary = `PR #${prNumber} was MERGED`;
|
|
27739
|
-
|
|
27823
|
+
events2.push({
|
|
27740
27824
|
kind: "merged",
|
|
27741
27825
|
id: `state:${prState}`,
|
|
27742
27826
|
repo,
|
|
@@ -27749,14 +27833,14 @@ function appendGithubStateEvent2(repo, prNumber, cursor, events, prState) {
|
|
|
27749
27833
|
}
|
|
27750
27834
|
return isTerminal;
|
|
27751
27835
|
}
|
|
27752
|
-
function appendComments2(repo, prNumber, cursor,
|
|
27836
|
+
function appendComments2(repo, prNumber, cursor, events2, comments, idPrefix, historyPrimed, summary, body) {
|
|
27753
27837
|
for (const comment of comments) {
|
|
27754
27838
|
const id = idPrefix + String(comment.id);
|
|
27755
27839
|
if (cursor.commentIds.includes(id)) continue;
|
|
27756
27840
|
cursor.commentIds.push(id);
|
|
27757
27841
|
if (!historyPrimed) continue;
|
|
27758
27842
|
const text = body(comment);
|
|
27759
|
-
|
|
27843
|
+
events2.push({
|
|
27760
27844
|
kind: "comment",
|
|
27761
27845
|
id,
|
|
27762
27846
|
repo,
|
|
@@ -27786,22 +27870,22 @@ async function pollGithub2(repo, prNumber, cursorRaw) {
|
|
|
27786
27870
|
const parsedCursor = /* @__PURE__ */ safeParse2222(GithubCursorSchema2, cursorRaw);
|
|
27787
27871
|
let cursor = emptyGithubCursor2();
|
|
27788
27872
|
if (parsedCursor.success) cursor = parsedCursor.output;
|
|
27789
|
-
const
|
|
27873
|
+
const events2 = [];
|
|
27790
27874
|
const ref = `#${prNumber}`;
|
|
27791
27875
|
if (!githubToken2()) {
|
|
27792
27876
|
if (!warnedMissingToken3) {
|
|
27793
27877
|
warnedMissingToken3 = true;
|
|
27794
27878
|
console.warn("[sourcefed] GH_TOKEN or GITHUB_TOKEN is not set; GitHub monitors will not poll");
|
|
27795
27879
|
}
|
|
27796
|
-
return { events, cursor, terminal: false };
|
|
27880
|
+
return { events: events2, cursor, terminal: false };
|
|
27797
27881
|
}
|
|
27798
27882
|
const data = await fetchPrData2(repo, prNumber);
|
|
27799
|
-
if (!data) return { events, cursor, terminal: false };
|
|
27883
|
+
if (!data) return { events: events2, cursor, terminal: false };
|
|
27800
27884
|
let historyPrimed = cursor.historyPrimed;
|
|
27801
27885
|
if (!historyPrimed) historyPrimed = Boolean(cursor.reviewIds.length || cursor.commentIds.length || cursor.ciState || cursor.mergeable || cursor.prState);
|
|
27802
27886
|
const lineCommentsPrimed = cursor.lineCommentsPrimed;
|
|
27803
27887
|
const conversationCommentsPrimed = cursor.conversationCommentsPrimed;
|
|
27804
|
-
appendGithubReviews2(repo, prNumber, cursor,
|
|
27888
|
+
appendGithubReviews2(repo, prNumber, cursor, events2, data.reviews ?? [], historyPrimed);
|
|
27805
27889
|
if (!cursor.historyPrimed) cursor.historyPrimed = true;
|
|
27806
27890
|
const lineComments = await fetchGithubComments2(`repos/${repo}/pulls/${prNumber}/comments`);
|
|
27807
27891
|
if (lineComments) {
|
|
@@ -27809,7 +27893,7 @@ async function pollGithub2(repo, prNumber, cursorRaw) {
|
|
|
27809
27893
|
repo,
|
|
27810
27894
|
prNumber,
|
|
27811
27895
|
cursor,
|
|
27812
|
-
|
|
27896
|
+
events2,
|
|
27813
27897
|
lineComments,
|
|
27814
27898
|
"c:",
|
|
27815
27899
|
lineCommentsPrimed,
|
|
@@ -27828,7 +27912,7 @@ async function pollGithub2(repo, prNumber, cursorRaw) {
|
|
|
27828
27912
|
repo,
|
|
27829
27913
|
prNumber,
|
|
27830
27914
|
cursor,
|
|
27831
|
-
|
|
27915
|
+
events2,
|
|
27832
27916
|
conversationComments,
|
|
27833
27917
|
"issue:",
|
|
27834
27918
|
conversationCommentsPrimed,
|
|
@@ -27837,12 +27921,12 @@ async function pollGithub2(repo, prNumber, cursorRaw) {
|
|
|
27837
27921
|
);
|
|
27838
27922
|
cursor.conversationCommentsPrimed = true;
|
|
27839
27923
|
}
|
|
27840
|
-
await appendGithubCiEvent2(repo, prNumber, cursor,
|
|
27924
|
+
await appendGithubCiEvent2(repo, prNumber, cursor, events2, data.statusCheckRollup ?? []);
|
|
27841
27925
|
const mergeable = data.mergeable ?? data.mergeStateStatus ?? "UNKNOWN";
|
|
27842
|
-
appendGithubConflictEvent2(repo, prNumber, cursor,
|
|
27926
|
+
appendGithubConflictEvent2(repo, prNumber, cursor, events2, mergeable);
|
|
27843
27927
|
const prState = data.state ?? "OPEN";
|
|
27844
|
-
const terminal = appendGithubStateEvent2(repo, prNumber, cursor,
|
|
27845
|
-
return { events, cursor, terminal };
|
|
27928
|
+
const terminal = appendGithubStateEvent2(repo, prNumber, cursor, events2, prState);
|
|
27929
|
+
return { events: events2, cursor, terminal };
|
|
27846
27930
|
}
|
|
27847
27931
|
var githubPollMonitor2 = new PollMonitor4({
|
|
27848
27932
|
run: async (source, cursor) => {
|
|
@@ -28620,11 +28704,11 @@ function jiraChangeValue2(item, side) {
|
|
|
28620
28704
|
function formatJiraChangelogItem2(item) {
|
|
28621
28705
|
return `${item.field}: ${jiraChangeValue2(item, "from")} \u2192 ${jiraChangeValue2(item, "to")}`;
|
|
28622
28706
|
}
|
|
28623
|
-
function isTerminalStatus2(
|
|
28624
|
-
if (typeof
|
|
28707
|
+
function isTerminalStatus2(status2) {
|
|
28708
|
+
if (typeof status2 !== "string") return false;
|
|
28625
28709
|
const terminalStatus = process.env.SOURCEFED_JIRA_TERMINAL_STATUS;
|
|
28626
28710
|
if (!terminalStatus) return false;
|
|
28627
|
-
return
|
|
28711
|
+
return status2.trim().toLowerCase() === terminalStatus.trim().toLowerCase();
|
|
28628
28712
|
}
|
|
28629
28713
|
var TRACKED_CHANGE_FIELDS2 = /* @__PURE__ */ new Set(["summary", "status", "assignee", "priority", "labels"]);
|
|
28630
28714
|
function meaningfulChangelogItems2(items) {
|
|
@@ -28650,12 +28734,12 @@ function jiraChangelogEvent2(issueKey, history) {
|
|
|
28650
28734
|
terminal
|
|
28651
28735
|
};
|
|
28652
28736
|
}
|
|
28653
|
-
function appendJiraChangelog2(issueKey, cursor,
|
|
28737
|
+
function appendJiraChangelog2(issueKey, cursor, events2, histories) {
|
|
28654
28738
|
const total = histories.length;
|
|
28655
28739
|
if (cursor.changelogCount !== 0 && total > cursor.changelogCount) {
|
|
28656
28740
|
for (const history of histories.slice(cursor.changelogCount)) {
|
|
28657
28741
|
const event = jiraChangelogEvent2(issueKey, history);
|
|
28658
|
-
if (event)
|
|
28742
|
+
if (event) events2.push(event);
|
|
28659
28743
|
}
|
|
28660
28744
|
}
|
|
28661
28745
|
cursor.changelogCount = total;
|
|
@@ -28666,7 +28750,7 @@ function adfText2(node) {
|
|
|
28666
28750
|
if (Array.isArray(node.content)) return node.content.map(adfText2).join("");
|
|
28667
28751
|
return "";
|
|
28668
28752
|
}
|
|
28669
|
-
function appendJiraComments2(issueKey, cursor,
|
|
28753
|
+
function appendJiraComments2(issueKey, cursor, events2, comments) {
|
|
28670
28754
|
const freshComments = [];
|
|
28671
28755
|
for (const comment of comments) {
|
|
28672
28756
|
const id = Number(comment.id);
|
|
@@ -28679,7 +28763,7 @@ function appendJiraComments2(issueKey, cursor, events, comments) {
|
|
|
28679
28763
|
}
|
|
28680
28764
|
for (const comment of freshComments) {
|
|
28681
28765
|
const body = adfText2(comment.body);
|
|
28682
|
-
|
|
28766
|
+
events2.push({
|
|
28683
28767
|
kind: "comment",
|
|
28684
28768
|
id: `comment:${comment.id}`,
|
|
28685
28769
|
issueKey,
|
|
@@ -28692,11 +28776,11 @@ function appendJiraComments2(issueKey, cursor, events, comments) {
|
|
|
28692
28776
|
cursor.commentIds.push(Number(comment.id));
|
|
28693
28777
|
}
|
|
28694
28778
|
}
|
|
28695
|
-
function appendJiraDescription2(issueKey, cursor,
|
|
28779
|
+
function appendJiraDescription2(issueKey, cursor, events2, description) {
|
|
28696
28780
|
let descriptionVersion = "none";
|
|
28697
28781
|
if (description) descriptionVersion = JSON.stringify(description).slice(0, 40);
|
|
28698
28782
|
if (cursor.descriptionVersion !== void 0 && descriptionVersion !== cursor.descriptionVersion) {
|
|
28699
|
-
|
|
28783
|
+
events2.push({
|
|
28700
28784
|
kind: "description",
|
|
28701
28785
|
id: `description:${descriptionVersion}`,
|
|
28702
28786
|
issueKey,
|
|
@@ -28730,15 +28814,15 @@ async function pollJira2(issueKey, cursorRaw) {
|
|
|
28730
28814
|
const parsedCursor = /* @__PURE__ */ safeParse232(JiraCursorSchema2, cursorRaw);
|
|
28731
28815
|
let cursor = emptyJiraCursor2();
|
|
28732
28816
|
if (parsedCursor.success) cursor = parsedCursor.output;
|
|
28733
|
-
const
|
|
28817
|
+
const events2 = [];
|
|
28734
28818
|
const comments = await jiraFetch2(`/issue/${issueKey}/comment?maxResults=50&orderBy=-created`);
|
|
28735
|
-
appendJiraComments2(issueKey, cursor,
|
|
28819
|
+
appendJiraComments2(issueKey, cursor, events2, comments.comments ?? []);
|
|
28736
28820
|
const issue2 = await jiraFetch2(`/issue/${issueKey}?expand=changelog&fields=status,description`);
|
|
28737
28821
|
const statusName = issue2.fields?.status?.name;
|
|
28738
|
-
appendJiraDescription2(issueKey, cursor,
|
|
28822
|
+
appendJiraDescription2(issueKey, cursor, events2, issue2.fields?.description);
|
|
28739
28823
|
const changelog = issue2.changelog?.histories ?? [];
|
|
28740
|
-
appendJiraChangelog2(issueKey, cursor,
|
|
28741
|
-
return { events, cursor, terminal: isTerminalStatus2(statusName) };
|
|
28824
|
+
appendJiraChangelog2(issueKey, cursor, events2, changelog);
|
|
28825
|
+
return { events: events2, cursor, terminal: isTerminalStatus2(statusName) };
|
|
28742
28826
|
}
|
|
28743
28827
|
var jiraPollMonitor2 = new PollMonitor22({
|
|
28744
28828
|
run: async (source, cursor) => {
|
|
@@ -29652,9 +29736,9 @@ function parseSlackReadResult2(result, cursorRaw) {
|
|
|
29652
29736
|
if (!cursor.latestTs) return true;
|
|
29653
29737
|
return messageTimestamp2(message.ts) > messageTimestamp2(cursor.latestTs);
|
|
29654
29738
|
});
|
|
29655
|
-
let
|
|
29739
|
+
let events2 = [];
|
|
29656
29740
|
if (cursor.primed) {
|
|
29657
|
-
|
|
29741
|
+
events2 = freshMessages.map((message) => {
|
|
29658
29742
|
let text = "[message without text]";
|
|
29659
29743
|
if (message.text?.trim()) text = message.text.trim();
|
|
29660
29744
|
let author = users.get(message.user);
|
|
@@ -29677,7 +29761,7 @@ function parseSlackReadResult2(result, cursorRaw) {
|
|
|
29677
29761
|
if (messageTimestamp2(message.ts) > messageTimestamp2(latestTs)) latestTs = message.ts;
|
|
29678
29762
|
}
|
|
29679
29763
|
cursor.latestTs = latestTs;
|
|
29680
|
-
return { events, cursor };
|
|
29764
|
+
return { events: events2, cursor };
|
|
29681
29765
|
}
|
|
29682
29766
|
var SLACK_API2 = "https://slack.com/api";
|
|
29683
29767
|
var warnedMissingToken22 = false;
|
|
@@ -30019,9 +30103,9 @@ function buildServer(daemon) {
|
|
|
30019
30103
|
async (uri, variables) => {
|
|
30020
30104
|
const target = decodeTarget2(String(variables.targetId));
|
|
30021
30105
|
if (!target) throw new Error("invalid Sourcefed target resource");
|
|
30022
|
-
const
|
|
30106
|
+
const events2 = await daemon.readEvents(target);
|
|
30023
30107
|
return {
|
|
30024
|
-
contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify({ events }) }]
|
|
30108
|
+
contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify({ events: events2 }) }]
|
|
30025
30109
|
};
|
|
30026
30110
|
}
|
|
30027
30111
|
);
|
|
@@ -30034,6 +30118,275 @@ function errorResult(error2) {
|
|
|
30034
30118
|
return { content: [{ type: "text", text: error2 }], isError: true };
|
|
30035
30119
|
}
|
|
30036
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
|
+
|
|
30037
30390
|
// packages/cli/src/skills/command.ts
|
|
30038
30391
|
import { readFile as readFile7, readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
|
30039
30392
|
import path9 from "node:path";
|
|
@@ -30042,7 +30395,7 @@ import path9 from "node:path";
|
|
|
30042
30395
|
import { statSync } from "node:fs";
|
|
30043
30396
|
import { readdir, readFile as readFile6, stat } from "node:fs/promises";
|
|
30044
30397
|
import path8 from "node:path";
|
|
30045
|
-
import { fileURLToPath } from "node:url";
|
|
30398
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
30046
30399
|
|
|
30047
30400
|
// packages/cli/src/skills/frontmatter.ts
|
|
30048
30401
|
function parseFrontmatter(content) {
|
|
@@ -30122,7 +30475,7 @@ async function discoverSkills() {
|
|
|
30122
30475
|
return skills;
|
|
30123
30476
|
}
|
|
30124
30477
|
function skillRoot() {
|
|
30125
|
-
let dir = path8.dirname(
|
|
30478
|
+
let dir = path8.dirname(fileURLToPath2(import.meta.url));
|
|
30126
30479
|
for (let depth = 0; depth < 6; depth++) {
|
|
30127
30480
|
const marker = path8.join(dir, "data");
|
|
30128
30481
|
if (statSync(marker, { throwIfNoEntry: false })?.isDirectory()) return dir;
|
|
@@ -30130,7 +30483,7 @@ function skillRoot() {
|
|
|
30130
30483
|
if (parent === dir) break;
|
|
30131
30484
|
dir = parent;
|
|
30132
30485
|
}
|
|
30133
|
-
return path8.dirname(
|
|
30486
|
+
return path8.dirname(fileURLToPath2(import.meta.url));
|
|
30134
30487
|
}
|
|
30135
30488
|
|
|
30136
30489
|
// packages/cli/src/skills/command.ts
|
|
@@ -30226,252 +30579,31 @@ ${content}
|
|
|
30226
30579
|
}
|
|
30227
30580
|
|
|
30228
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
|
+
};
|
|
30229
30589
|
async function main(argv = process.argv.slice(2)) {
|
|
30230
|
-
const [command,
|
|
30231
|
-
|
|
30232
|
-
|
|
30233
|
-
|
|
30234
|
-
|
|
30235
|
-
if (command === "mcp") {
|
|
30236
|
-
await runMcp([subcommand, ...rest].filter((value) => value !== void 0));
|
|
30237
|
-
return;
|
|
30238
|
-
}
|
|
30239
|
-
if (command === "monitor") {
|
|
30240
|
-
await runMonitor(subcommand, rest);
|
|
30241
|
-
return;
|
|
30242
|
-
}
|
|
30243
|
-
if (command === "skills") {
|
|
30244
|
-
await runSkillsCommand([subcommand, ...rest].filter((value) => value !== void 0));
|
|
30245
|
-
return;
|
|
30246
|
-
}
|
|
30247
|
-
printUsage();
|
|
30248
|
-
process.exitCode = 1;
|
|
30249
|
-
}
|
|
30250
|
-
function createDaemon() {
|
|
30251
|
-
return new SourcefedDaemon({
|
|
30252
|
-
stateDir: process.env.SOURCEFED_STATE_DIR
|
|
30253
|
-
});
|
|
30254
|
-
}
|
|
30255
|
-
async function runDaemon(args) {
|
|
30256
|
-
const port = Number(flag(args, "port") ?? process.env.SOURCEFED_DAEMON_PORT ?? 18787);
|
|
30257
|
-
const hostname = flag(args, "host") ?? process.env.SOURCEFED_DAEMON_HOST ?? "127.0.0.1";
|
|
30258
|
-
const token = process.env.SOURCEFED_DAEMON_TOKEN;
|
|
30259
|
-
if (!isLoopback(hostname) && !token) {
|
|
30260
|
-
throw new Error("binding the daemon to a non-loopback address requires SOURCEFED_DAEMON_TOKEN");
|
|
30261
|
-
}
|
|
30262
|
-
const daemon = createDaemon();
|
|
30263
|
-
await daemon.start();
|
|
30264
|
-
let server;
|
|
30265
|
-
try {
|
|
30266
|
-
server = await serveHttp({
|
|
30267
|
-
hostname,
|
|
30268
|
-
port,
|
|
30269
|
-
handler: (request) => handleDaemonHttpRequest(daemon, request, { token })
|
|
30270
|
-
});
|
|
30271
|
-
const webhookServer = await startWebhookServer(daemon.runtime);
|
|
30272
|
-
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)");
|
|
30273
|
-
} catch (error2) {
|
|
30274
|
-
await daemon.stop();
|
|
30275
|
-
throw error2;
|
|
30276
|
-
}
|
|
30277
|
-
console.error(`[sourcefed] daemon on http://${hostname}:${server.port} (rpc, events, webhooks)`);
|
|
30278
|
-
await new Promise(() => {
|
|
30279
|
-
});
|
|
30280
|
-
}
|
|
30281
|
-
function isLoopback(hostname) {
|
|
30282
|
-
return hostname === "127.0.0.1" || hostname === "::1" || hostname === "localhost";
|
|
30283
|
-
}
|
|
30284
|
-
async function runMcp(args) {
|
|
30285
|
-
const mode = args[0] ?? "--stdio";
|
|
30286
|
-
if (mode === "--stdio") {
|
|
30287
|
-
const daemon2 = createDaemon();
|
|
30288
|
-
const mcp2 = createSourcefedStdio(daemon2);
|
|
30289
|
-
await daemon2.start();
|
|
30290
|
-
const webhookServer = await startWebhookServer(mcp2.runtime);
|
|
30291
|
-
await serveStdio(mcp2.factory);
|
|
30292
|
-
await webhookServer?.stop();
|
|
30293
|
-
await mcp2.close();
|
|
30294
|
-
return;
|
|
30295
|
-
}
|
|
30296
|
-
if (mode !== "--http") {
|
|
30297
|
-
throw new Error("mcp expects --stdio or --http");
|
|
30298
|
-
}
|
|
30299
|
-
const port = Number(flag(args, "port") ?? process.env.SOURCEFED_MCP_PORT ?? 18788);
|
|
30300
|
-
const hostname = flag(args, "host") ?? process.env.SOURCEFED_MCP_HOST ?? "127.0.0.1";
|
|
30301
|
-
const token = process.env.SOURCEFED_DAEMON_TOKEN;
|
|
30302
|
-
if (!isLoopback(hostname) && !token) {
|
|
30303
|
-
throw new Error("binding the MCP server to a non-loopback address requires SOURCEFED_DAEMON_TOKEN");
|
|
30304
|
-
}
|
|
30305
|
-
const daemon = createDaemon();
|
|
30306
|
-
const mcp = createSourcefedMcp(daemon);
|
|
30307
|
-
await daemon.start();
|
|
30308
|
-
let server;
|
|
30309
|
-
try {
|
|
30310
|
-
server = await serveHttp({
|
|
30311
|
-
hostname,
|
|
30312
|
-
port,
|
|
30313
|
-
handler: (request) => {
|
|
30314
|
-
const url2 = new URL(request.url);
|
|
30315
|
-
if (url2.pathname === "/mcp" && token && request.headers.get("authorization") !== `Bearer ${token}`) {
|
|
30316
|
-
return new Response("unauthorized", { status: 401 });
|
|
30317
|
-
}
|
|
30318
|
-
return url2.pathname === "/mcp" ? mcp.handler.fetch(request) : daemon.webhook(request);
|
|
30319
|
-
}
|
|
30320
|
-
});
|
|
30321
|
-
} catch (error2) {
|
|
30322
|
-
await mcp.close();
|
|
30323
|
-
throw error2;
|
|
30324
|
-
}
|
|
30325
|
-
console.error(`[sourcefed] MCP server on http://${hostname}:${server.port}/mcp`);
|
|
30326
|
-
await new Promise(() => {
|
|
30327
|
-
});
|
|
30328
|
-
}
|
|
30329
|
-
async function startWebhookServer(runtime) {
|
|
30330
|
-
const enabled = Boolean(
|
|
30331
|
-
process.env.SOURCEFED_ENABLE_WEBHOOKS === "1" || process.env.SOURCEFED_WEBHOOK_PORT || process.env.SOURCEFED_GITHUB_WEBHOOK_SECRET || process.env.SOURCEFED_SLACK_SIGNING_SECRET
|
|
30332
|
-
);
|
|
30333
|
-
if (!enabled) return void 0;
|
|
30334
|
-
try {
|
|
30335
|
-
return await serveHttp({
|
|
30336
|
-
hostname: process.env.SOURCEFED_WEBHOOK_HOST ?? "127.0.0.1",
|
|
30337
|
-
port: Number(process.env.SOURCEFED_WEBHOOK_PORT ?? 8788),
|
|
30338
|
-
handler: (request) => runtime.webhook(request)
|
|
30339
|
-
});
|
|
30340
|
-
} catch (error2) {
|
|
30341
|
-
console.error(`[sourcefed] webhook listener unavailable; continuing with MCP and polling: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
30342
|
-
return void 0;
|
|
30343
|
-
}
|
|
30344
|
-
}
|
|
30345
|
-
async function runMonitor(subcommand, args) {
|
|
30346
|
-
const target = {
|
|
30347
|
-
kind: flag(args, "target-kind") ?? "cli",
|
|
30348
|
-
id: flag(args, "target-id") ?? process.env.SOURCEFED_TARGET_ID ?? os7.hostname()
|
|
30349
|
-
};
|
|
30350
|
-
const clientOptions = {
|
|
30351
|
-
name: "sourcefed-cli",
|
|
30352
|
-
url: process.env.SOURCEFED_DAEMON_URL ?? defaultDaemonUrl()
|
|
30353
|
-
};
|
|
30354
|
-
const client = await connectDaemonClient(clientOptions);
|
|
30355
|
-
try {
|
|
30356
|
-
if (subcommand === "list") {
|
|
30357
|
-
const result = await client.request("monitor.list", { target });
|
|
30358
|
-
printMonitors(result?.monitors ?? []);
|
|
30359
|
-
return;
|
|
30360
|
-
}
|
|
30361
|
-
if (subcommand === "logs") {
|
|
30362
|
-
const result = await client.request("monitor.logs", { target });
|
|
30363
|
-
printLogs(result?.logs ?? []);
|
|
30364
|
-
return;
|
|
30365
|
-
}
|
|
30366
|
-
if (subcommand === "sources") {
|
|
30367
|
-
printResult(await client.request("daemon.sourceTypes"));
|
|
30368
|
-
return;
|
|
30369
|
-
}
|
|
30370
|
-
const id = flag(args, "id");
|
|
30371
|
-
if ((subcommand === "status" || subcommand === "stop" || subcommand === "start" || subcommand === "remove") && !id) throw new Error(`${subcommand} requires --id`);
|
|
30372
|
-
if (subcommand === "status" || subcommand === "stop" || subcommand === "start") {
|
|
30373
|
-
printResult(await client.request(`monitor.${subcommand}`, { target, id }));
|
|
30374
|
-
return;
|
|
30375
|
-
}
|
|
30376
|
-
if (subcommand === "remove") {
|
|
30377
|
-
if (!args.includes("--yes")) throw new Error("remove is destructive: pass --yes to confirm");
|
|
30378
|
-
printResult(await client.request("monitor.remove", { target, id }));
|
|
30379
|
-
return;
|
|
30380
|
-
}
|
|
30381
|
-
if (subcommand === "events") {
|
|
30382
|
-
printResult(await client.request("monitor.events", { target }));
|
|
30383
|
-
return;
|
|
30384
|
-
}
|
|
30385
|
-
if (subcommand === "ack") {
|
|
30386
|
-
const eventIDs = (flag(args, "event-id") ?? "").split(",").filter(Boolean);
|
|
30387
|
-
if (eventIDs.length === 0) throw new Error("ack requires --event-id <id>[,<id>...]");
|
|
30388
|
-
printResult(await client.request("monitor.ack", { target, eventIDs }));
|
|
30389
|
-
return;
|
|
30390
|
-
}
|
|
30391
|
-
if (subcommand === "create") {
|
|
30392
|
-
const sourceType = flag(args, "source-type");
|
|
30393
|
-
const sourceTypes = await client.request("daemon.sourceTypes");
|
|
30394
|
-
if (!sourceType || !sourceTypes.sourceTypes?.includes(sourceType)) {
|
|
30395
|
-
throw new Error(`create requires --source-type ${sourceTypes.sourceTypes?.join("|") ?? "unknown"}`);
|
|
30396
|
-
}
|
|
30397
|
-
const input = {
|
|
30398
|
-
name: flag(args, "name") ?? sourceType,
|
|
30399
|
-
sourceType,
|
|
30400
|
-
target
|
|
30401
|
-
};
|
|
30402
|
-
const keyMap = {
|
|
30403
|
-
"issue-key": "issueKey",
|
|
30404
|
-
repo: "repo",
|
|
30405
|
-
"channel-id": "channelId",
|
|
30406
|
-
"thread-ts": "threadTs",
|
|
30407
|
-
"thread-url": "threadUrl"
|
|
30408
|
-
};
|
|
30409
|
-
for (const key of Object.keys(keyMap)) {
|
|
30410
|
-
const value = flag(args, key);
|
|
30411
|
-
if (value) input[keyMap[key]] = value;
|
|
30412
|
-
}
|
|
30413
|
-
const prNumber = flag(args, "pr-number");
|
|
30414
|
-
if (prNumber) input.prNumber = Number(prNumber);
|
|
30415
|
-
const pollIntervalSec = flag(args, "poll-interval-sec");
|
|
30416
|
-
if (pollIntervalSec) input.pollIntervalSec = Number(pollIntervalSec);
|
|
30417
|
-
printResult(await client.request("monitor.create", input));
|
|
30418
|
-
return;
|
|
30419
|
-
}
|
|
30420
|
-
} finally {
|
|
30421
|
-
await client.close();
|
|
30422
|
-
}
|
|
30423
|
-
throw new Error("monitor expects create, list, status, stop, start, remove, events, ack, logs, or sources");
|
|
30424
|
-
}
|
|
30425
|
-
function flag(args, name) {
|
|
30426
|
-
const index = args.indexOf(`--${name}`);
|
|
30427
|
-
return index >= 0 ? args[index + 1] : void 0;
|
|
30428
|
-
}
|
|
30429
|
-
function printMonitors(monitors) {
|
|
30430
|
-
if (monitors.length === 0) {
|
|
30431
|
-
console.log(dim("no monitors for this target"));
|
|
30432
|
-
return;
|
|
30433
|
-
}
|
|
30434
|
-
for (const monitor of monitors) {
|
|
30435
|
-
const status = monitor.enabled ? green("enabled") : red("stopped");
|
|
30436
|
-
const delivery = dim(monitor.delivery);
|
|
30437
|
-
console.log(`${monitor.icon} ${monitor.describe} ${status} ${delivery}`);
|
|
30438
|
-
}
|
|
30439
|
-
}
|
|
30440
|
-
function printLogs(logs) {
|
|
30441
|
-
if (logs.length === 0) {
|
|
30442
|
-
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;
|
|
30443
30595
|
return;
|
|
30444
30596
|
}
|
|
30445
|
-
|
|
30446
|
-
const time3 = dim(new Date(entry.at).toLocaleString());
|
|
30447
|
-
const marker = entry.actionable ? yellow("\u25B6") : dim("\xB7");
|
|
30448
|
-
console.log(`${marker} ${entry.icon} ${time3} ${entry.describe}`);
|
|
30449
|
-
console.log(` ${entry.summary}`);
|
|
30450
|
-
if (entry.body) console.log(dim(` ${entry.body}`));
|
|
30451
|
-
}
|
|
30452
|
-
}
|
|
30453
|
-
function green(text) {
|
|
30454
|
-
return `\x1B[32m${text}\x1B[0m`;
|
|
30455
|
-
}
|
|
30456
|
-
function red(text) {
|
|
30457
|
-
return `\x1B[31m${text}\x1B[0m`;
|
|
30458
|
-
}
|
|
30459
|
-
function yellow(text) {
|
|
30460
|
-
return `\x1B[33m${text}\x1B[0m`;
|
|
30461
|
-
}
|
|
30462
|
-
function dim(text) {
|
|
30463
|
-
return `\x1B[2m${text}\x1B[0m`;
|
|
30464
|
-
}
|
|
30465
|
-
function printResult(result) {
|
|
30466
|
-
console.log(JSON.stringify(result, null, 2));
|
|
30597
|
+
await run(args);
|
|
30467
30598
|
}
|
|
30468
30599
|
function printUsage() {
|
|
30469
30600
|
console.error("sourcefed daemon [--port PORT] [--host HOST]");
|
|
30470
30601
|
console.error("sourcefed mcp --stdio|--http [--port PORT]");
|
|
30471
|
-
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]");
|
|
30472
30603
|
console.error("sourcefed skills [list|get <name>|path [name]]");
|
|
30604
|
+
console.error("sourcefed setup [opencode|pi]");
|
|
30473
30605
|
}
|
|
30474
|
-
if (process.argv[1] && realpathSync(process.argv[1]) ===
|
|
30606
|
+
if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath3(import.meta.url)) {
|
|
30475
30607
|
main().catch((error2) => {
|
|
30476
30608
|
console.error(error2 instanceof Error ? error2.message : String(error2));
|
|
30477
30609
|
process.exitCode = 1;
|