@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.
- package/README.md +2 -2
- package/dist/chunk-UOTCX4ME.js +3963 -0
- package/dist/cli.js +474 -361
- package/dist/data/core/SKILL.md +10 -0
- package/dist/data/core/references/commands.md +1 -0
- 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/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__
|
|
@@ -917,31 +915,31 @@ var JsonMonitorEventQueue = class {
|
|
|
917
915
|
eventsFile;
|
|
918
916
|
queue = Promise.resolve();
|
|
919
917
|
async enqueue(input) {
|
|
920
|
-
return this.transact((
|
|
918
|
+
return this.transact((events2) => {
|
|
921
919
|
const queued = createQueuedMonitorEvent(input.monitor, input.event);
|
|
922
|
-
const existing =
|
|
920
|
+
const existing = events2.find((event) => event.id === queued.id);
|
|
923
921
|
if (existing) return existing;
|
|
924
|
-
|
|
922
|
+
events2.push(queued);
|
|
925
923
|
return queued;
|
|
926
924
|
});
|
|
927
925
|
}
|
|
928
926
|
async read(target) {
|
|
929
|
-
const
|
|
930
|
-
return
|
|
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((
|
|
931
|
+
await this.transact((events2) => {
|
|
934
932
|
const ids = new Set(eventIDs);
|
|
935
|
-
for (let index =
|
|
936
|
-
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);
|
|
937
935
|
}
|
|
938
936
|
});
|
|
939
937
|
}
|
|
940
938
|
async transact(operation) {
|
|
941
939
|
const next = this.queue.then(async () => {
|
|
942
|
-
const
|
|
943
|
-
const result = await operation(
|
|
944
|
-
await this.saveEvents(
|
|
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(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
2326
|
+
await appendGithubCiEvent(repo, prNumber, cursor, events2, data.statusCheckRollup ?? []);
|
|
2329
2327
|
const mergeable = data.mergeable ?? data.mergeStateStatus ?? "UNKNOWN";
|
|
2330
|
-
appendGithubConflictEvent(repo, prNumber, cursor,
|
|
2328
|
+
appendGithubConflictEvent(repo, prNumber, cursor, events2, mergeable);
|
|
2331
2329
|
const prState = data.state ?? "OPEN";
|
|
2332
|
-
const terminal = appendGithubStateEvent(repo, prNumber, cursor,
|
|
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(
|
|
3112
|
-
if (typeof
|
|
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
|
|
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,
|
|
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)
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
3219
|
+
const events2 = [];
|
|
3222
3220
|
const comments = await jiraFetch(`/issue/${issueKey}/comment?maxResults=50&orderBy=-created`);
|
|
3223
|
-
appendJiraComments(issueKey, cursor,
|
|
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,
|
|
3224
|
+
appendJiraDescription(issueKey, cursor, events2, issue2.fields?.description);
|
|
3227
3225
|
const changelog = issue2.changelog?.histories ?? [];
|
|
3228
|
-
appendJiraChangelog(issueKey, cursor,
|
|
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
|
|
4141
|
+
let events2 = [];
|
|
4144
4142
|
if (cursor.primed) {
|
|
4145
|
-
|
|
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
|
|
4360
|
-
for (const source of
|
|
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,
|
|
4672
|
+
appendLog(target, events2) {
|
|
4675
4673
|
const key = targetKey(target);
|
|
4676
4674
|
const existing = this.deliveryLog.get(key) ?? [];
|
|
4677
|
-
const merged = [...
|
|
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 = (
|
|
4838
|
-
if (!callbacks?.has(onEvents) ||
|
|
4839
|
-
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) => {
|
|
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 (
|
|
4842
|
+
const deliverWithRetry = async (events2, attempt) => {
|
|
4845
4843
|
try {
|
|
4846
|
-
await onEvents(
|
|
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(
|
|
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, (
|
|
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
|
|
5418
|
+
const start2 = source.startsWith("^") ? 1 : 0;
|
|
5354
5419
|
const end = source.endsWith("$") ? source.length - 1 : source.length;
|
|
5355
|
-
return source.slice(
|
|
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
|
|
14892
|
-
while (
|
|
14893
|
-
const code = value.codePointAt(
|
|
14956
|
+
let start2 = 0;
|
|
14957
|
+
while (start2 < value.length) {
|
|
14958
|
+
const code = value.codePointAt(start2);
|
|
14894
14959
|
if (code !== 9 && code !== 32) break;
|
|
14895
|
-
|
|
14960
|
+
start2 += 1;
|
|
14896
14961
|
}
|
|
14897
14962
|
let end = value.length;
|
|
14898
|
-
while (end >
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
23999
|
+
const ack2 = stampSubscriptionId({
|
|
23935
24000
|
method: "notifications/subscriptions/acknowledged",
|
|
23936
24001
|
params: { notifications: honored }
|
|
23937
24002
|
}, subscriptionId);
|
|
23938
|
-
writeNotification(
|
|
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(
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
27924
|
+
await appendGithubCiEvent2(repo, prNumber, cursor, events2, data.statusCheckRollup ?? []);
|
|
27860
27925
|
const mergeable = data.mergeable ?? data.mergeStateStatus ?? "UNKNOWN";
|
|
27861
|
-
appendGithubConflictEvent2(repo, prNumber, cursor,
|
|
27926
|
+
appendGithubConflictEvent2(repo, prNumber, cursor, events2, mergeable);
|
|
27862
27927
|
const prState = data.state ?? "OPEN";
|
|
27863
|
-
const terminal = appendGithubStateEvent2(repo, prNumber, cursor,
|
|
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(
|
|
28643
|
-
if (typeof
|
|
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
|
|
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,
|
|
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)
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
28817
|
+
const events2 = [];
|
|
28753
28818
|
const comments = await jiraFetch2(`/issue/${issueKey}/comment?maxResults=50&orderBy=-created`);
|
|
28754
|
-
appendJiraComments2(issueKey, cursor,
|
|
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,
|
|
28822
|
+
appendJiraDescription2(issueKey, cursor, events2, issue2.fields?.description);
|
|
28758
28823
|
const changelog = issue2.changelog?.histories ?? [];
|
|
28759
|
-
appendJiraChangelog2(issueKey, cursor,
|
|
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
|
|
29739
|
+
let events2 = [];
|
|
29675
29740
|
if (cursor.primed) {
|
|
29676
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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,
|
|
30250
|
-
|
|
30251
|
-
|
|
30252
|
-
|
|
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
|
-
|
|
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]) ===
|
|
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;
|