@xpufx/paseo-forges 0.1.0
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/LICENSE +21 -0
- package/README.md +109 -0
- package/client/active-forge.ts +47 -0
- package/client/board-alert.tsx +236 -0
- package/client/foreign-link.tsx +33 -0
- package/client/hook-queue-panel.tsx +412 -0
- package/client/issues-pill.tsx +1976 -0
- package/client/label-chip.tsx +116 -0
- package/client/linkifier.tsx +280 -0
- package/client/pill-label.ts +183 -0
- package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
- package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
- package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
- package/client/vendor/paseo-plugin-helper/host.ts +277 -0
- package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/index.ts +28 -0
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
- package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
- package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
- package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
- package/client/vendor/paseo-plugin-helper/query.ts +66 -0
- package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
- package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
- package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
- package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
- package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
- package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
- package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
- package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
- package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
- package/client/webhook-card.tsx +198 -0
- package/docs/specs/forge-workflow-gui.md +820 -0
- package/docs/workflow.md +339 -0
- package/examples/README.md +108 -0
- package/examples/hook-service/README.md +66 -0
- package/examples/hook-service/forge-hook.service +28 -0
- package/examples/hook-service/hook-server.mjs +265 -0
- package/examples/hook-service/hook.env.example +21 -0
- package/examples/labels/README.md +64 -0
- package/examples/labels/label-base.yaml +117 -0
- package/examples/skills/coding-agent/SKILL.md +262 -0
- package/examples/skills/coding-agent-fgjx/SKILL.md +271 -0
- package/examples/skills/orchestrator/SKILL.md +133 -0
- package/examples/skills/orchestrator-fgjx/SKILL.md +139 -0
- package/examples/tools/README.md +68 -0
- package/examples/tools/fgjx +464 -0
- package/package.json +38 -0
- package/paseo-plugin.json +4 -0
- package/server/forge-client.ts +484 -0
- package/server/forge-guard.ts +70 -0
- package/server/git-origin.ts +70 -0
- package/server/hook-queue.ts +127 -0
- package/server/issues.ts +542 -0
- package/server/settings.ts +52 -0
- package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
- package/server/vendor/paseo-plugin-helper/index.ts +18 -0
- package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
- package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
- package/server/vendor/paseo-plugin-helper/network.ts +91 -0
- package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
- package/server/vendor/paseo-plugin-helper/process.ts +186 -0
- package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
- package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
- package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
- package/server/vendor/paseo-plugin-helper/system.ts +128 -0
- package/server/vendor/paseo-plugin-helper/task.ts +116 -0
- package/server/vendor/paseo-plugin-helper/version.ts +153 -0
- package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
- package/shared/hook-queue.ts +140 -0
- package/shared/issues.ts +1750 -0
- package/shared/vendor/paseo-plugin-helper/README.md +11 -0
- package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
- package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
- package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
- package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
- package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
- package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
- package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
- package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
- package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
- package/shared/version.ts +2 -0
- package/shared/webhook.ts +223 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Forgejo webhook wire envelope: a `[forgejo-hook] ` prefixed JSON block
|
|
5
|
+
* (see hook-service history) followed by a blank line and the human `summarize()` line.
|
|
6
|
+
* Pure parsing with no UI imports so the timeline transformer and its tests run
|
|
7
|
+
* anywhere. Until the hook emits the envelope we also recognize today's
|
|
8
|
+
* plain summary line and normalize both sources to one card shape.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const FORGEJO_HOOK_PREFIX = "[forgejo-hook] ";
|
|
12
|
+
|
|
13
|
+
export const ForgejoSubjectSchema = z.object({
|
|
14
|
+
kind: z.string(),
|
|
15
|
+
number: z.number().optional(),
|
|
16
|
+
title: z.string().optional(),
|
|
17
|
+
url: z.string().optional(),
|
|
18
|
+
commentId: z.number().optional(),
|
|
19
|
+
ref: z.string().optional(),
|
|
20
|
+
commits: z.number().optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const ForgejoEnvelopeSchema = z.object({
|
|
24
|
+
forgejo: z.object({
|
|
25
|
+
version: z.number(),
|
|
26
|
+
event: z.string(),
|
|
27
|
+
action: z.string().default(""),
|
|
28
|
+
repo: z.string(),
|
|
29
|
+
repoUrl: z.string().optional(),
|
|
30
|
+
sender: z.string(),
|
|
31
|
+
subject: ForgejoSubjectSchema.optional(),
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type ForgejoEnvelope = z.infer<typeof ForgejoEnvelopeSchema>;
|
|
36
|
+
export type ForgejoSubject = z.infer<typeof ForgejoSubjectSchema>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Normalized card data shared by the envelope and summary-line paths. This is a
|
|
40
|
+
* display-only shape: every field the wire envelope carries is mapped here, and
|
|
41
|
+
* the raw message text is deliberately absent. The transformer never rewrites
|
|
42
|
+
* the source `AgentTimelineItem`, so the human line stays in the agent's
|
|
43
|
+
* context (see `PluginTimelineTransformerContribution`).
|
|
44
|
+
*/
|
|
45
|
+
export const forgejoWebhookCardSchema = z.object({
|
|
46
|
+
event: z.string(),
|
|
47
|
+
action: z.string(),
|
|
48
|
+
repo: z.string(),
|
|
49
|
+
repoUrl: z.string().optional(),
|
|
50
|
+
sender: z.string(),
|
|
51
|
+
subject: ForgejoSubjectSchema.nullable(),
|
|
52
|
+
version: z.number().optional(),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export type ForgejoWebhookCardData = z.infer<typeof forgejoWebhookCardSchema>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The exact target a subject title should link to. Issue/PR comments arrive
|
|
59
|
+
* with the anchor in the URL from the summary line but as a separate
|
|
60
|
+
* `commentId` in the envelope, so append the anchor when it is missing.
|
|
61
|
+
*/
|
|
62
|
+
export function subjectLinkUrl(subject: ForgejoSubject): string | undefined {
|
|
63
|
+
if (!subject.url) return undefined;
|
|
64
|
+
if (subject.commentId != null && !subject.url.includes(`#issuecomment-${subject.commentId}`)) {
|
|
65
|
+
return `${subject.url}#issuecomment-${subject.commentId}`;
|
|
66
|
+
}
|
|
67
|
+
return subject.url;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Splits an envelope-stamped message into its structured envelope and the
|
|
72
|
+
* remaining human text. The prefix is the signal we render on; malformed JSON
|
|
73
|
+
* or a payload that fails the schema returns null so callers fall back to the
|
|
74
|
+
* summary parser.
|
|
75
|
+
*/
|
|
76
|
+
export function parseForgejoWebhookEnvelope(
|
|
77
|
+
text: string,
|
|
78
|
+
): { envelope: ForgejoEnvelope; body: string } | null {
|
|
79
|
+
if (typeof text !== "string" || !text.startsWith(FORGEJO_HOOK_PREFIX)) return null;
|
|
80
|
+
const rest = text.slice(FORGEJO_HOOK_PREFIX.length).trimStart();
|
|
81
|
+
const sep = rest.indexOf("\n\n");
|
|
82
|
+
const json = sep === -1 ? rest : rest.slice(0, sep);
|
|
83
|
+
const body = sep === -1 ? "" : rest.slice(sep + 2).trim();
|
|
84
|
+
let raw: unknown;
|
|
85
|
+
try {
|
|
86
|
+
raw = JSON.parse(json);
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const parsed = ForgejoEnvelopeSchema.safeParse(raw);
|
|
91
|
+
if (!parsed.success) return null;
|
|
92
|
+
return { envelope: parsed.data, body };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const HOOK_HEAD = "🔔 Forgejo webhook incoming";
|
|
96
|
+
|
|
97
|
+
function subjectKindForEvent(event: string): string {
|
|
98
|
+
if (event === "issues") return "issue";
|
|
99
|
+
if (event === "issue_comment" || event === "pull_request" || event === "push") return event;
|
|
100
|
+
return "unknown";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function commentIdFromUrl(url: string | undefined): number | undefined {
|
|
104
|
+
if (!url) return undefined;
|
|
105
|
+
const match = /#issuecomment-(\d+)/.exec(url);
|
|
106
|
+
return match ? Number(match[1]) : undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Parse today's plain summary line
|
|
111
|
+
* (`🔔 Forgejo webhook incoming [event] repo (by sender) url`) into the same
|
|
112
|
+
* card shape as the envelope. Returns null for anything that is not a hook
|
|
113
|
+
* message, leaving non-hook chat untouched.
|
|
114
|
+
*/
|
|
115
|
+
export function parseForgejoWebhookSummary(text: string): ForgejoWebhookCardData | null {
|
|
116
|
+
if (typeof text !== "string") return null;
|
|
117
|
+
const trimmed = text.trim();
|
|
118
|
+
if (!trimmed.startsWith(HOOK_HEAD)) return null;
|
|
119
|
+
const rest = trimmed.slice(HOOK_HEAD.length).trim();
|
|
120
|
+
|
|
121
|
+
const ping = /^\(ping test\)\s+(.+?)\s+\(by\s+(.+?)\)\s*$/.exec(rest);
|
|
122
|
+
if (ping) {
|
|
123
|
+
return {
|
|
124
|
+
event: "ping",
|
|
125
|
+
action: "",
|
|
126
|
+
repo: ping[1].trim(),
|
|
127
|
+
sender: ping[2].trim(),
|
|
128
|
+
subject: null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const bracket = /^\[([^\]\s:]+)(?::([^\]]*))?\]\s+([\s\S]+)$/.exec(rest);
|
|
133
|
+
if (!bracket) return null;
|
|
134
|
+
const event = bracket[1].trim();
|
|
135
|
+
const action = (bracket[2] ?? "").trim();
|
|
136
|
+
const tail = bracket[3].trim();
|
|
137
|
+
|
|
138
|
+
const base = { event, action, sender: "", subject: null as ForgejoSubject | null };
|
|
139
|
+
|
|
140
|
+
if (event === "push") {
|
|
141
|
+
const push = /^(.*?)\s+(\d+)\s+commit\(s\)\s+by\s+(.+?)(?:\s+(\S+))?$/.exec(tail);
|
|
142
|
+
if (!push) return null;
|
|
143
|
+
const [repo, ...refParts] = push[1].trim().split(/\s+/);
|
|
144
|
+
const commits = Number(push[2]);
|
|
145
|
+
const sender = push[3].trim();
|
|
146
|
+
const url = push[4];
|
|
147
|
+
return {
|
|
148
|
+
...base,
|
|
149
|
+
repo,
|
|
150
|
+
sender,
|
|
151
|
+
...(url ? { repoUrl: url } : {}),
|
|
152
|
+
subject: {
|
|
153
|
+
kind: "push",
|
|
154
|
+
commits,
|
|
155
|
+
...(refParts.length ? { ref: refParts.join(" ") } : {}),
|
|
156
|
+
...(url ? { url } : {}),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const issue = /^(.+?)#(\d+|\?)(?:\s+(.*?))?\s*\(by\s+(.+?)\)(?:\s+(\S+))?$/.exec(tail);
|
|
162
|
+
if (issue) {
|
|
163
|
+
const number = issue[2] === "?" ? undefined : Number(issue[2]);
|
|
164
|
+
const title = (issue[3] ?? "").trim();
|
|
165
|
+
const url = issue[5];
|
|
166
|
+
return {
|
|
167
|
+
...base,
|
|
168
|
+
repo: issue[1].trim(),
|
|
169
|
+
sender: issue[4].trim(),
|
|
170
|
+
...(url ? { repoUrl: url } : {}),
|
|
171
|
+
subject: {
|
|
172
|
+
kind: subjectKindForEvent(event),
|
|
173
|
+
...(number != null ? { number } : {}),
|
|
174
|
+
...(title ? { title } : {}),
|
|
175
|
+
...(url ? { url } : {}),
|
|
176
|
+
...(commentIdFromUrl(url) != null ? { commentId: commentIdFromUrl(url) } : {}),
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const generic = /^(.+?)\s+\(by\s+(.+?)\)(?:\s+(\S+))?$/.exec(tail);
|
|
182
|
+
if (!generic) return null;
|
|
183
|
+
const url = generic[3];
|
|
184
|
+
return {
|
|
185
|
+
...base,
|
|
186
|
+
repo: generic[1].trim(),
|
|
187
|
+
sender: generic[2].trim(),
|
|
188
|
+
...(url ? { repoUrl: url } : {}),
|
|
189
|
+
subject: null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function envelopeToCard(envelope: ForgejoEnvelope): ForgejoWebhookCardData {
|
|
194
|
+
const { forgejo } = envelope;
|
|
195
|
+
return {
|
|
196
|
+
event: forgejo.event,
|
|
197
|
+
action: forgejo.action,
|
|
198
|
+
repo: forgejo.repo,
|
|
199
|
+
...(forgejo.repoUrl ? { repoUrl: forgejo.repoUrl } : {}),
|
|
200
|
+
sender: forgejo.sender,
|
|
201
|
+
subject: forgejo.subject ?? null,
|
|
202
|
+
version: forgejo.version,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Normalize either the v1 envelope or today's summary line to card data. */
|
|
207
|
+
export function parseForgejoWebhookCard(text: string): ForgejoWebhookCardData | null {
|
|
208
|
+
const stamped = parseForgejoWebhookEnvelope(text);
|
|
209
|
+
if (stamped) return envelopeToCard(stamped.envelope);
|
|
210
|
+
return parseForgejoWebhookSummary(text);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Build the single plugin timeline item for a hook message, or undefined so
|
|
215
|
+
* earlier transformers leave non-hook messages to the next claimer.
|
|
216
|
+
*/
|
|
217
|
+
export function forgejoWebhookItem(text: string) {
|
|
218
|
+
const data = parseForgejoWebhookCard(text);
|
|
219
|
+
if (!data) return undefined;
|
|
220
|
+
return {
|
|
221
|
+
items: [{ type: "plugin" as const, kind: "forgejo-webhook", version: 1, data }],
|
|
222
|
+
};
|
|
223
|
+
}
|