@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.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/client/active-forge.ts +47 -0
  4. package/client/board-alert.tsx +236 -0
  5. package/client/foreign-link.tsx +33 -0
  6. package/client/hook-queue-panel.tsx +412 -0
  7. package/client/issues-pill.tsx +1976 -0
  8. package/client/label-chip.tsx +116 -0
  9. package/client/linkifier.tsx +280 -0
  10. package/client/pill-label.ts +183 -0
  11. package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
  12. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
  13. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
  14. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
  15. package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
  16. package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
  17. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
  18. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
  19. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
  20. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
  21. package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
  22. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
  23. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
  24. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
  25. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
  26. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
  27. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
  28. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
  29. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
  30. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
  31. package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
  32. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
  33. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
  34. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
  35. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
  36. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
  37. package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
  38. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
  39. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
  40. package/client/vendor/paseo-plugin-helper/host.ts +277 -0
  41. package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
  42. package/client/vendor/paseo-plugin-helper/index.ts +28 -0
  43. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
  44. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
  45. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
  46. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
  47. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
  48. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
  49. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
  50. package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
  51. package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
  52. package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
  53. package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
  54. package/client/vendor/paseo-plugin-helper/query.ts +66 -0
  55. package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
  56. package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
  57. package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
  58. package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
  59. package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
  60. package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
  61. package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
  62. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
  63. package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
  64. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
  65. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
  66. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
  67. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
  68. package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
  69. package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
  70. package/client/webhook-card.tsx +198 -0
  71. package/docs/specs/forge-workflow-gui.md +820 -0
  72. package/docs/workflow.md +339 -0
  73. package/examples/README.md +108 -0
  74. package/examples/hook-service/README.md +66 -0
  75. package/examples/hook-service/forge-hook.service +28 -0
  76. package/examples/hook-service/hook-server.mjs +265 -0
  77. package/examples/hook-service/hook.env.example +21 -0
  78. package/examples/labels/README.md +64 -0
  79. package/examples/labels/label-base.yaml +117 -0
  80. package/examples/skills/coding-agent/SKILL.md +262 -0
  81. package/examples/skills/coding-agent-fgjx/SKILL.md +271 -0
  82. package/examples/skills/orchestrator/SKILL.md +133 -0
  83. package/examples/skills/orchestrator-fgjx/SKILL.md +139 -0
  84. package/examples/tools/README.md +68 -0
  85. package/examples/tools/fgjx +464 -0
  86. package/package.json +38 -0
  87. package/paseo-plugin.json +4 -0
  88. package/server/forge-client.ts +484 -0
  89. package/server/forge-guard.ts +70 -0
  90. package/server/git-origin.ts +70 -0
  91. package/server/hook-queue.ts +127 -0
  92. package/server/issues.ts +542 -0
  93. package/server/settings.ts +52 -0
  94. package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
  95. package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
  96. package/server/vendor/paseo-plugin-helper/index.ts +18 -0
  97. package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
  98. package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
  99. package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
  100. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
  101. package/server/vendor/paseo-plugin-helper/network.ts +91 -0
  102. package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
  103. package/server/vendor/paseo-plugin-helper/process.ts +186 -0
  104. package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
  105. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
  106. package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
  107. package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
  108. package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
  109. package/server/vendor/paseo-plugin-helper/system.ts +128 -0
  110. package/server/vendor/paseo-plugin-helper/task.ts +116 -0
  111. package/server/vendor/paseo-plugin-helper/version.ts +153 -0
  112. package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
  113. package/shared/hook-queue.ts +140 -0
  114. package/shared/issues.ts +1750 -0
  115. package/shared/vendor/paseo-plugin-helper/README.md +11 -0
  116. package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
  117. package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
  118. package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
  119. package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
  120. package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
  121. package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
  122. package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
  123. package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
  124. package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
  125. package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
  126. package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
  127. package/shared/version.ts +2 -0
  128. 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
+ }