@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,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
|
+
}
|