@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,464 @@
1
+ #!/bin/bash
2
+ # fgjx: fills fgj display gaps via `fgj api` and adds agent lifecycle workflow extensions.
3
+ #
4
+ # Sanitized example copy of the fgjx wrapper script
5
+ # (see tools/README.md). fgjx is only a shim: `fgj` is the authenticated transport
6
+ # that owns the host URL/token and performs the raw /api/v1 calls, so fgj must be
7
+ # available or the wrapper cannot do anything.
8
+ set -u
9
+
10
+ # Put fgj's off-PATH home on PATH so the wrapper finds it without a global install.
11
+ export PATH="$HOME/bin/.lib:$PATH"
12
+
13
+ if ! command -v fgj >/dev/null 2>&1; then
14
+ printf '%s\n' "fgjx: required backend 'fgj' not found on PATH." >&2
15
+ printf '%s\n' "fgjx only adds board-shaped verbs (labels, tables, envelope); fgj supplies the" >&2
16
+ printf '%s\n' "host/token config and does the raw /api/v1 calls. Install fgj, put it on PATH" >&2
17
+ printf '%s\n' "(or in \$HOME/bin/.lib), then retry." >&2
18
+ exit 127
19
+ fi
20
+
21
+ # Embedded helper functions for general-purpose Forgejo operations
22
+ _fgjx_parse_repo_name() {
23
+ local url="${1:-}"
24
+ [ -z "$url" ] && return 0
25
+ python3 -c 'import sys, re; m = re.search(r"[:/]([^/]+/[^/]+?)(?:\.git)?$", sys.argv[1]); print(m.group(1)) if m else None' "$url"
26
+ }
27
+
28
+ _fgjx_format_body() {
29
+ python3 -c '
30
+ import sys, textwrap
31
+
32
+ def wrap_body(body, width=100):
33
+ out = []
34
+ in_fence = False
35
+ for line in body.split("\n"):
36
+ s = line
37
+ if s.strip().startswith("```"):
38
+ in_fence = not in_fence
39
+ out.append(s)
40
+ continue
41
+ if in_fence or not s.strip() or len(s) <= width or s.startswith((" ", "\t", "> ", "| ", "- ", "* ", "#")):
42
+ out.append(s)
43
+ else:
44
+ out.append(textwrap.fill(s, width=width))
45
+ return "\n".join(out).rstrip() + "\n"
46
+
47
+ sys.stdout.write(wrap_body(sys.stdin.read()))
48
+ '
49
+ }
50
+
51
+ _fgjx_render_table() {
52
+ python3 -c '
53
+ import json, os, sys
54
+
55
+ try:
56
+ rows = json.load(sys.stdin)
57
+ except Exception:
58
+ sys.exit(1)
59
+
60
+ notby = os.environ.get("NOTBY") or ""
61
+ sortby = os.environ.get("SORTBY") or ""
62
+ since = os.environ.get("SINCE") or ""
63
+
64
+ if notby:
65
+ rows = [i for i in rows if ((i.get("user") or {}).get("login") != notby)]
66
+ if since:
67
+ rows = [i for i in rows if (i.get("updated_at") or "") >= since]
68
+ if sortby in ("updated", "created"):
69
+ rows.sort(key=lambda i: i.get(sortby + "_at") or "", reverse=True)
70
+
71
+ print("%-6s %-38s %-9s %-10s %s" % ("NUMBER", "LABELS", "STATE", "BY", "TITLE"))
72
+ for i in rows:
73
+ labs = ",".join(l["name"] for l in i.get("labels", []))
74
+ by = (i.get("user") or {}).get("login", "?")
75
+ print("%-6d %-38.38s %-9s %-10.10s %s" % (i["number"], labs, i["state"], by, i["title"][:50]))
76
+ '
77
+ }
78
+
79
+ _fgjx_render_view() {
80
+ python3 -c '
81
+ import json, os, sys, textwrap
82
+
83
+ try:
84
+ raw = json.load(sys.stdin)
85
+ except Exception:
86
+ sys.exit(1)
87
+
88
+ d = raw.get("issue", raw)
89
+ print("Issue #%d: %s" % (d["number"], d["title"]))
90
+ print("State: %s Labels: %s" % (d["state"], ", ".join(l["name"] for l in d.get("labels", [])) or "(none)"))
91
+ print("---")
92
+ print(d.get("body") or "(no body)")
93
+
94
+ comments_raw = os.environ.get("COMMENTS_JSON") or ""
95
+ try:
96
+ comments = json.loads(comments_raw) if comments_raw.strip().startswith("[") else []
97
+ except Exception:
98
+ comments = []
99
+
100
+ def wrap_body(body, width=100):
101
+ out = []
102
+ in_fence = False
103
+ for line in body.split("\n"):
104
+ s = line
105
+ if s.strip().startswith("```"):
106
+ in_fence = not in_fence
107
+ out.append(s)
108
+ continue
109
+ if in_fence or not s.strip() or len(s) <= width or s.startswith((" ", "\t", "> ", "| ", "- ", "* ", "#")):
110
+ out.append(s)
111
+ else:
112
+ out.append(textwrap.fill(s, width=width))
113
+ return "\n".join(out).rstrip()
114
+
115
+ if comments:
116
+ print("--- Comments (%d) ---" % len(comments))
117
+ for c in comments:
118
+ u = c.get("user") or {}
119
+ name = u.get("full_name") or u.get("login") or "?"
120
+ login = u.get("login") or "?"
121
+ who = "%s (@%s)" % (name, login) if name and name != login else login
122
+ print("")
123
+ print("[%s | %s]" % (who, (c.get("created_at") or "?")[:19].replace("T", " ")))
124
+ print(wrap_body(c.get("body") or "(no body)"))
125
+ '
126
+ }
127
+
128
+ _fgjx_label_edit() {
129
+ EDIT_REPO="$1" EDIT_NUM="$2" EDIT_HOST_ARG="$3" EDIT_ADDS="$4" EDIT_REMS="$5" python3 -c '
130
+ import json, sys, subprocess, os
131
+
132
+ repo = os.environ.get("EDIT_REPO")
133
+ num = os.environ.get("EDIT_NUM")
134
+ host_arg = os.environ.get("EDIT_HOST_ARG")
135
+
136
+ def split_names(raw):
137
+ # Accept both repeated flags and comma-joined values: "a,b" or "a\nb".
138
+ out = []
139
+ for line in (raw or "").split("\n"):
140
+ for part in line.split(","):
141
+ part = part.strip()
142
+ if part:
143
+ out.append(part)
144
+ return out
145
+
146
+ adds = split_names(os.environ.get("EDIT_ADDS"))
147
+ rems = split_names(os.environ.get("EDIT_REMS"))
148
+ h_tokens = host_arg.split() if host_arg else []
149
+
150
+ # Page the label fetch: repos can have more than one page of labels (#197).
151
+ name_to_id = {}
152
+ page = 1
153
+ while True:
154
+ cmd = ["fgj", "api", f"repos/{repo}/labels?limit=50&page={page}"] + h_tokens
155
+ res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
156
+ if res.returncode != 0:
157
+ sys.stderr.write(f"fgjx: label fetch failed (page {page}): {res.stderr.strip()}\n")
158
+ sys.exit(1)
159
+ try:
160
+ batch = json.loads(res.stdout)
161
+ except Exception:
162
+ sys.stderr.write("fgjx: label fetch returned invalid JSON\n")
163
+ sys.exit(1)
164
+ if not batch:
165
+ break
166
+ for label in batch:
167
+ name_to_id[label["name"].lower()] = label["id"]
168
+ if len(batch) < 50:
169
+ break
170
+ page += 1
171
+
172
+ # Resolve everything before writing: an unknown name is a hard error, not a skip.
173
+ missing = [n for n in (adds + rems) if n.lower() not in name_to_id]
174
+ if missing:
175
+ sys.stderr.write("fgjx: unknown label(s): %s\n" % ", ".join(missing))
176
+ sys.exit(1)
177
+
178
+ for name in adds:
179
+ lid = name_to_id[name.lower()]
180
+ p_cmd = ["fgj", "api", "-X", "POST", f"repos/{repo}/issues/{num}/labels"] + h_tokens + ["--input", "-"]
181
+ p = subprocess.Popen(p_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
182
+ p.communicate(json.dumps({"labels": [lid]}).encode("utf-8"))
183
+
184
+ for name in rems:
185
+ lid = name_to_id[name.lower()]
186
+ d_cmd = ["fgj", "api", "-X", "DELETE", f"repos/{repo}/issues/{num}/labels/{lid}"] + h_tokens
187
+ subprocess.run(d_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
188
+ '
189
+ }
190
+
191
+ # Resolve the optional envelope generator. The core wrapper never needs it; only
192
+ # `--envelope` does. Override the binary with $ENVELOPE_TOOL.
193
+ _fgjx_envelope() {
194
+ if [ -n "${ENVELOPE_TOOL:-}" ] && command -v "$ENVELOPE_TOOL" >/dev/null 2>&1; then
195
+ "$ENVELOPE_TOOL" envelope
196
+ elif command -v envelope-tool >/dev/null 2>&1; then
197
+ envelope-tool envelope
198
+ elif [ -x "$HOME/bin/envelope-tool" ]; then
199
+ "$HOME/bin/envelope-tool" envelope
200
+ else
201
+ printf '<sub>🤖 agent · %s</sub>' "$(date -u +%FT%TZ)"
202
+ fi
203
+ }
204
+
205
+ # Parse leading global flags so flags like `--hostname` or `-R` can appear before or after subcommands
206
+ GLOBAL_PRE_FLAGS=()
207
+ POSITIONAL_ARGS=()
208
+ while [ $# -gt 0 ]; do
209
+ case "$1" in
210
+ --hostname|-R|--repo|--config)
211
+ GLOBAL_PRE_FLAGS+=("$1" "$2")
212
+ shift 2
213
+ ;;
214
+ --hostname=*|-R=*|--repo=*|--config=*)
215
+ GLOBAL_PRE_FLAGS+=("$1")
216
+ shift
217
+ ;;
218
+ *)
219
+ POSITIONAL_ARGS+=("$1")
220
+ shift
221
+ ;;
222
+ esac
223
+ done
224
+
225
+ set -- "${POSITIONAL_ARGS[@]}" "${GLOBAL_PRE_FLAGS[@]}"
226
+ CMD="${1:-}"
227
+ SUB="${2:-}"
228
+
229
+ FGJX_NOTE='
230
+ fgjx extensions:
231
+ issue list Adds a LABELS column (native table omits labels).
232
+ issue view Shows labels header above the body, plus formatted comments.
233
+ issue create --envelope
234
+ Append clean agent envelope badge footer to new issue.
235
+ issue comment --format
236
+ Wrap the -b/--body text at 100 cols before posting.
237
+ issue comment --agent-envelope (alias --envelope)
238
+ Append clean agent envelope badge footer.
239
+ issue edit --add-label / --remove-label
240
+ Add/remove labels via API. Comma-joined values are split,
241
+ and an unknown label name fails non-zero.
242
+ issue list flags --not-by <login>, --sort updated|created, --since <date>.'
243
+
244
+ want_help=0
245
+ for a in "$@"; do
246
+ case "$a" in
247
+ -h|--help|help) want_help=1; break;;
248
+ esac
249
+ done
250
+ if [ "$want_help" = 1 ]; then
251
+ fgj "$@"
252
+ rc=$?
253
+ case "$*" in
254
+ *"issue list"*|*"issue view"*|*"issue "*)
255
+ printf '\n%s\n' "$FGJX_NOTE";;
256
+ *)
257
+ if [ -z "$CMD" ] || [ "$CMD" = "-h" ] || [ "$CMD" = "--help" ]; then
258
+ printf '\nfgjx: display-gap shim over fgj (labels in issue list/view).%s\n' "$FGJX_NOTE"
259
+ fi;;
260
+ esac
261
+ exit $rc
262
+ fi
263
+
264
+ # 1. issue list
265
+ if [ "$CMD" = "issue" ] && [ "$SUB" = "list" ]; then
266
+ shift 2
267
+ if printf '%s\n' "$@" | grep -q -- '--json'; then
268
+ exec fgj issue list "$@"
269
+ fi
270
+ NOTBY=""; SORTBY=""; SINCE=""; FWD=()
271
+ while [ $# -gt 0 ]; do
272
+ case "$1" in
273
+ --not-by) NOTBY="$2"; shift 2;;
274
+ --sort) SORTBY="$2"; shift 2;;
275
+ --since) SINCE="$2"; shift 2;;
276
+ *) FWD+=("$1"); shift;;
277
+ esac
278
+ done
279
+ OUT=$(fgj issue list --json "${FWD[@]}" 2>/dev/null) || { echo "fgjx: fgj call failed" >&2; exit 1; }
280
+ NOTBY="$NOTBY" SORTBY="$SORTBY" SINCE="$SINCE" echo "$OUT" | _fgjx_render_table
281
+ exit 0
282
+ fi
283
+
284
+ # 1b. issue create
285
+ if [ "$CMD" = "issue" ] && [ "$SUB" = "create" ]; then
286
+ use_format=0; use_envelope=0
287
+ for a in "$@"; do
288
+ case "$a" in
289
+ --format) use_format=1;;
290
+ --agent-envelope|--envelope) use_envelope=1;;
291
+ esac
292
+ done
293
+ if [ "$use_format" = 0 ] && [ "$use_envelope" = 0 ]; then
294
+ exec fgj "$@"
295
+ fi
296
+ shift 2
297
+ BODY=""; BODYPENDING=""; REST=()
298
+ for a in "$@"; do
299
+ if [ -n "$BODYPENDING" ]; then
300
+ BODY="$a"; BODYPENDING=""
301
+ else
302
+ case "$a" in
303
+ -b|--body) BODYPENDING=1;;
304
+ --body=*) BODY="${a#--body=}";;
305
+ --format|--agent-envelope|--envelope) ;;
306
+ *) REST+=("$a");;
307
+ esac
308
+ fi
309
+ done
310
+ if [ -z "$BODY" ]; then
311
+ exec fgj issue create "${REST[@]}"
312
+ fi
313
+ if [ "$use_format" = 1 ]; then
314
+ BODY=$(printf '%s' "$BODY" | _fgjx_format_body)
315
+ fi
316
+ if [ "$use_envelope" = 1 ]; then
317
+ ENVELOPE=$(_fgjx_envelope)
318
+ BODY=$(printf '%s
319
+
320
+ ---
321
+ %s' "$BODY" "$ENVELOPE")
322
+ fi
323
+ exec fgj issue create -b "$BODY" "${REST[@]}"
324
+ fi
325
+
326
+ # 2. issue comment
327
+ if [ "$CMD" = "issue" ] && [ "$SUB" = "comment" ]; then
328
+ use_format=0; use_envelope=0
329
+ for a in "$@"; do
330
+ case "$a" in
331
+ --format) use_format=1;;
332
+ --agent-envelope|--envelope) use_envelope=1;;
333
+ esac
334
+ done
335
+ if [ "$use_format" = 0 ] && [ "$use_envelope" = 0 ]; then
336
+ exec fgj "$@"
337
+ fi
338
+ shift 2
339
+ NUM=""; BODY=""; BODYPENDING=""; REST=()
340
+ for a in "$@"; do
341
+ if [ -n "$BODYPENDING" ]; then
342
+ BODY="$a"; BODYPENDING=""
343
+ else
344
+ case "$a" in
345
+ -b|--body) BODYPENDING=1;;
346
+ --body=*) BODY="${a#--body=}";;
347
+ --format|--agent-envelope|--envelope) ;;
348
+ [0-9]*) [ -z "$NUM" ] && NUM="$a" || REST+=("$a");;
349
+ *) REST+=("$a");;
350
+ esac
351
+ fi
352
+ done
353
+ if [ -z "$BODY" ]; then
354
+ exec fgj issue comment "$NUM" "${REST[@]}"
355
+ fi
356
+ if [ "$use_format" = 1 ]; then
357
+ BODY=$(printf '%s' "$BODY" | _fgjx_format_body)
358
+ fi
359
+ if [ "$use_envelope" = 1 ]; then
360
+ ENVELOPE=$(_fgjx_envelope)
361
+ BODY=$(printf '%s\n\n---\n%s' "$BODY" "$ENVELOPE")
362
+ fi
363
+ exec fgj issue comment "$NUM" -b "$BODY" "${REST[@]}"
364
+ fi
365
+
366
+ # 3. issue edit
367
+ if [ "$CMD" = "issue" ] && [ "$SUB" = "edit" ]; then
368
+ shift 2
369
+ NUM=""
370
+ ADD_LABELS=()
371
+ REM_LABELS=()
372
+ REST_FLAGS=()
373
+ REPO=""
374
+ HOST=""
375
+ prev=""
376
+ for a in "$@"; do
377
+ if [ -n "$prev" ]; then
378
+ case "$prev" in
379
+ --add-label) ADD_LABELS+=("$a"); prev=""; continue ;;
380
+ --remove-label) REM_LABELS+=("$a"); prev=""; continue ;;
381
+ -R|--repo) REPO="$a"; REST_FLAGS+=("-R" "$a"); prev=""; continue ;;
382
+ --hostname) HOST="$a"; REST_FLAGS+=("--hostname" "$a"); prev=""; continue ;;
383
+ esac
384
+ fi
385
+ case "$a" in
386
+ --add-label|--remove-label|-R|--repo|--hostname) prev="$a" ;;
387
+ --add-label=*) ADD_LABELS+=("${a#--add-label=}") ;;
388
+ --remove-label=*) REM_LABELS+=("${a#--remove-label=}") ;;
389
+ -R=*|--repo=*) REPO="${a#*=}"; REST_FLAGS+=("$a") ;;
390
+ --hostname=*) HOST="${a#*=}"; REST_FLAGS+=("$a") ;;
391
+ [0-9]*) [ -z "$NUM" ] && NUM="$a" || REST_FLAGS+=("$a") ;;
392
+ *) REST_FLAGS+=("$a") ;;
393
+ esac
394
+ done
395
+
396
+ if [ -z "$REPO" ]; then
397
+ origin_url=$(git remote get-url origin 2>/dev/null || true)
398
+ REPO=$(_fgjx_parse_repo_name "$origin_url")
399
+ fi
400
+
401
+ if [ -n "$NUM" ] && [ -n "$REPO" ] && { [ ${#ADD_LABELS[@]} -gt 0 ] || [ ${#REM_LABELS[@]} -gt 0 ]; }; then
402
+ api_host_arg=""
403
+ [ -n "$HOST" ] && api_host_arg="--hostname $HOST"
404
+ if ! _fgjx_label_edit "$REPO" "$NUM" "$api_host_arg" "$(printf '%s\n' "${ADD_LABELS[@]}")" "$(printf '%s\n' "${REM_LABELS[@]}")"; then
405
+ echo "fgjx: label update failed; no labels were changed" >&2
406
+ exit 1
407
+ fi
408
+ fi
409
+
410
+ HAS_REAL_EDITS=0
411
+ for f in "${REST_FLAGS[@]}"; do
412
+ case "$f" in
413
+ --title*|--body*|--state*) HAS_REAL_EDITS=1 ;;
414
+ esac
415
+ done
416
+ if [ "$HAS_REAL_EDITS" = 1 ]; then
417
+ exec fgj issue edit "$NUM" "${REST_FLAGS[@]}"
418
+ else
419
+ echo "Issue #$NUM updated"
420
+ exit 0
421
+ fi
422
+ fi
423
+
424
+ # 4. issue view
425
+ if [ "$CMD" = "issue" ] && [ "$SUB" = "view" ]; then
426
+ shift 2
427
+ NUM=""
428
+ REST=()
429
+ for a in "$@"; do
430
+ case "$a" in
431
+ [0-9]*) [ -z "$NUM" ] && NUM="$a" || REST+=("$a");;
432
+ *) REST+=("$a");;
433
+ esac
434
+ done
435
+ if printf '%s\n' "${REST[@]}" | grep -q -- '--json'; then
436
+ exec fgj issue view "$NUM" "${REST[@]}"
437
+ fi
438
+ OUT=$(fgj issue view "$NUM" --json "${REST[@]}" 2>/dev/null) || { echo "fgjx: fgj call failed" >&2; exit 1; }
439
+ REPO=""; HOST=""; prev=""
440
+ for a in "${REST[@]}"; do
441
+ case "$prev" in
442
+ -R|--repo) REPO="$a";;
443
+ --hostname) HOST="$a";;
444
+ esac
445
+ prev="$a"
446
+ done
447
+ if [ -z "$REPO" ]; then
448
+ origin_url=$(git remote get-url origin 2>/dev/null || true)
449
+ REPO=$(_fgjx_parse_repo_name "$origin_url")
450
+ fi
451
+ COMMENTS_JSON=""
452
+ if [ -n "$REPO" ] && [ -n "$NUM" ]; then
453
+ if [ -n "$HOST" ]; then
454
+ COMMENTS_JSON=$(fgj api "repos/$REPO/issues/$NUM/comments" --hostname "$HOST" 2>/dev/null || true)
455
+ else
456
+ COMMENTS_JSON=$(fgj api "repos/$REPO/issues/$NUM/comments" 2>/dev/null || true)
457
+ fi
458
+ fi
459
+ export COMMENTS_JSON
460
+ echo "$OUT" | _fgjx_render_view
461
+ exit 0
462
+ fi
463
+
464
+ exec fgj "$@"
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@xpufx/paseo-forges",
3
+ "version": "0.1.0",
4
+ "description": "Work with Forge/Gitea-family issues from inside Paseo via the embedded fetch API client.",
5
+ "type": "module",
6
+ "private": false,
7
+ "license": "MIT",
8
+ "scripts": {
9
+ "stamp": "node -e \"import('./server/vendor/paseo-plugin-helper/version.ts').then(m => m.stampVersion({ targetFile: 'shared/version.ts' }))\"",
10
+ "typecheck": "tsc --noEmit",
11
+ "test": "esbuild shared/issues.test.ts --bundle --platform=node --format=esm --outfile=${TMPDIR:-/tmp}/paseo-forges-issues.test.mjs --log-level=error && esbuild shared/webhook.test.ts --bundle --platform=node --format=esm --outfile=${TMPDIR:-/tmp}/paseo-forges-webhook.test.mjs --log-level=error && esbuild shared/hook-queue.test.ts --bundle --platform=node --format=esm --outfile=${TMPDIR:-/tmp}/paseo-forges-hook-queue.test.mjs --log-level=error && esbuild server/git-origin.test.ts --bundle --platform=node --format=esm --outfile=${TMPDIR:-/tmp}/paseo-forges-git-origin.test.mjs --log-level=error && esbuild server/registry.test.ts --bundle --platform=node --format=esm --outfile=${TMPDIR:-/tmp}/paseo-forges-registry.test.mjs --log-level=error && esbuild server/forge-guard.test.ts --bundle --platform=node --format=esm --outfile=${TMPDIR:-/tmp}/paseo-forges-forge-guard.test.mjs --log-level=error && esbuild server/forge-client.test.ts --bundle --platform=node --format=esm --outfile=${TMPDIR:-/tmp}/paseo-forges-forge-client.test.mjs --log-level=error && esbuild server/hook-queue.test.ts --bundle --platform=node --format=esm --outfile=${TMPDIR:-/tmp}/paseo-forges-server-hook-queue.test.mjs --log-level=error && node --test ${TMPDIR:-/tmp}/paseo-forges-issues.test.mjs ${TMPDIR:-/tmp}/paseo-forges-webhook.test.mjs ${TMPDIR:-/tmp}/paseo-forges-hook-queue.test.mjs ${TMPDIR:-/tmp}/paseo-forges-git-origin.test.mjs ${TMPDIR:-/tmp}/paseo-forges-registry.test.mjs ${TMPDIR:-/tmp}/paseo-forges-forge-guard.test.mjs ${TMPDIR:-/tmp}/paseo-forges-forge-client.test.mjs ${TMPDIR:-/tmp}/paseo-forges-server-hook-queue.test.mjs"
12
+ },
13
+ "devDependencies": {
14
+ "@getpaseo/plugin": "0.9.0-beta.2",
15
+ "@types/react": "^19.0.10",
16
+ "react": "19.1.0",
17
+ "react-native": "0.81.5",
18
+ "typescript": "^5.9.3"
19
+ },
20
+ "files": [
21
+ "paseo-plugin.json",
22
+ "README.md",
23
+ "LICENSE",
24
+ "client",
25
+ "server",
26
+ "shared",
27
+ "docs",
28
+ "examples",
29
+ "!**/*.test.ts",
30
+ "!**/*.test.tsx",
31
+ "!**/*.test.mjs",
32
+ "!**/.DS_Store"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/xpufx/paseo.git"
37
+ }
38
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "id": "forges",
3
+ "requirements": { "paseo": ">=0.8.0" }
4
+ }