@fdcn/sourcefed 0.3.7 → 0.3.8
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 +1 -1
- package/dist/{chunk-E6J3BAXP.js → chunk-5JL3UV72.js} +990 -99
- package/dist/cli.js +8716 -6926
- package/dist/daemon.js +994 -101
- package/dist/data/core/SKILL.md +9 -4
- package/dist/data/core/references/commands.md +6 -1
- package/dist/data/sourcefed/SKILL.md +3 -2
- package/dist/guidance.md +6 -2
- package/dist/mcp.js +6 -2
- package/dist/opencode.js +6 -2
- package/dist/pi.js +161 -39
- package/dist/tui-compiled.mjs +306 -166
- package/dist.dts/adapters/opencode/tools/monitor_create.d.ts +16 -0
- package/dist.dts/adapters/pi/src/dialog.d.ts +1 -2
- package/dist.dts/adapters/pi/src/monitors-dialog.d.ts +32 -0
- package/dist.dts/adapters/pi/src/monitors-dialog.test.d.ts +1 -0
- package/dist.dts/packages/core/src/types/monitor-create-input.d.ts +4 -0
- package/dist.dts/packages/daemon/src/registry.d.ts +2 -0
- package/dist.dts/packages/daemon/src/types/daemon.d.ts +4 -0
- package/dist.dts/providers/yab/src/index.d.ts +2 -0
- package/dist.dts/providers/yab/src/monitor.d.ts +14 -0
- package/dist.dts/providers/yab/src/poll/index.d.ts +2 -0
- package/dist.dts/providers/yab/src/poll/monitor.d.ts +3 -0
- package/dist.dts/providers/yab/src/poll/utils/feed-events.d.ts +5 -0
- package/dist.dts/providers/yab/src/poll/utils/index.d.ts +4 -0
- package/dist.dts/providers/yab/src/poll/utils/parse-feed.d.ts +3 -0
- package/dist.dts/providers/yab/src/poll/utils/poll-yab.d.ts +7 -0
- package/dist.dts/providers/yab/src/poll/utils/yab-mcp.d.ts +10 -0
- package/dist.dts/providers/yab/src/schema.d.ts +12 -0
- package/dist.dts/providers/yab/src/types/cursor.d.ts +4 -0
- package/dist.dts/providers/yab/src/types/event.d.ts +8 -0
- package/dist.dts/providers/yab/src/types/feed-entry.d.ts +8 -0
- package/dist.dts/providers/yab/src/types/index.d.ts +5 -0
- package/dist.dts/providers/yab/src/types/page.d.ts +6 -0
- package/dist.dts/providers/yab/src/types/source-record.d.ts +7 -0
- package/dist.dts/providers/yab/src/yab.test.d.ts +1 -0
- package/package.json +1 -1
package/dist/data/core/SKILL.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: core
|
|
3
|
-
description: Core sourcefed usage guide. Read this before creating monitors. Covers monitor creation per source (Jira issue, GitHub PR, Slack thread), event delivery and acknowledgement semantics, daemon modes, CLI usage, and troubleshooting. Use when the user asks to watch or monitor a Jira ticket, GitHub PR, Slack thread, or when sourcefed monitor events arrive in the session.
|
|
3
|
+
description: Core sourcefed usage guide. Read this before creating monitors. Covers monitor creation per source (Jira issue, GitHub PR, Slack thread, Yab feed), event delivery and acknowledgement semantics, daemon modes, CLI usage, and troubleshooting. Use when the user asks to watch or monitor a Jira ticket, GitHub PR, Slack thread, a feed or inbox they are signed in to in Yab, or when sourcefed monitor events arrive in the session.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Sourcefed core
|
|
7
7
|
|
|
8
|
-
Sourcefed lets you create **monitors** that watch a Jira issue, GitHub PR,
|
|
9
|
-
Slack thread or direct-message conversation and route NEW events into the session you're currently working in,
|
|
8
|
+
Sourcefed lets you create **monitors** that watch a Jira issue, GitHub PR,
|
|
9
|
+
Slack thread or direct-message conversation, or a feed read through Yab, and route NEW events into the session you're currently working in,
|
|
10
10
|
so you can react to them as they happen.
|
|
11
11
|
|
|
12
12
|
Every monitor is detect-only: it routes events into the session and never
|
|
@@ -21,6 +21,7 @@ affect that session's monitors.
|
|
|
21
21
|
- **GitHub PR**: `monitor_create({ name: "PR #42", sourceType: "github", repo: "owner/repo", prNumber: 42 })`
|
|
22
22
|
- **Slack thread**: `monitor_create({ name: "thread", sourceType: "slack", threadUrl: "<thread-url>" })`
|
|
23
23
|
- **Whole Slack DM**: `monitor_create({ name: "DM with Alex", sourceType: "slack", channelId: "D0123456789" })`
|
|
24
|
+
- **Yab feed**: `monitor_create({ name: "inbox", sourceType: "yab", url: "https://mail.google.com/mail/u/0/feed/atom", space: "Personal", match: "interview|offer", exclude: "newsletter" })`
|
|
24
25
|
|
|
25
26
|
You will be notified of:
|
|
26
27
|
|
|
@@ -30,9 +31,13 @@ You will be notified of:
|
|
|
30
31
|
- **Slack thread**: new replies in the thread.
|
|
31
32
|
- **Slack DM**: new top-level messages in the DM. Slack monitors are always detect-only
|
|
32
33
|
and never reply.
|
|
34
|
+
- **Yab feed**: each new entry of an Atom, RSS or JSON feed, read through the user's Yab
|
|
35
|
+
browser with their sign-ins (so private feeds like Gmail's unread-inbox feed work).
|
|
36
|
+
`space` picks the Yab space; `match`/`exclude` are case-insensitive regexes over the
|
|
37
|
+
entry's title, summary and author. Yab must be running.
|
|
33
38
|
|
|
34
39
|
GitHub and Slack use webhook delivery when configured and fall back to polling
|
|
35
|
-
when unavailable. Jira always
|
|
40
|
+
when unavailable. Jira and Yab always use polling. You can lower `pollIntervalSec`
|
|
36
41
|
(min 15) for time-sensitive work, but prefer the default to avoid hammering
|
|
37
42
|
APIs.
|
|
38
43
|
|
|
@@ -26,13 +26,17 @@ sourcefed skills path [name] Print skill directory path
|
|
|
26
26
|
|
|
27
27
|
```text
|
|
28
28
|
--name NAME Human-readable monitor name (defaults to the source type)
|
|
29
|
-
--source-type TYPE jira | github | slack
|
|
29
|
+
--source-type TYPE jira | github | slack | yab
|
|
30
30
|
--issue-key KEY Jira issue key (jira)
|
|
31
31
|
--repo OWNER/NAME GitHub repository (github)
|
|
32
32
|
--pr-number N GitHub PR number (github)
|
|
33
33
|
--channel-id ID Slack channel or DM ID; a D… ID alone monitors the whole DM
|
|
34
34
|
--thread-ts TS Slack parent message timestamp; omit for a whole-DM monitor
|
|
35
35
|
--thread-url URL Slack thread URL (slack)
|
|
36
|
+
--url URL Atom, RSS or JSON feed URL read through Yab (yab)
|
|
37
|
+
--space NAME Yab space whose sign-ins to use (yab)
|
|
38
|
+
--match REGEX Only deliver entries matching this case-insensitive regex (yab)
|
|
39
|
+
--exclude REGEX Skip entries matching this case-insensitive regex (yab)
|
|
36
40
|
--poll-interval-sec N Poll interval in seconds (min 15)
|
|
37
41
|
--target-kind KIND Target kind for ownership scoping (default: cli)
|
|
38
42
|
--target-id ID Target id for ownership scoping (default: hostname)
|
|
@@ -54,6 +58,7 @@ SOURCEFED_JIRA_TERMINAL_STATUS Jira status that auto-removes monitors
|
|
|
54
58
|
GH_TOKEN / GITHUB_TOKEN GitHub token for direct API polling
|
|
55
59
|
SOURCEFED_GITHUB_WEBHOOK_SECRET GitHub webhook secret (enables webhooks)
|
|
56
60
|
SOURCEFED_SLACK_SIGNING_SECRET Slack signing secret (enables webhooks)
|
|
61
|
+
SOURCEFED_YAB_COMMAND Yab binary for yab monitors (default /Applications/yab.app/Contents/MacOS/yab)
|
|
57
62
|
SOURCEFED_SKILLS_DIR Override the bundled skills directory
|
|
58
63
|
```
|
|
59
64
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sourcefed
|
|
3
|
-
description: Live Jira, GitHub, and
|
|
3
|
+
description: Live Jira, GitHub, Slack, and Yab feed monitoring for agent hosts. Create monitors that route new issue comments, PR reviews, CI failures, Slack thread or DM messages, and new entries of feeds read through Yab into the current session as they happen.
|
|
4
4
|
hidden: true
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Sourcefed
|
|
8
8
|
|
|
9
9
|
Sourcefed turns external collaboration threads into live agent input: create a
|
|
10
|
-
monitor for a Jira issue, GitHub PR, Slack thread,
|
|
10
|
+
monitor for a Jira issue, GitHub PR, Slack thread, Slack DM, or a feed read through Yab and NEW events are routed
|
|
11
11
|
into the session you are working in, so you can react to them as they happen.
|
|
12
12
|
|
|
13
13
|
This file is a discovery stub, not the usage guide. Before running any
|
|
@@ -25,6 +25,7 @@ releases, which is why it just points at `skills get core`.
|
|
|
25
25
|
## When to load this skill
|
|
26
26
|
|
|
27
27
|
- A user asks to monitor a Jira ticket, GitHub PR, Slack thread, or Slack DM.
|
|
28
|
+
- A user wants to hear about new entries in a feed or inbox they are signed in to in Yab.
|
|
28
29
|
- You found or are working on a Jira ticket, PR, thread, or DM and want new
|
|
29
30
|
activity routed into your session.
|
|
30
31
|
- You opened a PR and want to be notified of reviews, comments, CI failures,
|
package/dist/guidance.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# sourcefed monitors
|
|
2
2
|
|
|
3
|
-
Monitors watch a Jira issue, GitHub PR,
|
|
3
|
+
Monitors watch a Jira issue, GitHub PR, Slack thread, or a feed read through Yab and bring new events
|
|
4
|
+
into this session.
|
|
4
5
|
|
|
5
6
|
Create one with `monitor_create` when:
|
|
6
7
|
|
|
@@ -9,7 +10,10 @@ Create one with `monitor_create` when:
|
|
|
9
10
|
- your branch has a PR: `{ name: "<pr>", sourceType: "github", repo: "owner/name", prNumber: 42 }`
|
|
10
11
|
(reviews, comments, CI failures, conflicts, merge or close);
|
|
11
12
|
- you were given a Slack thread: `{ name: "<thread>", sourceType: "slack", threadUrl: "<url>" }`
|
|
12
|
-
(new messages; read-only, never replies)
|
|
13
|
+
(new messages; read-only, never replies);
|
|
14
|
+
- the user wants new entries of a feed they are signed in to in Yab (unread Gmail, a job board's RSS):
|
|
15
|
+
`{ name: "<feed>", sourceType: "yab", url: "https://mail.google.com/mail/u/0/feed/atom", space: "Personal", match: "interview|offer" }`
|
|
16
|
+
(each new entry; `match`/`exclude` are case-insensitive regexes; Yab must be running).
|
|
13
17
|
|
|
14
18
|
Keep the default `pollIntervalSec` unless the work is time-sensitive (min 15).
|
|
15
19
|
`monitor_list`, `monitor_status { id }` and `monitor_stop { id }` act on this session's monitors.
|
package/dist/mcp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
decodeTarget,
|
|
3
3
|
encodeTarget
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5JL3UV72.js";
|
|
5
5
|
import {
|
|
6
6
|
__export
|
|
7
7
|
} from "./chunk-MLKGABMK.js";
|
|
@@ -4680,6 +4680,10 @@ function buildServer(daemon) {
|
|
|
4680
4680
|
channelId: string2().optional(),
|
|
4681
4681
|
threadTs: string2().optional(),
|
|
4682
4682
|
threadUrl: string2().optional(),
|
|
4683
|
+
url: string2().optional(),
|
|
4684
|
+
space: string2().optional(),
|
|
4685
|
+
match: string2().optional(),
|
|
4686
|
+
exclude: string2().optional(),
|
|
4683
4687
|
pollIntervalSec: number2().min(15).optional(),
|
|
4684
4688
|
target: targetSchema
|
|
4685
4689
|
});
|
|
@@ -4693,7 +4697,7 @@ function buildServer(daemon) {
|
|
|
4693
4697
|
server.registerTool(
|
|
4694
4698
|
"monitor_create",
|
|
4695
4699
|
{
|
|
4696
|
-
description: "Create or reuse a monitor for a Jira issue, GitHub pull request, Slack thread, or
|
|
4700
|
+
description: "Create or reuse a monitor for a Jira issue, GitHub pull request, Slack thread, Slack DM, or a feed read through Yab.",
|
|
4697
4701
|
inputSchema: createSchema,
|
|
4698
4702
|
annotations: { idempotentHint: true, readOnlyHint: false }
|
|
4699
4703
|
},
|
package/dist/opencode.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
daemonCommand,
|
|
8
8
|
daemonEnvironment,
|
|
9
9
|
spawnLocalDaemon
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5JL3UV72.js";
|
|
11
11
|
import "./chunk-MLKGABMK.js";
|
|
12
12
|
|
|
13
13
|
// packages/sourcefed/src/server.ts
|
|
@@ -357,7 +357,7 @@ async function callMonitorTool(bridge, name, arguments_, sessionID) {
|
|
|
357
357
|
// adapters/opencode/tools/monitor_create.ts
|
|
358
358
|
var monitor_create_default = (bridge) => ({
|
|
359
359
|
name: "monitor_create",
|
|
360
|
-
description: "Create a detect-only monitor that watches a Jira issue, GitHub PR, Slack thread,
|
|
360
|
+
description: "Create a detect-only monitor that watches a Jira issue, GitHub PR, Slack thread, Slack DM, or a feed read through Yab (the user's browser, with their sign-ins) and routes NEW events into the current session. Slack monitors only read and notify; they never reply. GitHub and Slack use webhooks when configured and polling otherwise; Jira and Yab use polling.",
|
|
361
361
|
input: {
|
|
362
362
|
type: "object",
|
|
363
363
|
properties: {
|
|
@@ -369,6 +369,10 @@ var monitor_create_default = (bridge) => ({
|
|
|
369
369
|
channelId: { type: "string", description: "Slack channel or DM ID. A DM ID without threadTs monitors the whole DM conversation." },
|
|
370
370
|
threadTs: { type: "string", description: "Slack parent message timestamp. Omit it with a DM ID to monitor the whole DM conversation." },
|
|
371
371
|
threadUrl: { type: "string", description: "Slack thread URL (alternative to channelId + threadTs)" },
|
|
372
|
+
url: { type: "string", description: "Atom, RSS or JSON feed URL read through Yab with the user's sign-ins, e.g. https://mail.google.com/mail/u/0/feed/atom (required if sourceType=yab)" },
|
|
373
|
+
space: { type: "string", description: "Yab space whose sign-ins to use (yab); omit to use wherever the site is signed in" },
|
|
374
|
+
match: { type: "string", description: "Case-insensitive regex; only entries whose title, summary or author match are delivered (yab)" },
|
|
375
|
+
exclude: { type: "string", description: "Case-insensitive regex; entries that match are skipped (yab)" },
|
|
372
376
|
pollIntervalSec: { type: "number", description: "Optional polling interval in seconds (min 15, default 60)" }
|
|
373
377
|
},
|
|
374
378
|
required: ["name", "sourceType"],
|
package/dist/pi.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
daemonCommand,
|
|
7
7
|
daemonEnvironment,
|
|
8
8
|
spawnLocalDaemon
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-5JL3UV72.js";
|
|
10
10
|
import "./chunk-MLKGABMK.js";
|
|
11
11
|
|
|
12
12
|
// adapters/pi/src/index.ts
|
|
@@ -39,35 +39,6 @@ function showSourcefedDialog(_tui, theme, done, title, lines) {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
function monitorLines(monitors, theme) {
|
|
43
|
-
const active = monitors.filter((monitor) => monitor.enabled);
|
|
44
|
-
if (active.length === 0) return [{ text: theme.fg("dim", "No active monitors") }];
|
|
45
|
-
return active.flatMap((monitor) => {
|
|
46
|
-
const status = monitor.unresponsive ? theme.fg("error", "\u25CF") : theme.fg("success", "\u25CF");
|
|
47
|
-
const describe = theme.fg("text", monitor.describe);
|
|
48
|
-
const detail = monitor.detail ? theme.fg("muted", monitor.detail) : "";
|
|
49
|
-
const statusLabel = monitor.unresponsive ? "recovering connection" : "healthy";
|
|
50
|
-
const rows = [
|
|
51
|
-
["Name", monitor.name],
|
|
52
|
-
["Delivery", monitor.delivery],
|
|
53
|
-
["Poll interval", `${monitor.pollIntervalSec}s`],
|
|
54
|
-
["Created", formatTime(monitor.createdAt)],
|
|
55
|
-
["Updated", formatTime(monitor.updatedAt)],
|
|
56
|
-
["Last poll", formatTime(monitor.lastPolledAt)],
|
|
57
|
-
["Webhook heartbeat", formatTime(monitor.webhookHeartbeatAt)]
|
|
58
|
-
];
|
|
59
|
-
return [
|
|
60
|
-
{ text: `${status} ${monitor.icon} ${describe}${detail} [${statusLabel}]` },
|
|
61
|
-
...rows.map(([label, value]) => ({ text: theme.fg("muted", ` ${label}: ${value}`) }))
|
|
62
|
-
];
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
function formatTime(value) {
|
|
66
|
-
if (!value) return "never";
|
|
67
|
-
const date = new Date(value);
|
|
68
|
-
if (Number.isNaN(date.getTime())) return value;
|
|
69
|
-
return date.toLocaleString();
|
|
70
|
-
}
|
|
71
42
|
function logLines(logs, theme) {
|
|
72
43
|
if (logs.length === 0) return [{ text: theme.fg("dim", "No notifications sent yet") }];
|
|
73
44
|
return logs.flatMap((entry) => {
|
|
@@ -80,12 +51,145 @@ function logLines(logs, theme) {
|
|
|
80
51
|
});
|
|
81
52
|
}
|
|
82
53
|
|
|
54
|
+
// adapters/pi/src/monitors-dialog.ts
|
|
55
|
+
import { truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
|
|
56
|
+
var PROGRESS = { stop: "Stopping", start: "Starting", remove: "Deleting" };
|
|
57
|
+
var MonitorManager = class {
|
|
58
|
+
constructor(monitors, deps) {
|
|
59
|
+
this.deps = deps;
|
|
60
|
+
this.monitors = monitors;
|
|
61
|
+
}
|
|
62
|
+
monitors;
|
|
63
|
+
selected = 0;
|
|
64
|
+
mode = { kind: "browse" };
|
|
65
|
+
error;
|
|
66
|
+
handleInput(data) {
|
|
67
|
+
switch (this.mode.kind) {
|
|
68
|
+
case "busy":
|
|
69
|
+
return;
|
|
70
|
+
case "confirm-remove":
|
|
71
|
+
return this.handleConfirm(data, this.mode.monitor);
|
|
72
|
+
case "browse":
|
|
73
|
+
return this.handleBrowse(data);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
render(width) {
|
|
77
|
+
const { theme } = this.deps;
|
|
78
|
+
const active = this.monitors.filter((monitor) => monitor.enabled).length;
|
|
79
|
+
const border = theme.fg("accent", "\u2500".repeat(Math.max(0, width)));
|
|
80
|
+
const lines = [
|
|
81
|
+
border,
|
|
82
|
+
` ${theme.fg("accent", theme.bold(`Sourcefed monitors (${active} active)`))}`,
|
|
83
|
+
"",
|
|
84
|
+
...this.listLines(),
|
|
85
|
+
"",
|
|
86
|
+
...this.statusLines(),
|
|
87
|
+
border
|
|
88
|
+
];
|
|
89
|
+
return lines.map((line) => truncateToWidth2(line, width));
|
|
90
|
+
}
|
|
91
|
+
invalidate() {
|
|
92
|
+
}
|
|
93
|
+
handleBrowse(data) {
|
|
94
|
+
if (this.matches(data, "tui.select.cancel") || this.matches(data, "tui.select.confirm")) return this.deps.close();
|
|
95
|
+
if (this.matches(data, "tui.select.up")) return this.select(this.selected - 1);
|
|
96
|
+
if (this.matches(data, "tui.select.down")) return this.select(this.selected + 1);
|
|
97
|
+
const monitor = this.monitors[this.selected];
|
|
98
|
+
if (!monitor) return;
|
|
99
|
+
if (data === "s") void this.perform(monitor.enabled ? "stop" : "start", monitor);
|
|
100
|
+
if (data === "d") this.setMode({ kind: "confirm-remove", monitor });
|
|
101
|
+
}
|
|
102
|
+
handleConfirm(data, monitor) {
|
|
103
|
+
if (data === "y") void this.perform("remove", monitor);
|
|
104
|
+
else if (data === "n" || this.matches(data, "tui.select.cancel")) this.setMode({ kind: "browse" });
|
|
105
|
+
}
|
|
106
|
+
async perform(action, monitor) {
|
|
107
|
+
this.error = void 0;
|
|
108
|
+
this.setMode({ kind: "busy", label: `${PROGRESS[action]} ${monitor.name}\u2026` });
|
|
109
|
+
try {
|
|
110
|
+
this.monitors = await this.deps.run(action, monitor.id);
|
|
111
|
+
const kept = this.monitors.findIndex((candidate) => candidate.id === monitor.id);
|
|
112
|
+
this.selected = clamp(kept >= 0 ? kept : this.selected, this.monitors.length);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
this.error = error instanceof Error ? error.message : String(error);
|
|
115
|
+
}
|
|
116
|
+
this.setMode({ kind: "browse" });
|
|
117
|
+
}
|
|
118
|
+
select(index) {
|
|
119
|
+
this.selected = clamp(index, this.monitors.length);
|
|
120
|
+
this.deps.requestRender();
|
|
121
|
+
}
|
|
122
|
+
setMode(mode) {
|
|
123
|
+
this.mode = mode;
|
|
124
|
+
this.deps.requestRender();
|
|
125
|
+
}
|
|
126
|
+
matches(data, keybinding) {
|
|
127
|
+
return this.deps.keys.matches(data, keybinding);
|
|
128
|
+
}
|
|
129
|
+
listLines() {
|
|
130
|
+
const { theme } = this.deps;
|
|
131
|
+
if (this.monitors.length === 0) return [` ${theme.fg("dim", "No monitors")}`];
|
|
132
|
+
return this.monitors.flatMap((monitor, index) => {
|
|
133
|
+
const selected = index === this.selected;
|
|
134
|
+
const marker = selected ? theme.fg("accent", "\u203A") : " ";
|
|
135
|
+
const header = ` ${marker} ${statusDot(monitor, theme)} ${monitor.icon} ${theme.fg("text", monitor.describe)} [${statusLabel(monitor)}]`;
|
|
136
|
+
return selected ? [header, ...detailLines(monitor, theme)] : [header];
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
statusLines() {
|
|
140
|
+
const { theme } = this.deps;
|
|
141
|
+
const lines = [];
|
|
142
|
+
if (this.error) lines.push(` ${theme.fg("error", this.error)}`);
|
|
143
|
+
switch (this.mode.kind) {
|
|
144
|
+
case "busy":
|
|
145
|
+
lines.push(` ${theme.fg("muted", this.mode.label)}`);
|
|
146
|
+
break;
|
|
147
|
+
case "confirm-remove":
|
|
148
|
+
lines.push(` ${theme.fg("warning", `Delete ${this.mode.monitor.name}? It stops watching and its record is removed. y/n`)}`);
|
|
149
|
+
break;
|
|
150
|
+
case "browse":
|
|
151
|
+
lines.push(` ${theme.fg("dim", "\u2191\u2193 select \xB7 s stop/start \xB7 d delete \xB7 esc close")}`);
|
|
152
|
+
}
|
|
153
|
+
return lines;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
function clamp(index, length) {
|
|
157
|
+
return Math.min(Math.max(index, 0), Math.max(length - 1, 0));
|
|
158
|
+
}
|
|
159
|
+
function statusLabel(monitor) {
|
|
160
|
+
if (!monitor.enabled) return "stopped";
|
|
161
|
+
return monitor.unresponsive ? "recovering connection" : "healthy";
|
|
162
|
+
}
|
|
163
|
+
function statusDot(monitor, theme) {
|
|
164
|
+
if (!monitor.enabled) return theme.fg("dim", "\u25CF");
|
|
165
|
+
return theme.fg(monitor.unresponsive ? "error" : "success", "\u25CF");
|
|
166
|
+
}
|
|
167
|
+
function detailLines(monitor, theme) {
|
|
168
|
+
const rows = [
|
|
169
|
+
["Name", monitor.name],
|
|
170
|
+
["Delivery", monitor.delivery],
|
|
171
|
+
["Poll interval", `${monitor.pollIntervalSec}s`],
|
|
172
|
+
["Created", formatTime(monitor.createdAt)],
|
|
173
|
+
["Updated", formatTime(monitor.updatedAt)],
|
|
174
|
+
["Last poll", formatTime(monitor.lastPolledAt)],
|
|
175
|
+
["Webhook heartbeat", formatTime(monitor.webhookHeartbeatAt)]
|
|
176
|
+
];
|
|
177
|
+
return rows.map(([label, value]) => theme.fg("muted", ` ${label}: ${value}`));
|
|
178
|
+
}
|
|
179
|
+
function formatTime(value) {
|
|
180
|
+
if (!value) return "never";
|
|
181
|
+
const date = new Date(value);
|
|
182
|
+
if (Number.isNaN(date.getTime())) return value;
|
|
183
|
+
return date.toLocaleString();
|
|
184
|
+
}
|
|
185
|
+
|
|
83
186
|
// adapters/pi/src/index.ts
|
|
84
187
|
var STATUS_REFRESH_MS = 3e3;
|
|
85
188
|
var SOURCE_ICONS = {
|
|
86
189
|
jira: "\u{F0303}",
|
|
87
190
|
github: "\u{F02A4}",
|
|
88
|
-
slack: "\u{F04B1}"
|
|
191
|
+
slack: "\u{F04B1}",
|
|
192
|
+
yab: "\u{F059F}"
|
|
89
193
|
};
|
|
90
194
|
async function sourcefedExtension(pi) {
|
|
91
195
|
const listeners = /* @__PURE__ */ new Map();
|
|
@@ -113,16 +217,20 @@ async function sourcefedExtension(pi) {
|
|
|
113
217
|
pi.registerTool({
|
|
114
218
|
name: "sourcefed_monitor_create",
|
|
115
219
|
label: "Sourcefed monitor create",
|
|
116
|
-
description: "Create or reuse a Jira, GitHub PR, Slack thread,
|
|
220
|
+
description: "Create or reuse a Jira, GitHub PR, Slack thread, Slack DM, or Yab feed monitor for the current Pi session.",
|
|
117
221
|
parameters: Type.Object({
|
|
118
222
|
name: Type.String(),
|
|
119
|
-
sourceType: Type.Unsafe({ type: "string", enum: ["jira", "github", "slack"] }),
|
|
223
|
+
sourceType: Type.Unsafe({ type: "string", enum: ["jira", "github", "slack", "yab"] }),
|
|
120
224
|
issueKey: Type.Optional(Type.String()),
|
|
121
225
|
repo: Type.Optional(Type.String()),
|
|
122
226
|
prNumber: Type.Optional(Type.Number()),
|
|
123
227
|
channelId: Type.Optional(Type.String()),
|
|
124
228
|
threadTs: Type.Optional(Type.String()),
|
|
125
229
|
threadUrl: Type.Optional(Type.String()),
|
|
230
|
+
url: Type.Optional(Type.String()),
|
|
231
|
+
space: Type.Optional(Type.String()),
|
|
232
|
+
match: Type.Optional(Type.String()),
|
|
233
|
+
exclude: Type.Optional(Type.String()),
|
|
126
234
|
pollIntervalSec: Type.Optional(Type.Number())
|
|
127
235
|
}),
|
|
128
236
|
execute: async (_toolCallID, input, _signal, _onUpdate, ctx) => toolResult(await call(ctx, "monitor_create", input))
|
|
@@ -156,12 +264,23 @@ async function sourcefedExtension(pi) {
|
|
|
156
264
|
execute: async (_toolCallID, input, _signal, _onUpdate, ctx) => toolResult(await call(ctx, "monitor_start", input))
|
|
157
265
|
});
|
|
158
266
|
pi.registerCommand("sourcefed", {
|
|
159
|
-
description: "
|
|
267
|
+
description: "Show, stop, start, or delete Sourcefed monitors for the current Pi session",
|
|
160
268
|
handler: async (_args, ctx) => {
|
|
161
|
-
const
|
|
162
|
-
const
|
|
269
|
+
const monitors = await listMonitors(ctx);
|
|
270
|
+
const run = async (action, id) => {
|
|
271
|
+
const result = await call(ctx, `monitor_${action}`, { id });
|
|
272
|
+
if (result?.ok === false) throw new Error(result.error ?? `monitor ${action} failed`);
|
|
273
|
+
void refreshStatus();
|
|
274
|
+
return listMonitors(ctx);
|
|
275
|
+
};
|
|
163
276
|
await ctx.ui.custom(
|
|
164
|
-
(tui, theme,
|
|
277
|
+
(tui, theme, keybindings, done) => new MonitorManager(monitors, {
|
|
278
|
+
theme,
|
|
279
|
+
keys: keybindings,
|
|
280
|
+
run,
|
|
281
|
+
requestRender: () => tui.requestRender(),
|
|
282
|
+
close: () => done(null)
|
|
283
|
+
})
|
|
165
284
|
);
|
|
166
285
|
}
|
|
167
286
|
});
|
|
@@ -185,9 +304,12 @@ async function sourcefedExtension(pi) {
|
|
|
185
304
|
statusTimer = void 0;
|
|
186
305
|
}
|
|
187
306
|
}
|
|
188
|
-
async function
|
|
307
|
+
async function listMonitors(ctx) {
|
|
189
308
|
const result = await call(ctx, "monitor_list", {});
|
|
190
|
-
|
|
309
|
+
return Array.isArray(result?.monitors) ? result.monitors : [];
|
|
310
|
+
}
|
|
311
|
+
async function updateStatus(ctx) {
|
|
312
|
+
const monitors = await listMonitors(ctx);
|
|
191
313
|
const active = monitors.filter((monitor) => monitor.enabled);
|
|
192
314
|
if (active.length === 0) {
|
|
193
315
|
ctx.ui.setStatus("sourcefed", void 0);
|