opencode-skills-collection 3.1.14 → 3.1.16
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/bundled-skills/.antigravity-install-manifest.json +36 -1
- package/bundled-skills/007/scripts/full_audit.py +34 -22
- package/bundled-skills/007/scripts/quick_scan.py +24 -15
- package/bundled-skills/007/scripts/scanners/dependency_scanner.py +31 -19
- package/bundled-skills/007/scripts/scanners/injection_scanner.py +24 -14
- package/bundled-skills/007/scripts/scanners/secrets_scanner.py +24 -14
- package/bundled-skills/007/scripts/score_calculator.py +25 -13
- package/bundled-skills/2slides-ppt-generator/scripts/download_slides_pages_voices.py +15 -1
- package/bundled-skills/aegisops-ai/SKILL.md +1 -1
- package/bundled-skills/agent-self-scheduling/SKILL.md +88 -0
- package/bundled-skills/alpha-vantage/SKILL.md +3 -1
- package/bundled-skills/anti-sleep/SKILL.md +90 -0
- package/bundled-skills/api-security-best-practices/SKILL.md +1 -1
- package/bundled-skills/app-store-optimization/SKILL.md +7 -0
- package/bundled-skills/audio-transcriber/scripts/transcribe.py +17 -4
- package/bundled-skills/azure-mgmt-botservice-py/SKILL.md +2 -1
- package/bundled-skills/azure-resource-manager-mysql-dotnet/SKILL.md +2 -1
- package/bundled-skills/azure-resource-manager-postgresql-dotnet/SKILL.md +2 -1
- package/bundled-skills/azure-resource-manager-sql-dotnet/SKILL.md +2 -1
- package/bundled-skills/biopython/SKILL.md +2 -1
- package/bundled-skills/brain-to-docs/SKILL.md +54 -0
- package/bundled-skills/browser-harness/SKILL.md +220 -0
- package/bundled-skills/browser-harness/references/install.md +132 -0
- package/bundled-skills/bumblebee/scripts/render_report.py +16 -3
- package/bundled-skills/bun-development/SKILL.md +1 -1
- package/bundled-skills/cc-skill-security-review/SKILL.md +2 -2
- package/bundled-skills/claude-monitor/scripts/monitor.py +15 -2
- package/bundled-skills/cmux/SKILL.md +250 -0
- package/bundled-skills/code-polish/SKILL.md +192 -0
- package/bundled-skills/code-review-checklist/SKILL.md +1 -1
- package/bundled-skills/codex-subagent/SKILL.md +117 -0
- package/bundled-skills/competitor-analysis/scripts/capture_screenshots.mjs +24 -3
- package/bundled-skills/competitor-analysis/scripts/compile_report.mjs +13 -2
- package/bundled-skills/competitor-analysis/scripts/extract_vs_names.mjs +23 -2
- package/bundled-skills/competitor-analysis/scripts/gate_candidates.mjs +21 -0
- package/bundled-skills/competitor-analysis/scripts/list_urls.mjs +23 -2
- package/bundled-skills/competitor-analysis/scripts/merge_partials.mjs +24 -3
- package/bundled-skills/container-security-hardening/SKILL.md +1 -1
- package/bundled-skills/content-creator/scripts/brand_voice_analyzer.py +15 -1
- package/bundled-skills/content-creator/scripts/seo_optimizer.py +15 -1
- package/bundled-skills/context-kit/SKILL.md +135 -0
- package/bundled-skills/cyber-audit/SKILL.md +138 -0
- package/bundled-skills/deepapi/SKILL.md +649 -0
- package/bundled-skills/delegating-to-agents/SKILL.md +64 -0
- package/bundled-skills/developer-signup-flow/SKILL.md +1 -1
- package/bundled-skills/diary/scripts/fetch_diaries.py +14 -1
- package/bundled-skills/diary/scripts/prepare_context.py +15 -2
- package/bundled-skills/diary/scripts/sync_to_notion.py +14 -1
- package/bundled-skills/dispatch/SKILL.md +19 -7
- package/bundled-skills/distribute-skill-to-all-agents/SKILL.md +85 -0
- package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
- package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
- package/bundled-skills/docs/maintainers/repo-growth-seo.md +3 -3
- package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
- package/bundled-skills/docs/users/bundles.md +1 -1
- package/bundled-skills/docs/users/claude-code-skills.md +1 -1
- package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
- package/bundled-skills/docs/users/getting-started.md +1 -1
- package/bundled-skills/docs/users/kiro-integration.md +1 -1
- package/bundled-skills/docs/users/usage.md +4 -4
- package/bundled-skills/docs/users/visual-guide.md +4 -4
- package/bundled-skills/docx-official/ooxml/scripts/pack.py +30 -3
- package/bundled-skills/docx-official/ooxml/scripts/unpack.py +16 -3
- package/bundled-skills/drizzle-migration-conflict/scripts/check_drizzle_migrations.py +27 -17
- package/bundled-skills/ecl-harness-engineer/agents/creator-config.md +17 -3
- package/bundled-skills/ecl-harness-engineer/references/environment-detection-guide.md +9 -5
- package/bundled-skills/effective-agent-skills/SKILL.md +333 -0
- package/bundled-skills/electron-development/SKILL.md +6 -2
- package/bundled-skills/environment-setup-guide/SKILL.md +2 -5
- package/bundled-skills/expo-ui/scripts/list-components.js +5 -1
- package/bundled-skills/fable-safe-prompt/SKILL.md +79 -0
- package/bundled-skills/folder-specific-claude-and-agents-md/SKILL.md +96 -0
- package/bundled-skills/frontend-slides/scripts/extract-pptx.py +18 -4
- package/bundled-skills/gcp-cloud-run/SKILL.md +2 -1
- package/bundled-skills/gemini-api-integration/SKILL.md +3 -1
- package/bundled-skills/gemini-live-api-dev/SKILL.md +2 -1
- package/bundled-skills/gemini-omni-flash-api/scripts/video/generate_video.py +49 -14
- package/bundled-skills/goal-loop/SKILL.md +175 -0
- package/bundled-skills/hugging-face-evaluation/SKILL.md +1 -1
- package/bundled-skills/hugging-face-jobs/SKILL.md +1 -1
- package/bundled-skills/hugging-face-jobs/scripts/finepdfs-stats.py +14 -1
- package/bundled-skills/hugging-face-model-trainer/scripts/convert_to_gguf.py +10 -1
- package/bundled-skills/hugo-to-markdown/scripts/inventory_hugo_rules.py +14 -1
- package/bundled-skills/image-generator/SKILL.md +3 -1
- package/bundled-skills/instagram/scripts/export.py +18 -7
- package/bundled-skills/interview-style-doc-building/SKILL.md +86 -0
- package/bundled-skills/junta-leiloeiros/scripts/export.py +19 -10
- package/bundled-skills/landing-page-generator/scripts/landing_page_scaffolder.py +15 -1
- package/bundled-skills/linear-claude-skill/SKILL.md +4 -2
- package/bundled-skills/linkedin-post-writer/SKILL.md +159 -0
- package/bundled-skills/linkedin-post-writer/references/hook-formulas.md +482 -0
- package/bundled-skills/llm-council/SKILL.md +4 -2
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package-lock.json +5 -6
- package/bundled-skills/loki-mode/examples/todo-app-generated/backend/package.json +2 -3
- package/bundled-skills/markdown-rendering/SKILL.md +72 -0
- package/bundled-skills/matematico-tao/scripts/complexity_analyzer.py +14 -1
- package/bundled-skills/matematico-tao/scripts/dependency_graph.py +15 -2
- package/bundled-skills/mobile-design/scripts/mobile_audit.py +20 -6
- package/bundled-skills/monte-carlo-validation-notebook/scripts/resolve_dbt_schema.py +15 -2
- package/bundled-skills/multi-agent-architect/SKILL.md +1 -1
- package/bundled-skills/odoo-rpc-api/SKILL.md +2 -1
- package/bundled-skills/pci-compliance/SKILL.md +3 -1
- package/bundled-skills/pdf-official/scripts/create_validation_image.py +17 -3
- package/bundled-skills/pdf-official/scripts/extract_form_field_info.py +1 -1
- package/bundled-skills/personal-tool-builder/SKILL.md +1 -1
- package/bundled-skills/pi-custom-model/SKILL.md +68 -0
- package/bundled-skills/pi-web-search/SKILL.md +74 -0
- package/bundled-skills/playwright-skill/run.js +19 -1
- package/bundled-skills/pptx-official/ooxml/scripts/pack.py +30 -3
- package/bundled-skills/pptx-official/ooxml/scripts/unpack.py +16 -3
- package/bundled-skills/pptx-official/scripts/inventory.py +17 -4
- package/bundled-skills/pptx-official/scripts/rearrange.py +15 -2
- package/bundled-skills/pptx-official/scripts/replace.py +16 -3
- package/bundled-skills/product-manager-toolkit/scripts/customer_interview_analyzer.py +15 -1
- package/bundled-skills/production-code-audit/SKILL.md +1 -1
- package/bundled-skills/push-skill-to-github/SKILL.md +61 -0
- package/bundled-skills/rclone-cli/references/commands/rclone_completion_powershell.md +3 -2
- package/bundled-skills/read-all-adrs/SKILL.md +35 -0
- package/bundled-skills/remote-gpu-trainer/scripts/verify_local.py +14 -1
- package/bundled-skills/research-prompt/SKILL.md +63 -0
- package/bundled-skills/run-deep-swe/SKILL.md +120 -0
- package/bundled-skills/senior-architect/scripts/architecture_diagram_generator.py +15 -2
- package/bundled-skills/senior-architect/scripts/dependency_analyzer.py +15 -2
- package/bundled-skills/senior-architect/scripts/project_architect.py +15 -2
- package/bundled-skills/senior-frontend/scripts/bundle_analyzer.py +14 -1
- package/bundled-skills/senior-frontend/scripts/frontend_scaffolder.py +14 -1
- package/bundled-skills/senior-fullstack/scripts/code_quality_analyzer.py +15 -2
- package/bundled-skills/senior-fullstack/scripts/fullstack_scaffolder.py +15 -2
- package/bundled-skills/senior-fullstack/scripts/project_scaffolder.py +15 -2
- package/bundled-skills/setup-help/SKILL.md +50 -0
- package/bundled-skills/shodan-reconnaissance/SKILL.md +2 -1
- package/bundled-skills/short/SKILL.md +28 -0
- package/bundled-skills/skill-creator/scripts/init_skill.py +15 -2
- package/bundled-skills/skill-creator/scripts/package_skill.py +29 -12
- package/bundled-skills/skill-creator/scripts/quick_validate.py +14 -2
- package/bundled-skills/skill-creator-ms/SKILL.md +1 -1
- package/bundled-skills/skill-installer/scripts/install_skill.py +42 -8
- package/bundled-skills/skill-installer/scripts/package_skill.py +29 -17
- package/bundled-skills/skill-installer/scripts/validate_skill.py +37 -22
- package/bundled-skills/stripe-integration/SKILL.md +7 -3
- package/bundled-skills/swiftui-expert-skill/scripts/instruments_parser/xctrace.py +7 -1
- package/bundled-skills/swiftui-expert-skill/scripts/instruments_parser/xml_utils.py +7 -1
- package/bundled-skills/taisly-social-media-posting/SKILL.md +79 -0
- package/bundled-skills/technical-tutorials/SKILL.md +1 -1
- package/bundled-skills/telegram/scripts/setup_project.py +17 -2
- package/bundled-skills/time-ledger/SKILL.md +118 -0
- package/bundled-skills/trading-ledger/SKILL.md +121 -0
- package/bundled-skills/videodb/scripts/ws_listener.py +17 -4
- package/bundled-skills/voice-ai-development/SKILL.md +2 -1
- package/bundled-skills/vps-server-management/SKILL.md +72 -0
- package/bundled-skills/weaviate/scripts/weaviate_conn.py +20 -18
- package/bundled-skills/wgm/SKILL.md +137 -0
- package/bundled-skills/whatsapp-cloud-api/scripts/setup_project.py +16 -1
- package/bundled-skills/workorai/SKILL.md +137 -0
- package/bundled-skills/wp-site-health-auditor/SKILL.md +341 -0
- package/bundled-skills/wp-site-health-auditor/references/catalog.md +135 -0
- package/bundled-skills/writing-skills/render-graphs.js +13 -2
- package/bundled-skills/x-twitter-scraper/SKILL.md +3 -1
- package/bundled-skills/youtube-notetaker/scripts/serve.py +41 -31
- package/bundled-skills/youtube-notetaker/scripts/vtt_to_transcript.py +16 -2
- package/bundled-skills/youtube-notetaker/scripts/write_library_item.py +15 -1
- package/bundled-skills/youtube-transcript/SKILL.md +120 -0
- package/package.json +5 -1
- package/skills_index.json +786 -6
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
<!-- Vendored from https://github.com/sergebulaev/linkedin-skills/blob/main/references/hook-formulas.md (MIT).
|
|
2
|
+
Internal cross-references to the upstream curator's private draft notebook were removed;
|
|
3
|
+
the skeletons and reference numbers below are complete and sufficient to apply each formula. -->
|
|
4
|
+
|
|
5
|
+
# 16 LinkedIn Hook Formulas - 2026 Edition
|
|
6
|
+
|
|
7
|
+
Each formula has a skeleton, why it works, and a reference engagement number from the original post that defined it.
|
|
8
|
+
|
|
9
|
+
F1-F10 are the original long-form thought-leadership set. F11-F16 were validated in 2026 against a large corpus of above-average performers across 10 verticals; they skew shorter and more emotional, and each is tagged with its primary engagement goal (comments / reposts / likes / saves). Pick by goal first (see "Engagement-goal split" below), then by topic.
|
|
10
|
+
|
|
11
|
+
**Reading the reference numbers:** F1-F10 cite engagement with a baseline multiplier (a real format effect, e.g. "19.64x baseline"). F11-F16 cite absolute reach from the 2026 corpus, which can be inflated by reshares or a famous author. Treat F11-F16 numbers as a reach ceiling, not a like-for-like comparison against F1-F10. Where the reach was source-driven rather than format-driven, the formula says so.
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- F1 - Platform Risk Anaphora
|
|
16
|
+
- F2 - R.I.P. Category Obituary
|
|
17
|
+
- F3 - Year-over-Year Pivot
|
|
18
|
+
- F4 - Time-Anchor Confession
|
|
19
|
+
- F5 - Self-Proving Meta
|
|
20
|
+
- F6 - Comment-Gate Lead Magnet
|
|
21
|
+
- F7 - Odd-Precision Money Ledger
|
|
22
|
+
- F8 - Paid-vs-Free Reversal
|
|
23
|
+
- F9 - Curiosity-Gap Teaser
|
|
24
|
+
- F10 - Contrarian + Historical Receipts
|
|
25
|
+
- F11 - Emotional Cold-Open
|
|
26
|
+
- F12 - Permission Slip
|
|
27
|
+
- F13 - Bait-and-Switch Reversal
|
|
28
|
+
- F14 - Named Gratitude / Tribute
|
|
29
|
+
- F15 - Explain-to-Kids Simplification
|
|
30
|
+
- F16 - Status-Strip Humility
|
|
31
|
+
- Engagement-goal split
|
|
32
|
+
- Choosing which formula to use
|
|
33
|
+
- Hook micro-rules
|
|
34
|
+
- Never do
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## F1 - Platform Risk Anaphora
|
|
39
|
+
|
|
40
|
+
**Reference:** 4,240 eng.
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
{Platform1} can {restrict|shadowban|throttle} you {timing}.
|
|
44
|
+
{Platform2} can {bad thing} for {reason}.
|
|
45
|
+
|
|
46
|
+
[4-5 more anaphoric lines, escalating specificity]
|
|
47
|
+
|
|
48
|
+
You don't own {audience}. You don't own {feed}. You're renting {attention}.
|
|
49
|
+
|
|
50
|
+
[Concrete horror anecdote with real number: "I watched a friend lose 180k followers in an afternoon"]
|
|
51
|
+
|
|
52
|
+
Here's what most people miss: [reframe: what the real asset is].
|
|
53
|
+
|
|
54
|
+
So I changed how I work:
|
|
55
|
+
- [tactic 1]
|
|
56
|
+
- [tactic 2]
|
|
57
|
+
- [tactic 3]
|
|
58
|
+
|
|
59
|
+
[Metaphor close: "castles on rented land vs roads"]
|
|
60
|
+
[Product mention as natural conclusion, one sentence, no pitch verbs]
|
|
61
|
+
[Personal-audit question]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Why:** Loss aversion stacked 5x. Identity threat. Solution list earns the close.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## F2 - R.I.P. Category Obituary
|
|
69
|
+
|
|
70
|
+
**Reference:** 3,822 eng.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
R.I.P. {category}.
|
|
74
|
+
|
|
75
|
+
Cause of death: {specific mechanism + numbers}.
|
|
76
|
+
|
|
77
|
+
[Concrete evidence, 2-3 paragraphs with dates and stats]
|
|
78
|
+
|
|
79
|
+
I defended {old thing} publicly through most of 2025.
|
|
80
|
+
|
|
81
|
+
It worked. Until [pivotal event + date].
|
|
82
|
+
|
|
83
|
+
Here's what actually changed under the hood:
|
|
84
|
+
|
|
85
|
+
1. [Change 1 with stat]
|
|
86
|
+
2. [Change 2 with stat]
|
|
87
|
+
...
|
|
88
|
+
6. [Change 6 with stat]
|
|
89
|
+
|
|
90
|
+
The winners in 2026 aren't {old-winner-type}. They're {new-winner-type}.
|
|
91
|
+
|
|
92
|
+
[One-line philosophical close]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Why:** Status-threat + relief combo. Reframes "I'm behind" as "the game changed." Removes shame, invites curiosity.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## F3 - Year-over-Year Pivot
|
|
100
|
+
|
|
101
|
+
**Reference:** 494 eng (3.74x baseline).
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
In {last year}, I {humble benchmark}.
|
|
105
|
+
|
|
106
|
+
In {this year}, I'm {transformational goal}.
|
|
107
|
+
|
|
108
|
+
Here's what actually changed.
|
|
109
|
+
|
|
110
|
+
[Vulnerable truth + specific numbers (12 -> 1,000 posts)]
|
|
111
|
+
[The identity reframe: "the shift wasn't tools, it was identity"]
|
|
112
|
+
[3-beat imperative close]
|
|
113
|
+
|
|
114
|
+
[Mirror question: "What's your {last}->{this} pivot? One line below."]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Why:** Two-line hook carries 80% of the weight. Mirror CTA compounds engagement algorithmically.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## F4 - Time-Anchor Confession
|
|
122
|
+
|
|
123
|
+
**Reference:** 1,519+ eng.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
{N} {days|months|years} ago, I stopped {behavior}.
|
|
127
|
+
|
|
128
|
+
Here's what happened.
|
|
129
|
+
|
|
130
|
+
[2-year backstory of why the old behavior worked: concrete numbers]
|
|
131
|
+
|
|
132
|
+
[The quiet cost, what it did to you internally]
|
|
133
|
+
|
|
134
|
+
So in {month} I stopped. [New behavior, 2-3 lines]
|
|
135
|
+
|
|
136
|
+
[Metric dropped by N%. Expected worse.]
|
|
137
|
+
|
|
138
|
+
What surprised me: [counterintuitive upside, specific wins]
|
|
139
|
+
|
|
140
|
+
[One-line reframe: "X attracts Y. Z attracts the right Y."]
|
|
141
|
+
|
|
142
|
+
[Mirror question: "What's something you stopped doing that quietly made your work better?"]
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Why:** Confession earns the room. Specific numbers kill the "vibes" energy. Close turns every commenter into a mini-confession.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## F5 - Self-Proving Meta
|
|
150
|
+
|
|
151
|
+
**Reference:** 1,082 eng / 435 comments.
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
Most LinkedIn posts die in the first 30 minutes.
|
|
155
|
+
|
|
156
|
+
Not because {common reason}. Because {real reason}.
|
|
157
|
+
|
|
158
|
+
[Reveal the metric: "reply latency in first 60 min = 3.4x reach"]
|
|
159
|
+
|
|
160
|
+
So here's the test.
|
|
161
|
+
|
|
162
|
+
For the next 24 hours, I will {specific commitment}.
|
|
163
|
+
|
|
164
|
+
You do two things:
|
|
165
|
+
1. [Low-bar action]
|
|
166
|
+
2. [Verification action]
|
|
167
|
+
|
|
168
|
+
If the thesis is right, {outcome}.
|
|
169
|
+
If it's wrong, I owe you a post admitting it.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Why:** Claim is validated by reader action. Every comment is evidence. Public accountability hook.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## F6 - Comment-Gate Lead Magnet
|
|
177
|
+
|
|
178
|
+
**Reference:** 717-3,008 eng.
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
[Authority number: "We've helped creators publish 47,000+ posts in 14 months"]
|
|
182
|
+
|
|
183
|
+
[Pattern observation the authority earned]
|
|
184
|
+
|
|
185
|
+
So I turned that workflow into {N named items}. [Drop them into X, type one command, get the output.]
|
|
186
|
+
|
|
187
|
+
What's inside:
|
|
188
|
+
- [Item 1]
|
|
189
|
+
- [Item 2]
|
|
190
|
+
...
|
|
191
|
+
- [Item 12]
|
|
192
|
+
|
|
193
|
+
Free. No email wall. [Light scarcity: "48 hours only, I'll DM the link personally"]
|
|
194
|
+
|
|
195
|
+
Comment "{keyword}" below + connect with me and I'll send the bundle.
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Why:** Capped reach but huge DM conversion. Named bundle + real authority = 300-800 comments if the bundle is genuine.
|
|
199
|
+
|
|
200
|
+
**Warning:** This is engagement bait. Ship only when the weekly goal is list-building, not thought leadership. LinkedIn suppresses pure "comment X" posts.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## F7 - Odd-Precision Money Ledger
|
|
205
|
+
|
|
206
|
+
**Reference:** 1,755 eng (9.4x baseline).
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
{Odd, specific dollar number: "$873.47"}
|
|
210
|
+
|
|
211
|
+
[1-line context of what this number covers]
|
|
212
|
+
|
|
213
|
+
Here is every line item, from the ledger, nothing rounded:
|
|
214
|
+
|
|
215
|
+
- {tool 1}: $X.YZ
|
|
216
|
+
- {tool 2}: $X.YZ
|
|
217
|
+
...
|
|
218
|
+
|
|
219
|
+
[What the total replaces: "$14,200 team cost"]
|
|
220
|
+
|
|
221
|
+
[The thing that surprised you: what broke, what worked]
|
|
222
|
+
|
|
223
|
+
[Identity reframe close: "Tradesmen flip houses, SEOs flip blogs, AI founders flip {X}"]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Why:** Non-rounded numbers signal real accounting. Ledger is screenshot-bait. Dwell time stays high.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## F8 - Paid-vs-Free Reversal
|
|
231
|
+
|
|
232
|
+
**Reference:** 550 eng (19.64x baseline, highest multiplier in the set).
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
I charge {audience} $X for {service}.
|
|
236
|
+
|
|
237
|
+
Screw it. Today it's free.
|
|
238
|
+
|
|
239
|
+
Below is the exact {N-step} teardown I run before I'll take a client. It's called the {NAMED-FRAMEWORK}.
|
|
240
|
+
|
|
241
|
+
[Block an hour, open X in one tab, Y in another, grade yourself honestly.]
|
|
242
|
+
|
|
243
|
+
1. {STEP-1-NAME}: [actionable instruction with specific ratio or example]
|
|
244
|
+
2. {STEP-2-NAME}: [actionable instruction]
|
|
245
|
+
...
|
|
246
|
+
7. {STEP-7-NAME}: [actionable instruction]
|
|
247
|
+
|
|
248
|
+
That's the {framework}. Run it today. Most {audience} find 3 fixes in the first 20 minutes.
|
|
249
|
+
|
|
250
|
+
[Soft scarcity close: "Want me to run {framework} on your profile personally? Connect + send me yours, I'll pick 5 this week."]
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Why:** Reversal hook (price to free) creates pattern interrupt. Named framework signals proprietary thinking. Checklist drives saves (5x likes under 360Brew).
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## F9 - Curiosity-Gap Teaser
|
|
258
|
+
|
|
259
|
+
**Reference:** 306 eng (4.25x baseline).
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
Yesterday, our {system} did something.
|
|
263
|
+
|
|
264
|
+
Something we didn't program it to do.
|
|
265
|
+
|
|
266
|
+
[One sensory anchor: "I was watching the logs from my kitchen, half-reading, half-making coffee."]
|
|
267
|
+
|
|
268
|
+
[Specific reveal, not a platitude: the concrete thing that happened]
|
|
269
|
+
|
|
270
|
+
[Reframe: what it means for the category, one paragraph]
|
|
271
|
+
|
|
272
|
+
[Sensory detail: "held a cold cup of coffee for about ten minutes"]
|
|
273
|
+
|
|
274
|
+
[Philosophical close naming an unnamed feeling, ending with a question]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Why:** Line 1 is incomplete + line 2 deepens the gap = scroll-locked brain. Sensory anchor kills the AI-slop pattern detector.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## F10 - Contrarian + Historical Receipts
|
|
282
|
+
|
|
283
|
+
**Reference:** 3,083 eng.
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
{Sacred cow} has been dying since {year}.
|
|
287
|
+
|
|
288
|
+
{Month Year}: {event}. "{Death prediction.}"
|
|
289
|
+
{Month Year}: {event}. "{Death prediction.}"
|
|
290
|
+
[6-9 total dated entries, each 1-2 lines]
|
|
291
|
+
|
|
292
|
+
Every quarter for N years. Every cycle: the same obituary. The same LinkedIn carousel.
|
|
293
|
+
|
|
294
|
+
Here's the counterpunch.
|
|
295
|
+
|
|
296
|
+
[Hard stat with source: "$391B -> $1.81T, 35.9% CAGR"]
|
|
297
|
+
[Second stat: "the shippers grew 3-10x in the same window"]
|
|
298
|
+
|
|
299
|
+
What actually died wasn't {X}. It was {specific subset}. [2-3 lines of who.]
|
|
300
|
+
|
|
301
|
+
What's thriving: {opposite subset with specifics}.
|
|
302
|
+
|
|
303
|
+
[Binary identity close:]
|
|
304
|
+
If you're still {losing behavior}, you already lost.
|
|
305
|
+
If you're {winning behavior}, you already won.
|
|
306
|
+
|
|
307
|
+
[Provocative question: "What's the most embarrassing X-killed-Y prediction you remember?"]
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Why:** Receipt list is a dwell-time machine. Binary identity close forces commenters to pick a side publicly.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## F11 - Emotional Cold-Open
|
|
315
|
+
|
|
316
|
+
**Reference:** highest single post in the 2026 corpus (256k eng), but that reach came from a generic emotional reshare, not the format. Treat it as a ceiling, not a promise. Primary goal: **likes**.
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
{One short line dropped into the emotional peak of a real story: the moment of breaking, loss, or impossible odds. No setup.}
|
|
320
|
+
|
|
321
|
+
{Subject} had almost {given up / lost everything} after {the struggle}.
|
|
322
|
+
|
|
323
|
+
[Mid-scene narrative: 3-6 short lines, present-tense, sensory. The reader is already inside the moment.]
|
|
324
|
+
|
|
325
|
+
[The turn: what changed, who showed up, what it cost.]
|
|
326
|
+
|
|
327
|
+
[One-line meaning, not a moral. Let the story carry it.]
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Why:** Starting at the emotional peak (in medias res) skips the warm-up the scroll punishes. Raw feeling out-travels expertise in every vertical.
|
|
331
|
+
|
|
332
|
+
**Warning:** Do NOT write the first line in ALL CAPS even though many source posts did; all-caps openers read as AI/cringe. Carry the intensity with word choice, not caps. Only use a true story; half the top emotional posts in the corpus were generic reshares, and readers punish manufactured stakes.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## F12 - Permission Slip
|
|
337
|
+
|
|
338
|
+
**Reference:** 29k eng, comment-heavy. Primary goal: **comments**.
|
|
339
|
+
|
|
340
|
+
```
|
|
341
|
+
I don't know who needs to hear this today, but {reassuring truth aimed at one anonymous reader}.
|
|
342
|
+
|
|
343
|
+
[2-4 lines that make the reassurance specific and earned, not a platitude.]
|
|
344
|
+
|
|
345
|
+
[A small, concrete permission: "you're allowed to {X}".]
|
|
346
|
+
|
|
347
|
+
[Soft close that invites the reader to self-identify.]
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Why:** Anonymous second-person reassurance makes readers tag themselves in the comments ("I needed this today"). Comments over-index hard.
|
|
351
|
+
|
|
352
|
+
**Warning:** This is the most formulaic creator-economy opener in the set. Effective but visibly engineered. Use no more than once or twice a month, and only when the reassurance is something you actually believe, or it reads as hollow.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## F13 - Bait-and-Switch Reversal
|
|
357
|
+
|
|
358
|
+
**Reference:** 195k eng (top of the Startups/Tech corpus). Primary goal: **likes**.
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
Enough is enough. No more {beloved perk / standard practice} at {company / in my workflow}.
|
|
362
|
+
|
|
363
|
+
We're also cutting {second thing}.
|
|
364
|
+
|
|
365
|
+
[Beat of suspense: let the reader assume bad news.]
|
|
366
|
+
|
|
367
|
+
[The reveal: it's actually an upgrade. Here's what we replaced it with and why it's better.]
|
|
368
|
+
|
|
369
|
+
[What the change really stands for: the value underneath.]
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Why:** Fake bad news weaponizes loss aversion, then the positive reveal releases it. The whiplash is the engine.
|
|
373
|
+
|
|
374
|
+
**Warning:** Only works if the reveal genuinely resolves positive. A real cut dressed as good news will get torn apart in the comments.
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## F14 - Named Gratitude / Tribute
|
|
379
|
+
|
|
380
|
+
**Reference:** 109k eng, but from a celebrity tribute reshare. The format helps; the fame did the heavy lifting. Primary goal: **reposts**.
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
To {Name}, {Name}, and {Name}: thank you for {the specific quality or thing they did}.
|
|
384
|
+
|
|
385
|
+
[2-4 lines naming what each person or the group actually did. Specific, not generic praise.]
|
|
386
|
+
|
|
387
|
+
[Why it mattered to you / to the work.]
|
|
388
|
+
|
|
389
|
+
[One-line close that honors them, not you.]
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Why:** Publicly naming real people invites everyone tagged or adjacent to repost and amplify. Celebrating others gets shared far more than self-promotion.
|
|
393
|
+
|
|
394
|
+
**Warning:** Name real people for real reasons. Tactical name-dropping to borrow reach is transparent and backfires.
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## F15 - Explain-to-Kids Simplification
|
|
399
|
+
|
|
400
|
+
**Reference:** 23k eng, 2,184 reposts. Primary goal: **saves** (LinkedIn exposes no public save count, so the high repost count stands in as the save-worthiness proxy here, not a sign this is a repost formula).
|
|
401
|
+
|
|
402
|
+
```
|
|
403
|
+
{Jargon term} explained to kids.
|
|
404
|
+
|
|
405
|
+
Explanations for kids... and adults!
|
|
406
|
+
|
|
407
|
+
{emoji} {TERM}: what does it stand for?
|
|
408
|
+
{emoji} {first part} = {plain-language meaning}
|
|
409
|
+
{emoji} {second part} = {plain-language meaning}
|
|
410
|
+
[continue the scannable, emoji-anchored glossary]
|
|
411
|
+
|
|
412
|
+
[One-line "now you'll never forget it" close.]
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Why:** A scannable, emoji-anchored simplification of something dense is saved and reshared as a reference. Works for any jargon-heavy field (finance, law, medicine, engineering).
|
|
416
|
+
|
|
417
|
+
**Warning:** Keep the simplification correct. Condescending or wrong explanations of your own field destroy authority.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## F16 - Status-Strip Humility
|
|
422
|
+
|
|
423
|
+
**Reference:** viral-tier on likes in the corpus (no exact count). Primary goal: **likes**.
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
Outside, I get called {impressive titles like founder, CEO, investor}.
|
|
427
|
+
|
|
428
|
+
At home, none of that survives {the humbling moment}.
|
|
429
|
+
|
|
430
|
+
[The scene that strips the status: a kid, a partner, a quiet failure.]
|
|
431
|
+
|
|
432
|
+
[What the contrast taught you, in one or two lines.]
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Why:** Trading prestige for relatability converts authority into warmth. Senior people get likes by showing the human under the title.
|
|
436
|
+
|
|
437
|
+
**Warning:** Don't humble-brag. The titles in line one should set up a genuine deflation, not a flex disguised as one.
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Engagement-goal split
|
|
442
|
+
|
|
443
|
+
Pick the formula by what you want the post to earn. This split held across all 10 verticals in the 2026 corpus:
|
|
444
|
+
|
|
445
|
+
| Goal | Earned by | Formulas |
|
|
446
|
+
|---|---|---|
|
|
447
|
+
| **Comments** | questions, contrarian/unpopular takes, vulnerability, self-tagging | F4 Confession, F10 Contrarian, F12 Permission Slip, F9 Curiosity-Gap |
|
|
448
|
+
| **Reposts** | quotable maxims, tributes, "X isn't Y" distinctions | F14 Named Gratitude, F2 R.I.P., F8 Paid-vs-Free |
|
|
449
|
+
| **Likes** | emotional stories, celebrations, status-strip, spectacle | F11 Emotional Cold-Open, F13 Bait-and-Switch, F16 Status-Strip |
|
|
450
|
+
| **Saves** | simplifications, exact how-to, frameworks | F15 Explain-to-Kids, F7 Odd-Precision Money, F8 Paid-vs-Free |
|
|
451
|
+
|
|
452
|
+
## Choosing which formula to use
|
|
453
|
+
|
|
454
|
+
| Topic type | Best formula | Why |
|
|
455
|
+
|---|---|---|
|
|
456
|
+
| Platform/category argument | F1 Anaphora | Loss aversion stacks cleanly |
|
|
457
|
+
| Industry era ending | F2 R.I.P. / F10 Contrarian | Force side-picking |
|
|
458
|
+
| Personal year recap | F3 Year-over-Year / F4 Confession | Identity shift reads as earned |
|
|
459
|
+
| Product demo in public | F5 Self-Proving Meta | Structural self-proof |
|
|
460
|
+
| Big authority giveaway | F6 Comment-Gate / F8 Paid-vs-Free | List-building tier |
|
|
461
|
+
| Founder build-log | F7 Odd-Precision Money | Screenshot-bait ledger |
|
|
462
|
+
| Emergent/surprise story | F9 Curiosity-Gap | Scroll-lock hook |
|
|
463
|
+
| Real story with emotional stakes | F11 Emotional Cold-Open | In-medias-res beats the warm-up |
|
|
464
|
+
| Encouragement to a discouraged audience | F12 Permission Slip | Readers self-tag in comments |
|
|
465
|
+
| Policy/process change that's an upgrade | F13 Bait-and-Switch | Fake bad news then relief |
|
|
466
|
+
| Thanking mentors / team / a departing colleague | F14 Named Gratitude | Named people amplify it |
|
|
467
|
+
| Demystifying jargon | F15 Explain-to-Kids | Save-worthy reference |
|
|
468
|
+
| Senior person wanting warmth, not distance | F16 Status-Strip | Prestige traded for relatability |
|
|
469
|
+
|
|
470
|
+
## Hook micro-rules
|
|
471
|
+
|
|
472
|
+
- **"How I" beats "How to".** First-person experience ("How I cut CAC by 62%") outperforms generic instruction ("How to cut CAC") by 2-3x. Swap every "How to" hook to "How I" unless the post is a pure framework with no narrator.
|
|
473
|
+
- **Specific number in the first sentence** raises expand-rate by ~35%. $873.47 beats $900. 40,000 beats "a lot of".
|
|
474
|
+
- **Real failure in the first 3 lines** outperforms polished framing by **8.5x**. Lead with what broke.
|
|
475
|
+
|
|
476
|
+
## Never do
|
|
477
|
+
|
|
478
|
+
- Blend two hooks in one post (dilutes both)
|
|
479
|
+
- Use F5 Self-Proving Meta if you won't actually keep the promise
|
|
480
|
+
- Use F6 Comment-Gate more than once per month
|
|
481
|
+
- Pair F7 Money Ledger with made-up numbers; readers know
|
|
482
|
+
- Use F1 Anaphora to frame LinkedIn as inferior (algo penalty on LinkedIn)
|
|
@@ -56,7 +56,7 @@ if [ -z "$FIREWORKS_API_KEY" ]; then
|
|
|
56
56
|
echo "ERROR: FIREWORKS_API_KEY is not set."
|
|
57
57
|
echo "Create a Fireworks AI account at: https://fireworks.ai/"
|
|
58
58
|
echo "Then export it in your shell profile (~/.zshrc or ~/.bashrc):"
|
|
59
|
-
echo ' export FIREWORKS_API_KEY
|
|
59
|
+
echo ' read -rsp "Fireworks API key: " FIREWORKS_API_KEY; echo; export FIREWORKS_API_KEY'
|
|
60
60
|
exit 1
|
|
61
61
|
fi
|
|
62
62
|
echo "FIREWORKS_API_KEY is set."
|
|
@@ -589,7 +589,9 @@ PYEOF
|
|
|
589
589
|
1. Create a Fireworks AI account at https://fireworks.ai/ and grab your API key from the dashboard
|
|
590
590
|
2. Export it in your shell profile:
|
|
591
591
|
```bash
|
|
592
|
-
|
|
592
|
+
read -rsp "Fireworks API key: " FIREWORKS_API_KEY
|
|
593
|
+
echo
|
|
594
|
+
export FIREWORKS_API_KEY
|
|
593
595
|
```
|
|
594
596
|
3. Restart your terminal or run `source ~/.zshrc`
|
|
595
597
|
4. Invoke this skill when you want multiple open-weight AI perspectives on a question
|
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
"name": "todo-app-backend",
|
|
9
9
|
"version": "1.0.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"better-sqlite3": "^12.
|
|
11
|
+
"better-sqlite3": "^12.11.1",
|
|
12
12
|
"cors": "^2.8.6",
|
|
13
13
|
"express": "^4.18.2",
|
|
14
|
-
"express-rate-limit": "^8.5.2"
|
|
15
|
-
"ip-address": "^10.2.0"
|
|
14
|
+
"express-rate-limit": "^8.5.2"
|
|
16
15
|
},
|
|
17
16
|
"devDependencies": {
|
|
18
17
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -303,9 +302,9 @@
|
|
|
303
302
|
"license": "MIT"
|
|
304
303
|
},
|
|
305
304
|
"node_modules/better-sqlite3": {
|
|
306
|
-
"version": "12.
|
|
307
|
-
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.
|
|
308
|
-
"integrity": "sha512-
|
|
305
|
+
"version": "12.11.1",
|
|
306
|
+
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.11.1.tgz",
|
|
307
|
+
"integrity": "sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==",
|
|
309
308
|
"hasInstallScript": true,
|
|
310
309
|
"license": "MIT",
|
|
311
310
|
"dependencies": {
|
|
@@ -9,11 +9,10 @@
|
|
|
9
9
|
"dev": "ts-node src/index.ts"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"better-sqlite3": "^12.
|
|
12
|
+
"better-sqlite3": "^12.11.1",
|
|
13
13
|
"cors": "^2.8.6",
|
|
14
14
|
"express": "^4.18.2",
|
|
15
|
-
"express-rate-limit": "^8.5.2"
|
|
16
|
-
"ip-address": "^10.2.0"
|
|
15
|
+
"express-rate-limit": "^8.5.2"
|
|
17
16
|
},
|
|
18
17
|
"devDependencies": {
|
|
19
18
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: markdown-rendering
|
|
3
|
+
description: "Open Markdown reliably in cmux panes and recover from blank rendered surfaces."
|
|
4
|
+
category: productivity
|
|
5
|
+
risk: safe
|
|
6
|
+
source: community
|
|
7
|
+
source_repo: davidondrej/skills
|
|
8
|
+
source_type: community
|
|
9
|
+
date_added: "2026-07-07"
|
|
10
|
+
author: davidondrej
|
|
11
|
+
tags: [markdown, cmux, rendering]
|
|
12
|
+
tools: [claude, codex]
|
|
13
|
+
license: "MIT"
|
|
14
|
+
license_source: "https://github.com/davidondrej/skills/blob/main/LICENSE"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Markdown Rendering in cmux
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- Use when opening Markdown in cmux shows a blank pane or wrong layout.
|
|
22
|
+
- Use when you need to display a Markdown file in a stable cmux right pane.
|
|
23
|
+
|
|
24
|
+
## The Problem
|
|
25
|
+
|
|
26
|
+
`cmux markdown open` defaults to **spawning a brand-new pane** every time, even with `--direction right`. The common "fix" — moving the new markdown surface into the existing right pane with `move-surface` — **bugs out: the moved viewer renders BLANK.** The surface keeps `type=markdown` and looks healthy, but shows nothing.
|
|
27
|
+
|
|
28
|
+
So you get stuck: either a stray extra pane, or a blank viewer after moving it.
|
|
29
|
+
|
|
30
|
+
## The Rule
|
|
31
|
+
|
|
32
|
+
You have exactly two reliable options. **Never `move-surface` a markdown viewer** — that is the path that bugs.
|
|
33
|
+
|
|
34
|
+
### Option A — Open it right on the first try
|
|
35
|
+
|
|
36
|
+
If there is no usable right pane yet, just let cmux create one and leave it where it lands:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cmux markdown open /abs/path/file.md --direction right --focus false
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Do NOT then move it. If it spawned where you want it, you're done.
|
|
43
|
+
|
|
44
|
+
### Option B — Close existing right pane(s), then open fresh
|
|
45
|
+
|
|
46
|
+
If there are other right panes in the way (and they're unused or irrelevant), **close them first**, then open the markdown fresh as a new right pane:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 1. find panes in THIS workspace
|
|
50
|
+
cmux list-panes --workspace "$CMUX_WORKSPACE_ID"
|
|
51
|
+
|
|
52
|
+
# 2. close the unused/irrelevant right pane(s) by closing their surfaces
|
|
53
|
+
cmux list-pane-surfaces --pane pane:NN
|
|
54
|
+
cmux close-surface --surface surface:XX # repeat per surface in that pane
|
|
55
|
+
|
|
56
|
+
# 3. THEN open the markdown fresh — it creates its own clean right pane
|
|
57
|
+
cmux markdown open /abs/path/file.md --direction right --focus false
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Hard Rules
|
|
61
|
+
|
|
62
|
+
- **Never `move-surface` a markdown viewer.** It renders blank afterward. This is the core bug this skill exists for.
|
|
63
|
+
- Open it correctly the first time (Option A), OR close the conflicting right pane(s) and open a fresh right pane from scratch (Option B).
|
|
64
|
+
- Only close panes that are unused or irrelevant — never close a pane the user is working in.
|
|
65
|
+
- Always anchor to `$CMUX_WORKSPACE_ID`; never assume the visually focused workspace.
|
|
66
|
+
- Pass `--focus false` so you don't steal the user's focus.
|
|
67
|
+
- You can't screenshot/read a markdown surface to verify it. If unsure it rendered, ask the user.
|
|
68
|
+
|
|
69
|
+
## Limitations
|
|
70
|
+
|
|
71
|
+
- Adapted from `davidondrej/skills`; verify local paths, tools, credentials, and agent features before acting.
|
|
72
|
+
- For commands, remote access, scheduling, browser automation, or file-changing workflows, get explicit user approval and confirm the target environment first.
|
|
@@ -18,6 +18,19 @@ import json
|
|
|
18
18
|
import argparse
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
|
+
|
|
22
|
+
def safe_user_path(path_value, base_dir="."):
|
|
23
|
+
"""Resolve a CLI path under the current workspace."""
|
|
24
|
+
if base_dir != ".":
|
|
25
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
26
|
+
base_path = Path.cwd().resolve()
|
|
27
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
28
|
+
try:
|
|
29
|
+
resolved_path.relative_to(base_path)
|
|
30
|
+
except ValueError as exc:
|
|
31
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
32
|
+
return resolved_path
|
|
33
|
+
|
|
21
34
|
# Fix Unicode output on Windows (cp1252 terminal)
|
|
22
35
|
if sys.platform == 'win32':
|
|
23
36
|
try:
|
|
@@ -498,7 +511,7 @@ def main():
|
|
|
498
511
|
analyzer.print_report(report)
|
|
499
512
|
|
|
500
513
|
if args.output:
|
|
501
|
-
output_path =
|
|
514
|
+
output_path = safe_user_path(args.output)
|
|
502
515
|
if args.json:
|
|
503
516
|
output_path.write_text(json.dumps(report, indent=2, ensure_ascii=False))
|
|
504
517
|
else:
|
|
@@ -16,6 +16,19 @@ import sys
|
|
|
16
16
|
import json
|
|
17
17
|
import argparse
|
|
18
18
|
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def safe_user_path(path_value, base_dir="."):
|
|
22
|
+
"""Resolve a CLI path under the current workspace."""
|
|
23
|
+
if base_dir != ".":
|
|
24
|
+
raise ValueError("Custom base directories are not supported for CLI paths")
|
|
25
|
+
base_path = Path.cwd().resolve()
|
|
26
|
+
resolved_path = Path(path_value).expanduser().resolve()
|
|
27
|
+
try:
|
|
28
|
+
resolved_path.relative_to(base_path)
|
|
29
|
+
except ValueError as exc:
|
|
30
|
+
raise ValueError(f"Path escapes allowed directory: {path_value}") from exc
|
|
31
|
+
return resolved_path
|
|
19
32
|
from dataclasses import dataclass, field
|
|
20
33
|
from typing import Dict, List, Set, Optional, Tuple
|
|
21
34
|
|
|
@@ -518,14 +531,14 @@ def main():
|
|
|
518
531
|
output = analyzer.to_dot()
|
|
519
532
|
print(output)
|
|
520
533
|
if args.output:
|
|
521
|
-
|
|
534
|
+
safe_user_path(args.output).write_text(output)
|
|
522
535
|
print(f"\n✅ Arquivo DOT salvo: {args.output}")
|
|
523
536
|
print(" Para visualizar: dot -Tpng deps.dot -o deps.png")
|
|
524
537
|
else:
|
|
525
538
|
analyzer.print_report(report)
|
|
526
539
|
|
|
527
540
|
if args.output and args.format != 'dot':
|
|
528
|
-
|
|
541
|
+
safe_user_path(args.output).write_text(
|
|
529
542
|
json.dumps(report, indent=2, ensure_ascii=False),
|
|
530
543
|
encoding='utf-8'
|
|
531
544
|
)
|