opencode-skills-collection 4.0.47 → 4.0.49

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 (63) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +11 -2
  2. package/bundled-skills/agent-qa-authoring/SKILL.md +3 -1
  3. package/bundled-skills/agy-delegate/SKILL.md +1 -1
  4. package/bundled-skills/aider-delegate/SKILL.md +1 -1
  5. package/bundled-skills/api-rate-limit-handler/SKILL.md +1 -1
  6. package/bundled-skills/atlas-cloud-media/SKILL.md +52 -18
  7. package/bundled-skills/babysit-pr/SKILL.md +1 -1
  8. package/bundled-skills/boost-asio-pro/references/pre-cpp20.md +8 -1
  9. package/bundled-skills/check-identity-pack/SKILL.md +88 -0
  10. package/bundled-skills/claude-delegate/SKILL.md +1 -1
  11. package/bundled-skills/cline-delegate/SKILL.md +1 -1
  12. package/bundled-skills/codex-delegate/SKILL.md +1 -1
  13. package/bundled-skills/commandcode-delegate/SKILL.md +1 -1
  14. package/bundled-skills/copilot-delegate/SKILL.md +1 -1
  15. package/bundled-skills/cursor-delegate/SKILL.md +1 -1
  16. package/bundled-skills/detect-ai-text/SKILL.md +91 -0
  17. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  18. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  19. package/bundled-skills/docs/maintainers/repo-growth-seo.md +1 -1
  20. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  21. package/bundled-skills/docs/users/aas-core.md +1 -1
  22. package/bundled-skills/docs/users/bundles.md +1 -1
  23. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  24. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  25. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  26. package/bundled-skills/docs/users/usage.md +3 -3
  27. package/bundled-skills/docs/users/visual-guide.md +4 -4
  28. package/bundled-skills/extract-document-data/SKILL.md +99 -0
  29. package/bundled-skills/falsify/SKILL.md +18 -1
  30. package/bundled-skills/find-matching-tenders/SKILL.md +100 -0
  31. package/bundled-skills/graceful-shutdown/SKILL.md +76 -70
  32. package/bundled-skills/grok-delegate/SKILL.md +1 -1
  33. package/bundled-skills/inngest/SKILL.md +2 -0
  34. package/bundled-skills/kimi-delegate/SKILL.md +1 -1
  35. package/bundled-skills/liuguang-banlan-ui/SKILL.md +6 -4
  36. package/bundled-skills/liuguang-banlan-ui/scripts/manifest_parser.py +178 -0
  37. package/bundled-skills/liuguang-banlan-ui/scripts/measure_preview.py +14 -17
  38. package/bundled-skills/liuguang-banlan-ui/scripts/validate_manifest.py +12 -16
  39. package/bundled-skills/lovable-cleanup/SKILL.md +32 -6
  40. package/bundled-skills/lovable-cleanup/references/favicon-vercel-cleanup.md +106 -0
  41. package/bundled-skills/lovable-cleanup/scripts/write-transparent-favicon.js +88 -0
  42. package/bundled-skills/multi-source-search/scripts/validate_report.py +5 -0
  43. package/bundled-skills/omp-delegate/SKILL.md +1 -1
  44. package/bundled-skills/opencode-delegate/SKILL.md +1 -1
  45. package/bundled-skills/pentest-tools/references/pentest-ai-agents-matrix.md +8 -5
  46. package/bundled-skills/pi-delegate/SKILL.md +1 -1
  47. package/bundled-skills/prompt-caching/SKILL.md +3 -1
  48. package/bundled-skills/qoder-delegate/SKILL.md +1 -1
  49. package/bundled-skills/screen-adverse-media/SKILL.md +100 -0
  50. package/bundled-skills/slideops/SKILL.md +201 -0
  51. package/bundled-skills/trigger-dev/SKILL.md +2 -0
  52. package/bundled-skills/unsloth-finetuning/SKILL.md +45 -2
  53. package/bundled-skills/upstash-qstash/SKILL.md +1 -1
  54. package/bundled-skills/upstash-ratelimit/SKILL.md +186 -0
  55. package/bundled-skills/upstash-redis/SKILL.md +156 -0
  56. package/bundled-skills/verify-citations/SKILL.md +88 -0
  57. package/bundled-skills/verify-document/SKILL.md +94 -0
  58. package/bundled-skills/vibe-delegate/SKILL.md +1 -1
  59. package/bundled-skills/warp-delegate/SKILL.md +1 -1
  60. package/bundled-skills/zcode-delegate/SKILL.md +1 -1
  61. package/package.json +1 -1
  62. package/skills_index.json +347 -54
  63. package/bundled-skills/ui-slop-score/SKILL.md +0 -80
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env python3
2
+ """Parse the data-only subset used by Liuguang theme manifests."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import pathlib
8
+ import re
9
+ from dataclasses import dataclass
10
+
11
+
12
+ MAX_MANIFEST_BYTES = 256 * 1024
13
+ MAX_NESTING_DEPTH = 64
14
+
15
+
16
+ class ManifestSyntaxError(ValueError):
17
+ """Raised when a manifest contains code or unsupported JavaScript syntax."""
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class Token:
22
+ kind: str
23
+ value: str
24
+ offset: int
25
+
26
+
27
+ TOKEN_RE = re.compile(
28
+ r"""
29
+ (?P<whitespace>\s+)
30
+ | (?P<line_comment>//[^\r\n]*)
31
+ | (?P<block_comment>/\*.*?\*/)
32
+ | (?P<string>"(?:\\["\\/bfnrt]|\\u[0-9A-Fa-f]{4}|[^"\\\x00-\x1f])*")
33
+ | (?P<number>-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?)
34
+ | (?P<identifier>[A-Za-z_$][A-Za-z0-9_$]*)
35
+ | (?P<punctuation>[{}\[\]:,;=.])
36
+ """,
37
+ re.VERBOSE | re.DOTALL,
38
+ )
39
+
40
+
41
+ def tokenize(source: str) -> list[Token]:
42
+ tokens: list[Token] = []
43
+ offset = 0
44
+ while offset < len(source):
45
+ match = TOKEN_RE.match(source, offset)
46
+ if match is None:
47
+ raise ManifestSyntaxError(f"unsupported syntax at byte offset {offset}")
48
+ kind = match.lastgroup
49
+ if kind not in {"whitespace", "line_comment", "block_comment"}:
50
+ tokens.append(Token(kind or "unknown", match.group(0), offset))
51
+ offset = match.end()
52
+ tokens.append(Token("eof", "", len(source)))
53
+ return tokens
54
+
55
+
56
+ class Parser:
57
+ def __init__(self, source: str) -> None:
58
+ self.tokens = tokenize(source)
59
+ self.index = 0
60
+
61
+ @property
62
+ def current(self) -> Token:
63
+ return self.tokens[self.index]
64
+
65
+ def take(self, value: str | None = None, kind: str | None = None) -> Token:
66
+ token = self.current
67
+ if value is not None and token.value != value:
68
+ raise ManifestSyntaxError(
69
+ f"expected {value!r} at byte offset {token.offset}, found {token.value!r}"
70
+ )
71
+ if kind is not None and token.kind != kind:
72
+ raise ManifestSyntaxError(
73
+ f"expected {kind} at byte offset {token.offset}, found {token.value!r}"
74
+ )
75
+ self.index += 1
76
+ return token
77
+
78
+ def accept(self, value: str) -> bool:
79
+ if self.current.value != value:
80
+ return False
81
+ self.index += 1
82
+ return True
83
+
84
+ def parse(self) -> dict:
85
+ self.take("window")
86
+ self.take(".")
87
+ self.take("SPECTRAL_THEME")
88
+ self.take("=")
89
+ value = self.parse_value(0)
90
+ self.accept(";")
91
+ self.take(kind="eof")
92
+ if not isinstance(value, dict):
93
+ raise ManifestSyntaxError("SPECTRAL_THEME must be an object")
94
+ return value
95
+
96
+ def parse_value(self, depth: int):
97
+ if depth > MAX_NESTING_DEPTH:
98
+ raise ManifestSyntaxError("manifest nesting limit exceeded")
99
+ token = self.current
100
+ if token.value == "{":
101
+ return self.parse_object(depth + 1)
102
+ if token.value == "[":
103
+ return self.parse_array(depth + 1)
104
+ if token.kind == "string":
105
+ self.index += 1
106
+ return json.loads(token.value)
107
+ if token.kind == "number":
108
+ self.index += 1
109
+ try:
110
+ return (
111
+ float(token.value)
112
+ if any(mark in token.value for mark in ".eE")
113
+ else int(token.value)
114
+ )
115
+ except ValueError as error:
116
+ raise ManifestSyntaxError("numeric literal exceeds the supported range") from error
117
+ if token.kind == "identifier" and token.value in {"true", "false", "null"}:
118
+ self.index += 1
119
+ return {"true": True, "false": False, "null": None}[token.value]
120
+ raise ManifestSyntaxError(
121
+ f"only data literals are allowed at byte offset {token.offset}; found {token.value!r}"
122
+ )
123
+
124
+ def parse_object(self, depth: int) -> dict:
125
+ result: dict = {}
126
+ self.take("{")
127
+ if self.accept("}"):
128
+ return result
129
+ while True:
130
+ token = self.current
131
+ if token.kind == "identifier":
132
+ key = self.take(kind="identifier").value
133
+ elif token.kind == "string":
134
+ key = json.loads(self.take(kind="string").value)
135
+ else:
136
+ raise ManifestSyntaxError(
137
+ f"object keys must be identifiers or strings at byte offset {token.offset}"
138
+ )
139
+ if key in result:
140
+ raise ManifestSyntaxError(f"duplicate object key: {key}")
141
+ self.take(":")
142
+ result[key] = self.parse_value(depth)
143
+ if self.accept("}"):
144
+ return result
145
+ self.take(",")
146
+ if self.accept("}"):
147
+ return result
148
+
149
+ def parse_array(self, depth: int) -> list:
150
+ result: list = []
151
+ self.take("[")
152
+ if self.accept("]"):
153
+ return result
154
+ while True:
155
+ result.append(self.parse_value(depth))
156
+ if self.accept("]"):
157
+ return result
158
+ self.take(",")
159
+ if self.accept("]"):
160
+ return result
161
+
162
+
163
+ def parse_manifest(source: str) -> dict:
164
+ return Parser(source).parse()
165
+
166
+
167
+ def load_manifest(path: pathlib.Path) -> dict:
168
+ with path.open("rb") as manifest:
169
+ raw = manifest.read(MAX_MANIFEST_BYTES + 1)
170
+ if len(raw) > MAX_MANIFEST_BYTES:
171
+ raise ManifestSyntaxError(
172
+ f"manifest exceeds the {MAX_MANIFEST_BYTES}-byte size limit"
173
+ )
174
+ try:
175
+ source = raw.decode("utf-8-sig")
176
+ except UnicodeDecodeError as error:
177
+ raise ManifestSyntaxError("manifest must be valid UTF-8") from error
178
+ return parse_manifest(source)
@@ -6,9 +6,10 @@ from __future__ import annotations
6
6
  import argparse
7
7
  import json
8
8
  import pathlib
9
- import subprocess
10
9
  import sys
11
10
 
11
+ from manifest_parser import ManifestSyntaxError, load_manifest
12
+
12
13
  try:
13
14
  import numpy as np
14
15
  from PIL import Image
@@ -29,21 +30,6 @@ except ModuleNotFoundError as exc:
29
30
  raise SystemExit(2)
30
31
 
31
32
 
32
- def load_config(path: pathlib.Path) -> dict:
33
- source = (
34
- "global.window={};require(require('path').resolve(process.argv[1]));"
35
- "process.stdout.write(JSON.stringify(window.SPECTRAL_THEME));"
36
- )
37
- result = subprocess.run(
38
- ["node", "-e", source, str(path)],
39
- check=True,
40
- capture_output=True,
41
- text=True,
42
- encoding="utf-8",
43
- )
44
- return json.loads(result.stdout)
45
-
46
-
47
33
  def srgb_to_oklab(rgb: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
48
34
  linear = np.where(
49
35
  rgb <= 0.04045,
@@ -129,7 +115,18 @@ def main() -> None:
129
115
  parser.add_argument("--image", required=True, type=pathlib.Path)
130
116
  parser.add_argument("--config", required=True, type=pathlib.Path)
131
117
  args = parser.parse_args()
132
- config = load_config(args.config.resolve())
118
+ try:
119
+ config = load_manifest(args.config.resolve())
120
+ except (ManifestSyntaxError, OSError) as error:
121
+ print(
122
+ json.dumps(
123
+ {"status": "invalid", "errors": [str(error)]},
124
+ ensure_ascii=False,
125
+ indent=2,
126
+ ),
127
+ file=sys.stderr,
128
+ )
129
+ raise SystemExit(1) from error
133
130
  payload = {
134
131
  "schemaVersion": "1.0",
135
132
  "theme": config["label"],
@@ -8,23 +8,9 @@ import json
8
8
  import math
9
9
  import pathlib
10
10
  import re
11
- import subprocess
12
11
  import sys
13
12
 
14
-
15
- def load_manifest(path: pathlib.Path) -> dict:
16
- program = (
17
- "global.window={};require(require('path').resolve(process.argv[1]));"
18
- "process.stdout.write(JSON.stringify(window.SPECTRAL_THEME));"
19
- )
20
- result = subprocess.run(
21
- ["node", "-e", program, str(path)],
22
- check=True,
23
- capture_output=True,
24
- text=True,
25
- encoding="utf-8",
26
- )
27
- return json.loads(result.stdout)
13
+ from manifest_parser import ManifestSyntaxError, load_manifest
28
14
 
29
15
 
30
16
  def require(condition: bool, message: str, errors: list[str]) -> None:
@@ -161,7 +147,17 @@ def main() -> None:
161
147
  parser = argparse.ArgumentParser()
162
148
  parser.add_argument("config", type=pathlib.Path)
163
149
  args = parser.parse_args()
164
- config = load_manifest(args.config.resolve())
150
+ try:
151
+ config = load_manifest(args.config.resolve())
152
+ except (ManifestSyntaxError, OSError) as error:
153
+ print(
154
+ json.dumps(
155
+ {"status": "invalid", "errors": [str(error)]},
156
+ ensure_ascii=False,
157
+ indent=2,
158
+ )
159
+ )
160
+ raise SystemExit(1) from error
165
161
  errors = validate(config)
166
162
  if errors:
167
163
  print(json.dumps({"status": "invalid", "errors": errors}, ensure_ascii=False, indent=2))
@@ -1,13 +1,15 @@
1
1
  ---
2
2
  name: lovable-cleanup
3
- description: "Audits and strips Lovable scaffolding from Vite + React projects — removes lovable-tagger, swaps placeholder assets, prunes unused Radix deps, and cleans generated docs so the codebase ships as yours."
3
+ description: "Audits and strips Lovable scaffolding from Vite + React projects — removes lovable-tagger, swaps placeholder assets, prunes unused Radix deps, cleans generated docs, and neutralizes stale favicon/CDN caching so the codebase ships as yours."
4
4
  risk: safe
5
5
  source: community
6
6
  source_repo: whoisabhishekadhikari/lovable-cleanup
7
7
  source_type: community
8
8
  author: whoisabhishekadhikari
9
9
  date_added: "2026-06-13"
10
- tags: [lovable, cleanup, vite, react, shadcn, devtools]
10
+ date_updated: "2026-08-31"
11
+ version: "2.0.0"
12
+ tags: [lovable, cleanup, vite, react, shadcn, devtools, vercel, favicon]
11
13
  tools: [claude, cursor, codex, antigravity, gemini-cli]
12
14
  ---
13
15
 
@@ -23,7 +25,7 @@ tools: [claude, cursor, codex, antigravity, gemini-cli]
23
25
  Lovable (lovable.dev) bootstraps Vite + React + shadcn/ui projects with its own tagger
24
26
  dependency, branding, placeholder assets, and generated markdown docs baked in. Most
25
27
  developers export from Lovable and want a clean, ownable codebase before shipping or
26
- open-sourcing. This skill covers all 14 areas where Lovable leaves fingerprints.
28
+ open-sourcing. This skill covers all 15 areas where Lovable leaves fingerprints.
27
29
 
28
30
  ---
29
31
 
@@ -75,6 +77,8 @@ shadcn components via the `asChild` prop.
75
77
  6. Environment & Git (Areas 9 & 12) — security sweep
76
78
  7. SEO / deploy (Area 11) — usually a no-op; confirm and move on
77
79
  8. Unused deps (Area 13) — safe to defer until after ship if on a deadline
80
+ 9. Favicon / CDN cache (Area 15) — do ASAP after assets are swapped; browser
81
+ or CDN caching can keep the old icon visible, so verify the live response
78
82
 
79
83
  ---
80
84
 
@@ -137,7 +141,7 @@ Replace these files (keep filenames, swap content):
137
141
 
138
142
  | File | Action |
139
143
  |---|---|
140
- | `favicon.ico` | Replace with real icon |
144
+ | `favicon.ico` | Overwrite with real icon — do NOT just delete, see Area 15 |
141
145
  | `favicon.png` | Replace with real icon |
142
146
  | `og-image.png` / `logo.png` | Replace with real brand assets |
143
147
  | `placeholder.svg` | Usually unused — safe to delete |
@@ -292,6 +296,17 @@ grep -in "lovable" components.json eslint.config.js
292
296
 
293
297
  ---
294
298
 
299
+ ### Area 15 · Favicon removal & stale CDN caches (Vercel)
300
+
301
+ Lovable ships a default `favicon.ico` that browsers auto-request from site root
302
+ and that can remain visible after cleanup through browser or CDN caching. Handle
303
+ the four steps — replace the path, link all icon flavours, keep unversioned icon
304
+ URLs revalidatable, and verify after deploy — then purge the confirmed Vercel
305
+ project cache only if the live response stays stale. Full commands/JSON live in
306
+ [references/favicon-vercel-cleanup.md](references/favicon-vercel-cleanup.md).
307
+
308
+ ---
309
+
295
310
  ## Master Scan Command
296
311
 
297
312
  <!-- security-allowlist: recursive grep across project directory, read-only, no network -->
@@ -342,6 +357,9 @@ Agent:
342
357
  - ✅ **Do:** Run dep removal (Areas 2 & 7) before touching source files
343
358
  - ✅ **Do:** Skim Lovable-generated docs before deleting — may contain useful arch notes
344
359
  - ✅ **Do:** Verify `npm run build` passes after every batch of changes
360
+ - ✅ **Do:** Replace favicons at the existing paths (Area 15), then verify the
361
+ live response and purge only the confirmed project if it remains stale
362
+ - ✅ **Do:** Deploy replacement favicon content and cache headers in the same commit
345
363
  - ✅ **Do:** Replace OG image before launch — it directly affects social sharing previews
346
364
  - ❌ **Don't:** Remove `@radix-ui/react-slot` — it's an indirect dep of most shadcn components
347
365
  - ❌ **Don't:** Leave empty env vars like `LOVABLE_PROJECT_ID=` — delete the whole line
@@ -350,8 +368,8 @@ Agent:
350
368
 
351
369
  ## Limitations
352
370
 
353
- - This skill does not create or source brand assets (favicons, OG images) it only flags
354
- what needs replacing. The user must supply real assets.
371
+ - This skill does not create or source real brand assets (favicons, OG images). Area 15
372
+ generates a transparent placeholder ICO only — the user must supply genuine artwork.
355
373
  - Dep pruning (Area 13) is safe but not foolproof — some Radix packages are indirect deps
356
374
  not caught by a direct `grep`. Always verify with `npm run build`.
357
375
  - The skill does not modify `components.json` aliases automatically — it only scans and
@@ -380,6 +398,14 @@ the `from '@radix-ui/...'` import to find which component depends on it.
380
398
  **Solution:** Check for a `<Helmet>` or `<Head>` component in `src/App.tsx` or a layout
381
399
  wrapper — React-level title tags override `index.html` at runtime.
382
400
 
401
+ ### Problem: Old favicon still serving after deletion (Vercel)
402
+
403
+ **Symptoms:** `curl -sI https://<domain>/favicon.ico` returns the old ETag with
404
+ `x-vercel-cache: HIT` after the replacement deployment.
405
+ **Solution:** Overwrite `public/favicon.ico` with replacement content (a transparent
406
+ 1×1 ICO if no real asset yet), verify the custom domain, then use the explicit
407
+ Vercel CDN purge only if the response remains stale — see Area 15.
408
+
383
409
  ---
384
410
 
385
411
  ## Related Skills
@@ -0,0 +1,106 @@
1
+ # Favicon removal & stale CDN caches (Vercel)
2
+
3
+ Lovable ships a default `favicon.ico`, and browsers auto-request it from site
4
+ root even when `index.html` links a different icon. Browser and CDN layers can
5
+ therefore keep showing the old icon after a replacement deployment. Treat the
6
+ cleanup as both a file-path and cache-verification problem.
7
+
8
+ ## 1 · Overwrite in place, don't delete
9
+
10
+ Prefer replacing the same path in the deployment instead of deleting it. The
11
+ production URL then keeps returning a valid icon while the new bytes establish
12
+ a new content identity; browsers that request `/favicon.ico` implicitly do not
13
+ fall back to an old cached asset merely because the HTML link changed.
14
+
15
+ If no real brand icon is ready, use the bundled helper to write a valid
16
+ transparent 1×1 ICO. It resolves the physical project root, rejects a symlinked
17
+ `public/` directory or favicon target, writes an exclusive same-directory
18
+ temporary file with no-follow semantics where available, then atomically
19
+ renames it into place.
20
+
21
+ <!-- security-allowlist: writes a 70-byte ICO into the project's public/, local only -->
22
+ ```bash
23
+ node "<skill-dir>/scripts/write-transparent-favicon.js" "$PWD"
24
+ ```
25
+
26
+ ## 2 · Link all icon flavours in `index.html`
27
+
28
+ Browsers may request `/favicon.ico` even without a link, so keep that path and
29
+ the modern/Apple entry points available:
30
+
31
+ ```html
32
+ <link rel="icon" type="image/x-icon" href="/favicon.ico" />
33
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
34
+ <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
35
+ ```
36
+
37
+ `apple-touch-icon.png` must be a real PNG (recommended 180×180). A solid brand-
38
+ colour square is an acceptable placeholder; flag it for later replacement.
39
+
40
+ ## 3 · Keep unversioned icon URLs revalidatable (`vercel.json`)
41
+
42
+ Vercel documents `public, max-age=0, must-revalidate` as its default response
43
+ policy and recommends long-lived `immutable` caching for content-hashed assets.
44
+ The standard favicon entry points below are not content-hashed, so keep them
45
+ revalidatable unless the HTML points at a versioned filename:
46
+
47
+ - `/favicon.ico`, `/favicon.svg`, `/apple-touch-icon.png` →
48
+ `public, max-age=0, must-revalidate`
49
+ - A content-hashed asset such as `/favicon-a1b2c3.svg` may use
50
+ `public, max-age=31536000, immutable`.
51
+
52
+ ```json
53
+ {
54
+ "headers": [
55
+ {
56
+ "source": "/favicon.ico",
57
+ "headers": [{ "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }]
58
+ },
59
+ {
60
+ "source": "/favicon.svg",
61
+ "headers": [{ "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }]
62
+ },
63
+ {
64
+ "source": "/apple-touch-icon.png",
65
+ "headers": [{ "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }]
66
+ }
67
+ ]
68
+ }
69
+ ```
70
+
71
+ Never mark an unversioned icon URL `immutable`: it tells browsers not to
72
+ revalidate for the max-age lifetime, so a replacement may not propagate for up
73
+ to a year. Use `immutable` only on content-hashed URLs.
74
+
75
+ ## 4 · Verify after deploy
76
+
77
+ <!-- security-allowlist: remote curl header check of own domain, read-only -->
78
+ ```bash
79
+ curl -sI https://YOUR-DOMAIN/favicon.ico \
80
+ | grep -i "cache-control\|etag\|x-vercel-cache"
81
+ ```
82
+
83
+ Expect the replacement ETag and `Cache-Control: public, max-age=0,
84
+ must-revalidate` on the unversioned icon URLs.
85
+
86
+ If the confirmed production domain still serves the old edge response, verify
87
+ the linked Vercel project and team first, ask for explicit approval, then purge
88
+ that project's CDN cache:
89
+
90
+ <!-- security-allowlist: explicit remote cache purge for the confirmed Vercel project; requires user approval -->
91
+ ```bash
92
+ vercel cache purge --type cdn
93
+ ```
94
+
95
+ Re-run the header check after the purge. Do not purge a project inferred only
96
+ from the current directory or a preview URL.
97
+
98
+ **Gotcha — the staging URL:** a `*.vercel.app` preview may be SSO-protected
99
+ (`_vercel_sso_nonce` 302) and wrap deploys in a provider frame that injects
100
+ platform branding. Always verify icons on the real custom domain.
101
+
102
+ ## Official references
103
+
104
+ - [Vercel Cache-Control headers](https://vercel.com/docs/caching/cache-control-headers)
105
+ - [Vercel CDN cache](https://vercel.com/docs/caching/cdn-cache)
106
+ - [Vercel cache purge CLI](https://vercel.com/docs/cli/cache)
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const crypto = require("node:crypto");
5
+ const fs = require("node:fs");
6
+ const path = require("node:path");
7
+
8
+ function fail(message) {
9
+ throw new Error(`Refusing favicon write: ${message}`);
10
+ }
11
+
12
+ function existingStat(target) {
13
+ try {
14
+ return fs.lstatSync(target);
15
+ } catch (error) {
16
+ if (error && error.code === "ENOENT") return null;
17
+ throw error;
18
+ }
19
+ }
20
+
21
+ function transparentIco() {
22
+ const bytes = Buffer.alloc(6 + 16 + 40 + 8);
23
+ bytes.writeUInt16LE(1, 2);
24
+ bytes.writeUInt16LE(1, 4);
25
+ bytes.writeUInt8(1, 6);
26
+ bytes.writeUInt8(1, 7);
27
+ bytes.writeUInt16LE(1, 10);
28
+ bytes.writeUInt16LE(32, 12);
29
+ bytes.writeUInt32LE(40 + 8, 14);
30
+ bytes.writeUInt32LE(22, 18);
31
+ bytes.writeUInt32LE(40, 22);
32
+ bytes.writeInt32LE(1, 26);
33
+ bytes.writeInt32LE(2, 30);
34
+ bytes.writeUInt16LE(1, 34);
35
+ bytes.writeUInt16LE(32, 36);
36
+ bytes.writeUInt32LE(0, 38);
37
+ bytes.writeUInt32LE(8, 42);
38
+ return bytes;
39
+ }
40
+
41
+ function main() {
42
+ const requestedRoot = path.resolve(process.argv[2] || process.cwd());
43
+ const projectRoot = fs.realpathSync(requestedRoot);
44
+ const publicDirectory = path.join(projectRoot, "public");
45
+ const publicStat = fs.lstatSync(publicDirectory);
46
+ if (publicStat.isSymbolicLink() || !publicStat.isDirectory()) {
47
+ fail("public/ must be a real directory, not a symlink or special file");
48
+ }
49
+ if (fs.realpathSync(publicDirectory) !== publicDirectory) {
50
+ fail("public/ resolves outside the physical project path");
51
+ }
52
+
53
+ const target = path.join(publicDirectory, "favicon.ico");
54
+ const targetStat = existingStat(target);
55
+ if (targetStat && targetStat.isSymbolicLink()) {
56
+ fail("public/favicon.ico is a symbolic link");
57
+ }
58
+ if (targetStat && !targetStat.isFile()) {
59
+ fail("public/favicon.ico is not a regular file");
60
+ }
61
+
62
+ const temporary = path.join(
63
+ publicDirectory,
64
+ `.favicon.ico.${process.pid}.${crypto.randomBytes(8).toString("hex")}.tmp`,
65
+ );
66
+ const flags =
67
+ fs.constants.O_WRONLY |
68
+ fs.constants.O_CREAT |
69
+ fs.constants.O_EXCL |
70
+ (fs.constants.O_NOFOLLOW || 0);
71
+ let descriptor;
72
+ try {
73
+ descriptor = fs.openSync(temporary, flags, 0o600);
74
+ fs.writeFileSync(descriptor, transparentIco());
75
+ fs.fsyncSync(descriptor);
76
+ fs.fchmodSync(descriptor, 0o644);
77
+ fs.closeSync(descriptor);
78
+ descriptor = undefined;
79
+ fs.renameSync(temporary, target);
80
+ } finally {
81
+ if (descriptor !== undefined) fs.closeSync(descriptor);
82
+ const temporaryStat = existingStat(temporary);
83
+ if (temporaryStat && temporaryStat.isFile()) fs.unlinkSync(temporary);
84
+ }
85
+ process.stdout.write(`Wrote ${target}\n`);
86
+ }
87
+
88
+ main();
@@ -21,6 +21,11 @@ def canonical_url(value):
21
21
  """Return a conservative identity for an HTTP(S) URL, or None if invalid."""
22
22
  if not nonempty(value):
23
23
  return None
24
+ if any(
25
+ character.isspace() or ord(character) < 0x20 or ord(character) == 0x7F
26
+ for character in value
27
+ ):
28
+ return None
24
29
  try:
25
30
  parsed = urlsplit(value)
26
31
  hostname = parsed.hostname
@@ -2,7 +2,7 @@
2
2
  name: omp-delegate
3
3
  description: Delegate coding tasks to Oh My Pi (`omp`) only when the user explicitly
4
4
  requests it, while the orchestrator retains review and landing responsibility.
5
- risk: safe
5
+ risk: critical
6
6
  category: agent-orchestration
7
7
  source: https://github.com/amElnagdy/delegate-skills
8
8
  source_repo: amElnagdy/delegate-skills
@@ -2,7 +2,7 @@
2
2
  name: opencode-delegate
3
3
  description: Delegate coding tasks to the OpenCode CLI only when the user explicitly
4
4
  requests it, while the orchestrator retains review and landing responsibility.
5
- risk: safe
5
+ risk: critical
6
6
  category: agent-orchestration
7
7
  source: https://github.com/amElnagdy/delegate-skills
8
8
  source_repo: amElnagdy/delegate-skills
@@ -10,11 +10,14 @@
10
10
 
11
11
  ## 安装方式(外部可选)
12
12
 
13
- ```bash
14
- curl -fsSL https://raw.githubusercontent.com/0xSteph/pentest-ai-agents/main/install.sh | bash # security-allowlist: curl-pipe-bash
15
- ```
16
-
17
- 或者只参考其 prompt 模式,不实际安装。
13
+ 默认只参考其 prompt 模式,不安装外部代码。不得从可变的 `main` 分支
14
+ 下载并直接执行安装脚本,也不得把下载内容通过管道交给 shell。
15
+
16
+ 如果用户明确要求安装,先在仓库页面选择并记录一个完整的 40 位 commit
17
+ SHA,在私有审查目录中下载该 commit 的完整源码,核对仓库身份、许可证、
18
+ 文件清单、安装脚本及其后续下载。只有在展示确切变更和命令并获得单独批准
19
+ 后,才可从已审查的本地副本执行;任一依赖仍指向分支、标签、`latest` 或未审查
20
+ 远程脚本时必须停止。
18
21
 
19
22
  ---
20
23
 
@@ -2,7 +2,7 @@
2
2
  name: pi-delegate
3
3
  description: Delegate coding tasks to the Pi coding agent CLI (`pi`) only when the
4
4
  user explicitly requests it, while the orchestrator retains review and landing responsibility.
5
- risk: safe
5
+ risk: critical
6
6
  category: agent-orchestration
7
7
  source: https://github.com/amElnagdy/delegate-skills
8
8
  source_repo: amElnagdy/delegate-skills
@@ -34,7 +34,7 @@ Caching strategies for LLM prompts including Anthropic prompt caching, response
34
34
  ### Primary_tools
35
35
 
36
36
  - Anthropic Prompt Caching - Native prompt caching in Claude API
37
- - Redis - In-memory cache for responses
37
+ - Redis - In-memory cache for responses (ioredis on servers; @upstash/redis over HTTP on serverless, see `upstash-redis`)
38
38
  - OpenAI Caching - Automatic caching in OpenAI API
39
39
 
40
40
  ## Patterns
@@ -91,6 +91,8 @@ import { createHash } from 'crypto';
91
91
  import Redis from 'ioredis';
92
92
 
93
93
  const redis = new Redis(process.env.REDIS_URL);
94
+ // Serverless/edge alternative without a persistent connection:
95
+ // import { Redis } from '@upstash/redis'; const redis = Redis.fromEnv(); // then use redis.set(key, value, { ex: ttl })
94
96
 
95
97
  class ResponseCache {
96
98
  private ttl = 3600; // 1 hour default
@@ -2,7 +2,7 @@
2
2
  name: qoder-delegate
3
3
  description: Delegate coding tasks to the Qoder CLI (`qodercli`) only when the user
4
4
  explicitly requests it, while the orchestrator retains review and landing responsibility.
5
- risk: safe
5
+ risk: critical
6
6
  category: agent-orchestration
7
7
  source: https://github.com/amElnagdy/delegate-skills
8
8
  source_repo: amElnagdy/delegate-skills