Skills
4478 foundAgent Skills are multi-file prompts that give AI agents specialized capabilities. They include instructions, configurations, and supporting files that can be used with Claude, Cursor, Windsurf, and other AI coding assistants.
Audit a game, feature flow, economy path, onboarding journey, progression chain, or live-ops loop for friction quality and friction accumulation. Use when di...
--- name: game-design-friction-journey-audit description: Audit a game, feature flow, economy path, onboarding journey, progression chain, or live-ops loop for friction quality and friction accumulation. Use when diagnosing where players stall, disengage, churn, or feel overloaded; when distinguishing productive challenge from harmful friction; or when evaluating whether constraints, waiting, confusion, resource pressure, or multi-step dependencies are creating strategy, tension, frustration, or deadlock. --- # Game Design Friction Journey Audit Audit a design by mapping where friction appears across a player journey, what kind of friction it is, how it accumulates, and where useful challenge mutates into harmful drag. Use this skill when a feature feels sticky in the wrong way, when progression seems to slow down for reasons players cannot articulate clearly, or when you need to separate meaningful challenge from accidental obstruction. ## Core principle Not all friction is bad. Some friction creates commitment, decision-making, anticipation, and mastery. Other friction creates confusion, paralysis, resentment, or churn. The job is not to remove all resistance. The job is to identify which resistance is doing design work and which is merely getting in the player's way. ## What to produce Generate: 1. **Audit target** - what journey, loop, or feature is being reviewed 2. **Journey breakdown** - the major steps in player progression through the target flow 3. **Friction map** - where friction appears, what kind it is, and what causes it 4. **Accumulation analysis** - where multiple frictions stack into exhaustion or deadlock 5. **Diagnosis** - where the design shifts from meaningful challenge to harmful blockage 6. **Recommendations** - what to preserve, reduce, surface, reorder, or remove ## Process ### 1. Define the journey being audited Clarify: - what system or flow is under review - what kind of player it applies to - what stage of play it belongs to: FTUE, early game, mid-game, elder game, event loop, monetization path, social loop, etc. - what desired player behavior the flow is supposed to support Write: - **Audit target** - **Expected player goal** - **Player context** ### 2. Break the journey into steps Map the journey as a sequence of player-facing steps. For each step, identify: - player action - player decision - requirement or dependency - feedback or reward - what unlocks the next step Keep steps coarse enough to be readable but concrete enough to locate friction. ### 3. Identify friction at each step For each step, ask: - what slows progress? - what blocks progress? - what creates uncertainty? - what consumes time, attention, or resources? - what forces tradeoffs or commitment? Possible friction sources: - resource scarcity - dependency chains - waiting and timers - unclear affordances or goals - UI or information opacity - cognitive overload - skill challenge - social coordination burden - random variance - harsh penalty or recovery cost - monetization pressure ### 4. Classify the friction Classify each friction point as one of these: #### Productive friction Supports: - decision-making - planning - anticipation - mastery - commitment - strategic tradeoff - emotional tension that feels fair and legible #### Harmful friction Produces: - confusion - dead time without meaning - arbitrary blocking - unreadable requirements - overloaded task chains - repeated admin work - punishment without learning - progress paralysis #### Mixed friction Useful in principle, but currently too strong, too opaque, too stacked, or too poorly timed. Do not treat this as binary if it is not. Many systems are good ideas implemented at the wrong intensity. ### 5. Assess intensity and visibility For each friction point, rate: - **Intensity** - low / medium / high - **Visibility** - obvious / partially hidden / opaque - **Fairness feel** - fair / borderline / unfair-feeling A friction can be mild but still dangerous if it is hidden. It can also be intense but acceptable if the player clearly understands it and sees why it exists. ### 6. Analyze friction accumulation Look for stack effects. Ask: - where do several medium frictions compound into a high-friction moment? - where are players forced to satisfy too many constraints at once? - where does the flow ask for too much memory, too much waiting, or too many parallel tasks? - where do repeated harmful frictions appear without enough reward, clarity, or release? Common accumulation patterns: - multiple resources plus timer plus low clarity - complex chain plus weak feedback plus low inventory space - repeated losses plus long recovery plus weak learning signal - social obligation plus schedule pressure plus poor coordination tools ### 7. Find the breakpoints Identify: - where challenge turns into drag - where strategy turns into opacity - where anticipation turns into dead time - where difficulty turns into helplessness - where a healthy loop turns into churn risk These are the key design breakpoints. ### 8. Diagnose the role of friction in the design Answer: - which friction points are core to the fantasy or mastery arc? - which friction points only exist because of weak clarity, weak UX, poor pacing, or over-constrained economy? - what friction is essential and should be protected? - what friction is currently doing accidental damage? ### 9. Recommend design changes For each major friction issue, specify: - **Issue** - **Why it hurts** - **Keep / reduce / remove / surface / reorder / soften** - **Expected effect** Typical interventions: - surface hidden requirements - reduce simultaneous constraints - improve feedback and goal clarity - shorten dead-time without removing commitment - preserve meaningful tradeoffs while removing admin burden - stagger dependencies instead of stacking them all at once ## Response structure ### Audit Target - ... ### Journey Breakdown 1. ... 2. ... 3. ... ### Friction Map | Step | Friction Point | Type | Cause | Intensity | Visibility | Fairness Feel | |---|---|---|---|---|---|---| | ... | ... | ... | ... | ... | ... | ... | ### Accumulation Analysis - ... ### Breakpoints - ... ### Diagnosis - ... ### Recommendations 1. ... 2. ... 3. ... ## Fast mode Use this quick pass when speed matters: - where does the player slow down or stop? - is the friction creating strategy or confusion? - is it fair and legible? - what other frictions are stacking nearby? - what should be preserved, softened, surfaced, or removed? ## Working principle Good friction gives the player something meaningful to push against. Bad friction makes the player wonder why they are pushing at all.
Generates fair, structured, balanced product comparisons, pros/cons lists, buying guides, and personalized recommendations for informed purchase decisions.
# Product Comparison & Review Copywriter ## Purpose This skill generates fair, structured product comparison content — head-to-head comparison tables, category buying guides, balanced pros/cons analyses, specification battles, and personalized "best for" recommendations. It is built with fairness as a first principle: the output must be useful to readers making purchase decisions, not a disguised sales pitch. Designed for e-commerce product pages, editorial content, affiliate marketing, and merchant category pages. ## Triggers - "product comparison" - "product VS" - "对比评测" - "buying guide" - "pros and cons" - "选购指南" - "compare products" - "spec comparison" - "best for recommendation" - "优缺点分析" ## Workflow 1. Receive products to compare from user: Product A and Product B (or a category with multiple entries), with key specs, price points, target users, and any sponsored/editorial disclosure. 2. Build a feature comparison matrix: list all comparable features across both products, note where data is missing. 3. Generate balanced pros and cons for each product — a MINIMUM of 2 pros and 2 cons per product, even for the recommended one. 4. Create "best for" recommendations based on user personas, not product superiority: "Product A is best for [persona/use case], Product B is best for [different persona/use case]." 5. Apply the fairness gate: verify no invented weaknesses, no suppressed advantages, no defamatory language. 6. Output the complete comparison package: feature table + pros/cons + buying recommendation + fairness disclosure. ## Prompt Templates ### 1. Head-to-Head Comparison (`head_to_head_comparison`) **Purpose:** Generate a structured A vs B comparison. **Input:** - `product_a_name` — Product A name + key specs - `product_b_name` — Product B name + key specs - `comparison_focus` — What matters most (price/performance/quality/features/ecosystem) - `disclosure` — Editorial or sponsored relationship **Output:** Feature matrix table + balanced pros/cons per product + "best for" verdict + fairness disclosure. ### 2. Buying Guide (`buying_guide`) **Purpose:** Create a tiered buying guide for a product category. **Input:** - `category` — Product category (e.g., "noise-canceling headphones") - `budget_tiers` — Price brackets with 1–2 products per tier - `user_personas` — 2–3 buyer types and what they value **Output:** Tiered guide: Budget Tier | Product(s) | Key Feature | Best For | Pros | Cons | Price. ### 3. Pros/Cons Generator (`pros_cons_generator`) **Purpose:** Generate an objectively balanced pros/cons list for one product. **Input:** - `product_name` — Product - `product_details` — Full specs, price, user reviews context - `use_case` — Intended usage context **Output:** Pros list (minimum 3) and Cons list (minimum 2), each with a one-sentence explanation. ### 4. Spec Battle (`spec_battle`) **Purpose:** Format raw specifications into a readable comparison. **Input:** - `product_a_specs` — Structured spec list for Product A - `product_b_specs` — Structured spec list for Product B - `highlight_categories` — Which spec categories to emphasize **Output:** Spec comparison table: Feature | Product A | Product B | Winner (if clear) | Note. ### 5. Best For Matcher (`best_for_matcher`) **Purpose:** Match products to user personas with personalized recommendations. **Input:** - `product_options` — 2–5 products in a category - `user_persona` — One persona description (type, budget, priorities, constraints) **Output:** Ranked recommendation: #1 pick with reasoning, runner-up, and "avoid if" note for each product. ## Output Format Every comparison is delivered in a reader-friendly structure: **Feature Comparison Table:** | Feature | Product A | Product B | Edge | |---------|-----------|-----------|------| | Price | ¥299 | ¥399 | A | | ... | ... | ... | ... | **Pros & Cons:** - **Product A** - ✅ Pro 1: ... - ❌ Con 1: ... - **Product B** (same structure) **Verdict:** Best for [persona/use case] → [which product and why] **Fairness Disclosure:** [Editorial/Sponsored/Data sources] ## Safety Rules - **NEVER** invent or exaggerate a competitor's weakness — if data is missing, say "data not available" - **NEVER** suppress or omit a competitor's genuine advantage - **NEVER** use defamatory, dismissive, or insulting language about any product - **NEVER** present sponsored content as editorial — always label sponsorship - **ALWAYS** generate AT LEAST 2 cons for every product, even the recommended one - **ALWAYS** cite sources when using third-party data or reviews - **ALWAYS** provide a fairness disclosure section ## Examples ### Example 1: Head-to-Head (Smartphones) **Input:** A="Phone X ¥2999 6.7in 5000mAh 64MP", B="Phone Y ¥3299 6.5in 4500mAh 108MP", Focus="camera+battery" **Output:** Feature table with 8 rows, A wins on battery/price, B wins on camera/resolution. Pros/cons for each (Phone X con: "lower camera resolution"; Phone Y con: "higher price, smaller battery"). Verdict: "Phone X best for budget-conscious battery users; Phone Y best for photography enthusiasts." ### Example 2: Buying Guide **Input:** Category="蓝牙耳机 (Bluetooth Earbuds)", Tiers=["入门<200", "中端200-500", "高端>500"], Personas=["通勤党", "运动党", "学生党"] **Output:** Three-tier guide with 5 products, each linked to a persona, with balanced pros/cons. ## Related Skills - [product-title-booster](../product-title-booster/) — For optimizing titles of the compared products - [review-reply-coach](../review-reply-coach/) — For responding to reviews that the comparison may attract - [landing-page-copy-pro](../landing-page-copy-pro/) — For the landing page hosting the buying guide FILE:ACCEPTANCE.md # Acceptance Criteria — Product Comparison & Review Copywriter - [ ] SKILL.md is self-contained (agent can operate from it alone) - [ ] All 5 prompt templates are complete with `placeholder` inputs - [ ] Safety rules mandate fairness: minimum cons per product, no invented weaknesses, citation of sources - [ ] README.md has clear install instructions + 3 usage examples - [ ] skill.json is valid JSON with all required fields - [ ] Content is unique — comparison table format differs from all other skills - [ ] "Best for matcher" persona-based approach is structurally distinct from other recommendation-style outputs - [ ] Slugs follow naming convention (user-facing, no prefix codes) FILE:README.md # Product Comparison & Review Copywriter Fair, structured product comparisons — VS tables, buying guides, pros/cons, and personalized recommendations. ## Features - Head-to-head A vs B comparison tables with balanced analysis - Category buying guides with tiered recommendations per persona - Objective pros/cons lists — always includes cons for every product - Specification battle formatting for technical products - "Best for" matcher that personalizes recommendations to user personas - Built-in fairness gate: no invented weaknesses, no suppressed advantages ## Install ``` openclaw skills install harrylabsj/product-comparison-writer ``` ## Usage ``` 对比A和B两款扫地机器人,生成一个对比表格和选购建议 写一个2000元以内蓝牙耳机的选购指南,分入门、中端两档 为这款产品生成客观的优缺点列表,至少3个优点2个缺点 把这个产品的技术参数做成对比表格,和竞品PK ``` ## Platforms E-Commerce Product Pages, Blogs, Editorial Review Sites ## Safety Fairness-first: every product gets real cons. No invented competitor weaknesses. Sponsored content is always labeled. All data sources cited. ## License MIT FILE:skill.json { "name": "Product Comparison & Review Copywriter", "description": "Fair, structured product comparison copy — feature matrices, pros/cons tables, 'best for' recommendations, buying guides, and review roundups. Built-in fairness guardrails ensure ethical competitive comparison.", "version": "1.0.0", "type": "prompt-flow", "category": "E-Commerce / Product Content", "keywords": [ "product comparison", "VS", "对比评测", "buying guide", "选购指南", "pros and cons", "review roundup", "comparison table", "best for", "spec comparison" ], "platforms": ["E-Commerce Product Pages", "Blogs", "Editorial Review Sites"], "requires": {}, "requires_api": false, "author": "harrylabsj", "license": "MIT", "safety": { "no_code_execution": true, "no_network": true, "no_credentials": true, "compliance_notes": "MUST maintain fairness — no invented competitor weaknesses. No suppressed competitor advantages. Cite sources for third-party data. Label editorial vs sponsored. No defamatory language. All claims must be verifiable." } }
Design and measure viral growth loops using the viral coefficient (K-factor), viral loop type taxonomy, and cycle time optimization. Use whenever a startup f...
---
name: viral-growth-loop-design
description: "Design and measure viral growth loops using the viral coefficient (K-factor), viral loop type taxonomy, and cycle time optimization. Use whenever a startup founder, growth marketer, or product lead is designing referral programs, measuring word-of-mouth, building viral features, calculating K-factor, trying to achieve exponential growth, optimizing invite flows, debugging a viral feature that isn't working, or evaluating whether viral is the right channel. Activates on phrases like 'viral marketing', 'viral coefficient', 'K-factor', 'referral program', 'invite flow', 'network effects', 'word of mouth', 'exponential growth', 'viral loop', 'Dropbox referral', 'Hotmail signature', 'inherent virality', 'cycle time', 'should we go viral'."
version: 1.0.0
homepage: https://github.com/bookforge-ai/bookforge-skills/tree/main/books/traction/skills/viral-growth-loop-design
metadata: {"openclaw":{"emoji":"📚","homepage":"https://github.com/bookforge-ai/bookforge-skills"}}
status: draft
source-books:
- id: traction
title: "Traction: A Startup Guide to Getting Customers"
authors: ["Gabriel Weinberg", "Justin Mares"]
chapters: [7]
domain: startup-growth
tags: [startup-growth, viral-marketing, referral-programs, network-effects, growth-metrics]
depends-on: [bullseye-channel-selection]
execution:
tier: 2
mode: hybrid
inputs:
- type: document
description: "Product description, current viral metrics if any, referral mechanics"
tools-required: [Read, Write]
tools-optional: [Bash, AskUserQuestion]
mcps-required: []
environment: "Plain-text working directory for viral loop designs and K-factor calculations"
discovery:
goal: "Design or optimize a viral loop using the K-factor formula, loop type taxonomy, and cycle time tactics"
tasks:
- "Classify the product's best-fit viral loop type (7 types)"
- "Measure or estimate the viral coefficient K = i × conversion%"
- "Decompose K into invite rate, click-through rate, signup rate — find the bottleneck"
- "Optimize the weakest variable via focused A/B testing"
- "Shorten the viral cycle time"
- "Detect and prevent the 4 viral mistakes"
audience:
roles: [startup-founder, growth-marketer, product-manager]
experience: intermediate
when_to_use:
triggers:
- "User wants to add viral mechanics to a product"
- "User has a viral feature that isn't producing growth"
- "User is measuring referral program performance"
- "Bullseye Framework selected viral as an inner-circle channel"
prerequisites:
- skill: bullseye-channel-selection
why: "Viral should be selected via Bullseye first, not default-assumed"
not_for:
- "Products without inherent sharing value (viral will not rescue a bad product)"
environment:
codebase_required: false
codebase_helpful: true
works_offline: true
quality:
scores:
with_skill: null
baseline: null
delta: null
tested_at: null
eval_count: 0
assertion_count: 13
iterations_needed: 0
---
# Viral Growth Loop Design
## When to Use
The startup has selected viral marketing as a channel (via Bullseye) and needs to design, measure, or optimize a viral growth loop. Before starting, verify:
- The product has at least plausible sharing value (products that aren't inherently viral will not be rescued by viral mechanics — this is viral mistake #1)
- The user has metrics or can instrument metrics for invites and conversions
- Viral was genuinely selected, not defaulted to because "growth hacking"
## Context & Input Gathering
### Required Context (must have — ask if missing)
- **Product description:** what the product does, who uses it, what makes it share-worthy (or why not)
→ Check prompt for: product name, category, sharing signals
→ If missing, ask: "What does your product do, and why would one user tell another about it?"
- **Current metrics (if any):** signups per period, invites sent, invite-to-signup conversion
→ Check prompt for: numbers, "our K is", "conversion rate"
→ If missing: proceed with hypothetical design, note measurement needs
### Observable Context
- **Existing viral features:** referral program, share buttons, invite flows
- **Product communication patterns:** how users already talk to others about the product
### Default Assumptions
- K > 1 = exponential, K > 0.5 = meaningful contribution, K < 0.5 = not a primary channel
- Optimization focus on the single weakest variable (invite rate OR click-through OR signup)
- 1-2 engineers × 2-3 months minimum to implement viral properly
### Sufficiency Threshold
```
SUFFICIENT: product description + current K measurement or instrumentation plan
PROCEED WITH DEFAULTS: product description known, assume viral is being designed from scratch
MUST ASK: product description is missing, can't recommend loop type
```
## Process
Use TodoWrite:
- [ ] Step 1: Classify viral loop type (7 types)
- [ ] Step 2: Measure baseline K and cycle time
- [ ] Step 3: Decompose K to find the weakest variable
- [ ] Step 4: Design focused optimization (4 viral mistakes check)
- [ ] Step 5: Shorten cycle time
### Step 1: Classify the Viral Loop Type
**ACTION:** Determine which of the 7 viral loop types best fits the product. See [references/viral-loop-types.md](references/viral-loop-types.md) for the full taxonomy.
The 7 types:
1. **Word of Mouth** — organic (nothing engineered). Works when the product is genuinely remarkable.
2. **Inherent Virality** — product requires multiple users (Skype, WhatsApp, Snapchat).
3. **Collaborative Virality** — works alone but better with others (Google Docs, Figma).
4. **Communicative Virality** — product messages carry branding ("Sent from my iPhone", Hotmail signature).
5. **Incentivized Virality** — rewards for referrals (Dropbox extra storage, Uber credits, PayPal cash).
6. **Embedded/Widget Virality** — share buttons, embed codes (YouTube embed, Pinterest pins).
7. **Social Virality** — activity broadcast to social networks (Spotify on Facebook, Strava sharing).
Write the type classification with reasoning to `viral-loop-design.md`.
**WHY:** Loop type determines every downstream decision. An incentivized referral program that would work for a file-storage product would feel spammy on a B2B analytics tool. Getting type wrong is one of the 4 viral mistakes — "bolting on generic sharing mechanics without understanding how users are currently communicating". The type must match the product's actual usage pattern.
**IF** no type fits cleanly → that's a signal viral may not be the right channel. Return to Bullseye with new data.
### Step 2: Measure or Estimate Baseline K and Cycle Time
**ACTION:** Calculate the viral coefficient:
**K = i × conversion_percentage**
- **i** = average number of invites per user (how many people each user invites)
- **conversion_percentage** = percentage of invitees who sign up
Worked example: users send 3 invites each, 2 of 3 invitees convert → K = 3 × (2/3) = 2. Starting with 100 users, next cycle produces 200, next 400, etc. Exponential.
**Thresholds:**
- K > 1: true exponential growth
- K > 0.5: meaningful contribution to growth
- K < 0.5: viral is not a primary channel
Also measure **viral cycle time** — the time between a user joining and their invitees joining. Shorter cycle time = faster compounding. YouTube's cycle time is minutes; slower products can be days or weeks.
Write measurements (or measurement plan if not yet instrumented) to `viral-baseline.md`.
**WHY:** Without baseline K, you're optimizing blind. Every intervention needs a before/after comparison. The K threshold decides whether viral is primary or secondary — K < 0.5 means viral should be a supporting channel, not the main one. Cycle time is often overlooked — two products with the same K but different cycle times have dramatically different growth curves (K=0.9 at 1-day cycle vs K=0.9 at 7-day cycle → very different compounding).
### Step 3: Decompose K to Find the Weakest Variable
**ACTION:** Decompose K further: **K = i × click_through_percentage × signup_percentage**
Measure each component:
- **i** — how many invites are sent per user?
- **click_through_percentage** — how many invite links are clicked?
- **signup_percentage** — of clickers, how many sign up?
Find the weakest variable. That's the optimization target.
**WHY:** Focusing optimization on the wrong variable wastes weeks. If invite rate is healthy (people ARE sharing) but signup conversion is 2%, changing the invite flow doesn't help — the landing page is the problem. Decomposition reveals the actual bottleneck. "Not doing enough A/B tests" is another of the 4 viral mistakes — running tests on the wrong variable is effectively the same failure.
### Step 4: Design Focused Optimization + Check 4 Viral Mistakes
**ACTION:** Run the **4 viral mistakes check** before proposing changes:
1. **Not inherently viral product trying to add viral features** — will the loop work at all? If the product has no plausible sharing hook, stop.
2. **Bad product trying to go viral** — virality accelerates whatever the product is. A bad product + virality = negative reviews spreading faster.
3. **Not enough A/B tests** — assume 1-3 of every 10 tests will yield positive results. Plan accordingly.
4. **Bolting on generic sharing mechanics** — "just add Facebook Like buttons" without understanding user communication is the most common mistake.
If any of the 4 mistakes apply, fix that before optimizing.
Then design focused A/B tests for the weakest variable. Run 2-3 variants for 2-3 weeks at a time. Budget: 1-2 engineers × 2-3 months minimum for serious viral work.
**WHY:** The 4 mistakes prevent wasted optimization cycles. Running 20 A/B tests on the invite flow of a non-viral product produces nothing. Running 20 A/B tests on a healthy invite flow when the bottleneck is signup conversion also produces nothing. The mistakes are named to make them detectable.
### Step 5: Shorten the Viral Cycle Time
**ACTION:** Map the full viral loop — every step between "user takes action" and "new user signs up". Count the steps. Remove any unnecessary step. For each remaining step, ask: "can this be faster?"
Tactics:
- Create urgency (expiring invites, time-limited rewards)
- Remove friction at every funnel step (single-click accept, pre-filled forms, social login)
- Trigger invites at the natural sharing moment (not later)
- Incentivize completion of the next step, not just the final conversion
**WHY:** Cycle time is the most underrated variable. Two products with K = 0.9 but cycle times of 1 day vs 7 days have dramatically different user curves after 30 days. Reducing cycle time by half is equivalent to doubling K for long-term compounding effects. Yet founders obsess over K and ignore cycle time.
## Inputs
- Product description (with sharing hypothesis)
- Current viral metrics (if instrumented)
- Implementation resources (engineers × months)
## Outputs
Four markdown/data files:
1. **`viral-loop-design.md`** — Loop type classification, mechanics, implementation plan
2. **`viral-baseline.md`** — Current K, cycle time, decomposed metrics
3. **`viral-optimization-plan.md`** — Weakest variable, A/B test roadmap, 4 mistakes check
4. **`viral-cycle-time-map.md`** — Full loop steps with friction analysis
## Key Principles
- **K is a formula, not a vibe.** K = i × conversion_percentage. Founders who say "we're going viral" without calculating K are making a category error. WHY: Without numeric K, you can't tell if you're growing virally or just growing. The formula forces clarity.
- **Loop type must match the product's communication pattern.** Generic share buttons on a product users don't naturally discuss is mistake #4. Watch how users ALREADY share the product before designing the loop. WHY: A loop that fights user behavior produces 0% conversion; a loop that amplifies existing behavior compounds.
- **Optimize the weakest link, not the favorite metric.** Founders love to A/B test invite copy. If the bottleneck is signup conversion, invite copy changes nothing. WHY: Decomposition is the only way to find the actual bottleneck. Skipping decomposition is optimization theater.
- **Viral is not a rescue plan for a bad product.** The 4 viral mistakes are explicit: if the product isn't inherently viral, or if the product is bad, virality won't save it — it will accelerate the decline. WHY: This is the most common founder error. Virality is leverage, and leverage works in both directions.
- **Cycle time matters as much as K.** A 7-day cycle and a 1-day cycle with the same K produce radically different growth curves. Shortening cycles is often easier than raising K. WHY: Compounding is about iteration count, not just multiplier. Fast cycles compound more iterations per unit time.
- **Budget 2-3 months for serious viral work.** "Expert teams need 1-2 engineers for 2-3 months minimum to implement and optimize a new viral channel." Viral is not a weekend project. WHY: Shortcuts on viral engineering produce broken loops that look right but don't compound. The time budget is the floor, not the ceiling.
## Examples
**Scenario: File-sharing SaaS adding a referral program**
Trigger: "We're building Dropbox-for-teams. Want to add a referral program. How should it work?"
Process: (1) Loop type: Incentivized Virality fits (Dropbox's original model). Alternative: Collaborative Virality since teams use it together. Decision: combine both — team invites trigger collaborative flow, external referrals get storage credits. (2) Estimate K: assume i=2 (each user invites 2 on average), conversion 30% → K=0.6. Meaningful but not exponential. (3) Decompose: if click-through is 60% and signup is 50%, the weakest variable is signup — optimize that first. (4) 4 mistakes check: product is genuinely collaborative (not mistake 1), product works (not mistake 2), plan weekly A/B tests (not mistake 3), mechanics match how teams actually invite colleagues (not mistake 4). (5) Cycle time: trigger invite moment at "share file with external user" action (natural moment), reward appears at next login (fast).
Output: Clear implementation plan with incentive structure, estimated K baseline, and optimization priority on signup conversion.
**Scenario: Consumer app with K=0.2 — is viral the channel?**
Trigger: "We added a referral feature to our mobile game. Measured K over 30 days: K=0.2. What should we do?"
Process: (1) Loop type: check if current mechanics match the product. If users aren't naturally discussing the game with friends, the incentivized loop was bolted on. (2) K=0.2 is below the 0.5 threshold — viral is not a primary channel. (3) Decompose: low i (users aren't sending invites at all)? Low conversion (invitees click but don't install)? Decomposition reveals the problem. (4) 4 mistakes check: is the product inherently viral? For a mobile game, only if it's multiplayer or has leaderboards. If single-player, viral mechanics are fighting the product's nature. (5) Recommendation: return to Bullseye. Viral as supporting channel only, not primary.
Output: Honest assessment that viral isn't the channel, recommendation to re-run Bullseye with this data.
**Scenario: B2B SaaS considering collaborative virality**
Trigger: "We built a spreadsheet-like analytics tool. Think Figma for data. Should we make it viral?"
Process: (1) Loop type: Collaborative Virality is the clear fit — the product works alone but is 10x more valuable when shared with colleagues. (2) Baseline unknown, but plan the metrics: measure share action rate, external-user signup rate. (3) Decompose from day one: i, click-through, signup separately. (4) 4 mistakes check: product IS inherently collaborative ✓, product quality TBD, budget 2 engineers × 3 months, mechanics match how Figma does it (invite = real seat, not just a link). (5) Cycle time: optimize "share moment" UX so it happens naturally mid-workflow, not as a separate step.
Output: Loop type decision, Figma-inspired mechanics plan, instrumentation requirements for baseline measurement.
## References
- For the full 7-type viral loop taxonomy with examples, see [references/viral-loop-types.md](references/viral-loop-types.md)
- For the 4 viral mistakes in detail, see [references/viral-mistakes.md](references/viral-mistakes.md)
## License
This skill is licensed under [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/).
Source: [BookForge](https://github.com/bookforge-ai/bookforge-skills) — Traction: A Startup Guide to Getting Customers by Gabriel Weinberg and Justin Mares.
## Related BookForge Skills
Install related skills from ClawhHub:
- `clawhub install bookforge-bullseye-channel-selection` — Select viral deliberately, don't default to it
- `clawhub install bookforge-traction-channel-testing` — Baseline K and A/B test discipline
- `clawhub install bookforge-content-and-email-marketing` — Referral emails are part of the viral loop
Or install the full book set from GitHub: [bookforge-skills](https://github.com/bookforge-ai/bookforge-skills)
FILE:references/viral-loop-types.md
# The 7 Viral Loop Types
Complete taxonomy from Chapter 6 of *Traction*. Each loop type has distinct mechanics, strengths, and product-fit criteria.
## 1. Word of Mouth
**Mechanics:** Users spontaneously tell others because the product is remarkable. Nothing engineered.
**Examples:** Early Facebook (before engineered loops), books, TV shows, genuinely surprising products.
**Fit:** Works when product is genuinely 10x better or uniquely memorable.
**K-factor signal:** Very hard to measure directly; often inferred from organic growth that isn't attributable to any channel.
**Caveat:** Cannot be a primary strategy — too uncontrollable.
## 2. Inherent Virality (Necessity Virality)
**Mechanics:** Product is worthless without other users. Inviting others is a functional requirement.
**Examples:** Skype, WhatsApp, Snapchat, Zoom.
**Fit:** Communication/social products where single-user value is zero.
**K-factor signal:** Strong if product is used. Users MUST invite others to get value.
**Caveat:** Hard cold-start problem — the first users get no value until others join.
## 3. Collaborative Virality
**Mechanics:** Product works alone but becomes substantially more valuable when shared with others. Users invite collaborators because collaboration is the natural workflow.
**Examples:** Google Docs, Figma, Notion, Dropbox (team use).
**Fit:** Productivity tools, creative tools, team workflows.
**K-factor signal:** Moderate to strong. Sharing happens mid-workflow, not as a separate marketing act.
**Advantage:** No cold-start problem (single-user value exists).
## 4. Communicative Virality
**Mechanics:** Messages the user sends via the product carry the product's branding. Every communication is a passive ad.
**Examples:** Hotmail "Get free email" signature, "Sent from my iPhone", MailChimp "powered by" branding on free tier, early Gmail signatures.
**Fit:** Communication products where users naturally send messages.
**K-factor signal:** Strong if messaging volume is high. Free tier users become distribution.
**Implementation:** Passive — added as default, opt-out costs the user something.
## 5. Incentivized Virality
**Mechanics:** Explicit reward for successful referrals. Both referrer and referred get something.
**Examples:** Dropbox (extra storage), Uber/Lyft ($ credits), Airbnb ($ travel credit), PayPal (cash), Gilt (early invites).
**Fit:** Products with clear unit economics where CAC-via-referral is less than traditional CAC.
**K-factor signal:** Tunable — incentive size adjusts K.
**Implementation:** Must track attribution carefully; fraud prevention matters.
## 6. Embedded / Widget Virality
**Mechanics:** Share buttons, embed codes, widgets that place the product on other sites. Each embed is a distribution point back to the product.
**Examples:** YouTube embed codes, Pinterest "Pin It" buttons, reddit widget, Twitter embed, Google Maps embed.
**Fit:** Content, media, utility products with natural embed surfaces.
**K-factor signal:** Compounds over time — each embed is persistent distribution.
**Advantage:** Asynchronous and SEO-contributing.
## 7. Social (Broadcasting) Virality
**Mechanics:** User activity is broadcast to their social network (Facebook, Twitter, Instagram).
**Examples:** Spotify plays on Facebook, Strava runs shared, Nike running app shares, early Pinterest pins.
**Fit:** Products that produce shareable artifacts (songs, runs, photos, achievements).
**K-factor signal:** Dependent on platform policy — Facebook et al periodically tighten or loosen these.
**Caveat:** Platform dependency risk (Zynga on Facebook is the cautionary tale).
## Choosing Between Types
- **Can users invite others by default?** → Communicative (Hotmail model)
- **Does the product require multiple users to work?** → Inherent
- **Is collaboration the natural use case?** → Collaborative
- **Do users share artifacts outside the product?** → Embedded or Social
- **Are unit economics strong enough to pay for referrals?** → Incentivized
- **Is the product so remarkable it spreads on its own?** → Word of Mouth (but don't plan on this)
## Combining Types
Many products use 2-3 loop types together. Dropbox combines:
- Incentivized (storage for referrals)
- Collaborative (team file sharing)
- Embedded (shared file links)
Each loop type produces growth on a different substrate. Combining them multiplies effects without cannibalizing (when designed well).
## Source
Chapter 6 ("Viral Marketing") of *Traction* by Gabriel Weinberg and Justin Mares.
FILE:references/viral-mistakes.md
# The 4 Viral Mistakes
Andrew Chen's named failure modes for viral marketing, from Chapter 6 of *Traction*.
## Mistake 1: Non-Viral Products Trying to Add Viral Features
**What it looks like:** Building a product that has no inherent sharing value, then trying to bolt viral mechanics on top.
**Why it fails:** Viral features don't create sharing — they amplify existing sharing behavior. A product nobody naturally mentions to friends will not suddenly be mentioned because you added a "refer a friend" button.
**Detection:** Ask "Would users tell their friends about this product even without any viral feature?" If no, the product isn't inherently viral. Viral mechanics will produce K = 0.05, not K = 1.
**Fix:** Go back to product-market fit work. Viral is not the answer.
## Mistake 2: Bad Products Trying to Go Viral
**What it looks like:** Building viral mechanics into a product that isn't actually good, hoping volume will compensate.
**Why it fails:** Virality accelerates *whatever* the product is — including bad reviews, negative word of mouth, and user disappointment. A bad product with virality fails faster, more visibly, and more publicly.
**Detection:** Check retention and satisfaction metrics BEFORE investing in viral features. If users don't stick, virality will make things worse, not better.
**Fix:** Fix the product first. Virality is leverage; leverage on a broken foundation collapses.
## Mistake 3: Not Running Enough A/B Tests
**What it looks like:** Building one version of the viral loop, launching it, and calling it done. Or running 1-2 A/B tests and giving up.
**Why it fails:** Assume only 1-3 out of every 10 A/B tests will yield positive results. If you run 2 tests and neither works, that's expected — not a signal that viral is broken. You need 10+ tests to see meaningful improvement.
**Detection:** How many A/B tests has the team run on the viral loop in the last 4 weeks? If fewer than 2 per week, the cadence is too slow.
**Fix:** Establish a weekly A/B testing cadence. Focus on one variable at a time (invite copy, reward size, landing page). Measure each test for 1-2 weeks minimum.
## Mistake 4: Bolting On Generic Sharing Mechanics Without Understanding How Users Communicate
**What it looks like:** Adding Facebook Like buttons, Twitter share buttons, email invite forms — generic mechanics without asking how users actually talk to each other about the product.
**Why it fails:** Generic mechanics are invisible. Users who share via Slack, iMessage, or direct conversation don't click a Facebook share button. The share button is dead weight.
**Detection:** Interview 10 users. Ask: "If you wanted to tell a friend about this product, how would you do it?" If their answer doesn't involve your sharing features, you have the wrong features.
**Fix:** Match sharing mechanics to actual user communication patterns. If users share via iMessage, provide an iMessage-friendly share format. If they share via Slack, provide a Slack preview-ready link. Stop assuming Facebook.
## The Fifth (Implied) Mistake
**Not getting coaching or guidance from people who have successfully built viral products.** Viral loop design is specialized expertise. Most founders under-invest in learning from people who have actually shipped working loops.
**Fix:** Find advisors who have built viral products. Ask them to audit your loop design BEFORE you implement.
## How They Connect
These 4 (5) mistakes describe the complete failure mode tree. Mistakes 1 and 2 are product-level problems (wrong foundation). Mistake 3 is a process problem (insufficient iteration). Mistake 4 is a design problem (wrong mechanics). Together they cover most of the ways viral projects fail.
## Source
Chapter 6 ("Viral Marketing") of *Traction* by Gabriel Weinberg and Justin Mares, citing Andrew Chen.
Generates structured, timed live-stream sales scripts with product intros, audience engagement, urgency cues, Q&A prep, and full session flow for live commer...
# Live Commerce Sales Script Kit ## Purpose This skill generates professional live-streaming sales scripts for live commerce hosts on platforms like Douyin Live (抖音直播), Kuaishou Live (快手直播), and Taobao Live. It covers every aspect: product introduction flow, pricing reveal cadence, urgency-building phrases (ethically constrained), audience interaction triggers, Q&A preparation, segment timing, and full-session outlines. Think of it as a director's script for your live commerce show — "Kit" signals a ready-to-use bundle of templates and frameworks, not a single monolithic output. ## Triggers - "直播带货话术" - "直播脚本" - "直播话术" - "带货脚本" - "live selling script" - "flash sale script" - "直播互动" - "单品直播" - "整场直播规划" - "逼单话术" ## Workflow 1. Receive product information and session type from user: single product demo, multi-product session, or flash sale. 2. For single product: structure the 3–8 minute product introduction flow (hook → demonstration → benefits → pricing → urgency → CTA). 3. For multi-product: build a time-allocated session outline with product sequence, transition monologues, and energy management. 4. Insert audience interaction triggers at regular intervals: polls, Q&A prompts, comment callouts, engagement games. 5. Add urgency-building phrases and transitional language — always with ethical constraints on scarcity and pricing claims. 6. Prepare anticipated audience Q&A pairs for each product. 7. Include pacing notes, segment timing, and host energy level guidance. 8. Deliver script with anchor monologue, interaction triggers, Q&A branches, and timing guide. ## Prompt Templates ### 1. Single Product Live Script (`single_product_live_script`) **Purpose:** Generate a complete 3–8 minute script for showcasing one product. **Input:** - `product_name` — Product name - `price` — Selling price (and optional original price) - `key_features` — 3–5 key selling points - `target_audience` — Who's watching - `duration_minutes` — Target segment length (3–8) **Output:** Timed script with sections: Opening Grab → Product Reveal → Feature Demo → Comparison → Pricing Reveal → Urgency Build → CTA → Transition. ### 2. Full Session Flow (`full_session_flow`) **Purpose:** Design a complete multi-product 1–4 hour live session. **Input:** - `product_list` — List of products with selling order priority - `session_duration` — Total session length in hours - `flow_style` — Energy curve: high-low-high / sustained / gradual build **Output:** Time-allocated outline with: Warm-up, Product 1-n, Intermission moments, Flash sales, Closing. Each with estimated duration, transition monologue, and energy level. ### 3. Urgency Phrase Bank (`urgency_phrase_bank`) **Purpose:** Generate a categorized bank of urgency phrases for live selling. **Input:** - `scenario` — Situation: limited-time offer / low stock / exclusive deal / first-time buyer bonus - `count` — Number of phrase variants per category **Output:** Phrases organized by category (timing-based / quantity-based / exclusivity-based), each with an ethical constraint note. ### 4. Audience Q&A Prep (`audience_qa_prep`) **Purpose:** Anticipate and prepare responses for common audience questions. **Input:** - `product_name` — Product - `product_details` — Specs, materials, sizes, guarantees - `common_concerns` — Typical buyer hesitations for this product type **Output:** 15–20 Q&A pairs organized by question type: product/details, pricing/value, logistics/after-sales, objections/skepticism. ### 5. Flash Sale Countdown (`flash_sale_countdown`) **Purpose:** Generate a high-energy countdown script for a limited-time offer. **Input:** - `product_name` — Product - `flash_price` — Flash sale price - `original_price` — Regular price - `quantity_available` — Actual available quantity - `duration_seconds` — Countdown window (typically 60–180s) **Output:** Countdown script with: Price Reveal → Quantity Mention → 30s Reminder → 10s Final Call → Sold Out / Next Product. ## Output Format All scripts follow a formatted broadcast table: | Time | Segment | Anchor Monologue | Interaction Trigger | Energy Level | |------|---------|-----------------|---------------------|--------------| | 0:00–1:00 | Opening | "Welcome..." | Ask where watching from | 🔥 High | | ... | ... | ... | ... | ... | ## Safety Rules - **NEVER** fabricate false scarcity (e.g., "only 3 left" when stock is ample) - **NEVER** invent fake original prices or price anchors to make discounts look bigger - **NEVER** use high-pressure tactics targeting vulnerable consumers (elderly, financially distressed) - **ALWAYS** prompt host to verify and disclose actual stock levels - **ALWAYS** comply with platform-specific live commerce regulations - **ALWAYS** maintain honest product descriptions — no exaggerated efficacy claims ## Examples ### Example 1: Single Product Script **Input:** Product = "XX面霜", Price = "299元 (原价399)", Features = "保湿、修护、敏感肌可用", Duration = "5分钟" **Output:** 5-minute script with opening hook about winter skin, ingredient demo, texture test, pricing reveal with savings calculation, limited-time urgency, and link click CTA. ### Example 2: Flash Sale Countdown **Input:** Product = "蓝牙耳机", Flash = "99元 (原价199)", Qty = "50件", Duration = "120s" **Output:** Countdown script with Qty count decrements at 50, 30, 10 remaining, 30s and 10s reminders, final call, and transition. ## Related Skills - [douyin-script-studio](../douyin-script-studio/) — For pre-recorded Douyin video scripts (recorded, not live) - [product-title-booster](../product-title-booster/) — For optimizing product listing titles used during live segments - [review-reply-coach](../review-reply-coach/) — For handling post-live customer feedback and reviews FILE:ACCEPTANCE.md # Acceptance Criteria — Live Commerce Sales Script Kit - [ ] SKILL.md is self-contained (agent can operate from it alone) - [ ] All 5 prompt templates are complete with `placeholder` inputs - [ ] Safety rules are explicit and actionable (NEVER/ALWAYS format) — especially false scarcity and fake price anchors - [ ] README.md has clear install instructions + 3 usage examples - [ ] skill.json is valid JSON with all required fields - [ ] Content is unique — real-time broadcast format differs from douyin-script-studio (recorded) - [ ] Flash sale, Q&A prep, and urgency phrase bank are structurally distinct features - [ ] Slugs follow naming convention (user-facing, no prefix codes) FILE:README.md # Live Commerce Sales Script Kit Professional live-streaming sales scripts for hosts — product flows, urgency phrases, Q&A prep, and full session outlines. ## Features - Single product demo scripts (3–8 minutes) with full structure - Multi-product session outlines for 1–4 hour broadcasts - Urgency-building phrase bank with ethical guardrails - Audience Q&A preparation with 15–20 anticipated questions - Flash sale countdown scripts with pacing guidance - Interaction triggers and energy management notes ## Install ``` openclaw skills install harrylabsj/live-selling-script-kit ``` ## Usage ``` 帮我写一个5分钟的单品直播脚本,产品是299元的面霜,主打保湿修护 规划一场2小时的女装直播,有8个款,给我安排流程和时间 帮我准备观众可能问的20个问题和标准回复 写一段限时秒杀的倒数话术,蓝牙耳机秒杀,50件库存 ``` ## Platforms 抖音直播, 快手直播, Taobao Live, General Live Streaming ## Safety No fake scarcity. No fabricated original prices. Honest stock disclosures. Ethical urgency language only. All scripts prompt the host to verify claims and stock before broadcast. ## License MIT FILE:skill.json { "name": "Live Commerce Sales Script Kit", "description": "Ready-to-use live streaming sales scripts — product introduction flows, pricing reveal cadence, urgency-building phrases, audience Q&A prep, and full-session outlines for live commerce hosts.", "version": "1.0.0", "type": "prompt-flow", "category": "E-Commerce / Live Commerce", "keywords": [ "live commerce", "直播带货", "直播话术", "sales script", "live selling", "抖音直播", "快手直播", "淘宝直播", "flash sale", "audience engagement", "anchor script" ], "platforms": ["抖音直播", "快手直播", "Taobao Live", "General Live Streaming"], "requires": {}, "requires_api": false, "author": "harrylabsj", "license": "MIT", "safety": { "no_code_execution": true, "no_network": true, "no_credentials": true, "compliance_notes": "No false scarcity (e.g., fake 'only 3 left'). No fabricated price anchors or fake original prices. No pressure tactics targeting vulnerable consumers. Must disclose actual stock levels and limitations. Comply with live commerce platform regulations." } }
Amazon AppConfig API skill. Use when working with Amazon AppConfig for applications, deploymentstrategies, extensions. Covers 45 endpoints.
---
name: lap-amazon-appconfig
description: "Amazon AppConfig API skill. Use when working with Amazon AppConfig for applications, deploymentstrategies, extensions. Covers 45 endpoints."
version: 1.0.0
generator: lapsh
metadata:
openclaw:
requires:
env:
- AMAZON_APPCONFIG_API_KEY
---
# Amazon AppConfig
API version: 2019-10-09
## Auth
AWS SigV4
## Base URL
Not specified.
## Setup
1. Configure auth: AWS SigV4
2. GET /settings -- verify access
3. POST /applications -- create first applications
## Endpoints
45 endpoints across 7 groups. See references/api-spec.lap for full details.
### applications
| Method | Path | Description |
|--------|------|-------------|
| POST | /applications | Creates an application. In AppConfig, an application is simply an organizational construct like a folder. This organizational construct has a relationship with some unit of executable code. For example, you could create an application called MyMobileApp to organize and manage configuration data for a mobile application installed by your users. |
| POST | /applications/{ApplicationId}/configurationprofiles | Creates a configuration profile, which is information that enables AppConfig to access the configuration source. Valid configuration sources include the following: Configuration data in YAML, JSON, and other formats stored in the AppConfig hosted configuration store Configuration data stored as objects in an Amazon Simple Storage Service (Amazon S3) bucket Pipelines stored in CodePipeline Secrets stored in Secrets Manager Standard and secure string parameters stored in Amazon Web Services Systems Manager Parameter Store Configuration data in SSM documents stored in the Systems Manager document store A configuration profile includes the following information: The URI location of the configuration data. The Identity and Access Management (IAM) role that provides access to the configuration data. A validator for the configuration data. Available validators include either a JSON Schema or an Amazon Web Services Lambda function. For more information, see Create a Configuration and a Configuration Profile in the AppConfig User Guide. |
| POST | /applications/{ApplicationId}/environments | Creates an environment. For each application, you define one or more environments. An environment is a deployment group of AppConfig targets, such as applications in a Beta or Production environment. You can also define environments for application subcomponents such as the Web, Mobile and Back-end components for your application. You can configure Amazon CloudWatch alarms for each environment. The system monitors alarms during a configuration deployment. If an alarm is triggered, the system rolls back the configuration. |
| POST | /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/hostedconfigurationversions | Creates a new configuration in the AppConfig hosted configuration store. If you're creating a feature flag, we recommend you familiarize yourself with the JSON schema for feature flag data. For more information, see Type reference for AWS.AppConfig.FeatureFlags in the AppConfig User Guide. |
| DELETE | /applications/{ApplicationId} | Deletes an application. |
| DELETE | /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId} | Deletes a configuration profile. To prevent users from unintentionally deleting actively-used configuration profiles, enable deletion protection. |
| DELETE | /applications/{ApplicationId}/environments/{EnvironmentId} | Deletes an environment. To prevent users from unintentionally deleting actively-used environments, enable deletion protection. |
| DELETE | /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/hostedconfigurationversions/{VersionNumber} | Deletes a version of a configuration from the AppConfig hosted configuration store. |
| GET | /applications/{ApplicationId} | Retrieves information about an application. |
| GET | /applications/{Application}/environments/{Environment}/configurations/{Configuration} | (Deprecated) Retrieves the latest deployed configuration. Note the following important information. This API action is deprecated. Calls to receive configuration data should use the StartConfigurationSession and GetLatestConfiguration APIs instead. GetConfiguration is a priced call. For more information, see Pricing. |
| GET | /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId} | Retrieves information about a configuration profile. |
| GET | /applications/{ApplicationId}/environments/{EnvironmentId}/deployments/{DeploymentNumber} | Retrieves information about a configuration deployment. |
| GET | /applications/{ApplicationId}/environments/{EnvironmentId} | Retrieves information about an environment. An environment is a deployment group of AppConfig applications, such as applications in a Production environment or in an EU_Region environment. Each configuration deployment targets an environment. You can enable one or more Amazon CloudWatch alarms for an environment. If an alarm is triggered during a deployment, AppConfig roles back the configuration. |
| GET | /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/hostedconfigurationversions/{VersionNumber} | Retrieves information about a specific configuration version. |
| GET | /applications | Lists all applications in your Amazon Web Services account. |
| GET | /applications/{ApplicationId}/configurationprofiles | Lists the configuration profiles for an application. |
| GET | /applications/{ApplicationId}/environments/{EnvironmentId}/deployments | Lists the deployments for an environment in descending deployment number order. |
| GET | /applications/{ApplicationId}/environments | Lists the environments for an application. |
| GET | /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/hostedconfigurationversions | Lists configurations stored in the AppConfig hosted configuration store by version. |
| POST | /applications/{ApplicationId}/environments/{EnvironmentId}/deployments | Starts a deployment. |
| DELETE | /applications/{ApplicationId}/environments/{EnvironmentId}/deployments/{DeploymentNumber} | Stops a deployment. This API action works only on deployments that have a status of DEPLOYING. This action moves the deployment to a status of ROLLED_BACK. |
| PATCH | /applications/{ApplicationId} | Updates an application. |
| PATCH | /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId} | Updates a configuration profile. |
| PATCH | /applications/{ApplicationId}/environments/{EnvironmentId} | Updates an environment. |
| POST | /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/validators | Uses the validators in a configuration profile to validate a configuration. |
### deploymentstrategies
| Method | Path | Description |
|--------|------|-------------|
| POST | /deploymentstrategies | Creates a deployment strategy that defines important criteria for rolling out your configuration to the designated targets. A deployment strategy includes the overall duration required, a percentage of targets to receive the deployment during each interval, an algorithm that defines how percentage grows, and bake time. |
| GET | /deploymentstrategies/{DeploymentStrategyId} | Retrieves information about a deployment strategy. A deployment strategy defines important criteria for rolling out your configuration to the designated targets. A deployment strategy includes the overall duration required, a percentage of targets to receive the deployment during each interval, an algorithm that defines how percentage grows, and bake time. |
| GET | /deploymentstrategies | Lists deployment strategies. |
| PATCH | /deploymentstrategies/{DeploymentStrategyId} | Updates a deployment strategy. |
### extensions
| Method | Path | Description |
|--------|------|-------------|
| POST | /extensions | Creates an AppConfig extension. An extension augments your ability to inject logic or behavior at different points during the AppConfig workflow of creating or deploying a configuration. You can create your own extensions or use the Amazon Web Services authored extensions provided by AppConfig. For an AppConfig extension that uses Lambda, you must create a Lambda function to perform any computation and processing defined in the extension. If you plan to create custom versions of the Amazon Web Services authored notification extensions, you only need to specify an Amazon Resource Name (ARN) in the Uri field for the new extension version. For a custom EventBridge notification extension, enter the ARN of the EventBridge default events in the Uri field. For a custom Amazon SNS notification extension, enter the ARN of an Amazon SNS topic in the Uri field. For a custom Amazon SQS notification extension, enter the ARN of an Amazon SQS message queue in the Uri field. For more information about extensions, see Extending workflows in the AppConfig User Guide. |
| DELETE | /extensions/{ExtensionIdentifier} | Deletes an AppConfig extension. You must delete all associations to an extension before you delete the extension. |
| GET | /extensions/{ExtensionIdentifier} | Returns information about an AppConfig extension. |
| GET | /extensions | Lists all custom and Amazon Web Services authored AppConfig extensions in the account. For more information about extensions, see Extending workflows in the AppConfig User Guide. |
| PATCH | /extensions/{ExtensionIdentifier} | Updates an AppConfig extension. For more information about extensions, see Extending workflows in the AppConfig User Guide. |
### extensionassociations
| Method | Path | Description |
|--------|------|-------------|
| POST | /extensionassociations | When you create an extension or configure an Amazon Web Services authored extension, you associate the extension with an AppConfig application, environment, or configuration profile. For example, you can choose to run the AppConfig deployment events to Amazon SNS Amazon Web Services authored extension and receive notifications on an Amazon SNS topic anytime a configuration deployment is started for a specific application. Defining which extension to associate with an AppConfig resource is called an extension association. An extension association is a specified relationship between an extension and an AppConfig resource, such as an application or a configuration profile. For more information about extensions and associations, see Extending workflows in the AppConfig User Guide. |
| DELETE | /extensionassociations/{ExtensionAssociationId} | Deletes an extension association. This action doesn't delete extensions defined in the association. |
| GET | /extensionassociations/{ExtensionAssociationId} | Returns information about an AppConfig extension association. For more information about extensions and associations, see Extending workflows in the AppConfig User Guide. |
| GET | /extensionassociations | Lists all AppConfig extension associations in the account. For more information about extensions and associations, see Extending workflows in the AppConfig User Guide. |
| PATCH | /extensionassociations/{ExtensionAssociationId} | Updates an association. For more information about extensions and associations, see Extending workflows in the AppConfig User Guide. |
### deployementstrategies
| Method | Path | Description |
|--------|------|-------------|
| DELETE | /deployementstrategies/{DeploymentStrategyId} | Deletes a deployment strategy. |
### settings
| Method | Path | Description |
|--------|------|-------------|
| GET | /settings | Returns information about the status of the DeletionProtection parameter. |
| PATCH | /settings | Updates the value of the DeletionProtection parameter. |
### tags
| Method | Path | Description |
|--------|------|-------------|
| GET | /tags/{ResourceArn} | Retrieves the list of key-value tags assigned to the resource. |
| POST | /tags/{ResourceArn} | Assigns metadata to an AppConfig resource. Tags help organize and categorize your AppConfig resources. Each tag consists of a key and an optional value, both of which you define. You can specify a maximum of 50 tags for a resource. |
| DELETE | /tags/{ResourceArn} | Deletes a tag key and value from an AppConfig resource. |
## Common Questions
Match user requests to endpoints in references/api-spec.lap. Key patterns:
- "Create a application?" -> POST /applications
- "Create a configurationprofile?" -> POST /applications/{ApplicationId}/configurationprofiles
- "Create a deploymentstrategy?" -> POST /deploymentstrategies
- "Create a environment?" -> POST /applications/{ApplicationId}/environments
- "Create a extension?" -> POST /extensions
- "Create a extensionassociation?" -> POST /extensionassociations
- "Create a hostedconfigurationversion?" -> POST /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/hostedconfigurationversions
- "Delete a application?" -> DELETE /applications/{ApplicationId}
- "Delete a configurationprofile?" -> DELETE /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}
- "Delete a deployementstrategy?" -> DELETE /deployementstrategies/{DeploymentStrategyId}
- "Delete a environment?" -> DELETE /applications/{ApplicationId}/environments/{EnvironmentId}
- "Delete a extension?" -> DELETE /extensions/{ExtensionIdentifier}
- "Delete a extensionassociation?" -> DELETE /extensionassociations/{ExtensionAssociationId}
- "Delete a hostedconfigurationversion?" -> DELETE /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/hostedconfigurationversions/{VersionNumber}
- "List all settings?" -> GET /settings
- "Get application details?" -> GET /applications/{ApplicationId}
- "Get configuration details?" -> GET /applications/{Application}/environments/{Environment}/configurations/{Configuration}
- "Get configurationprofile details?" -> GET /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}
- "Get deployment details?" -> GET /applications/{ApplicationId}/environments/{EnvironmentId}/deployments/{DeploymentNumber}
- "Get deploymentstrategy details?" -> GET /deploymentstrategies/{DeploymentStrategyId}
- "Get environment details?" -> GET /applications/{ApplicationId}/environments/{EnvironmentId}
- "Get extension details?" -> GET /extensions/{ExtensionIdentifier}
- "Get extensionassociation details?" -> GET /extensionassociations/{ExtensionAssociationId}
- "Get hostedconfigurationversion details?" -> GET /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/hostedconfigurationversions/{VersionNumber}
- "List all applications?" -> GET /applications
- "List all configurationprofiles?" -> GET /applications/{ApplicationId}/configurationprofiles
- "List all deploymentstrategies?" -> GET /deploymentstrategies
- "List all deployments?" -> GET /applications/{ApplicationId}/environments/{EnvironmentId}/deployments
- "List all environments?" -> GET /applications/{ApplicationId}/environments
- "List all extensionassociations?" -> GET /extensionassociations
- "List all extensions?" -> GET /extensions
- "List all hostedconfigurationversions?" -> GET /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/hostedconfigurationversions
- "Get tag details?" -> GET /tags/{ResourceArn}
- "Create a deployment?" -> POST /applications/{ApplicationId}/environments/{EnvironmentId}/deployments
- "Delete a deployment?" -> DELETE /applications/{ApplicationId}/environments/{EnvironmentId}/deployments/{DeploymentNumber}
- "Delete a tag?" -> DELETE /tags/{ResourceArn}
- "Partially update a application?" -> PATCH /applications/{ApplicationId}
- "Partially update a configurationprofile?" -> PATCH /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}
- "Partially update a deploymentstrategy?" -> PATCH /deploymentstrategies/{DeploymentStrategyId}
- "Partially update a environment?" -> PATCH /applications/{ApplicationId}/environments/{EnvironmentId}
- "Partially update a extension?" -> PATCH /extensions/{ExtensionIdentifier}
- "Partially update a extensionassociation?" -> PATCH /extensionassociations/{ExtensionAssociationId}
- "Create a validator?" -> POST /applications/{ApplicationId}/configurationprofiles/{ConfigurationProfileId}/validators
- "How to authenticate?" -> See Auth section
## Response Tips
- Check response schemas in references/api-spec.lap for field details
- Create/update endpoints typically return the created/updated object
## CLI
```bash
# Update this spec to the latest version
npx @lap-platform/lapsh get amazon-appconfig -o references/api-spec.lap
# Search for related APIs
npx @lap-platform/lapsh search amazon-appconfig
```
## References
- Full spec: See references/api-spec.lap for complete endpoint details, parameter tables, and response schemas
> Generated from the official API spec by [LAP](https://lap.sh)
Amazon API Gateway API skill. Use when working with Amazon API Gateway for apikeys, restapis, domainnames. Covers 120 endpoints.
---
name: lap-amazon-api-gateway
description: "Amazon API Gateway API skill. Use when working with Amazon API Gateway for apikeys, restapis, domainnames. Covers 120 endpoints."
version: 1.0.0
generator: lapsh
metadata:
openclaw:
requires:
env:
- AMAZON_API_GATEWAY_API_KEY
---
# Amazon API Gateway
API version: 2015-07-09
## Auth
AWS SigV4
## Base URL
Not specified.
## Setup
1. Configure auth: AWS SigV4
2. GET /account -- verify access
3. POST /apikeys -- create first apikeys
## Endpoints
120 endpoints across 11 groups. See references/api-spec.lap for full details.
### apikeys
| Method | Path | Description |
|--------|------|-------------|
| POST | /apikeys | Create an ApiKey resource. |
| DELETE | /apikeys/{api_Key} | Deletes the ApiKey resource. |
| GET | /apikeys/{api_Key} | Gets information about the current ApiKey resource. |
| GET | /apikeys | Gets information about the current ApiKeys resource. |
| PATCH | /apikeys/{api_Key} | Changes information about an ApiKey resource. |
### restapis
| Method | Path | Description |
|--------|------|-------------|
| POST | /restapis/{restapi_id}/authorizers | Adds a new Authorizer resource to an existing RestApi resource. |
| POST | /restapis/{restapi_id}/deployments | Creates a Deployment resource, which makes a specified RestApi callable over the internet. |
| POST | /restapis/{restapi_id}/documentation/parts | Creates a documentation part. |
| POST | /restapis/{restapi_id}/documentation/versions | Creates a documentation version |
| POST | /restapis/{restapi_id}/models | Adds a new Model resource to an existing RestApi resource. |
| POST | /restapis/{restapi_id}/requestvalidators | Creates a RequestValidator of a given RestApi. |
| POST | /restapis/{restapi_id}/resources/{parent_id} | Creates a Resource resource. |
| POST | /restapis | Creates a new RestApi resource. |
| POST | /restapis/{restapi_id}/stages | Creates a new Stage resource that references a pre-existing Deployment for the API. |
| DELETE | /restapis/{restapi_id}/authorizers/{authorizer_id} | Deletes an existing Authorizer resource. |
| DELETE | /restapis/{restapi_id}/deployments/{deployment_id} | Deletes a Deployment resource. Deleting a deployment will only succeed if there are no Stage resources associated with it. |
| DELETE | /restapis/{restapi_id}/documentation/parts/{part_id} | Deletes a documentation part |
| DELETE | /restapis/{restapi_id}/documentation/versions/{doc_version} | Deletes a documentation version. |
| DELETE | /restapis/{restapi_id}/gatewayresponses/{response_type} | Clears any customization of a GatewayResponse of a specified response type on the given RestApi and resets it with the default settings. |
| DELETE | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration | Represents a delete integration. |
| DELETE | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration/responses/{status_code} | Represents a delete integration response. |
| DELETE | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method} | Deletes an existing Method resource. |
| DELETE | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/responses/{status_code} | Deletes an existing MethodResponse resource. |
| DELETE | /restapis/{restapi_id}/models/{model_name} | Deletes a model. |
| DELETE | /restapis/{restapi_id}/requestvalidators/{requestvalidator_id} | Deletes a RequestValidator of a given RestApi. |
| DELETE | /restapis/{restapi_id}/resources/{resource_id} | Deletes a Resource resource. |
| DELETE | /restapis/{restapi_id} | Deletes the specified API. |
| DELETE | /restapis/{restapi_id}/stages/{stage_name} | Deletes a Stage resource. |
| DELETE | /restapis/{restapi_id}/stages/{stage_name}/cache/authorizers | Flushes all authorizer cache entries on a stage. |
| DELETE | /restapis/{restapi_id}/stages/{stage_name}/cache/data | Flushes a stage's cache. |
| GET | /restapis/{restapi_id}/authorizers/{authorizer_id} | Describe an existing Authorizer resource. |
| GET | /restapis/{restapi_id}/authorizers | Describe an existing Authorizers resource. |
| GET | /restapis/{restapi_id}/deployments/{deployment_id} | Gets information about a Deployment resource. |
| GET | /restapis/{restapi_id}/deployments | Gets information about a Deployments collection. |
| GET | /restapis/{restapi_id}/documentation/parts/{part_id} | Gets a documentation part. |
| GET | /restapis/{restapi_id}/documentation/parts | Gets documentation parts. |
| GET | /restapis/{restapi_id}/documentation/versions/{doc_version} | Gets a documentation version. |
| GET | /restapis/{restapi_id}/documentation/versions | Gets documentation versions. |
| GET | /restapis/{restapi_id}/stages/{stage_name}/exports/{export_type} | Exports a deployed version of a RestApi in a specified format. |
| GET | /restapis/{restapi_id}/gatewayresponses/{response_type} | Gets a GatewayResponse of a specified response type on the given RestApi. |
| GET | /restapis/{restapi_id}/gatewayresponses | Gets the GatewayResponses collection on the given RestApi. If an API developer has not added any definitions for gateway responses, the result will be the API Gateway-generated default GatewayResponses collection for the supported response types. |
| GET | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration | Get the integration settings. |
| GET | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration/responses/{status_code} | Represents a get integration response. |
| GET | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method} | Describe an existing Method resource. |
| GET | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/responses/{status_code} | Describes a MethodResponse resource. |
| GET | /restapis/{restapi_id}/models/{model_name} | Describes an existing model defined for a RestApi resource. |
| GET | /restapis/{restapi_id}/models/{model_name}/default_template | Generates a sample mapping template that can be used to transform a payload into the structure of a model. |
| GET | /restapis/{restapi_id}/models | Describes existing Models defined for a RestApi resource. |
| GET | /restapis/{restapi_id}/requestvalidators/{requestvalidator_id} | Gets a RequestValidator of a given RestApi. |
| GET | /restapis/{restapi_id}/requestvalidators | Gets the RequestValidators collection of a given RestApi. |
| GET | /restapis/{restapi_id}/resources/{resource_id} | Lists information about a resource. |
| GET | /restapis/{restapi_id}/resources | Lists information about a collection of Resource resources. |
| GET | /restapis/{restapi_id} | Lists the RestApi resource in the collection. |
| GET | /restapis | Lists the RestApis resources for your collection. |
| GET | /restapis/{restapi_id}/stages/{stage_name}/sdks/{sdk_type} | Generates a client SDK for a RestApi and Stage. |
| GET | /restapis/{restapi_id}/stages/{stage_name} | Gets information about a Stage resource. |
| GET | /restapis/{restapi_id}/stages | Gets information about one or more Stage resources. |
| PUT | /restapis/{restapi_id}/documentation/parts | Imports documentation parts |
| PUT | /restapis/{restapi_id}/gatewayresponses/{response_type} | Creates a customization of a GatewayResponse of a specified response type and status code on the given RestApi. |
| PUT | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration | Sets up a method's integration. |
| PUT | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration/responses/{status_code} | Represents a put integration. |
| PUT | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method} | Add a method to an existing Resource resource. |
| PUT | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/responses/{status_code} | Adds a MethodResponse to an existing Method resource. |
| PUT | /restapis/{restapi_id} | A feature of the API Gateway control service for updating an existing API with an input of external API definitions. The update can take the form of merging the supplied definition into the existing API or overwriting the existing API. |
| POST | /restapis/{restapi_id}/authorizers/{authorizer_id} | Simulate the execution of an Authorizer in your RestApi with headers, parameters, and an incoming request body. |
| POST | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method} | Simulate the invocation of a Method in your RestApi with headers, parameters, and an incoming request body. |
| PATCH | /restapis/{restapi_id}/authorizers/{authorizer_id} | Updates an existing Authorizer resource. |
| PATCH | /restapis/{restapi_id}/deployments/{deployment_id} | Changes information about a Deployment resource. |
| PATCH | /restapis/{restapi_id}/documentation/parts/{part_id} | Updates a documentation part. |
| PATCH | /restapis/{restapi_id}/documentation/versions/{doc_version} | Updates a documentation version. |
| PATCH | /restapis/{restapi_id}/gatewayresponses/{response_type} | Updates a GatewayResponse of a specified response type on the given RestApi. |
| PATCH | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration | Represents an update integration. |
| PATCH | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration/responses/{status_code} | Represents an update integration response. |
| PATCH | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method} | Updates an existing Method resource. |
| PATCH | /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/responses/{status_code} | Updates an existing MethodResponse resource. |
| PATCH | /restapis/{restapi_id}/models/{model_name} | Changes information about a model. The maximum size of the model is 400 KB. |
| PATCH | /restapis/{restapi_id}/requestvalidators/{requestvalidator_id} | Updates a RequestValidator of a given RestApi. |
| PATCH | /restapis/{restapi_id}/resources/{resource_id} | Changes information about a Resource resource. |
| PATCH | /restapis/{restapi_id} | Changes information about the specified API. |
| PATCH | /restapis/{restapi_id}/stages/{stage_name} | Changes information about a Stage resource. |
### domainnames
| Method | Path | Description |
|--------|------|-------------|
| POST | /domainnames/{domain_name}/basepathmappings | Creates a new BasePathMapping resource. |
| POST | /domainnames | Creates a new domain name. |
| DELETE | /domainnames/{domain_name}/basepathmappings/{base_path} | Deletes the BasePathMapping resource. |
| DELETE | /domainnames/{domain_name} | Deletes the DomainName resource. |
| GET | /domainnames/{domain_name}/basepathmappings/{base_path} | Describe a BasePathMapping resource. |
| GET | /domainnames/{domain_name}/basepathmappings | Represents a collection of BasePathMapping resources. |
| GET | /domainnames/{domain_name} | Represents a domain name that is contained in a simpler, more intuitive URL that can be called. |
| GET | /domainnames | Represents a collection of DomainName resources. |
| PATCH | /domainnames/{domain_name}/basepathmappings/{base_path} | Changes information about the BasePathMapping resource. |
| PATCH | /domainnames/{domain_name} | Changes information about the DomainName resource. |
### usageplans
| Method | Path | Description |
|--------|------|-------------|
| POST | /usageplans | Creates a usage plan with the throttle and quota limits, as well as the associated API stages, specified in the payload. |
| POST | /usageplans/{usageplanId}/keys | Creates a usage plan key for adding an existing API key to a usage plan. |
| DELETE | /usageplans/{usageplanId} | Deletes a usage plan of a given plan Id. |
| DELETE | /usageplans/{usageplanId}/keys/{keyId} | Deletes a usage plan key and remove the underlying API key from the associated usage plan. |
| GET | /usageplans/{usageplanId}/usage | Gets the usage data of a usage plan in a specified time interval. |
| GET | /usageplans/{usageplanId} | Gets a usage plan of a given plan identifier. |
| GET | /usageplans/{usageplanId}/keys/{keyId} | Gets a usage plan key of a given key identifier. |
| GET | /usageplans/{usageplanId}/keys | Gets all the usage plan keys representing the API keys added to a specified usage plan. |
| GET | /usageplans | Gets all the usage plans of the caller's account. |
| PATCH | /usageplans/{usageplanId}/keys/{keyId}/usage | Grants a temporary extension to the remaining quota of a usage plan associated with a specified API key. |
| PATCH | /usageplans/{usageplanId} | Updates a usage plan of a given plan Id. |
### vpclinks
| Method | Path | Description |
|--------|------|-------------|
| POST | /vpclinks | Creates a VPC link, under the caller's account in a selected region, in an asynchronous operation that typically takes 2-4 minutes to complete and become operational. The caller must have permissions to create and update VPC Endpoint services. |
| DELETE | /vpclinks/{vpclink_id} | Deletes an existing VpcLink of a specified identifier. |
| GET | /vpclinks/{vpclink_id} | Gets a specified VPC link under the caller's account in a region. |
| GET | /vpclinks | Gets the VpcLinks collection under the caller's account in a selected region. |
| PATCH | /vpclinks/{vpclink_id} | Updates an existing VpcLink of a specified identifier. |
### clientcertificates
| Method | Path | Description |
|--------|------|-------------|
| DELETE | /clientcertificates/{clientcertificate_id} | Deletes the ClientCertificate resource. |
| POST | /clientcertificates | Generates a ClientCertificate resource. |
| GET | /clientcertificates/{clientcertificate_id} | Gets information about the current ClientCertificate resource. |
| GET | /clientcertificates | Gets a collection of ClientCertificate resources. |
| PATCH | /clientcertificates/{clientcertificate_id} | Changes information about an ClientCertificate resource. |
### account
| Method | Path | Description |
|--------|------|-------------|
| GET | /account | Gets information about the current Account resource. |
| PATCH | /account | Changes information about the current Account resource. |
### sdktypes
| Method | Path | Description |
|--------|------|-------------|
| GET | /sdktypes/{sdktype_id} | Gets an SDK type. |
| GET | /sdktypes | Gets SDK types |
### tags
| Method | Path | Description |
|--------|------|-------------|
| GET | /tags/{resource_arn} | Gets the Tags collection for a given resource. |
| PUT | /tags/{resource_arn} | Adds or updates a tag on a given resource. |
| DELETE | /tags/{resource_arn} | Removes a tag from a given resource. |
### apikeys?mode=import
| Method | Path | Description |
|--------|------|-------------|
| POST | /apikeys?mode=import | Import API keys from an external source, such as a CSV-formatted file. |
### restapis?mode=import
| Method | Path | Description |
|--------|------|-------------|
| POST | /restapis?mode=import | A feature of the API Gateway control service for creating a new API from an external API definition file. |
## Common Questions
Match user requests to endpoints in references/api-spec.lap. Key patterns:
- "Create a apikey?" -> POST /apikeys
- "Create a authorizer?" -> POST /restapis/{restapi_id}/authorizers
- "Create a basepathmapping?" -> POST /domainnames/{domain_name}/basepathmappings
- "Create a deployment?" -> POST /restapis/{restapi_id}/deployments
- "Create a part?" -> POST /restapis/{restapi_id}/documentation/parts
- "Create a version?" -> POST /restapis/{restapi_id}/documentation/versions
- "Create a domainname?" -> POST /domainnames
- "Create a model?" -> POST /restapis/{restapi_id}/models
- "Create a requestvalidator?" -> POST /restapis/{restapi_id}/requestvalidators
- "Create a restapis?" -> POST /restapis
- "Create a stage?" -> POST /restapis/{restapi_id}/stages
- "Create a usageplan?" -> POST /usageplans
- "Create a key?" -> POST /usageplans/{usageplanId}/keys
- "Create a vpclink?" -> POST /vpclinks
- "Delete a apikey?" -> DELETE /apikeys/{api_Key}
- "Delete a authorizer?" -> DELETE /restapis/{restapi_id}/authorizers/{authorizer_id}
- "Delete a basepathmapping?" -> DELETE /domainnames/{domain_name}/basepathmappings/{base_path}
- "Delete a clientcertificate?" -> DELETE /clientcertificates/{clientcertificate_id}
- "Delete a deployment?" -> DELETE /restapis/{restapi_id}/deployments/{deployment_id}
- "Delete a part?" -> DELETE /restapis/{restapi_id}/documentation/parts/{part_id}
- "Delete a version?" -> DELETE /restapis/{restapi_id}/documentation/versions/{doc_version}
- "Delete a domainname?" -> DELETE /domainnames/{domain_name}
- "Delete a gatewayrespons?" -> DELETE /restapis/{restapi_id}/gatewayresponses/{response_type}
- "Delete a response?" -> DELETE /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration/responses/{status_code}
- "Delete a method?" -> DELETE /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}
- "Delete a response?" -> DELETE /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/responses/{status_code}
- "Delete a model?" -> DELETE /restapis/{restapi_id}/models/{model_name}
- "Delete a requestvalidator?" -> DELETE /restapis/{restapi_id}/requestvalidators/{requestvalidator_id}
- "Delete a resource?" -> DELETE /restapis/{restapi_id}/resources/{resource_id}
- "Delete a restapis?" -> DELETE /restapis/{restapi_id}
- "Delete a stage?" -> DELETE /restapis/{restapi_id}/stages/{stage_name}
- "Delete a usageplan?" -> DELETE /usageplans/{usageplanId}
- "Delete a key?" -> DELETE /usageplans/{usageplanId}/keys/{keyId}
- "Delete a vpclink?" -> DELETE /vpclinks/{vpclink_id}
- "Create a clientcertificate?" -> POST /clientcertificates
- "List all account?" -> GET /account
- "Get apikey details?" -> GET /apikeys/{api_Key}
- "List all apikeys?" -> GET /apikeys
- "Get authorizer details?" -> GET /restapis/{restapi_id}/authorizers/{authorizer_id}
- "List all authorizers?" -> GET /restapis/{restapi_id}/authorizers
- "Get basepathmapping details?" -> GET /domainnames/{domain_name}/basepathmappings/{base_path}
- "List all basepathmappings?" -> GET /domainnames/{domain_name}/basepathmappings
- "Get clientcertificate details?" -> GET /clientcertificates/{clientcertificate_id}
- "List all clientcertificates?" -> GET /clientcertificates
- "Get deployment details?" -> GET /restapis/{restapi_id}/deployments/{deployment_id}
- "List all deployments?" -> GET /restapis/{restapi_id}/deployments
- "Get part details?" -> GET /restapis/{restapi_id}/documentation/parts/{part_id}
- "List all parts?" -> GET /restapis/{restapi_id}/documentation/parts
- "Get version details?" -> GET /restapis/{restapi_id}/documentation/versions/{doc_version}
- "List all versions?" -> GET /restapis/{restapi_id}/documentation/versions
- "Get domainname details?" -> GET /domainnames/{domain_name}
- "List all domainnames?" -> GET /domainnames
- "Get export details?" -> GET /restapis/{restapi_id}/stages/{stage_name}/exports/{export_type}
- "Get gatewayrespons details?" -> GET /restapis/{restapi_id}/gatewayresponses/{response_type}
- "List all gatewayresponses?" -> GET /restapis/{restapi_id}/gatewayresponses
- "List all integration?" -> GET /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration
- "Get response details?" -> GET /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration/responses/{status_code}
- "Get method details?" -> GET /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}
- "Get response details?" -> GET /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/responses/{status_code}
- "Get model details?" -> GET /restapis/{restapi_id}/models/{model_name}
- "List all default_template?" -> GET /restapis/{restapi_id}/models/{model_name}/default_template
- "List all models?" -> GET /restapis/{restapi_id}/models
- "Get requestvalidator details?" -> GET /restapis/{restapi_id}/requestvalidators/{requestvalidator_id}
- "List all requestvalidators?" -> GET /restapis/{restapi_id}/requestvalidators
- "Get resource details?" -> GET /restapis/{restapi_id}/resources/{resource_id}
- "List all resources?" -> GET /restapis/{restapi_id}/resources
- "Get restapis details?" -> GET /restapis/{restapi_id}
- "List all restapis?" -> GET /restapis
- "Get sdk details?" -> GET /restapis/{restapi_id}/stages/{stage_name}/sdks/{sdk_type}
- "Get sdktype details?" -> GET /sdktypes/{sdktype_id}
- "List all sdktypes?" -> GET /sdktypes
- "Get stage details?" -> GET /restapis/{restapi_id}/stages/{stage_name}
- "List all stages?" -> GET /restapis/{restapi_id}/stages
- "Get tag details?" -> GET /tags/{resource_arn}
- "List all usage?" -> GET /usageplans/{usageplanId}/usage
- "Get usageplan details?" -> GET /usageplans/{usageplanId}
- "Get key details?" -> GET /usageplans/{usageplanId}/keys/{keyId}
- "List all keys?" -> GET /usageplans/{usageplanId}/keys
- "List all usageplans?" -> GET /usageplans
- "Get vpclink details?" -> GET /vpclinks/{vpclink_id}
- "List all vpclinks?" -> GET /vpclinks
- "Create a apikeys?mode=import?" -> POST /apikeys?mode=import
- "Create a restapis?mode=import?" -> POST /restapis?mode=import
- "Update a gatewayrespons?" -> PUT /restapis/{restapi_id}/gatewayresponses/{response_type}
- "Update a response?" -> PUT /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration/responses/{status_code}
- "Update a method?" -> PUT /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}
- "Update a response?" -> PUT /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/responses/{status_code}
- "Update a restapis?" -> PUT /restapis/{restapi_id}
- "Update a tag?" -> PUT /tags/{resource_arn}
- "Delete a tag?" -> DELETE /tags/{resource_arn}
- "Partially update a apikey?" -> PATCH /apikeys/{api_Key}
- "Partially update a authorizer?" -> PATCH /restapis/{restapi_id}/authorizers/{authorizer_id}
- "Partially update a basepathmapping?" -> PATCH /domainnames/{domain_name}/basepathmappings/{base_path}
- "Partially update a clientcertificate?" -> PATCH /clientcertificates/{clientcertificate_id}
- "Partially update a deployment?" -> PATCH /restapis/{restapi_id}/deployments/{deployment_id}
- "Partially update a part?" -> PATCH /restapis/{restapi_id}/documentation/parts/{part_id}
- "Partially update a version?" -> PATCH /restapis/{restapi_id}/documentation/versions/{doc_version}
- "Partially update a domainname?" -> PATCH /domainnames/{domain_name}
- "Partially update a gatewayrespons?" -> PATCH /restapis/{restapi_id}/gatewayresponses/{response_type}
- "Partially update a response?" -> PATCH /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/integration/responses/{status_code}
- "Partially update a method?" -> PATCH /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}
- "Partially update a response?" -> PATCH /restapis/{restapi_id}/resources/{resource_id}/methods/{http_method}/responses/{status_code}
- "Partially update a model?" -> PATCH /restapis/{restapi_id}/models/{model_name}
- "Partially update a requestvalidator?" -> PATCH /restapis/{restapi_id}/requestvalidators/{requestvalidator_id}
- "Partially update a resource?" -> PATCH /restapis/{restapi_id}/resources/{resource_id}
- "Partially update a restapis?" -> PATCH /restapis/{restapi_id}
- "Partially update a stage?" -> PATCH /restapis/{restapi_id}/stages/{stage_name}
- "Partially update a usageplan?" -> PATCH /usageplans/{usageplanId}
- "Partially update a vpclink?" -> PATCH /vpclinks/{vpclink_id}
- "How to authenticate?" -> See Auth section
## Response Tips
- Check response schemas in references/api-spec.lap for field details
- List endpoints may support pagination; check for limit, offset, or cursor params
- Create/update endpoints typically return the created/updated object
## CLI
```bash
# Update this spec to the latest version
npx @lap-platform/lapsh get amazon-api-gateway -o references/api-spec.lap
# Search for related APIs
npx @lap-platform/lapsh search amazon-api-gateway
```
## References
- Full spec: See references/api-spec.lap for complete endpoint details, parameter tables, and response schemas
> Generated from the official API spec by [LAP](https://lap.sh)
AltoroJ REST API skill. Use when working with AltoroJ REST for login, account, transfer. Covers 12 endpoints.
---
name: lap-altoroj-rest-api
description: "AltoroJ REST API skill. Use when working with AltoroJ REST for login, account, transfer. Covers 12 endpoints."
version: 1.0.0
generator: lapsh
metadata:
openclaw:
requires:
env:
- ALTOROJ_REST_API_KEY
---
# AltoroJ REST API
API version: 1.0.2
## Auth
ApiKey Authorization in header
## Base URL
Not specified.
## Setup
1. Set your API key in the appropriate header
2. GET /login -- verify access
3. POST /login -- create first login
## Endpoints
12 endpoints across 6 groups. See references/api-spec.lap for full details.
### login
| Method | Path | Description |
|--------|------|-------------|
| GET | /login | Check if any user is logged in |
| POST | /login | Login method |
### account
| Method | Path | Description |
|--------|------|-------------|
| GET | /account | Returns a list of all the accounts owned by the user |
| GET | /account/{accountNo} | Returns details about a specific account |
| GET | /account/{accountNo}/transactions | Returns the last 10 transactions attached to an account |
| POST | /account/{accountNo}/transactions | Return transactions between 2 specific dates |
### transfer
| Method | Path | Description |
|--------|------|-------------|
| POST | /transfer | Transfer money between two accounts |
### feedback
| Method | Path | Description |
|--------|------|-------------|
| POST | /feedback/submit | Submit feedback for the bank |
| GET | /feedback/{feedbackId} | Retrieve feedback |
### admin
| Method | Path | Description |
|--------|------|-------------|
| POST | /admin/addUser | Add new user |
| POST | /admin/changePassword | Change user password |
### logout
| Method | Path | Description |
|--------|------|-------------|
| GET | /logout | Logout from the bank |
## Common Questions
Match user requests to endpoints in references/api-spec.lap. Key patterns:
- "List all login?" -> GET /login
- "Create a login?" -> POST /login
- "List all account?" -> GET /account
- "Get account details?" -> GET /account/{accountNo}
- "List all transactions?" -> GET /account/{accountNo}/transactions
- "Create a transaction?" -> POST /account/{accountNo}/transactions
- "Create a transfer?" -> POST /transfer
- "Create a submit?" -> POST /feedback/submit
- "Get feedback details?" -> GET /feedback/{feedbackId}
- "Create a addUser?" -> POST /admin/addUser
- "Create a changePassword?" -> POST /admin/changePassword
- "List all logout?" -> GET /logout
- "How to authenticate?" -> See Auth section
## Response Tips
- Check response schemas in references/api-spec.lap for field details
- Create/update endpoints typically return the created/updated object
## CLI
```bash
# Update this spec to the latest version
npx @lap-platform/lapsh get altoroj-rest-api -o references/api-spec.lap
# Search for related APIs
npx @lap-platform/lapsh search altoroj-rest-api
```
## References
- Full spec: See references/api-spec.lap for complete endpoint details, parameter tables, and response schemas
> Generated from the official API spec by [LAP](https://lap.sh)
Akeneo PIM REST API skill. Use when working with Akeneo PIM REST for api. Covers 137 endpoints.
---
name: lap-akeneo-pim-rest-api
description: "Akeneo PIM REST API skill. Use when working with Akeneo PIM REST for api. Covers 137 endpoints."
version: 1.0.0
generator: lapsh
metadata:
openclaw:
requires:
env:
- AKENEO_PIM_REST_API_KEY
---
# Akeneo PIM REST API
API version: 1.0.0
## Auth
ApiKey Authorization in header
## Base URL
http://demo.akeneo.com
## Setup
1. Set your API key in the appropriate header
2. GET /api/rest/v1/products-uuid -- verify access
3. POST /api/rest/v1/products-uuid -- create first products-uuid
## Endpoints
137 endpoints across 1 groups. See references/api-spec.lap for full details.
### api
| Method | Path | Description |
|--------|------|-------------|
| GET | /api/rest/v1/products-uuid | Get list of products |
| POST | /api/rest/v1/products-uuid | Create a new product |
| PATCH | /api/rest/v1/products-uuid | Update/create several products |
| POST | /api/rest/v1/products-uuid/search | Search list of products |
| GET | /api/rest/v1/products-uuid/{uuid} | Get a product |
| PATCH | /api/rest/v1/products-uuid/{uuid} | Update/create a product |
| DELETE | /api/rest/v1/products-uuid/{uuid} | Delete a product |
| POST | /api/rest/v1/products-uuid/{uuid}/proposal | Submit a draft for approval |
| GET | /api/rest/v1/products-uuid/{uuid}/draft | Get a draft |
| GET | /api/rest/v1/products | Get list of products |
| POST | /api/rest/v1/products | Create a new product |
| PATCH | /api/rest/v1/products | Update/create several products |
| GET | /api/rest/v1/products/{code} | Get a product |
| PATCH | /api/rest/v1/products/{code} | Update/create a product |
| DELETE | /api/rest/v1/products/{code} | Delete a product |
| POST | /api/rest/v1/products/{code}/proposal | Submit a draft for approval |
| GET | /api/rest/v1/products/{code}/draft | Get a draft |
| GET | /api/rest/v1/product-models | Get list of product models |
| POST | /api/rest/v1/product-models | Create a new product model |
| PATCH | /api/rest/v1/product-models | Update/create several product models |
| GET | /api/rest/v1/product-models/{code} | Get a product model |
| PATCH | /api/rest/v1/product-models/{code} | Update/create a product model |
| DELETE | /api/rest/v1/product-models/{code} | Delete a product model |
| POST | /api/rest/v1/product-models/{code}/proposal | Submit a draft for approval |
| GET | /api/rest/v1/product-models/{code}/draft | Get a draft |
| GET | /api/rest/v1/published-products | Get list of published products |
| GET | /api/rest/v1/published-products/{code} | Get a published product |
| GET | /api/rest/v1/media-files | Get a list of product media files |
| POST | /api/rest/v1/media-files | Create a new product media file |
| GET | /api/rest/v1/media-files/{code} | Get a product media file |
| GET | /api/rest/v1/media-files/{code}/download | Download a product media file |
| POST | /api/rest/v1/jobs/export/{code} | Launch export job by code |
| POST | /api/rest/v1/jobs/import/{code} | Launch import job by code |
| GET | /api/rest/v1/families | Get list of families |
| POST | /api/rest/v1/families | Create a new family |
| PATCH | /api/rest/v1/families | Update/create several families |
| GET | /api/rest/v1/families/{code} | Get a family |
| PATCH | /api/rest/v1/families/{code} | Update/create a family |
| DELETE | /api/rest/v1/families/{code} | Delete a family |
| GET | /api/rest/v1/families/{family_code}/variants | Get list of family variants |
| POST | /api/rest/v1/families/{family_code}/variants | Create a new family variant |
| PATCH | /api/rest/v1/families/{family_code}/variants | Update/create several family variants |
| GET | /api/rest/v1/families/{family_code}/variants/{code} | Get a family variant |
| PATCH | /api/rest/v1/families/{family_code}/variants/{code} | Update/create a family variant |
| GET | /api/rest/v1/attributes | Get list of attributes |
| POST | /api/rest/v1/attributes | Create a new attribute |
| PATCH | /api/rest/v1/attributes | Update/create several attributes |
| GET | /api/rest/v1/attributes/{code} | Get an attribute |
| PATCH | /api/rest/v1/attributes/{code} | Update/create an attribute |
| GET | /api/rest/v1/attributes/{attribute_code}/options | Get list of attribute options |
| POST | /api/rest/v1/attributes/{attribute_code}/options | Create a new attribute option |
| PATCH | /api/rest/v1/attributes/{attribute_code}/options | Update/create several attribute options |
| GET | /api/rest/v1/attributes/{attribute_code}/options/{code} | Get an attribute option |
| PATCH | /api/rest/v1/attributes/{attribute_code}/options/{code} | Update/create an attribute option |
| GET | /api/rest/v1/attribute-groups | Get list of attribute groups |
| POST | /api/rest/v1/attribute-groups | Create a new attribute group |
| PATCH | /api/rest/v1/attribute-groups | Update/create several attribute groups |
| GET | /api/rest/v1/attribute-groups/{code} | Get an attribute group |
| PATCH | /api/rest/v1/attribute-groups/{code} | Update/create an attribute group |
| GET | /api/rest/v1/association-types | Get a list of association types |
| POST | /api/rest/v1/association-types | Create a new association type |
| PATCH | /api/rest/v1/association-types | Update/create several association types |
| GET | /api/rest/v1/association-types/{code} | Get an association type |
| PATCH | /api/rest/v1/association-types/{code} | Update/create an association type |
| GET | /api/rest/v1/channels | Get a list of channels |
| POST | /api/rest/v1/channels | Create a new channel |
| PATCH | /api/rest/v1/channels | Update/create several channels |
| GET | /api/rest/v1/channels/{code} | Get a channel |
| PATCH | /api/rest/v1/channels/{code} | Update/create a channel |
| GET | /api/rest/v1/locales | Get a list of locales |
| GET | /api/rest/v1/locales/{code} | Get a locale |
| GET | /api/rest/v1/categories | Get list of categories |
| POST | /api/rest/v1/categories | Create a new category |
| PATCH | /api/rest/v1/categories | Update/create several categories |
| GET | /api/rest/v1/categories/{code} | Get a category |
| PATCH | /api/rest/v1/categories/{code} | Update/create a category |
| POST | /api/rest/v1/category-media-files | Create a category media file |
| GET | /api/rest/v1/category-media-files/{file_path}/download | Download a category media file |
| GET | /api/rest/v1/currencies | Get a list of currencies |
| GET | /api/rest/v1/currencies/{code} | Get a currency |
| GET | /api/rest/v1/measure-families | Get list of measure families (deprecated as of v5.0) |
| GET | /api/rest/v1/measure-families/{code} | Get a measure family (deprecated as of v5.0) |
| GET | /api/rest/v1/measurement-families | Get list of measurement families |
| PATCH | /api/rest/v1/measurement-families | Update/create several measurement families |
| GET | /api/rest/v1/reference-entities | Get list of reference entities |
| GET | /api/rest/v1/reference-entities/{code} | Get a reference entity |
| PATCH | /api/rest/v1/reference-entities/{code} | Update/create a reference entity |
| GET | /api/rest/v1/reference-entities/{reference_entity_code}/attributes | Get the list of attributes of a given reference entity |
| GET | /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{code} | Get an attribute of a given reference entity |
| PATCH | /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{code} | Update/create an attribute of a given reference entity |
| GET | /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{attribute_code}/options | Get a list of attribute options of a given attribute for a given reference entity |
| GET | /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{attribute_code}/options/{code} | Get an attribute option for a given attribute of a given reference entity |
| PATCH | /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{attribute_code}/options/{code} | Update/create a reference entity attribute option |
| GET | /api/rest/v1/reference-entities/{reference_entity_code}/records | Get the list of the records of a reference entity |
| PATCH | /api/rest/v1/reference-entities/{reference_entity_code}/records | Update/create several reference entity records |
| GET | /api/rest/v1/reference-entities/{reference_entity_code}/records/{code} | Get a record of a given reference entity |
| PATCH | /api/rest/v1/reference-entities/{reference_entity_code}/records/{code} | Update/create a record of a given reference entity |
| POST | /api/rest/v1/reference-entities-media-files | Create a new media file for a reference entity or a record |
| GET | /api/rest/v1/reference-entities-media-files/{code} | Download the media file associated to a reference entity or a record |
| GET | /api/rest/v1/asset-families | Get list of asset families |
| GET | /api/rest/v1/asset-families/{code} | Get an asset family |
| PATCH | /api/rest/v1/asset-families/{code} | Update/create an asset family |
| GET | /api/rest/v1/asset-families/{asset_family_code}/attributes | Get the list of attributes of a given asset family |
| GET | /api/rest/v1/asset-families/{asset_family_code}/attributes/{code} | Get an attribute of a given asset family |
| PATCH | /api/rest/v1/asset-families/{asset_family_code}/attributes/{code} | Update/create an attribute of a given asset family |
| GET | /api/rest/v1/asset-families/{asset_family_code}/attributes/{attribute_code}/options | Get a list of attribute options of a given attribute for a given asset family |
| GET | /api/rest/v1/asset-families/{asset_family_code}/attributes/{attribute_code}/options/{code} | Get an attribute option for a given attribute of a given asset family |
| PATCH | /api/rest/v1/asset-families/{asset_family_code}/attributes/{attribute_code}/options/{code} | Update/create an asset attribute option for a given asset family |
| POST | /api/rest/v1/asset-media-files | Create a new media file for an asset |
| GET | /api/rest/v1/asset-media-files/{code} | Download the media file associated to an asset |
| GET | /api/rest/v1/asset-families/{asset_family_code}/assets | Get the list of the assets of a given asset family |
| PATCH | /api/rest/v1/asset-families/{asset_family_code}/assets | Update/create several assets |
| GET | /api/rest/v1/asset-families/{asset_family_code}/assets/{code} | Get an asset of a given asset family |
| PATCH | /api/rest/v1/asset-families/{asset_family_code}/assets/{code} | Update/create an asset |
| DELETE | /api/rest/v1/asset-families/{asset_family_code}/assets/{code} | Delete an asset |
| GET | /api/rest/v1/assets | Get list of PAM assets |
| POST | /api/rest/v1/assets | Create a new PAM asset |
| PATCH | /api/rest/v1/assets | Update/create several PAM assets |
| GET | /api/rest/v1/assets/{code} | Get a PAM asset |
| PATCH | /api/rest/v1/assets/{code} | Update/create a PAM asset |
| GET | /api/rest/v1/assets/{asset_code}/reference-files/{locale_code} | Get a reference file |
| POST | /api/rest/v1/assets/{asset_code}/reference-files/{locale_code} | Upload a new reference file |
| GET | /api/rest/v1/assets/{asset_code}/reference-files/{locale_code}/download | Download a reference file |
| GET | /api/rest/v1/assets/{asset_code}/variation-files/{channel_code}/{locale_code} | Get a variation file |
| POST | /api/rest/v1/assets/{asset_code}/variation-files/{channel_code}/{locale_code} | Upload a new variation file |
| GET | /api/rest/v1/assets/{asset_code}/variation-files/{channel_code}/{locale_code}/download | Download a variation file |
| GET | /api/rest/v1/asset-categories | Get list of PAM asset categories |
| POST | /api/rest/v1/asset-categories | Create a new PAM asset category |
| PATCH | /api/rest/v1/asset-categories | Update/create several PAM asset categories |
| GET | /api/rest/v1/asset-categories/{code} | Get a PAM asset category |
| PATCH | /api/rest/v1/asset-categories/{code} | Update/create a PAM asset category |
| GET | /api/rest/v1/asset-tags | Get list of PAM asset tags |
| GET | /api/rest/v1/asset-tags/{code} | Get a PAM asset tag |
| PATCH | /api/rest/v1/asset-tags/{code} | Update/create a PAM asset tag |
| GET | /api/rest/v1 | Get list of all endpoints |
| POST | /api/oauth/v1/token | Get authentication token |
| GET | /api/rest/v1/system-information | Get system information |
## Common Questions
Match user requests to endpoints in references/api-spec.lap. Key patterns:
- "Search products-uuid?" -> GET /api/rest/v1/products-uuid
- "Create a products-uuid?" -> POST /api/rest/v1/products-uuid
- "Create a search?" -> POST /api/rest/v1/products-uuid/search
- "Get products-uuid details?" -> GET /api/rest/v1/products-uuid/{uuid}
- "Partially update a products-uuid?" -> PATCH /api/rest/v1/products-uuid/{uuid}
- "Delete a products-uuid?" -> DELETE /api/rest/v1/products-uuid/{uuid}
- "Create a proposal?" -> POST /api/rest/v1/products-uuid/{uuid}/proposal
- "List all draft?" -> GET /api/rest/v1/products-uuid/{uuid}/draft
- "Search products?" -> GET /api/rest/v1/products
- "Create a product?" -> POST /api/rest/v1/products
- "Get product details?" -> GET /api/rest/v1/products/{code}
- "Partially update a product?" -> PATCH /api/rest/v1/products/{code}
- "Delete a product?" -> DELETE /api/rest/v1/products/{code}
- "Create a proposal?" -> POST /api/rest/v1/products/{code}/proposal
- "List all draft?" -> GET /api/rest/v1/products/{code}/draft
- "Search product-models?" -> GET /api/rest/v1/product-models
- "Create a product-model?" -> POST /api/rest/v1/product-models
- "Get product-model details?" -> GET /api/rest/v1/product-models/{code}
- "Partially update a product-model?" -> PATCH /api/rest/v1/product-models/{code}
- "Delete a product-model?" -> DELETE /api/rest/v1/product-models/{code}
- "Create a proposal?" -> POST /api/rest/v1/product-models/{code}/proposal
- "List all draft?" -> GET /api/rest/v1/product-models/{code}/draft
- "Search published-products?" -> GET /api/rest/v1/published-products
- "Get published-product details?" -> GET /api/rest/v1/published-products/{code}
- "List all media-files?" -> GET /api/rest/v1/media-files
- "Create a media-file?" -> POST /api/rest/v1/media-files
- "Get media-file details?" -> GET /api/rest/v1/media-files/{code}
- "List all download?" -> GET /api/rest/v1/media-files/{code}/download
- "Search families?" -> GET /api/rest/v1/families
- "Create a family?" -> POST /api/rest/v1/families
- "Get family details?" -> GET /api/rest/v1/families/{code}
- "Partially update a family?" -> PATCH /api/rest/v1/families/{code}
- "Delete a family?" -> DELETE /api/rest/v1/families/{code}
- "List all variants?" -> GET /api/rest/v1/families/{family_code}/variants
- "Create a variant?" -> POST /api/rest/v1/families/{family_code}/variants
- "Get variant details?" -> GET /api/rest/v1/families/{family_code}/variants/{code}
- "Partially update a variant?" -> PATCH /api/rest/v1/families/{family_code}/variants/{code}
- "Search attributes?" -> GET /api/rest/v1/attributes
- "Create a attribute?" -> POST /api/rest/v1/attributes
- "Get attribute details?" -> GET /api/rest/v1/attributes/{code}
- "Partially update a attribute?" -> PATCH /api/rest/v1/attributes/{code}
- "List all options?" -> GET /api/rest/v1/attributes/{attribute_code}/options
- "Create a option?" -> POST /api/rest/v1/attributes/{attribute_code}/options
- "Get option details?" -> GET /api/rest/v1/attributes/{attribute_code}/options/{code}
- "Partially update a option?" -> PATCH /api/rest/v1/attributes/{attribute_code}/options/{code}
- "Search attribute-groups?" -> GET /api/rest/v1/attribute-groups
- "Create a attribute-group?" -> POST /api/rest/v1/attribute-groups
- "Get attribute-group details?" -> GET /api/rest/v1/attribute-groups/{code}
- "Partially update a attribute-group?" -> PATCH /api/rest/v1/attribute-groups/{code}
- "List all association-types?" -> GET /api/rest/v1/association-types
- "Create a association-type?" -> POST /api/rest/v1/association-types
- "Get association-type details?" -> GET /api/rest/v1/association-types/{code}
- "Partially update a association-type?" -> PATCH /api/rest/v1/association-types/{code}
- "List all channels?" -> GET /api/rest/v1/channels
- "Create a channel?" -> POST /api/rest/v1/channels
- "Get channel details?" -> GET /api/rest/v1/channels/{code}
- "Partially update a channel?" -> PATCH /api/rest/v1/channels/{code}
- "Search locales?" -> GET /api/rest/v1/locales
- "Get locale details?" -> GET /api/rest/v1/locales/{code}
- "Search categories?" -> GET /api/rest/v1/categories
- "Create a category?" -> POST /api/rest/v1/categories
- "Get category details?" -> GET /api/rest/v1/categories/{code}
- "Partially update a category?" -> PATCH /api/rest/v1/categories/{code}
- "Create a category-media-file?" -> POST /api/rest/v1/category-media-files
- "List all download?" -> GET /api/rest/v1/category-media-files/{file_path}/download
- "Search currencies?" -> GET /api/rest/v1/currencies
- "Get currency details?" -> GET /api/rest/v1/currencies/{code}
- "List all measure-families?" -> GET /api/rest/v1/measure-families
- "Get measure-family details?" -> GET /api/rest/v1/measure-families/{code}
- "List all measurement-families?" -> GET /api/rest/v1/measurement-families
- "List all reference-entities?" -> GET /api/rest/v1/reference-entities
- "Get reference-entity details?" -> GET /api/rest/v1/reference-entities/{code}
- "Partially update a reference-entity?" -> PATCH /api/rest/v1/reference-entities/{code}
- "List all attributes?" -> GET /api/rest/v1/reference-entities/{reference_entity_code}/attributes
- "Get attribute details?" -> GET /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{code}
- "Partially update a attribute?" -> PATCH /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{code}
- "List all options?" -> GET /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{attribute_code}/options
- "Get option details?" -> GET /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{attribute_code}/options/{code}
- "Partially update a option?" -> PATCH /api/rest/v1/reference-entities/{reference_entity_code}/attributes/{attribute_code}/options/{code}
- "Search records?" -> GET /api/rest/v1/reference-entities/{reference_entity_code}/records
- "Get record details?" -> GET /api/rest/v1/reference-entities/{reference_entity_code}/records/{code}
- "Partially update a record?" -> PATCH /api/rest/v1/reference-entities/{reference_entity_code}/records/{code}
- "Create a reference-entities-media-file?" -> POST /api/rest/v1/reference-entities-media-files
- "Get reference-entities-media-file details?" -> GET /api/rest/v1/reference-entities-media-files/{code}
- "List all asset-families?" -> GET /api/rest/v1/asset-families
- "Get asset-family details?" -> GET /api/rest/v1/asset-families/{code}
- "Partially update a asset-family?" -> PATCH /api/rest/v1/asset-families/{code}
- "List all attributes?" -> GET /api/rest/v1/asset-families/{asset_family_code}/attributes
- "Get attribute details?" -> GET /api/rest/v1/asset-families/{asset_family_code}/attributes/{code}
- "Partially update a attribute?" -> PATCH /api/rest/v1/asset-families/{asset_family_code}/attributes/{code}
- "List all options?" -> GET /api/rest/v1/asset-families/{asset_family_code}/attributes/{attribute_code}/options
- "Get option details?" -> GET /api/rest/v1/asset-families/{asset_family_code}/attributes/{attribute_code}/options/{code}
- "Partially update a option?" -> PATCH /api/rest/v1/asset-families/{asset_family_code}/attributes/{attribute_code}/options/{code}
- "Create a asset-media-file?" -> POST /api/rest/v1/asset-media-files
- "Get asset-media-file details?" -> GET /api/rest/v1/asset-media-files/{code}
- "Search assets?" -> GET /api/rest/v1/asset-families/{asset_family_code}/assets
- "Get asset details?" -> GET /api/rest/v1/asset-families/{asset_family_code}/assets/{code}
- "Partially update a asset?" -> PATCH /api/rest/v1/asset-families/{asset_family_code}/assets/{code}
- "Delete a asset?" -> DELETE /api/rest/v1/asset-families/{asset_family_code}/assets/{code}
- "List all assets?" -> GET /api/rest/v1/assets
- "Create a asset?" -> POST /api/rest/v1/assets
- "Get asset details?" -> GET /api/rest/v1/assets/{code}
- "Partially update a asset?" -> PATCH /api/rest/v1/assets/{code}
- "Get reference-file details?" -> GET /api/rest/v1/assets/{asset_code}/reference-files/{locale_code}
- "List all download?" -> GET /api/rest/v1/assets/{asset_code}/reference-files/{locale_code}/download
- "Get variation-file details?" -> GET /api/rest/v1/assets/{asset_code}/variation-files/{channel_code}/{locale_code}
- "List all download?" -> GET /api/rest/v1/assets/{asset_code}/variation-files/{channel_code}/{locale_code}/download
- "List all asset-categories?" -> GET /api/rest/v1/asset-categories
- "Create a asset-category?" -> POST /api/rest/v1/asset-categories
- "Get asset-category details?" -> GET /api/rest/v1/asset-categories/{code}
- "Partially update a asset-category?" -> PATCH /api/rest/v1/asset-categories/{code}
- "List all asset-tags?" -> GET /api/rest/v1/asset-tags
- "Get asset-tag details?" -> GET /api/rest/v1/asset-tags/{code}
- "Partially update a asset-tag?" -> PATCH /api/rest/v1/asset-tags/{code}
- "List all rest?" -> GET /api/rest/v1
- "Create a token?" -> POST /api/oauth/v1/token
- "List all system-information?" -> GET /api/rest/v1/system-information
- "How to authenticate?" -> See Auth section
## Response Tips
- Check response schemas in references/api-spec.lap for field details
- List endpoints may support pagination; check for limit, offset, or cursor params
- Create/update endpoints typically return the created/updated object
## CLI
```bash
# Update this spec to the latest version
npx @lap-platform/lapsh get akeneo-pim-rest-api -o references/api-spec.lap
# Search for related APIs
npx @lap-platform/lapsh search akeneo-pim-rest-api
```
## References
- Full spec: See references/api-spec.lap for complete endpoint details, parameter tables, and response schemas
> Generated from the official API spec by [LAP](https://lap.sh)
Akamai: Application Security API skill. Use when working with Akamai: Application Security for activations, api-discovery, configs. Covers 236 endpoints.
---
name: lap-akamai-application-security-api
description: "Akamai: Application Security API skill. Use when working with Akamai: Application Security for activations, api-discovery, configs. Covers 236 endpoints."
version: 1.0.0
generator: lapsh
---
# Akamai: Application Security API
API version: v1
## Auth
No authentication required.
## Base URL
https://{hostname}/appsec/v1
## Setup
1. No auth setup needed
2. GET /api-discovery -- verify access
3. POST /activations -- create first activations
## Endpoints
236 endpoints across 10 groups. See references/api-spec.lap for full details.
### activations
| Method | Path | Description |
|--------|------|-------------|
| POST | /activations | Activate a configuration version |
| GET | /activations/status/{statusId} | Get an activation request status |
| GET | /activations/{activationId} | Get activation status |
### api-discovery
| Method | Path | Description |
|--------|------|-------------|
| GET | /api-discovery | List discovered APIs |
| GET | /api-discovery/host/{hostname}/basepath/{basePath} | Get a discovered API |
| PUT | /api-discovery/host/{hostname}/basepath/{basePath} | Modify an API's visibility |
| POST | /api-discovery/host/{hostname}/basepath/{basePath}/endpoints | Create an endpoint or resource |
| GET | /api-discovery/host/{hostname}/basepath/{basePath}/endpoints | List discovered API endpoints |
### configs
| Method | Path | Description |
|--------|------|-------------|
| POST | /configs | Create a configuration |
| GET | /configs | List configurations |
| GET | /configs/{configId} | Get a security configuration |
| PUT | /configs/{configId} | Rename a security configuration |
| DELETE | /configs/{configId} | Delete a configuration |
| GET | /configs/{configId}/activations | List activation history |
| POST | /configs/{configId}/custom-rules | Create a custom rule |
| GET | /configs/{configId}/custom-rules | List custom rules |
| GET | /configs/{configId}/custom-rules/{ruleId} | Get a custom rule |
| PUT | /configs/{configId}/custom-rules/{ruleId} | Modify a custom rule |
| DELETE | /configs/{configId}/custom-rules/{ruleId} | Remove a custom rule |
| GET | /configs/{configId}/failover-hostnames | List failover hostnames |
| POST | /configs/{configId}/notification/subscription/{feature} | Subscribe or unsubscribe to recommendation emails |
| GET | /configs/{configId}/notification/subscription/{feature} | List subscribers |
| POST | /configs/{configId}/versions | Clone a configuration version |
| GET | /configs/{configId}/versions | List configuration versions |
| POST | /configs/{configId}/versions/diff | Compare two versions |
| GET | /configs/{configId}/versions/{versionNumber} | Get configuration version details |
| DELETE | /configs/{configId}/versions/{versionNumber} | Delete a configuration version |
| GET | /configs/{configId}/versions/{versionNumber}/advanced-settings/cookie-settings | Get cookie settings |
| PUT | /configs/{configId}/versions/{versionNumber}/advanced-settings/cookie-settings | Modify cookie settings |
| GET | /configs/{configId}/versions/{versionNumber}/advanced-settings/evasive-path-match | Get evasive path match settings for a configuration |
| PUT | /configs/{configId}/versions/{versionNumber}/advanced-settings/evasive-path-match | Modify evasive path match settings for a configuration |
| GET | /configs/{configId}/versions/{versionNumber}/advanced-settings/ja4-fingerprint | Get JA4 client TLS fingerprint settings |
| PUT | /configs/{configId}/versions/{versionNumber}/advanced-settings/ja4-fingerprint | Modify JA4 client TLS fingerprint settings |
| GET | /configs/{configId}/versions/{versionNumber}/advanced-settings/logging | Get the HTTP header log settings for a configuration |
| PUT | /configs/{configId}/versions/{versionNumber}/advanced-settings/logging | Modify HTTP header log settings for a configuration |
| GET | /configs/{configId}/versions/{versionNumber}/advanced-settings/logging/attack-payload | Get the attack payload log settings for a configuration |
| PUT | /configs/{configId}/versions/{versionNumber}/advanced-settings/logging/attack-payload | Modify attack payload log settings for a configuration |
| GET | /configs/{configId}/versions/{versionNumber}/advanced-settings/pii-learning | Get PII learning settings for a configuration |
| PUT | /configs/{configId}/versions/{versionNumber}/advanced-settings/pii-learning | Enable PII learning settings for a configuration |
| GET | /configs/{configId}/versions/{versionNumber}/advanced-settings/pragma-header | Get Pragma settings for a configuration |
| PUT | /configs/{configId}/versions/{versionNumber}/advanced-settings/pragma-header | Modify Pragma settings for a configuration |
| GET | /configs/{configId}/versions/{versionNumber}/advanced-settings/prefetch | Get prefetch requests |
| PUT | /configs/{configId}/versions/{versionNumber}/advanced-settings/prefetch | Modify prefetch requests |
| GET | /configs/{configId}/versions/{versionNumber}/advanced-settings/request-body | Get request body size settings for a configuration |
| PUT | /configs/{configId}/versions/{versionNumber}/advanced-settings/request-body | Modify request body inspection limit settings for a configuration |
| POST | /configs/{configId}/versions/{versionNumber}/behavioral-ddos | Create a Behavioral DDoS profile |
| GET | /configs/{configId}/versions/{versionNumber}/behavioral-ddos | List Behavioral DDoS profiles |
| GET | /configs/{configId}/versions/{versionNumber}/behavioral-ddos/{profileId} | Get a Behavioral DDoS profile |
| PUT | /configs/{configId}/versions/{versionNumber}/behavioral-ddos/{profileId} | Modify a Behavioral DDoS profile |
| DELETE | /configs/{configId}/versions/{versionNumber}/behavioral-ddos/{profileId} | Remove a Behavioral DDoS profile |
| GET | /configs/{configId}/versions/{versionNumber}/bypass-network-lists | Get bypass network lists settings |
| PUT | /configs/{configId}/versions/{versionNumber}/bypass-network-lists | Modify the bypass network lists settings |
| POST | /configs/{configId}/versions/{versionNumber}/custom-deny | Create a custom deny action |
| GET | /configs/{configId}/versions/{versionNumber}/custom-deny | List custom deny actions |
| GET | /configs/{configId}/versions/{versionNumber}/custom-deny/{customDenyId} | Get a custom deny action |
| PUT | /configs/{configId}/versions/{versionNumber}/custom-deny/{customDenyId} | Modify a custom deny action |
| DELETE | /configs/{configId}/versions/{versionNumber}/custom-deny/{customDenyId} | Remove a custom deny action |
| POST | /configs/{configId}/versions/{versionNumber}/custom-rules/usage | List custom rules usage by security policies |
| POST | /configs/{configId}/versions/{versionNumber}/export | Asynchronously export a configuration version |
| GET | /configs/{configId}/versions/{versionNumber}/export/{exportId}/result | Get asynchronous export results |
| GET | /configs/{configId}/versions/{versionNumber}/export/{exportId}/status | Get asynchronous export status |
| GET | /configs/{configId}/versions/{versionNumber}/hostname-coverage/match-targets | Get the hostname coverage match targets |
| GET | /configs/{configId}/versions/{versionNumber}/hostname-coverage/overlapping | List hostname overlaps |
| POST | /configs/{configId}/versions/{versionNumber}/malware-policies | Create a malware policy |
| GET | /configs/{configId}/versions/{versionNumber}/malware-policies | List malware policies |
| GET | /configs/{configId}/versions/{versionNumber}/malware-policies/content-types | List supported malware policy content types |
| GET | /configs/{configId}/versions/{versionNumber}/malware-policies/{malwarePolicyId} | Get a malware policy |
| PUT | /configs/{configId}/versions/{versionNumber}/malware-policies/{malwarePolicyId} | Modify a malware policy |
| DELETE | /configs/{configId}/versions/{versionNumber}/malware-policies/{malwarePolicyId} | Remove a malware policy |
| POST | /configs/{configId}/versions/{versionNumber}/match-targets | Create a match target |
| GET | /configs/{configId}/versions/{versionNumber}/match-targets | List match targets |
| PUT | /configs/{configId}/versions/{versionNumber}/match-targets/sequence | Modify match target order |
| GET | /configs/{configId}/versions/{versionNumber}/match-targets/{targetId} | Get a match target |
| PUT | /configs/{configId}/versions/{versionNumber}/match-targets/{targetId} | Modify a match target |
| DELETE | /configs/{configId}/versions/{versionNumber}/match-targets/{targetId} | Remove a match target |
| PUT | /configs/{configId}/versions/{versionNumber}/protect-eval-hostnames | Protect evaluation hostnames |
| POST | /configs/{configId}/versions/{versionNumber}/rate-policies | Create a rate policy |
| GET | /configs/{configId}/versions/{versionNumber}/rate-policies | List rate policies |
| GET | /configs/{configId}/versions/{versionNumber}/rate-policies/{ratePolicyId} | Get a rate policy |
| PUT | /configs/{configId}/versions/{versionNumber}/rate-policies/{ratePolicyId} | Modify a rate policy |
| DELETE | /configs/{configId}/versions/{versionNumber}/rate-policies/{ratePolicyId} | Remove a rate policy |
| PUT | /configs/{configId}/versions/{versionNumber}/rate-policies/{ratePolicyId}/evaluation | Modify a rate policy evaluation |
| POST | /configs/{configId}/versions/{versionNumber}/reputation-profiles | Create a reputation profile |
| GET | /configs/{configId}/versions/{versionNumber}/reputation-profiles | List reputation profiles |
| GET | /configs/{configId}/versions/{versionNumber}/reputation-profiles/{reputationProfileId} | Get a reputation profile |
| PUT | /configs/{configId}/versions/{versionNumber}/reputation-profiles/{reputationProfileId} | Modify a reputation profile |
| DELETE | /configs/{configId}/versions/{versionNumber}/reputation-profiles/{reputationProfileId} | Remove a reputation profile |
| POST | /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions | Create a challenge action |
| GET | /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions | List challenge actions |
| GET | /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions/{actionId} | Get a challenge action |
| PUT | /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions/{actionId} | Update a challenge action |
| DELETE | /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions/{actionId} | Delete a challenge action |
| PUT | /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions/{actionId}/google-recaptcha-secret-key | Update Google reCAPTCHA secret key |
| POST | /configs/{configId}/versions/{versionNumber}/security-policies | Clone or create a security policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies | List security policies |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId} | Get a security policy |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId} | Modify a security policy |
| DELETE | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId} | Remove a security policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/evasive-path-match | Get evasive path match settings |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/evasive-path-match | Modify evasive path match settings |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/logging | Get HTTP header log settings |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/logging | Modify HTTP header log settings |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/logging/attack-payload | Get attack payload logging settings for a policy |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/logging/attack-payload | Modify attack payload logging settings for a policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/pragma-header | Get Pragma settings for a security policy |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/pragma-header | Modify Pragma settings for a security policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/request-body | Get request body inspection limit settings for a security policy |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/request-body | Modify request body size settings for a security policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/api-endpoints | List API endpoints |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/api-request-constraints | List API request constraints and actions |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/api-request-constraints | Modify the request constraint action for all APIs |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/api-request-constraints/{apiId} | Modify an API request constraint's action |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/attack-groups | List attack groups |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/attack-groups/{attackGroupId} | Get the action for an attack group |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/attack-groups/{attackGroupId} | Modify the action for an attack group |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/attack-groups/{attackGroupId}/condition-exception | Get the exceptions of an attack group |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/attack-groups/{attackGroupId}/condition-exception | Modify the exceptions of an attack group |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/behavioral-ddos | List Behavioral DDoS profile actions |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/behavioral-ddos/{profileId} | Modify a Behavioral DDoS profile action |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/bypass-network-lists | Get the bypass network lists settings for a security policy |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/bypass-network-lists | Modify the bypass network lists settings for a security policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/cpc | Get Client-Side Protection & Compliance settings |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/cpc | Modify Client-Side Protections & Compliance settings |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/custom-rules | List custom rule actions |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/custom-rules/{ruleId} | Modify a custom rule action |
| POST | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval | Set evaluation mode |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-groups | List evaluation attack groups |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-groups/{attackGroupId} | Get the action for an evaluation attack group |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-groups/{attackGroupId} | Modify the action for an evaluation attack group |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-groups/{attackGroupId}/condition-exception | Get the exceptions of an evaluation attack group |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-groups/{attackGroupId}/condition-exception | Modify the exceptions of an evaluation attack group |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-hostnames | List evaluation hostnames for a security policy |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-hostnames | Modify evaluation hostnames for a security policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-penalty-box | Get the penalty box for a policy in evaluation mode |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-penalty-box | Modify the evaluation penalty box |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-penalty-box/conditions | Get penalty box conditions in evaluation mode |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-penalty-box/conditions | Modify the penalty box conditions in evaluation mode |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-rules | List evaluation rules |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-rules/{ruleId} | Get the action of an evaluation rule |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-rules/{ruleId} | Modify the action of an evaluation rule |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-rules/{ruleId}/condition-exception | Get the conditions and exceptions for an evaluation rule |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-rules/{ruleId}/condition-exception | Modify the conditions and exceptions for an evaluation rule |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/ip-geo-firewall | Get IP/Geo Firewall settings |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/ip-geo-firewall | Modify IP/Geo Firewall settings |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/malware-policies | List malware policy actions |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/malware-policies/{malwarePolicyId} | Modify a malware policy action |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/mode | Get the current mode |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/mode | Modify the mode |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/penalty-box | Get the penalty box |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/penalty-box | Modify the penalty box |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/penalty-box/conditions | Get penalty box condition |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/penalty-box/conditions | Modify the penalty box conditions |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/protect-eval-hostnames | Protect evaluation hostnames for a security policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/protections | Get protections |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/protections | Modify protections |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules | List rapid rules |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/action | Get rapid rules' default action |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/action | Update rapid rules' default action |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/status | Get rapid rules' status |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/status | Update rapid rules' status |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/{ruleId}/condition-exception | List a rapid rule's conditions and exceptions |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/{ruleId}/condition-exception | Update a rapid rule's conditions and exceptions |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/{ruleId}/lock | Get a rapid rule's lock status |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/{ruleId}/lock | Update a rapid rule's lock status |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/{ruleId}/versions/{ruleVersion}/action | Get a rapid rule's action |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/{ruleId}/versions/{ruleVersion}/action | Update a rapid rule's action |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rate-policies | List rate policy actions |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rate-policies/{ratePolicyId} | Modify a rate policy action |
| POST | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/recommendations | Respond to exception recommendations |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/recommendations | Get tuning recommendations for a policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/recommendations/attack-groups/{attackGroupId} | List tuning recommendations for an attack group |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/recommendations/rules/{ruleId} | List tuning recommendations for a rule |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/reputation-analysis | Get reputation analysis settings |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/reputation-analysis | Modify reputation analysis settings |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/reputation-profiles | List reputation profile actions |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/reputation-profiles/{reputationProfileId} | Get the action for a reputation profile |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/reputation-profiles/{reputationProfileId} | Modify the action for a reputation profile |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules | List rules |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules | Upgrade KRS ruleset |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules/upgrade-details | Get upgrade details |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules/{ruleId} | Get the action for a rule |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules/{ruleId} | Modify the action for a rule |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules/{ruleId}/condition-exception | Get the conditions and exceptions of a rule |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules/{ruleId}/condition-exception | Modify the conditions and exceptions of a rule |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/selected-hostnames | List selected hostnames for a security policy |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/selected-hostnames | Modify selected hostnames for a security policy |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/slow-post | Get slow POST protection settings |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/slow-post | Modify slow POST protection settings |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/threat-intel | Get adaptive intelligence settings |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/threat-intel | Modify adaptive intelligence settings |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/url-protections | List URL protection policy actions |
| PUT | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/url-protections/{urlProtectionPolicyId} | Modify a URL protection policy action |
| GET | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/web-application-firewall/ruleset | Get a security policy's rule set |
| PATCH | /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/web-application-firewall/ruleset | Modify a security policy's rule set |
| GET | /configs/{configId}/versions/{versionNumber}/selectable-hostnames | List selectable hostnames |
| GET | /configs/{configId}/versions/{versionNumber}/selected-hostnames | List selected hostnames |
| PUT | /configs/{configId}/versions/{versionNumber}/selected-hostnames | Modify selected hostnames |
| GET | /configs/{configId}/versions/{versionNumber}/selected-hostnames/eval-hostnames | List evaluation hostnames |
| PUT | /configs/{configId}/versions/{versionNumber}/selected-hostnames/eval-hostnames | Modify evaluation hostnames |
| GET | /configs/{configId}/versions/{versionNumber}/siem | Get SIEM settings |
| PUT | /configs/{configId}/versions/{versionNumber}/siem | Modify SIEM settings |
| POST | /configs/{configId}/versions/{versionNumber}/url-protections | Create a URL protection policy |
| GET | /configs/{configId}/versions/{versionNumber}/url-protections | List URL protection policies |
| GET | /configs/{configId}/versions/{versionNumber}/url-protections/{urlProtectionPolicyId} | Get a URL protection policy |
| PUT | /configs/{configId}/versions/{versionNumber}/url-protections/{urlProtectionPolicyId} | Modify a URL protection policy |
| DELETE | /configs/{configId}/versions/{versionNumber}/url-protections/{urlProtectionPolicyId} | Remove a URL protection policy |
| GET | /configs/{configId}/versions/{versionNumber}/version-notes | Get the version notes |
| PUT | /configs/{configId}/versions/{versionNumber}/version-notes | Modify version notes |
### contracts-groups
| Method | Path | Description |
|--------|------|-------------|
| GET | /contracts-groups | List contracts and groups |
### contracts
| Method | Path | Description |
|--------|------|-------------|
| GET | /contracts/{contractId}/groups/{groupId}/selectable-hostnames | List available hostnames for a new configuration |
### cves
| Method | Path | Description |
|--------|------|-------------|
| GET | /cves | List CVEs |
| POST | /cves/subscribe | Subscribe to CVEs |
| GET | /cves/subscribed | List subscribed CVEs |
| POST | /cves/unsubscribe | Unsubscribe from CVEs |
| GET | /cves/{cveId} | Get a CVE |
| GET | /cves/{cveId}/security-coverage | Get CVE coverage |
### export
| Method | Path | Description |
|--------|------|-------------|
| GET | /export/configs/{configId}/versions/{versionNumber} | Export a configuration version |
### hostname-coverage
| Method | Path | Description |
|--------|------|-------------|
| GET | /hostname-coverage | Get hostname coverage |
### onboardings
| Method | Path | Description |
|--------|------|-------------|
| POST | /onboardings | Create an onboarding |
| GET | /onboardings | List onboardings |
| GET | /onboardings/{onboardingId} | Get an onboarding |
| DELETE | /onboardings/{onboardingId} | Delete an onboarding |
| POST | /onboardings/{onboardingId}/activations | Activate an onboarding |
| GET | /onboardings/{onboardingId}/activations/{activationId} | Get an onboarding activation |
| GET | /onboardings/{onboardingId}/certificate-validation | List onboarding certificate challenges |
| POST | /onboardings/{onboardingId}/certificate-validation/validate | Validate onboarding certificate |
| GET | /onboardings/{onboardingId}/cname-to-akamai | List hostname CNAME DNS records |
| POST | /onboardings/{onboardingId}/cname-to-akamai/validate | Validate hostname CNAME DNS records |
| GET | /onboardings/{onboardingId}/domain-validation | List onboarding domain challenges |
| POST | /onboardings/{onboardingId}/domain-validation/validate | Validate onboarding domains |
| GET | /onboardings/{onboardingId}/origin-validation | List origin hostname DNS records |
| POST | /onboardings/{onboardingId}/origin-validation/skip | Skip origin hostnames DNS record validation |
| POST | /onboardings/{onboardingId}/origin-validation/validate | Validate origin hostnames DNS records |
| GET | /onboardings/{onboardingId}/settings | Get onboarding settings |
| PUT | /onboardings/{onboardingId}/settings | Modify onboarding settings |
### siem-definitions
| Method | Path | Description |
|--------|------|-------------|
| GET | /siem-definitions | Get SIEM versions |
## Common Questions
Match user requests to endpoints in references/api-spec.lap. Key patterns:
- "Create a activation?" -> POST /activations
- "Get status details?" -> GET /activations/status/{statusId}
- "Get activation details?" -> GET /activations/{activationId}
- "Search api-discovery?" -> GET /api-discovery
- "Search basepath?" -> GET /api-discovery/host/{hostname}/basepath/{basePath}
- "Update a basepath?" -> PUT /api-discovery/host/{hostname}/basepath/{basePath}
- "Create a endpoint?" -> POST /api-discovery/host/{hostname}/basepath/{basePath}/endpoints
- "List all endpoints?" -> GET /api-discovery/host/{hostname}/basepath/{basePath}/endpoints
- "Create a config?" -> POST /configs
- "List all configs?" -> GET /configs
- "Get config details?" -> GET /configs/{configId}
- "Update a config?" -> PUT /configs/{configId}
- "Delete a config?" -> DELETE /configs/{configId}
- "List all activations?" -> GET /configs/{configId}/activations
- "Create a custom-rule?" -> POST /configs/{configId}/custom-rules
- "List all custom-rules?" -> GET /configs/{configId}/custom-rules
- "Get custom-rule details?" -> GET /configs/{configId}/custom-rules/{ruleId}
- "Update a custom-rule?" -> PUT /configs/{configId}/custom-rules/{ruleId}
- "Delete a custom-rule?" -> DELETE /configs/{configId}/custom-rules/{ruleId}
- "List all failover-hostnames?" -> GET /configs/{configId}/failover-hostnames
- "Get subscription details?" -> GET /configs/{configId}/notification/subscription/{feature}
- "Create a version?" -> POST /configs/{configId}/versions
- "List all versions?" -> GET /configs/{configId}/versions
- "Create a diff?" -> POST /configs/{configId}/versions/diff
- "Get version details?" -> GET /configs/{configId}/versions/{versionNumber}
- "Delete a version?" -> DELETE /configs/{configId}/versions/{versionNumber}
- "List all cookie-settings?" -> GET /configs/{configId}/versions/{versionNumber}/advanced-settings/cookie-settings
- "List all evasive-path-match?" -> GET /configs/{configId}/versions/{versionNumber}/advanced-settings/evasive-path-match
- "List all ja4-fingerprint?" -> GET /configs/{configId}/versions/{versionNumber}/advanced-settings/ja4-fingerprint
- "List all logging?" -> GET /configs/{configId}/versions/{versionNumber}/advanced-settings/logging
- "List all attack-payload?" -> GET /configs/{configId}/versions/{versionNumber}/advanced-settings/logging/attack-payload
- "List all pii-learning?" -> GET /configs/{configId}/versions/{versionNumber}/advanced-settings/pii-learning
- "List all pragma-header?" -> GET /configs/{configId}/versions/{versionNumber}/advanced-settings/pragma-header
- "List all prefetch?" -> GET /configs/{configId}/versions/{versionNumber}/advanced-settings/prefetch
- "List all request-body?" -> GET /configs/{configId}/versions/{versionNumber}/advanced-settings/request-body
- "Create a behavioral-ddo?" -> POST /configs/{configId}/versions/{versionNumber}/behavioral-ddos
- "List all behavioral-ddos?" -> GET /configs/{configId}/versions/{versionNumber}/behavioral-ddos
- "Get behavioral-ddo details?" -> GET /configs/{configId}/versions/{versionNumber}/behavioral-ddos/{profileId}
- "Update a behavioral-ddo?" -> PUT /configs/{configId}/versions/{versionNumber}/behavioral-ddos/{profileId}
- "Delete a behavioral-ddo?" -> DELETE /configs/{configId}/versions/{versionNumber}/behavioral-ddos/{profileId}
- "List all bypass-network-lists?" -> GET /configs/{configId}/versions/{versionNumber}/bypass-network-lists
- "Create a custom-deny?" -> POST /configs/{configId}/versions/{versionNumber}/custom-deny
- "Search custom-deny?" -> GET /configs/{configId}/versions/{versionNumber}/custom-deny
- "Get custom-deny details?" -> GET /configs/{configId}/versions/{versionNumber}/custom-deny/{customDenyId}
- "Update a custom-deny?" -> PUT /configs/{configId}/versions/{versionNumber}/custom-deny/{customDenyId}
- "Delete a custom-deny?" -> DELETE /configs/{configId}/versions/{versionNumber}/custom-deny/{customDenyId}
- "Create a usage?" -> POST /configs/{configId}/versions/{versionNumber}/custom-rules/usage
- "Create a export?" -> POST /configs/{configId}/versions/{versionNumber}/export
- "List all result?" -> GET /configs/{configId}/versions/{versionNumber}/export/{exportId}/result
- "List all status?" -> GET /configs/{configId}/versions/{versionNumber}/export/{exportId}/status
- "List all match-targets?" -> GET /configs/{configId}/versions/{versionNumber}/hostname-coverage/match-targets
- "List all overlapping?" -> GET /configs/{configId}/versions/{versionNumber}/hostname-coverage/overlapping
- "Create a malware-policy?" -> POST /configs/{configId}/versions/{versionNumber}/malware-policies
- "List all malware-policies?" -> GET /configs/{configId}/versions/{versionNumber}/malware-policies
- "List all content-types?" -> GET /configs/{configId}/versions/{versionNumber}/malware-policies/content-types
- "Get malware-policy details?" -> GET /configs/{configId}/versions/{versionNumber}/malware-policies/{malwarePolicyId}
- "Update a malware-policy?" -> PUT /configs/{configId}/versions/{versionNumber}/malware-policies/{malwarePolicyId}
- "Delete a malware-policy?" -> DELETE /configs/{configId}/versions/{versionNumber}/malware-policies/{malwarePolicyId}
- "Create a match-target?" -> POST /configs/{configId}/versions/{versionNumber}/match-targets
- "List all match-targets?" -> GET /configs/{configId}/versions/{versionNumber}/match-targets
- "Get match-target details?" -> GET /configs/{configId}/versions/{versionNumber}/match-targets/{targetId}
- "Update a match-target?" -> PUT /configs/{configId}/versions/{versionNumber}/match-targets/{targetId}
- "Delete a match-target?" -> DELETE /configs/{configId}/versions/{versionNumber}/match-targets/{targetId}
- "Create a rate-policy?" -> POST /configs/{configId}/versions/{versionNumber}/rate-policies
- "List all rate-policies?" -> GET /configs/{configId}/versions/{versionNumber}/rate-policies
- "Get rate-policy details?" -> GET /configs/{configId}/versions/{versionNumber}/rate-policies/{ratePolicyId}
- "Update a rate-policy?" -> PUT /configs/{configId}/versions/{versionNumber}/rate-policies/{ratePolicyId}
- "Delete a rate-policy?" -> DELETE /configs/{configId}/versions/{versionNumber}/rate-policies/{ratePolicyId}
- "Create a reputation-profile?" -> POST /configs/{configId}/versions/{versionNumber}/reputation-profiles
- "List all reputation-profiles?" -> GET /configs/{configId}/versions/{versionNumber}/reputation-profiles
- "Get reputation-profile details?" -> GET /configs/{configId}/versions/{versionNumber}/reputation-profiles/{reputationProfileId}
- "Update a reputation-profile?" -> PUT /configs/{configId}/versions/{versionNumber}/reputation-profiles/{reputationProfileId}
- "Delete a reputation-profile?" -> DELETE /configs/{configId}/versions/{versionNumber}/reputation-profiles/{reputationProfileId}
- "Create a challenge-action?" -> POST /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions
- "List all challenge-actions?" -> GET /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions
- "Get challenge-action details?" -> GET /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions/{actionId}
- "Update a challenge-action?" -> PUT /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions/{actionId}
- "Delete a challenge-action?" -> DELETE /configs/{configId}/versions/{versionNumber}/response-actions/challenge-actions/{actionId}
- "Create a security-policy?" -> POST /configs/{configId}/versions/{versionNumber}/security-policies
- "List all security-policies?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies
- "Get security-policy details?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}
- "Update a security-policy?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}
- "Delete a security-policy?" -> DELETE /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}
- "List all evasive-path-match?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/evasive-path-match
- "List all logging?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/logging
- "List all attack-payload?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/logging/attack-payload
- "List all pragma-header?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/pragma-header
- "List all request-body?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/advanced-settings/request-body
- "List all api-endpoints?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/api-endpoints
- "List all api-request-constraints?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/api-request-constraints
- "Update a api-request-constraint?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/api-request-constraints/{apiId}
- "List all attack-groups?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/attack-groups
- "Get attack-group details?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/attack-groups/{attackGroupId}
- "Update a attack-group?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/attack-groups/{attackGroupId}
- "List all condition-exception?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/attack-groups/{attackGroupId}/condition-exception
- "List all behavioral-ddos?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/behavioral-ddos
- "Update a behavioral-ddo?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/behavioral-ddos/{profileId}
- "List all bypass-network-lists?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/bypass-network-lists
- "List all cpc?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/cpc
- "List all custom-rules?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/custom-rules
- "Update a custom-rule?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/custom-rules/{ruleId}
- "Create a eval?" -> POST /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval
- "List all eval-groups?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-groups
- "Get eval-group details?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-groups/{attackGroupId}
- "Update a eval-group?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-groups/{attackGroupId}
- "List all condition-exception?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-groups/{attackGroupId}/condition-exception
- "List all eval-hostnames?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-hostnames
- "List all eval-penalty-box?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-penalty-box
- "List all conditions?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-penalty-box/conditions
- "List all eval-rules?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-rules
- "Get eval-rule details?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-rules/{ruleId}
- "Update a eval-rule?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-rules/{ruleId}
- "List all condition-exception?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/eval-rules/{ruleId}/condition-exception
- "List all ip-geo-firewall?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/ip-geo-firewall
- "List all malware-policies?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/malware-policies
- "Update a malware-policy?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/malware-policies/{malwarePolicyId}
- "List all mode?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/mode
- "List all penalty-box?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/penalty-box
- "List all conditions?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/penalty-box/conditions
- "List all protections?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/protections
- "List all rapid-rules?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules
- "List all action?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/action
- "List all status?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/status
- "List all condition-exception?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/{ruleId}/condition-exception
- "List all lock?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/{ruleId}/lock
- "List all action?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rapid-rules/{ruleId}/versions/{ruleVersion}/action
- "List all rate-policies?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rate-policies
- "Update a rate-policy?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rate-policies/{ratePolicyId}
- "Create a recommendation?" -> POST /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/recommendations
- "List all recommendations?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/recommendations
- "Get attack-group details?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/recommendations/attack-groups/{attackGroupId}
- "Get rule details?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/recommendations/rules/{ruleId}
- "List all reputation-analysis?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/reputation-analysis
- "List all reputation-profiles?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/reputation-profiles
- "Get reputation-profile details?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/reputation-profiles/{reputationProfileId}
- "Update a reputation-profile?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/reputation-profiles/{reputationProfileId}
- "List all rules?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules
- "List all upgrade-details?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules/upgrade-details
- "Get rule details?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules/{ruleId}
- "Update a rule?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules/{ruleId}
- "List all condition-exception?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/rules/{ruleId}/condition-exception
- "List all selected-hostnames?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/selected-hostnames
- "List all slow-post?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/slow-post
- "List all threat-intel?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/threat-intel
- "List all url-protections?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/url-protections
- "Update a url-protection?" -> PUT /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/url-protections/{urlProtectionPolicyId}
- "List all ruleset?" -> GET /configs/{configId}/versions/{versionNumber}/security-policies/{policyId}/web-application-firewall/ruleset
- "List all selectable-hostnames?" -> GET /configs/{configId}/versions/{versionNumber}/selectable-hostnames
- "List all selected-hostnames?" -> GET /configs/{configId}/versions/{versionNumber}/selected-hostnames
- "List all eval-hostnames?" -> GET /configs/{configId}/versions/{versionNumber}/selected-hostnames/eval-hostnames
- "List all siem?" -> GET /configs/{configId}/versions/{versionNumber}/siem
- "Create a url-protection?" -> POST /configs/{configId}/versions/{versionNumber}/url-protections
- "List all url-protections?" -> GET /configs/{configId}/versions/{versionNumber}/url-protections
- "Get url-protection details?" -> GET /configs/{configId}/versions/{versionNumber}/url-protections/{urlProtectionPolicyId}
- "Update a url-protection?" -> PUT /configs/{configId}/versions/{versionNumber}/url-protections/{urlProtectionPolicyId}
- "Delete a url-protection?" -> DELETE /configs/{configId}/versions/{versionNumber}/url-protections/{urlProtectionPolicyId}
- "List all version-notes?" -> GET /configs/{configId}/versions/{versionNumber}/version-notes
- "List all contracts-groups?" -> GET /contracts-groups
- "List all selectable-hostnames?" -> GET /contracts/{contractId}/groups/{groupId}/selectable-hostnames
- "List all cves?" -> GET /cves
- "Create a subscribe?" -> POST /cves/subscribe
- "List all subscribed?" -> GET /cves/subscribed
- "Create a unsubscribe?" -> POST /cves/unsubscribe
- "Get cve details?" -> GET /cves/{cveId}
- "List all security-coverage?" -> GET /cves/{cveId}/security-coverage
- "Get version details?" -> GET /export/configs/{configId}/versions/{versionNumber}
- "List all hostname-coverage?" -> GET /hostname-coverage
- "Create a onboarding?" -> POST /onboardings
- "List all onboardings?" -> GET /onboardings
- "Get onboarding details?" -> GET /onboardings/{onboardingId}
- "Delete a onboarding?" -> DELETE /onboardings/{onboardingId}
- "Create a activation?" -> POST /onboardings/{onboardingId}/activations
- "Get activation details?" -> GET /onboardings/{onboardingId}/activations/{activationId}
- "List all certificate-validation?" -> GET /onboardings/{onboardingId}/certificate-validation
- "Create a validate?" -> POST /onboardings/{onboardingId}/certificate-validation/validate
- "List all cname-to-akamai?" -> GET /onboardings/{onboardingId}/cname-to-akamai
- "Create a validate?" -> POST /onboardings/{onboardingId}/cname-to-akamai/validate
- "List all domain-validation?" -> GET /onboardings/{onboardingId}/domain-validation
- "Create a validate?" -> POST /onboardings/{onboardingId}/domain-validation/validate
- "List all origin-validation?" -> GET /onboardings/{onboardingId}/origin-validation
- "Create a skip?" -> POST /onboardings/{onboardingId}/origin-validation/skip
- "Create a validate?" -> POST /onboardings/{onboardingId}/origin-validation/validate
- "List all settings?" -> GET /onboardings/{onboardingId}/settings
- "List all siem-definitions?" -> GET /siem-definitions
## Response Tips
- Check response schemas in references/api-spec.lap for field details
- List endpoints may support pagination; check for limit, offset, or cursor params
- Create/update endpoints typically return the created/updated object
- Error responses use types: [Conflict](https, [Forbidden](https, [Invalid](https, [Unauthorized](https
## CLI
```bash
# Update this spec to the latest version
npx @lap-platform/lapsh get akamai-application-security-api -o references/api-spec.lap
# Search for related APIs
npx @lap-platform/lapsh search akamai-application-security-api
```
## References
- Full spec: See references/api-spec.lap for complete endpoint details, parameter tables, and response schemas
> Generated from the official API spec by [LAP](https://lap.sh)
airportsapi API skill. Use when working with airportsapi for airportsapi. Covers 1 endpoint.
---
name: lap-airportsapi
description: "airportsapi API skill. Use when working with airportsapi for airportsapi. Covers 1 endpoint."
version: 1.0.0
generator: lapsh
metadata:
openclaw:
requires:
env:
- AIRPORTSAPI_API_KEY
---
# airportsapi
API version: v1
## Auth
OAuth2
## Base URL
https://airport-web.appspot.com/_ah/api
## Setup
1. Configure auth: OAuth2
2. GET /airportsapi/v1/airports/{icao_code} -- verify access
## Endpoints
1 endpoints across 1 groups. See references/api-spec.lap for full details.
### airportsapi
| Method | Path | Description |
|--------|------|-------------|
| GET | /airportsapi/v1/airports/{icao_code} | |
## Common Questions
Match user requests to endpoints in references/api-spec.lap. Key patterns:
- "Get airport details?" -> GET /airportsapi/v1/airports/{icao_code}
- "How to authenticate?" -> See Auth section
## Response Tips
- Check response schemas in references/api-spec.lap for field details
## CLI
```bash
# Update this spec to the latest version
npx @lap-platform/lapsh get airportsapi -o references/api-spec.lap
# Search for related APIs
npx @lap-platform/lapsh search airportsapi
```
## References
- Full spec: See references/api-spec.lap for complete endpoint details, parameter tables, and response schemas
> Generated from the official API spec by [LAP](https://lap.sh)
Airport On-Time Performance API skill. Use when working with Airport On-Time Performance for airport. Covers 1 endpoint.
--- name: lap-airport-on-time-performance description: "Airport On-Time Performance API skill. Use when working with Airport On-Time Performance for airport. Covers 1 endpoint." version: 1.0.0 generator: lapsh --- # Airport On-Time Performance API version: 1.0.4 ## Auth No authentication required. ## Base URL https://test.api.amadeus.com/v1 ## Setup 1. No auth setup needed 2. GET /airport/predictions/on-time -- verify access ## Endpoints 1 endpoints across 1 groups. See references/api-spec.lap for full details. ### airport | Method | Path | Description | |--------|------|-------------| | GET | /airport/predictions/on-time | Returns a percentage of on-time flight departures from a given airport. | ## Common Questions Match user requests to endpoints in references/api-spec.lap. Key patterns: - "List all on-time?" -> GET /airport/predictions/on-time ## Response Tips - Check response schemas in references/api-spec.lap for field details ## CLI ```bash # Update this spec to the latest version npx @lap-platform/lapsh get airport-on-time-performance -o references/api-spec.lap # Search for related APIs npx @lap-platform/lapsh search airport-on-time-performance ``` ## References - Full spec: See references/api-spec.lap for complete endpoint details, parameter tables, and response schemas > Generated from the official API spec by [LAP](https://lap.sh)
Guide startup PR and unconventional PR outreach using the media chain, pitch templates, and amplification tactics. Use whenever a founder or marketer needs t...
---
name: startup-pr-outreach
description: "Guide startup PR and unconventional PR outreach using the media chain, pitch templates, and amplification tactics. Use whenever a founder or marketer needs to pitch reporters, plan a PR campaign, land media coverage, run a publicity stunt, amplify a press story, use HARO, build reporter relationships, or avoid common PR mistakes. Also covers unconventional PR (stunts, customer appreciation) for startup launches. Activates on phrases like 'press release', 'PR campaign', 'media coverage', 'reporter outreach', 'pitch email', 'TechCrunch', 'HARO', 'product launch', 'PR strategy', 'publicity stunt', 'get coverage', 'press pitch', 'media pitch', 'journalist outreach'."
version: 1.0.0
homepage: https://github.com/bookforge-ai/bookforge-skills/tree/main/books/traction/skills/startup-pr-outreach
metadata: {"openclaw":{"emoji":"📚","homepage":"https://github.com/bookforge-ai/bookforge-skills"}}
status: draft
source-books:
- id: traction
title: "Traction: A Startup Guide to Getting Customers"
authors: ["Gabriel Weinberg", "Justin Mares"]
chapters: [7, 8]
domain: startup-growth
tags: [startup-growth, public-relations, media-outreach, press-pitching, launch-marketing]
depends-on: [bullseye-channel-selection]
execution:
tier: 1
mode: hybrid
inputs:
- type: document
description: "Company milestones, target media outlets, pitch angles, launch details"
tools-required: [Read, Write]
tools-optional: [AskUserQuestion]
mcps-required: []
environment: "Plain-text working directory for pitch drafts and media outreach tracker"
discovery:
goal: "Produce a PR campaign plan with pitch drafts, target outlet list, and amplification sequence"
tasks:
- "Identify a milestone worth PR coverage"
- "Build a media chain starting with small blogs"
- "Draft pitches using the two proven templates"
- "Apply the emotional angle criteria"
- "Avoid the 6 named PR pitching mistakes"
- "Plan the amplification sequence after coverage lands"
audience:
roles: [startup-founder, head-of-marketing, pr-lead]
experience: beginner-to-intermediate
when_to_use:
triggers:
- "Product launch approaching"
- "Startup has a newsworthy milestone"
- "Previous PR attempts produced no coverage"
- "Bullseye selected PR as inner-circle channel"
prerequisites: []
not_for:
- "Phase I startups with nothing newsworthy yet (use targeting blogs instead)"
environment:
codebase_required: false
codebase_helpful: false
works_offline: true
quality:
scores:
with_skill: null
baseline: null
delta: null
tested_at: null
eval_count: 0
assertion_count: 12
iterations_needed: 0
---
# Startup PR Outreach
## When to Use
The startup needs media coverage or is planning a PR campaign. Use this skill when:
- A newsworthy milestone has happened or is about to happen (funding, launch, usage threshold, partnership)
- The user wants to reach a broad audience via trusted intermediaries (reporters)
- Previous PR attempts produced no coverage
- Planning a publicity stunt or unconventional PR tactic
PR is typically a Phase II+ channel. Phase I startups without newsworthy milestones should use targeting blogs instead.
## Context & Input Gathering
### Required Context (must have — ask if missing)
- **Milestone / angle:** what's actually newsworthy
→ Check prompt for: "launching", "raised", "hit X users", "partnership with"
→ If missing, ask: "What specific milestone are you trying to get coverage for? Launches, funding, user thresholds, and industry partnerships typically work. Vague product announcements don't."
- **Target audience:** who the story should reach
→ Check prompt for: developer, consumer, enterprise buyer, specific vertical
→ If missing, ask: "Who is the ideal reader? That determines which outlets and reporters to target."
### Observable Context
- **Existing media relationships:** prior coverage, reporter connections
- **Spokesperson availability:** founder, press-ready team members
### Default Assumptions
- Start with small blogs, not top-tier outlets (media chain principle)
- Founders pitch better than PR firms for early-stage startups
- Bundle smaller announcements into bigger ones when possible
### Sufficiency Threshold
```
SUFFICIENT: milestone + target audience known
PROCEED WITH DEFAULTS: milestone known, infer target from context
MUST ASK: no milestone exists (not newsworthy)
```
## Process
Use TodoWrite:
- [ ] Step 1: Identify/bundle the newsworthy angle
- [ ] Step 2: Build the media chain (small → top)
- [ ] Step 3: Build reporter relationships via Twitter/HARO
- [ ] Step 4: Draft pitches using proven templates
- [ ] Step 5: Plan amplification sequence
### Step 1: Identify and Bundle the Newsworthy Angle
**ACTION:** Determine what's actually newsworthy. Strong angles include:
- Funding round (especially with notable investors)
- Product launch with a specific unique hook
- Usage threshold crossed (1M users, 100k searches, etc.)
- Partnership with a recognizable brand
- A stunt or unconventional event (see unconventional PR)
- An industry report or data set only you have
**Bundle smaller announcements.** Jason Kincaid's advice: don't pitch small milestones individually if they can be combined. "Launched feature X" is weak. "Launched feature X + hit 10k users + signed partnership with Y" is strong.
The **emotional angle test**: ask "will this elicit an emotion in readers beyond satisfaction?" Satisfaction is a non-viral emotion. Stories that make readers share need to produce surprise, delight, outrage, or curiosity.
**WHY:** Reporters receive 50+ pitches daily. The first filter is "is this actually a story?" Bundled, emotionally-engaging milestones clear the filter. Single-milestone pitches get ignored. This isn't about hype — it's about giving the reporter enough material to write an interesting article.
**IF** no angle emerges → delay PR, build more milestones first, or pivot to targeting blogs for content-led coverage.
### Step 2: Build the Media Chain (Small → Top)
**ACTION:** Stories filter UP the media chain. Small blogs → TechCrunch → New York Times. Start small, not at the top.
Identify the chain for your category:
- **Level 1 (entry):** Hacker News, Reddit, Product Hunt, niche industry blogs, HARO responses
- **Level 2 (mid-tier):** TechCrunch, The Verge, Wired, industry publications
- **Level 3 (top-tier):** NYT, WSJ, mainstream TV, national podcasts
Target Level 1 first. Top outlets (Level 2-3) often pick up stories from Level 1. DuckDuckGo's Time Magazine feature came via a Twitter relationship with a reporter who then included DDG in a Top 50 list — not via a cold pitch to Time.
**WHY:** Cold-pitching top outlets has near-zero success rate. Most top reporters scan Hacker News, Reddit, and small blogs looking for stories. Starting at Level 1 puts the story where top reporters are already looking. This is how stories naturally filter up — respecting the mechanic dramatically increases success.
### Step 3: Build Reporter Relationships
**ACTION:** Before pitching, identify and engage reporters who cover your category. Twitter is the easiest channel — many reporters have surprisingly few followers and engage with thoughtful replies.
Tactics:
- Follow reporters who cover your space
- Reply to their tweets with genuine context (not pitches)
- Respond to HARO (Help A Reporter Out) queries — this creates mentions and warm introductions
- Bookmark reporters' email addresses before you need them
**WHY:** Cold pitches to strangers have 1-2% response rates. Pitches from people a reporter recognizes from prior Twitter interactions have dramatically higher response rates. The relationship doesn't need to be deep — recognition alone is often enough. HARO is a fast path to a first mention, which then becomes social proof for the next outreach.
**IF** there's no time to build relationships organically → HARO is the fastest substitute. Answer 3-5 relevant queries weekly.
### Step 4: Draft Pitches Using Proven Templates
**ACTION:** Use one of the two templates from [references/pitch-templates.md](references/pitch-templates.md):
1. **Direct pitch:** Subject line with exclusive hook, short paragraphs (hook + product + demo link + exclusive offer), direct contact info at bottom.
2. **Ryan Holiday template:** Subject "Quick question", reference their prior work, tease the exclusive, give specific results ("25,000 paying customers in 2 months"), ask for their process.
Critical criteria for any pitch:
- Short — reporters scan, don't read
- Emotional hook — not "we built a product"
- Concrete specifics — numbers, names, dates
- One clear angle — not 3 competing ones
- Exclusive offer when possible (first access, embargo, data)
Run the **6 PR mistakes check** — see [references/pr-mistakes.md](references/pr-mistakes.md).
**WHY:** Pitch format matters more than most founders realize. The difference between a 50-word pitch and a 500-word pitch is a 10x response rate difference. Templates prevent founders from writing the "wall of text" mistake. The mistakes check prevents the most common failure modes (wall of text, bad timing, no emotional angle, PR firm via, unclear launch timing, bundling failures).
### Step 5: Plan the Amplification Sequence
**ACTION:** Coverage is step 1. Amplification is what turns coverage into traction. For each piece of coverage that lands:
1. **Submit to community sites:** Hacker News, Reddit, Product Hunt, Slashdot (category-appropriate), Digg
2. **Share on social:** Twitter, LinkedIn, Facebook, with founder personal accounts amplifying
3. **Pay to boost:** Run social ads pointing to the coverage page (often cheaper than ads pointing to landing pages)
4. **Email your list:** Point subscribers to the coverage
5. **Contact tier 2 reporters:** Share the coverage as evidence that the story has traction, invite follow-up
Write the amplification plan to `pr-amplification.md`.
**WHY:** A TechCrunch feature sends traffic for 24-48 hours. Amplification extends the half-life and creates the chain reaction that drives stories up to top-tier outlets. Founders who skip amplification get coverage but not the compounding effect coverage enables.
## Inputs
- Newsworthy milestone or bundled announcement
- Target audience
- Media chain for the category
- Reporter contact list (or plan to build one)
## Outputs
Four markdown files:
1. **`pr-angle.md`** — The story, bundled milestones, emotional hook
2. **`pr-media-chain.md`** — Target outlets by tier
3. **`pr-pitches.md`** — Draft pitches (direct + Ryan Holiday variants)
4. **`pr-amplification.md`** — Post-coverage amplification sequence
## Key Principles
- **Stories filter UP the media chain.** Don't start at the top. WHY: Top reporters get their ideas from small blogs. Starting at the top means cold-pitching someone who doesn't know you. Starting small means your story shows up where top reporters are already looking.
- **Bundle, don't drip.** One big announcement beats five small ones. WHY: Reporters want material. A bundled announcement gives them enough for a real article. Drip announcements get ignored individually.
- **Emotional angle trumps feature list.** Reporters need readers to share the story. Shares come from emotion, not features. WHY: "Satisfaction is a non-viral emotion." Stories worth sharing produce surprise, outrage, delight, or curiosity.
- **Founders pitch better than PR firms at early stage.** Most reporters ignore PR firm pitches. Founder pitches are more personal and show the founder cares. WHY: PR firms cost money and produce lower response rates for early-stage companies. Save the money, do it yourself, and learn the skill.
- **Amplification is mandatory.** Coverage without amplification is wasted potential. WHY: A single piece of coverage produces 24-48 hours of attention. Amplification extends it by weeks and creates the chain reaction to top-tier outlets.
- **Twitter is the reporter relationship channel.** Many reporters have accessible Twitter follower counts. WHY: LinkedIn and email are crowded. Twitter engagement is casual enough that reporters actually read replies. A month of thoughtful replies beats 50 cold emails.
## Examples
**Scenario: B2B SaaS product launch**
Trigger: "We're launching our analytics tool in 4 weeks. Want TechCrunch coverage. What should we do?"
Process: (1) Bundle milestones: launch + seed funding + 3 pilot customers = one big story. (2) Media chain: Product Hunt launch, Hacker News post, targeted tier-1 analytics blogs → tier-2 TechCrunch/VentureBeat → tier-3 coverage unlikely for early-stage. (3) Relationships: 4 weeks isn't enough to build organic relationships, so HARO + Twitter engagement with 5 reporters who cover analytics. (4) Pitches: direct pitch template, emphasize exclusive access, specific pilot customer results. (5) Amplification: day-of Hacker News + Product Hunt submission, founder Twitter thread, paid social boost to coverage URL.
Output: Week-by-week PR plan with pitch drafts, specific reporters, and amplification checklist.
**Scenario: Previous PR attempts failed**
Trigger: "We sent 30 pitches to TechCrunch reporters last month and got zero responses. What's wrong?"
Process: (1) Diagnose: cold-pitching top outlets directly is the most common PR mistake. Show the media chain — stories filter up, not down. (2) Review the pitches — apply the 6 mistakes check. Usually at least 3 apply (wall of text, no emotional hook, no clear angle, unclear timing). (3) Re-strategy: start at small blogs and HARO. Build Twitter relationships with 3-5 TechCrunch reporters over 4-6 weeks BEFORE any pitch. (4) Rewrite pitches using the Ryan Holiday template. (5) Amplification plan for when coverage lands.
Output: Diagnosis of why previous approach failed, corrected approach, and new pitch drafts.
**Scenario: Unconventional PR stunt**
Trigger: "We want to do a publicity stunt like Dollar Shave Club's video or Half.com renaming a town. What makes these work?"
Process: (1) Analyze the pattern: unique + surprising + shareable + on-brand. (2) Generate stunt ideas tied to the company's actual product (not random). (3) Evaluate each against emotional angle test. (4) Pick one and plan execution: budget, timing, amplification plan. (5) Have a backup: stunts have binary outcomes (viral or ignored) — have a secondary launch angle ready.
Output: Stunt plan with clear success criteria and backup launch angle.
## References
- For the two proven pitch templates, see [references/pitch-templates.md](references/pitch-templates.md)
- For the 6 PR pitching mistakes, see [references/pr-mistakes.md](references/pr-mistakes.md)
## License
This skill is licensed under [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/).
Source: [BookForge](https://github.com/bookforge-ai/bookforge-skills) — Traction: A Startup Guide to Getting Customers by Gabriel Weinberg and Justin Mares.
## Related BookForge Skills
Install related skills from ClawhHub:
- `clawhub install bookforge-bullseye-channel-selection` — Select PR via Bullseye deliberately
- `clawhub install bookforge-startup-traction-strategy-by-phase` — PR is typically Phase II+
- `clawhub install bookforge-content-and-email-marketing` — Content-led coverage is a parallel path
Or install the full book set from GitHub: [bookforge-skills](https://github.com/bookforge-ai/bookforge-skills)
FILE:references/pitch-templates.md
# PR Pitch Templates
Two proven templates from *Traction* Chapter 7.
## Template 1: Direct Pitch
```
Subject: Exclusive for [Outlet] — [One-line hook]
Hi [Reporter first name],
[One-sentence hook tied to a trend or pain point they cover.]
We're [Company Name], a [short category description]. Today we're [launching/announcing/releasing] [specific thing]. Here's why it matters: [1-2 sentences of emotional/strategic impact].
Specifics:
- [Concrete number or fact]
- [Concrete number or fact]
- [Concrete number or fact]
Demo: [link]
I can give [Outlet] an exclusive [first coverage / early access / embargo until X / data set].
Happy to hop on a 15-minute call this week if useful.
[Founder name]
[Founder title]
[Direct phone] | [Direct email]
```
**Usage notes:**
- Subject line is the most important element. Test multiple subjects.
- Keep paragraphs to 2 sentences max.
- The "exclusive" offer is what distinguishes your pitch from the 50 others the reporter got today.
- Direct contact info at bottom signals you're serious and easy to reach.
## Template 2: Ryan Holiday "Quick Question"
```
Subject: Quick question
Hi [Reporter first name],
I really enjoyed your piece on [specific article, not just "your work"]. The point about [specific insight from their article] resonated because [how it relates to what you're doing].
I have something that might interest your readers. In [timeframe], we've [specific achievement with numbers] — for example, [specific customer/metric/story].
I can give you the exclusive on [what you're offering]. What's your preferred process?
[Founder name]
```
**Usage notes:**
- The "Quick question" subject bypasses reporter spam filters (most PR pitches have promotional subjects).
- Referencing their specific prior work is critical — generic praise ("love your writing") fails.
- Numbers in the second paragraph prove the story is real.
- "What's your preferred process?" puts the ball in their court in a respectful way.
## Template Variants
**HARO response template:**
```
Subject: [Answering your HARO query about X]
Hi [Reporter],
I saw your HARO query about [topic]. I'm [name], founder of [company], and [specific credential that makes you relevant].
Here's my answer: [2-3 sentences of substantive response — not a plug].
Happy to provide more context or specific data if helpful. [Direct contact].
```
**Warm intro request (via investor/advisor):**
```
Subject: Intro request — [Reporter name] at [Outlet]
Hey [Investor/Advisor name],
Would you be willing to introduce me to [Reporter name] at [Outlet]? They cover [category], and we have a story I think would interest them: [one-sentence hook].
I've attached a 1-page overview they can skim. Happy to customize the intro note however works best for you.
Thanks!
[Founder name]
```
## What NOT to Include
- Long company backstories
- Bullet lists of all product features
- Marketing language ("revolutionary", "disruptive", "leading")
- Multiple competing story angles
- Generic "you'd love this" framing without specifics
## Source
Chapter 7 ("Public Relations") of *Traction* by Gabriel Weinberg and Justin Mares. The Ryan Holiday template is attributed to Ryan Holiday's *Trust Me, I'm Lying*, cited in Chapter 7.
FILE:references/pr-mistakes.md
# The 6 PR Pitching Mistakes
Named failure modes from Chapter 7 of *Traction*, based on interviews with reporters like Jason Kincaid (TechCrunch).
## Mistake 1: Wall of Text Emails
**What it looks like:** Long pitch emails with dense paragraphs, backstory, feature lists, and multiple angles.
**Why it fails:** Reporters scan, not read. A wall of text gets filed as "not worth the effort."
**Fix:** 150 words maximum. Short paragraphs. Scannable structure.
## Mistake 2: Unclear Launch Timing
**What it looks like:** Pitches that don't specify when the news is happening. "We're launching soon" or "sometime next month".
**Why it fails:** Reporters work on deadlines. If they can't tell WHEN to publish, they don't publish.
**Fix:** Specific date and time in every pitch. If there's an embargo, state it.
## Mistake 3: No Emotional Angle
**What it looks like:** Feature lists. "We built X that does Y." Neutral descriptions.
**Why it fails:** Readers share articles that make them feel something. Satisfaction is a non-viral emotion. If readers don't share, reporters don't get traffic, and they stop pitching that angle.
**Fix:** Ask "what emotion will readers feel?" Surprise, outrage, delight, curiosity. If the answer is "satisfaction" or nothing, find a different angle.
## Mistake 4: Bundling Failure (Announcement Drip)
**What it looks like:** Pitching small milestones individually instead of bundling them.
**Why it fails:** A reporter covering "we shipped feature X" next week and "we signed partner Y" the week after has been asked to write 2 weak articles instead of 1 strong one. They'll pass on both.
**Fix:** Jason Kincaid's rule: bundle smaller announcements together into one bigger announcement whenever possible.
## Mistake 5: PR Firm Via
**What it looks like:** Early-stage startup hires a PR firm that sends templated pitches on behalf of the company.
**Why it fails:** "Most print reporters we talked to said they ignore almost all pitches from PR firms but do listen to most founders." PR firms are expensive and produce lower response rates at early stage.
**Fix:** Founder-direct pitches. Save the $10k/month PR retainer for a later stage when the scale matters more than the authenticity.
## Mistake 6: No Specific Reference
**What it looks like:** "I love your work!" or "I'm a big fan of your writing." Generic praise.
**Why it fails:** Reporters get 20+ of these daily. Generic praise is worse than no praise — it signals the pitcher hasn't read anything specific.
**Fix:** Reference a specific article, a specific point in that article, and how it connects to your pitch. If you can't do that, don't mention their work.
## The Meta-Pattern
These 6 mistakes converge on one failure: **the pitch doesn't respect the reporter's time**. Every mistake makes more work for the reporter to extract the story. The fix is always the same — do more work upfront so the reporter does less work to decide "yes".
## Additional Failure Patterns (not numbered in book)
- **Pitching outside their beat:** Emailing a consumer tech reporter about a B2B SaaS product.
- **Follow-up spam:** 3 follow-ups in a week to a non-response. One follow-up after 5 days is acceptable.
- **Exclusive inflation:** Offering "exclusive" to 10 reporters simultaneously. One exclusive at a time.
- **Missing news hook:** Pitching without a time-sensitive trigger. "We've been around for 2 years" isn't news.
## Source
Chapter 7 ("Public Relations") of *Traction* by Gabriel Weinberg and Justin Mares.
Guide a startup to set a single quantified traction goal and define the critical path of milestones to reach it. Use whenever a founder needs to prioritize a...
---
name: startup-critical-path-planning
description: "Guide a startup to set a single quantified traction goal and define the critical path of milestones to reach it. Use whenever a founder needs to prioritize activities, set growth goals, define milestones, decide what NOT to work on, plan quarterly/yearly execution, cascade goals to teams, escape the 'too many things to do' trap, or apply a binary on-path/off-path filter to proposed work. Activates on phrases like 'traction goal', 'critical path', 'what should we focus on', 'too many priorities', 'prioritization', 'milestones', 'quarterly planning', 'yearly goals', 'OKRs', 'where should I spend my time', 'what NOT to do', 'company planning', 'goal setting', 'DuckDuckGo', 'roadmap'."
version: 1.0.0
homepage: https://github.com/bookforge-ai/bookforge-skills/tree/main/books/traction/skills/startup-critical-path-planning
metadata: {"openclaw":{"emoji":"📚","homepage":"https://github.com/bookforge-ai/bookforge-skills"}}
status: draft
source-books:
- id: traction
title: "Traction: A Startup Guide to Getting Customers"
authors: ["Gabriel Weinberg", "Justin Mares"]
chapters: [6]
domain: startup-growth
tags: [startup-growth, goal-setting, milestone-planning, startup-execution, prioritization]
depends-on: []
execution:
tier: 1
mode: plan-only
inputs:
- type: document
description: "Company state, candidate milestones, resource constraints, proposed work items"
tools-required: [Read, Write]
tools-optional: [AskUserQuestion]
mcps-required: []
environment: "Plain-text working directory for critical path document"
discovery:
goal: "Produce a written critical path with one traction goal, ordered necessary milestones, and an exclusion log"
tasks:
- "Define one specific quantified traction goal"
- "Enumerate every milestone that might be necessary"
- "Ruthlessly filter to only truly necessary milestones"
- "Order milestones by dependency"
- "Apply binary on-path/off-path filter to proposed work"
- "Cascade company critical path to department/individual paths"
audience:
roles: [startup-founder, growth-marketer, head-of-marketing]
experience: beginner-to-intermediate
when_to_use:
triggers:
- "Founder has too many priorities and can't decide what to cut"
- "Team is busy but growth isn't happening"
- "Planning a quarter or year of execution"
- "Deciding whether a proposed feature/activity is worth doing"
prerequisites: []
not_for:
- "User needs tactical channel advice (use bullseye-channel-selection)"
environment:
codebase_required: false
codebase_helpful: false
works_offline: true
quality:
scores:
with_skill: null
baseline: null
delta: null
tested_at: null
eval_count: 0
assertion_count: 11
iterations_needed: 0
---
# Startup Critical Path Planning
## When to Use
The startup has many possible things to work on and needs a filter for deciding what actually matters. Use this skill when:
- The team is busy but growth isn't moving
- A founder says "we have too many priorities"
- Planning a quarter or year where focus is required
- Evaluating whether a specific proposed feature, hire, or activity is worth doing
- Cascading company-level goals to department or individual work
This is a plan-only skill — the output is a written critical path document, not agent-executed work.
## Context & Input Gathering
### Required Context (must have — ask if missing)
- **Current company state:** stage, resources, biggest constraint
→ Check prompt for: metrics, team size, runway
→ If missing, ask: "What's your current state? Team size, runway, current metrics (users/revenue), biggest bottleneck?"
- **Candidate list of things being considered:** features, hires, activities the founder is weighing
→ Check prompt for: "we're thinking about", "might do X or Y", lists of activities
→ If missing, ask: "What's on your list of things you're considering doing? Include everything, even things you're not sure about."
### Observable Context
- **Prior goals and progress:** has the founder set goals before? How did they go?
- **Existing roadmap or planning docs:** what already exists
### Default Assumptions
- The user is over-loaded with options (the common case)
- Default goal horizon: 6-12 months
- The critical path will cut 50%+ of candidate items
### Sufficiency Threshold
```
SUFFICIENT: company state + candidate items + rough goal horizon known
PROCEED WITH DEFAULTS: company state known, infer candidates from context
MUST ASK: no company state at all
```
## Process
Use TodoWrite:
- [ ] Step 1: Define the single traction goal
- [ ] Step 2: Enumerate candidate milestones (brainstorm wide)
- [ ] Step 3: Filter to only truly necessary milestones
- [ ] Step 4: Order milestones by dependency
- [ ] Step 5: Apply binary on-path filter to ongoing work
- [ ] Step 6: Cascade to departments and individuals
### Step 1: Define the Single Traction Goal
**ACTION:** Help the user articulate ONE specific, quantified traction goal. It must:
- Be specific and measurable (1,000 paying customers, $50k MRR, 100M searches/month)
- Be time-boxed (by when)
- **Change something significant if achieved** — profitability, fundraisability, market leadership, next-phase unlock
If the user proposes multiple goals, force a choice. Multiple top-level goals is the same as no goal. Write the single goal to `critical-path.md`.
**WHY:** Without a single traction goal, every prioritization decision becomes political or vibes-based. With a single goal, every proposed activity gets a binary check: "does this help reach goal X?" Peter Drucker's version: "If you have more than three priorities, you have none." The single goal is the foundation of the entire skill.
**IF** the user can't pick one goal → ask "which of these, if achieved, would most change the trajectory of the business?" Use that.
**IF** the goal feels too ambitious → keep it. Ambition is fine. The test is whether achievement is significant, not whether it's likely.
### Step 2: Enumerate Candidate Milestones (Brainstorm Wide)
**ACTION:** Work backwards from the goal. List every milestone that might plausibly be necessary to reach it. Be generous — include product features, hires, marketing activities, partnerships, funding events, infrastructure, compliance. At this stage, include more than you need.
**WHY:** The brainstorm is explicitly wide because you can't filter what you haven't considered. A tight filter applied to a short list misses the non-obvious milestones. A tight filter applied to a long list catches what matters and cuts what doesn't.
### Step 3: Filter to Only Truly Necessary Milestones
**ACTION:** For each candidate milestone, apply this filter: **"If we skip this milestone, can we still plausibly hit the traction goal?"**
If the answer is "yes, we'd probably still hit it" → the milestone is NOT on the critical path. Move it to an **exclusion log** with a one-sentence reason.
Be ruthless. Most candidate milestones will be cut. The DuckDuckGo example: product features like images and auto-suggest were *excluded* from the critical path for Goal 2 (100M searches/month) even though users were asking for them — because they weren't strictly necessary for that specific goal. Those features came back onto the path for a later goal.
Write the filtered list to `critical-path.md` and the exclusion log to `critical-path-excluded.md`.
**WHY:** The exclusion is where the power of this framework comes from. "Necessary" is a higher bar than "useful". Many things are useful. Very few are necessary. Cutting the merely-useful is what frees resources to execute the necessary. If the exclusion log is short, you didn't cut hard enough — run the filter again.
**IF** the user resists cutting something → ask specifically: "Can we hit the goal without this?" If the answer isn't a definitive no, cut it.
### Step 4: Order Milestones by Dependency
**ACTION:** For the filtered list, identify which milestones must precede which. Build a dependency chain. The first milestone(s) in the chain are what the team should work on RIGHT NOW. Nothing else.
Prefer shortcuts: if a milestone can be satisfied by using an external provider rather than building in-house, take the shortcut. The goal is to reach the traction goal, not to build everything from scratch.
**WHY:** Dependency ordering reveals what actually has to happen first. It's common for teams to work on Milestone 5 while Milestones 1-4 are unfinished, because 5 is more interesting. Ordering forces the team to confront what's actually blocking progress.
### Step 5: Apply the Binary On-Path Filter
**ACTION:** For any ongoing work or newly proposed activity, apply the filter: **"Is this on the critical path?"** Binary answer — yes or no. If no, don't do it. Period.
This includes activities that feel productive: refactoring, technical debt, new features, exploratory research, speculative hires. If they're not on the path to the traction goal, they wait.
**WHY:** The binary filter is the forcing function. It's easy to rationalize off-path work as "important" or "strategic". The filter asks a narrower question: necessary for *this* goal, *right now*? Everything else is a distraction. DuckDuckGo's Gabriel Weinberg built DDG for 6+ years by maintaining this filter — most search startups died because they worked on everything.
**IF** an ongoing activity fails the filter → stop it. Reassign the resources to the first on-path milestone.
**IF** a proposed activity fails the filter → decline it. Queue it for after the current goal is reached.
### Step 6: Cascade to Departments and Individuals
**ACTION:** If the user has teams or direct reports, cascade the critical path down one level. Each team defines its own sub-critical-path aligned to the company goal. Each individual defines their own critical path aligned to the team goal.
Set a weekly review cadence: 1:1s and team meetings include a standing agenda item — "is the work this week on our critical path?"
**WHY:** Company-level critical paths get diluted at the department and individual level if not explicitly cascaded. The cascade ensures that what the founder calls the critical path is what each engineer, marketer, and salesperson is actually working on day-to-day. Weekly review is the accountability mechanism — if the team can't point to on-path work in a 1:1, the path isn't being followed.
## Inputs
- Current company state (metrics, team, runway, constraints)
- Candidate list of work items being considered
- Rough goal horizon (3, 6, 12 months)
## Outputs
Three markdown files:
1. **`critical-path.md`** — The single traction goal, filtered milestones in dependency order, next immediate steps
2. **`critical-path-excluded.md`** — Exclusion log of items considered but cut, with one-line reasons
3. **`critical-path-cascade.md`** *(if applicable)* — Department and individual sub-paths
## Key Principles
- **One goal, not three.** Multiple top-level goals is the same as no goal. Pick one. The one that, if achieved, changes the business trajectory most. WHY: Prioritization is impossible without a single anchor. Any decision can feel important if you're comparing it to vague multi-goal aspirations.
- **Necessary is a higher bar than useful.** Most candidate milestones are useful. Very few are necessary. The filter cuts the merely-useful. WHY: This is where the leverage is. Resources freed from useful-but-not-necessary work are what enable the necessary work to ship on time.
- **The exclusion log matters as much as the path.** Writing down what you're NOT doing, with reasons, is what prevents the cut items from creeping back in. WHY: Without the written exclusion, team members will re-propose cut items every few weeks. The log is a reference point: "we explicitly cut this for this reason."
- **Binary, not gradient.** Work is on the path or off the path. There is no "kind of on the path." Gradient evaluation produces wishy-washy prioritization. WHY: Binary forces a decision. Gradient lets people rationalize anything as "somewhat important."
- **Reassess after every milestone.** Completing a milestone changes what you know. The path that made sense at the start may not be the path from here. WHY: The critical path is not a one-time document. It's a living plan that updates with learning. Static paths become wrong as the world changes.
- **Take the shortcut.** If a milestone can be reached via an external provider, partnership, or existing tool, use that instead of building. WHY: The goal is the traction goal, not the pride of building everything yourself. Shortcuts compress time-to-goal, which is the whole point.
## Examples
**Scenario: Founder with 15 priorities**
Trigger: "We're a 6-person B2B SaaS startup, 3 months from running out of runway. Need to raise a Series A. We're working on: the new dashboard redesign, hiring a VP Marketing, a big feature release, onboarding automation, enterprise SSO, the blog we've been meaning to launch, getting on the Salesforce marketplace, rebuilding our pricing page, and a bunch of other things."
Process: (1) Goal: $30k MRR by month-end — the minimum to make an A story credible. (2) Brainstorm: all the items above plus ~8 more. (3) Filter — for each item ask "does this get us to $30k MRR this month?" Results: onboarding automation YES (converts trials faster), Salesforce marketplace MAYBE (takes too long to ship, move to exclusion), dashboard redesign NO (doesn't acquire customers), VP Marketing hire NO (won't ship this month), blog NO (too slow), pricing page NO, SSO NO (enterprise deals don't close this month). Of 15 items, only 3 survive: onboarding automation, closing 4 active trials that are on the edge, and accelerating one enterprise deal already in flight. (4) Order: close the enterprise deal first (biggest lever), accelerate trial closures, ship onboarding automation last. (5) Filter ongoing work: team was spending 40% of time on dashboard redesign — stop. Reallocate to closing the enterprise deal.
Output: `critical-path.md` with the 3 surviving items, `critical-path-excluded.md` with 12 items and reasons, immediate reallocation plan.
**Scenario: Startup 18 months in, still no focus**
Trigger: "Consumer mobile app, 18 months in, $0 revenue, $400k raised. We have a free app with 20k users. Founders disagree on whether to focus on ads, in-app purchases, or a B2B licensing deal."
Process: (1) Force one goal. Ask: "Which of these, if achieved in 6 months, would most change the trajectory?" — founders agree: first $10k MRR. (2) Brainstorm milestones for each of the 3 paths: ad-supported model, IAP, B2B licensing. (3) Filter: ad-supported model requires 500k+ users (can't hit in 6 months) → excluded. IAP requires product changes + payment infrastructure + marketing test → viable. B2B licensing requires 1 deal closure → viable and fastest. (4) Order: pursue B2B first (single deal = goal), IAP as parallel fallback. (5) Filter ongoing work: team was building ad infrastructure — stop, reassign to B2B outreach.
Output: Clear single goal, decisive cut of ad strategy, parallel B2B+IAP path with B2B as primary.
**Scenario: DuckDuckGo-style long-arc planning**
Trigger: "Privacy-focused product competing with incumbents. We have 10k users. Where do I even start with goals?"
Process: (1) Goal: specific user count that unlocks next phase — "100k monthly active users" as first goal (DDG-style cascade: product/messaging stable → break-even threshold → mainstream adoption). (2) Brainstorm all milestones that might contribute: mobile app, improved messaging, 1 piece of viral PR, API integration with a power user tool, SEO on "privacy" keywords, etc. (3) Filter: mobile app YES (retention driver), SEO on privacy keywords YES (aligned with cause), viral PR YES (one good story could 10x users), API integration MAYBE — moved to exclusion for this phase. (4) Order: SEO foundation first (slowest to compound), then PR preparation, then mobile app launch. (5) Filter proposed features: product team wants to add a new browser extension → apply filter → does this contribute to 100k MAU? Only if it ships in 3 weeks. Otherwise, exclude.
Output: Multi-goal cascade pattern inspired by DuckDuckGo's approach. One current goal with clear milestones. Features that don't serve it are explicitly excluded, reviewable at next goal transition.
## References
- For the DuckDuckGo three-goal cascade case study, see [references/duckduckgo-cascade.md](references/duckduckgo-cascade.md)
## License
This skill is licensed under [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/).
Source: [BookForge](https://github.com/bookforge-ai/bookforge-skills) — Traction: A Startup Guide to Getting Customers by Gabriel Weinberg and Justin Mares.
## Related BookForge Skills
Install related skills from ClawhHub:
- `clawhub install bookforge-bullseye-channel-selection` — The critical path's traction milestones often include channel selection
- `clawhub install bookforge-startup-traction-strategy-by-phase` — The critical path goal should match the startup's current phase
- `clawhub install bookforge-business-development-pipeline` — BD deals are frequently critical path milestones
Or install the full book set from GitHub: [bookforge-skills](https://github.com/bookforge-ai/bookforge-skills)
FILE:references/duckduckgo-cascade.md
# DuckDuckGo Critical Path Cascade
Gabriel Weinberg's account of how DuckDuckGo used the critical path framework across three sequential traction goals over 6+ years.
## The Three Sequential Goals
**Goal 1 (early years, ~2008-2010):** Product and messaging stable enough that users switch as their primary search engine and stick. This was essentially a product-market-fit milestone expressed as a retention threshold.
**Goal 2 (~2011-2013):** 100 million searches per month. This was the break-even threshold — enough volume to monetize sustainably. Roughly 2 years of work.
**Goal 3 (~2014+):** 1% of the general search market. This was the mainstream-adoption threshold — visible enough that media, competitors, and users treated DuckDuckGo as a credible search engine. Another ~2 years.
## The Filtered Milestones for Goal 2
Working backwards from "100 million searches/month":
- Faster page speed (retention + perception)
- Compelling mobile offering (mobile was rising share of searches)
- Broadcast TV coverage (biggest single-event traffic driver for search engines)
## The Exclusion Log for Goal 2
Features that users kept asking for but were EXCLUDED from the critical path for Goal 2:
- **Image search** — users wanted it, but it wasn't strictly necessary to reach 100M searches/month at the current user base. Deferred.
- **Auto-suggest** — similar reasoning. Useful but not necessary for the specific goal.
- **Articles on tech news sites** — doesn't move the needle at current scale; a TechCrunch feature sends X visitors, but at DuckDuckGo's volume, X is rounding error.
These features came BACK onto the critical path for Goal 3 (1% search market share), because mainstream adoption has less tolerance for missing basic features than early-adopter usage does.
## The Key Insights
1. **The same feature can be on-path for one goal and off-path for another.** Image search was off-path for Goal 2, on-path for Goal 3. The goal determines the path, not vice versa.
2. **Goals take years.** DuckDuckGo's goals each took approximately 2 years to achieve. This is not unusual. Ambitious traction goals are measured in years, not months.
3. **The founder's job is to hold the line.** Gabriel's role for those years was largely to say "no" to everything not on the current goal's critical path. Most search startups died because they couldn't hold that line.
4. **Patient differentiation can take 4+ years to pay off.** DuckDuckGo's privacy differentiation existed from 2009 but didn't become mainstream until the 2013 NSA leaks. The critical path didn't promise quick returns — it promised that if the milestones were hit, the company would be positioned for whatever external catalyst eventually came.
## The Cascade Pattern
Each goal enables the next. Goal 1 (product-market fit) creates the conditions for Goal 2 (sustainability). Goal 2 creates the conditions for Goal 3 (market share). You don't pick Goal 3 as the initial goal because you can't reach it without Goal 2 first.
## Source
Chapter 5 ("Critical Path") of *Traction* by Gabriel Weinberg and Justin Mares. Weinberg is the author of the book and founder of DuckDuckGo, so the case study comes from direct experience.
ITIL 5 Manager - Elite IT Service Management Advisor specializing in ITSM, FinOps, and IT governance using ITIL 5 DPSM framework.
---
name: li_itil_manager
description: ITIL 5 Manager - Elite IT Service Management Advisor specializing in ITSM, FinOps, and IT governance using ITIL 5 DPSM framework.
risk: safe
source: community
date_added: "2026-04-27"
triggers:
- "itil manager"
- "itil5"
- "itil 5"
- "it service management"
- "itsm advice"
- "service desk"
- "incident management"
- "problem management"
- "change management"
- "itil advisor"
- "itil consultant"
- "service lifecycle"
- "itil framework"
---
# ITIL 5 Manager (li_itil_manager)
## Purpose
A comprehensive ITIL 5 advisor combining Digital Product and Service Management (DPSM) with modern IT management practices. Provides strategic and operational guidance for IT managers, service desk leads, and digital leaders.
## When to Use
- Need ITIL 5 implementation guidance
- Managing IT service delivery and support
- Building or improving ITSM processes
- Implementing FinOps in IT operations
- Bridging IT and business communication
## Core Capabilities
- **ITIL 5 DPSM:** Digital Product and Service Management approach
- **Service Value Chain:** Plan, Engage, Design & Transform, Obtain/Build, Deliver & Support, Improve
- **Process Optimization:** Incident, Problem, Change, Knowledge, and Service Request Management
- **Executive Communication:** C-level storytelling and ROI reporting
- **FinOps Integration:** Connecting service cost to business value
## ITIL 5 Guiding Principles
1. Focus on value
2. Progress iteratively
3. Collaborate and promote visibility
4. Think and work holistically
5. Keep it simple
6. Optimize and automate
7. Everything is a relationship
## Mandatory Instructional Protocol (IMPORTANT)
**Before providing extended insights, case studies, or detailed examples of applicability, you MUST ask for user consent.**
* **Protocol:** Provide the core answer/solution first. Then, conclude with: *"Would you like deep insights into the applicability of this solution or a real-world resolution example?"*
* **Action:** Only provide the extra depth if the user explicitly confirms.
## Expert Instructions
### 1. Service Strategy & Value Co-creation
- Treat all IT services as Digital Products
- Define Service Offerings that support customer outcomes
- Establish Service Relationships with stakeholders
- Map service value to business outcomes
### 2. Service Design & Transformation
- Design service offerings that meet customer needs
- Define service levels and KPIs
- Create service catalogs
- Implement service quality metrics
### 3. Service Transition
- Manage changes effectively
- Implement release management
- Knowledge management practices
- Service validation and testing
### 4. Service Operation
- Incident Management lifecycle
- Problem Management for root cause
- Request Fulfilment
- Event Management and monitoring
- Access Management
### 5. Continual Improvement
- 7-step improvement model
- Process measurement and metrics
- CSI register for improvements
- Value realization tracking
### 6. FinOps for IT Services
- Connect spend to service value
- Unit economics for services
- Right-sizing and optimization
- Cloud and AI cost management
### 7. Communication Bridge
- Executive reporting with SIR (Situation-Impact-Resolution)
- Stakeholder management
- ROI-focused narratives
## Applicability Scenarios
- Implementing ITIL 5 from scratch
- Migrating from ITIL v4 to ITIL 5
- Incident escalation and resolution
- Change management best practices
- Service desk optimization
- IT budget and cost optimization
## References
- [IT Manager's Handbook](./references/it-manager-handbook.md)
- [Management Scenarios](./examples/management-scenarios.md)
- [IT Frameworks Guide](./references/it-management-frameworks.md)
## Limitations
- Strategic advisory only, not legal/financial auditing
- Advice quality depends on provided context
- Always verify against local regulations
FILE:README.de.md
# ITIL 5 Manager (li_itil_manager)
Elite IT-Service-Management-Berater, spezialisiert auf ITSM, FinOps und IT-Governance unter Verwendung des ITIL 5 DPSM-Frameworks.
## Überblick
Ein umfassender ITIL 5-Berater, der Digitale Produkt- und Service-Management (DPSM) mit modernen IT-Management-Praktiken kombiniert. Bietet strategische und operative Anleitung für IT-Manager, Service-Desk-Leiter und digitale Führungskräfte.
## Funktionen
- **ITIL 5 DPSM:** Digitales Produkt- und Service-Management-Ansatz
- **Service-Wertschöpfungskette:** Planen, Engagieren, Gestalten & Transformieren, Beschaffen/Bauen, Liefern & Unterstützen, Verbessern
- **Prozessoptimierung:** Incident-, Problem-, Änderungs-, Wissens- und Serviceanfrage-Management
- **Führungskommunikation:** C-Level Storytelling und ROI-Berichterstattung
- **FinOps-Integration:** Verbindung von Servicekosten mit Geschäftswert
## ITIL 5 Leitprinzipien
1. Auf Wert fokussieren
2. Iterativ vorgehen
3. Zusammenarbeiten und Sichtbarkeit fördern
4. Ganzheitlich denken und arbeiten
5. Einfachheit bewahren
6. Optimieren und automatisieren
7. Alles ist Beziehung
## Verwendung
Auslösen mit Schlüsselwörtern:
- `itil manager`
- `itil5` / `itil 5`
- `it service management`
- `incident management`
- `problem management`
- `change management`
- `service desk`
## Struktur
```
li_itil_manager/
├── SKILL.md
├── README.md
├── references/
│ ├── it-manager-handbook.md
│ └── it-management-frameworks.md
└── examples/
└── management-scenarios.md
```
## Version
- Aktuell: 1.0.0
- Datum: 2026-04-27
- Framework: ITIL 5 DPSM
## Lizenz
Community-Skill - MIT
FILE:README.en.md
# ITIL 5 Manager (li_itil_manager)
Elite IT Service Management Advisor specializing in ITSM, FinOps, and IT governance using ITIL 5 DPSM framework.
## Overview
A comprehensive ITIL 5 advisor combining Digital Product and Service Management (DPSM) with modern IT management practices. Provides strategic and operational guidance for IT managers, service desk leads, and digital leaders.
## Features
- **ITIL 5 DPSM:** Digital Product and Service Management approach
- **Service Value Chain:** Plan, Engage, Design & Transform, Obtain/Build, Deliver & Support, Improve
- **Process Optimization:** Incident, Problem, Change, Knowledge, and Service Request Management
- **Executive Communication:** C-level storytelling and ROI reporting
- **FinOps Integration:** Connecting service cost to business value
## ITIL 5 Guiding Principles
1. Focus on value
2. Progress iteratively
3. Collaborate and promote visibility
4. Think and work holistically
5. Keep it simple
6. Optimize and automate
7. Everything is a relationship
## Usage
Trigger with keywords:
- `itil manager`
- `itil5` / `itil 5`
- `it service management`
- `incident management`
- `problem management`
- `change management`
- `service desk`
## Structure
```
li_itil_manager/
├── SKILL.md # Skill definition
├── README.md # This file
├── references/
│ ├── it-manager-handbook.md
│ └── it-management-frameworks.md
└── examples/
└── management-scenarios.md
```
## Version
- Current: 1.0.0
- Date: 2026-04-27
- Framework: ITIL 5 DPSM
## License
Community skill - MIT
## Author
ClawHub Community
FILE:README.es.md
# ITIL 5 Manager (li_itil_manager)
Asesor élite de gestión de servicios de TI especializado en ITSM, FinOps y gobernanza de TI utilizando el marco ITIL 5 DPSM.
## Descripción
Un asesor completo de ITIL 5 que combina la Gestión de Productos y Servicios Digitales (DPSM) con prácticas modernas de gestión de TI. Proporciona orientación estratégica y operativa para gerentes de TI, líderes de mesa de servicio y líderes digitales.
## Características
- **ITIL 5 DPSM:** Enfoque de Gestión de Productos y Servicios Digitales
- **Cadena de Valor del Servicio:** Planificar, Involucrar, Diseñar y Transformar, Obtener/Construir, Entregar y Apoyar, Mejorar
- **Optimización de Procesos:** Gestión de Incidentes, Problemas, Cambios, Conocimiento y Solicitudes de Servicio
- **Comunicación Ejecutiva:** Narrativa para C-level e informes ROI
- **Integración FinOps:** Conectar costo del servicio con valor empresarial
## Principios Guia ITIL 5
1. Enfocarse en el valor
2. Progresar iterativamente
3. Colaborar y promover visibilidad
4. Pensar y trabajar holísticamente
5. Mantenerlo simple
6. Optimizar y automatizar
7. Todo es una relación
## Uso
Dispara con palabras clave:
- `itil manager`
- `itil5` / `itil 5`
- `it service management`
- `incident management`
- `problem management`
- `change management`
- `service desk`
## Estructura
```
li_itil_manager/
├── SKILL.md
├── README.md
├── references/
│ ├── it-manager-handbook.md
│ └── it-management-frameworks.md
└── examples/
└── management-scenarios.md
```
## Versión
- Actual: 1.0.0
- Fecha: 2026-04-27
- Framework: ITIL 5 DPSM
## Licencia
Habilidad comunitaria - MIT
FILE:README.fr.md
# ITIL 5 Manager (li_itil_manager)
Conseiller Elite en Gestion des Services IT spécialisé en ITSM, FinOps et Gouvernance IT utilisant le framework ITIL 5 DPSM.
## Aperçu
Un conseiller complet ITIL 5 combinant la Gestion des Produits et Services Numériques (DPSM) avec les pratiques modernes de gestion IT. Fournit des orientations stratégiques et opérationnelles pour les responsables IT, les responsables du service desk et les leaders numériques.
## Caractéristiques
- **ITIL 5 DPSM:** Approche de Gestion des Produits et Services Numériques
- **Chaîne de Valeur du Service:** Planifier, Engager, Concevoir et Transformer, Obtenir/Construire, Livrer et Supporter, Améliorer
- **Optimisation des Processus:** Gestion des Incidents, Problèmes, Changements, Connaissances et Demandes de Service
- **Communication Exécutive:** Storytelling pour C-level et rapports ROI
- **Intégration FinOps:** Connecter le coût du service à la valeur métier
## Principes Directeurs ITIL 5
1. Se concentrer sur la valeur
2. Progresser de manière itérative
3. Collaborer et promouvoir la visibilité
4. Penser et travailler holistiquement
5. Garder simple
6. Optimiser et automatiser
7. Tout est une relation
## Utilisation
Déclencher avec mots-clés:
- `itil manager`
- `itil5` / `itil 5`
- `it service management`
- `incident management`
- `problem management`
- `change management`
- `service desk`
## Structure
```
li_itil_manager/
├── SKILL.md
├── README.md
├── references/
│ ├── it-manager-handbook.md
│ └── it-management-frameworks.md
└── examples/
└── management-scenarios.md
```
## Version
- Actuelle: 1.0.0
- Date: 2026-04-27
- Framework: ITIL 5 DPSM
## Licence
Compétence communautaire - MIT
FILE:README.ja.md
# ITIL 5 Manager (li_itil_manager)
ITSM、FinOps、ITガバナンスにおけるエリートITサービス管理アドバイザー。ITIL 5 DPSMフレームワークを専門とします。
## 概要
デジタルプロダクト&サービス管理(DPSM)と最新のIT管理プラクティスを組み合わせた総合的なITIL 5アドバイザー。ITマネージャー、サービスデスクリーダー、デジタルリーダーへの戦略的および運用ガイダンスを提供します。
## 機能
- **ITIL 5 DPSM:** デジタルプロダクト&サービス管理アプローチ
- **サービスバリューチェーン:** プラン、エンゲージ、設計・変革、取得・構築、配信・支援、改善
- **プロセス最適化:** インシデント、問題、変更、ナレッジ、サービスリクエスト管理
- **エグゼクティブコミュニケーション:** CレベルストーリーテリングとROIレポート
- **FinOps統合:** サービスコストとビジネス価値の連携
## ITIL 5指導原則
1. 価値に焦点を当てる
2. 反復的に進捗する
3. コラボレーションと可視性の促進
4. holisticallyに考える
5. シンプルに保つ
6. 最適化と自動化
7. すべてが関係である
## 使用方法
以下のキーワードでトリガー:
- `itil manager`
- `itil5` / `itil 5`
- `it service management`
- `incident management`
- `problem management`
- `change management`
- `service desk`
## 構造
```
li_itil_manager/
├── SKILL.md
├── README.md
├── references/
│ ├── it-manager-handbook.md
│ └── it-management-frameworks.md
└── examples/
└── management-scenarios.md
```
## バージョン
- 現行: 1.0.0
- 日付: 2026-04-27
- フレームワーク: ITIL 5 DPSM
## ライセンス
コミュニティスキル - MIT
FILE:README.ko.md
# ITIL 5 Manager (li_itil_manager)
ITSM, FinOps 및 IT 거버넌스를 전문으로 하는 엘리트 IT 서비스 관리 자문관 ITIL 5 DPSM 프레임워크를 사용합니다.
## 개요
디지털 제품 및 서비스 관리(DPSM)와 최신 IT 관리 관행을 결합한 종합 ITIL 5 자문관입니다. IT 관리자, 서비스 데스크 리더 및 디지털 리더에게 전략적 및 운영 지침을 제공합니다.
## 기능
- **ITIL 5 DPSM:** 디지털 제품 및 서비스 관리 접근 방식
- **서비스 가치 사슬:** 계획, 참여, 설계 및 전환, 획득/구축, 제공 및 지원, 개선
- **프로세스 최적화:** 인시던트, 문제, 변경, 지식 및 서비스 요청 관리
- **임원 커뮤니케이션:** C 레벨 스토리텔링 및 ROI 보고
- **FinOps 통합:** 서비스 비용을 비즈니스 가치에 연결
## ITIL 5 지침 원칙
1. 가치에 집중
2. 반복적으로 진행
3. 협업 및 가시성 촉진
4. 전체적으로 생각하고 작업
5. 단순하게 유지
6. 최적화 및 자동화
7. 모든 것은 관계
## 사용 방법
키워드로 트리거:
- `itil manager`
- `itil5` / `itil 5`
- `it service management`
- `incident management`
- `problem management`
- `change management`
- `service desk`
## 구조
```
li_itil_manager/
├── SKILL.md
├── README.md
├── references/
│ ├── it-manager-handbook.md
│ └── it-management-frameworks.md
└── examples/
└── management-scenarios.md
```
## 버전
- 현재: 1.0.0
- 날짜: 2026-04-27
- 프레임워크: ITIL 5 DPSM
## 라이선스
커뮤니티 스킬 - MIT
FILE:README.md
# ITIL 5 Manager (li_itil_manager)
Elite IT Service Management Advisor specializing in ITSM, FinOps, and IT governance using ITIL 5 DPSM framework.
## Overview
A comprehensive ITIL 5 advisor combining Digital Product and Service Management (DPSM) with modern IT management practices. Provides strategic and operational guidance for IT managers, service desk leads, and digital leaders.
## Features
- **ITIL 5 DPSM:** Digital Product and Service Management approach
- **Service Value Chain:** Plan, Engage, Design & Transform, Obtain/Build, Deliver & Support, Improve
- **Process Optimization:** Incident, Problem, Change, Knowledge, and Service Request Management
- **Executive Communication:** C-level storytelling and ROI reporting
- **FinOps Integration:** Connecting service cost to business value
## ITIL 5 Guiding Principles
1. Focus on value
2. Progress iteratively
3. Collaborate and promote visibility
4. Think and work holistically
5. Keep it simple
6. Optimize and automate
7. Everything is a relationship
## Usage
Trigger with keywords:
- `itil manager`
- `itil5` / `itil 5`
- `it service management`
- `incident management`
- `problem management`
- `change management`
- `service desk`
## Structure
```
li_itil_manager/
├── SKILL.md # Skill definition
├── README.md # This file
├── references/
│ ├── it-manager-handbook.md # IT Management Handbook
│ └── it-management-frameworks.md
└── examples/
└── management-scenarios.md
```
## Version
- Current: 1.0.0
- Date: 2026-04-27
- Framework: ITIL 5 DPSM
## License
Community skill - MIT
## Author
ClawHub Community
FILE:README.pt.md
# ITIL 5 Manager (li_itil_manager)
Assessor Élite de Gerenciamento de Serviços de TI especializado em ITSM, FinOps e Governança de TI usando o framework ITIL 5 DPSM.
## Visão Geral
Um assessor abrangente de ITIL 5 combinando Gerenciamento de Produtos e Serviços Digitais (DPSM) com práticas modernas de gestão de TI. Fornece orientação estratégica e operacional para gerentes de TI, líderes de service desk e líderes digitais.
## Recursos
- **ITIL 5 DPSM:** Abordagem de Gerenciamento de Produtos e Serviços Digitais
- **Cadeia de Valor de Serviço:** Planejar, Engajar, Projetar e Transformar, Obter/Construir, Entregar e Suportar, Melhorar
- **Otimização de Processos:** Gerenciamento de Incidentes, Problemas, Mudanças, Conhecimento e Solicitações de Serviço
- **Comunicação Executiva:** Storytelling para C-level e relatórios de ROI
- **Integração FinOps:** Conectar custo de serviço com valor de negócio
## Princípios Guia ITIL 5
1. Focar no valor
2. Progredir iterativamente
3. Colaborar e promover visibilidade
4. Pensar e trabalhar holísticamente
5. Manter simples
6. Otimizar e automatizar
7. Tudo é uma relação
## Uso
Dispare com palavras-chave:
- `itil manager`
- `itil5` / `itil 5`
- `it service management`
- `incident management`
- `problem management`
- `change management`
- `service desk`
## Estrutura
```
li_itil_manager/
├── SKILL.md
├── README.md
├── references/
│ ├── it-manager-handbook.md
│ └── it-management-frameworks.md
└── examples/
└── management-scenarios.md
```
## Versão
- Atual: 1.0.0
- Data: 2026-04-27
- Framework: ITIL 5 DPSM
## Licença
Habilidade comunitária - MIT
FILE:README.zh-CN.md
# ITIL 5 Manager (li_itil_manager)
精英IT服务管理顾问,专注于使用ITIL 5 DPSM框架的ITSM、FinOps和IT治理。
## 概述
一个综合性的ITIL 5顾问,结合数字产品和服务管理(DPSM)与现代IT管理实践。为IT经理、服务台负责人和数字领导者提供战略和运营指导。
## 功能特点
- **ITIL 5 DPSM:** 数字产品和服务管理方法
- **服务价值链:** 计划、参与、设计与转型、获取/构建、交付与支持、改进
- **流程优化:** 事件、问题、变更、知识和 服务请求管理
- **高管沟通:** C级别故事化叙述和ROI报告
- **FinOps集成:** 连接服务成本与业务价值
## ITIL 5指导原则
1. 聚焦价值
2. 迭代推进
3. 协作并提升透明度
4. 全局思考和工作
5. 保持简洁
6. 优化和自动化
7. 一切都是关系
## 使用方法
使用以下关键词触发:
- `itil manager`
- `itil5` / `itil 5`
- `it service management`
- `incident management`
- `problem management`
- `change management`
- `service desk`
## 目录结构
```
li_itil_manager/
├── SKILL.md # Skill定义
├── README.md # 说明文档
├── references/
│ ├── it-manager-handbook.md # IT管理手册
│ └── it-management-frameworks.md
└── examples/
└── management-scenarios.md
```
## 版本信息
- 当前版本: 1.0.0
- 日期: 2026-04-27
- 框架: ITIL 5 DPSM
## 许可证
社区技能 - MIT
## 作者
ClawHub 社区
FILE:examples/management-scenarios.md
# ITIL Manager Scenarios
Common real-world ITIL management scenarios with expert-driven advice.
## Scenario 1: Implementing ITIL 5 from Scratch
**Situation:** Organization wants to adopt ITIL 5 DPSM approach.
**Expert Advice:**
- Start with ITIL 5 Guiding Principles - focus on value and collaboration
- Map current services to Digital Products
- Identify service relationships with stakeholders
- Implement Service Value Chain activities progressively
- Use "Progress iteratively" - start small, iterate
- **Question:** Would you like deep insights into implementation steps?
## Scenario 2: Major Incident Management
**Situation:** Critical system outage affecting business operations.
**Expert Advice (ITIL 5 Incident Management):**
- **Detect & Log:** Immediate incident creation
- **Categorize & Prioritize:** Impact and urgency assessment
- **Diagnose:** Technical investigation
- **Resolve:** Fix and restore service
- **Close:** Formal closure with customer sign-off
- Communication: Use SIR (Situation-Impact-Resolution) for updates
- Post-incident: Blameless review within 24 hours
- **Question:** Would you like deep insights into escalation procedures?
## Scenario 3: Change Management
**Situation:** Need to deploy major infrastructure change with minimum risk.
**Expert Advice (ITIL 5 Change Management):**
- **RFC:** Complete Request for Change with justification
- **Assessment:** Evaluate risk, impact, and cost
- ** CAB Review:** Present to Change Advisory Board
- **Planning:** Define rollback procedures
- **Implementation:** Execute in change window
- **Review:** Post-implementation review
- Follow "Think and work holistically" - consider all dependencies
- **Question:** Would you like deep insights into risk assessment?
## Scenario 4: Service Desk Optimization
**Situation:** High volume of tickets, low customer satisfaction.
**Expert Advice:**
- Analyze ticket categories and root causes
- Implement Service Request Management for repetitive tasks
- Build Knowledge Base for self-service
- Use "Optimize and automate" - automate routine requests
- Track FCR (First Contact Resolution) and CSAT metrics
- **Question:** Would you like deep insights into KPI optimization?
## Scenario 5: Problem Management
**Situation:** Recurring incidents from underlying root cause.
**Expert Advice:**
- Use Problem Management to find root cause
- Create Problem Record linked to related Incidents
- Analyze trends usingKeppler Incident Analysis
- Implement permanent fix through Change Management
- Update Knowledge Base with workarounds
- **Question:** Would you like deep insights into problem analysis techniques?
## Scenario 6: IT Budget and Cost Optimization
**Situation:** Need to optimize IT spend while maintaining service quality.
**Expert Advice (FinOps + ITIL 5):**
- Map service costs using value chain activities
- Identify under-utilized services
- Implement consumption-based pricing where possible
- Use "Focus on value" - cut low-value services
- Track Cost per Service and Cost per User metrics
- **Question:** Would you like deep insights into FinOps practices?
---
*Reference scenarios for ITIL 5 Manager skill.*
FILE:references/it-management-frameworks.md
# IT Management Frameworks Guide (2026)
Comprehensive guide for aligning IT with business objectives using world-class frameworks.
## 1. IT Governance & Strategy
* **COBIT (Control Objectives for Information and Related Technologies):** Focused on IT corporate governance. Helps align technology with business strategic objectives, manage risks, and ensure regulatory compliance.
* **ISO/IEC 38500:** Provides basic principles for efficient, effective, and acceptable use of IT within organizations, focusing on director responsibilities.
## 2. IT Service Management (ITSM) - ITIL 5
* **ITIL (Information Technology Infrastructure Library):** The global standard for service management. ITIL 5 focuses on the service lifecycle and Digital Product and Service Management (DPSM).
* **ITIL 5 DPSM (Digital Product and Service Management):** New approach treating all IT services as digital products, emphasizing continuous value creation.
* **ISO/IEC 20000:** International standard for IT service management, serving as a basis for organizational quality certifications.
* **MOF (Microsoft Operations Framework):** Adaptation of ITIL practices focused specifically on Microsoft technology ecosystems.
## 3. Enterprise Architecture
* **TOGAF (The Open Group Architecture Framework):** Specialized in designing, planning, and implementing enterprise architectures to ensure technology foundation supports business scalability.
## 4. Project Management & Agile
* **PMBOK (Project Management Body of Knowledge):** Guide for traditional project management (Waterfall/Predictive).
* **PRINCE2 (Projects in Controlled Environments):** Structured method focused on control, organization, and ongoing business justification.
* **Scrum / Agile:** Frameworks for complex project management with focus on rapid, iterative, adaptive delivery.
* **SAFe (Scaled Agile Framework):** Methodology for scaling agile practices in large organizations.
## 5. Security & Risk
* **NIST Cybersecurity Framework:** Guidelines for reducing cybersecurity risks in critical infrastructure and government.
* **ISO/IEC 27001:** International standard for implementing an Information Security Management System (ISMS).
* **FAIR (Factor Analysis of Information Risk):** Quantitative model for understanding and measuring information risk in financial terms.
## 6. Modern Operations & Innovation
* **DevOps Framework:** Full integration between development and operations to accelerate value delivery cycle.
* **SRE (Site Reliability Engineering):** Google's approach using software engineering to solve operations and scalability problems.
* **AIOps:** Use of Artificial Intelligence and Machine Learning to automate incident detection and optimize operational performance.
## Framework Selection Guide
| Need | Recommended Framework |
|------|----------------------|
| IT Governance | COBIT |
| Service Management | ITIL 5 DPSM |
| Enterprise Architecture | TOGAF |
| Traditional Projects | PMBOK/PRINCE2 |
| Agile Projects | Scrum/SAFe |
| Security | ISO 27001/NIST |
| Operations Optimization | DevOps/SRE/AIOps |
FILE:references/it-manager-handbook.md
# IT Manager Handbook (2026 Edition) - ITIL 5 Edition
A strategic reference for managing modern digital technical organizations with ITIL 5 foundation.
## 1. Leadership in a VUCA World
IT Management is now characterized by Volatility, Uncertainty, Complexity, and Ambiguity.
- **Adaptive Strategy:** Move from rigid 5-year plans to "Rolling 12-month Value Roadmaps."
- **Psychological Safety:** The foundation of high-performance engineering teams. Encourage blameless post-mortems and celebrate "smart failures."
- **ITIL 5 Guiding Principles:** Apply "Progress iteratively," "Collaborate and promote visibility," and "Think and work holistically" in leadership approach.
## 2. FinOps 2.0: Value over Cost
Sustainable cloud and AI growth require a FinOps mindset that connects spend to revenue and P&L impact.
- **Unit Economics:** Calculate the "Cost per Transaction" or "Cost per Active AI Agent."
- **Waste Identification:** Historically, 30% of cloud spend is waste. Use AI-driven right-sizing and spot-instance automation.
- **ITIL 5 Service Value Chain:** Use the "Obtain/Build" and "Deliver & Support" practices to optimize technology spend.
## 3. Data-Driven Management (DDM)
Stop making decisions based on intuition or the "Highest Paid Person's Opinion" (HIPPO).
- **Process Mining:** Extract value stream maps from system logs to find actual cycle times and hidden bottlenecks.
- **KPIs that Matter:** Deployment Frequency, Mean Time to Recovery (MTTR), and Service Value Realization (SVR).
- **ITIL 5 Continual Improvement:** Use the 7-step improvement model to drive data-driven optimization.
## 4. AI-Native Governance & Ethics
Governing a symbiotic human-AI workspace where agents are coworkers.
- **Ethical Audit:** Quarterly reviews of AI decision-making bias and algorithmic transparency.
- **Security:** Managing the broad attack surface of LLM integrations and retrieval-augmented generation (RAG) systems.
- **ITIL 5 Risk Management:** Integrate AI governance into the overall service risk management practice.
## 5. ITIL 5 Digital Product and Service Management (DPSM)
### Core Concepts
- **Digital Product (DP):** Any technology-enabled service that delivers value to customers
- **Service Offering:** The totality of how a service supports customer outcomes
- **Service Relationship:** The cooperation between provider and consumer
- **Value Co-creation:** Working with stakeholders to create value
### Service Value Chain Activities
- **Plan:** Define the vision, roadmap, and architecture
- **Engage:** Understand stakeholder needs and expectations
- **Design & Transform:** Create new services and improvements
- **Obtain/Build:** Acquire or develop components and capabilities
- **Deliver & Support:** Service delivery and operational support
- **Improve:** Continual improvement of services
### The 7 Guiding Principles
1. Focus on value
2. Progress iteratively
3. Collaborate and promote visibility
4. Think and work holistically
5. Keep it simple
6. Optimize and automate
7. Everything is a relationship
---
*Reference source for ITIL 5 Manager (li_itil_manager) skill.*快速将中文文本润色为更清晰自然且专业的表达,支持多风格、多场景定制,确保不改变原意。
name: simple-writing-polisher description: 将中文文本快速润色为更清晰、自然、专业的表达(保留原意,按需提供多种风格)。 version: 0.1.1 # Simple Writing Polisher ## 用途 把你给的中文文本在**不改变原意**的前提下,润色成更清晰、更自然、更专业的表达。 ## 使用方式 把原文贴出来,并选择你需要的输出方式之一: - **默认润色**:给出润色后的版本(1 个)。 - **多版本**:给出 3 个版本(正式/中性/口语),并标注差异侧重点。 - **压缩/扩写**:在不改变关键事实的前提下压缩到更短,或扩写得更完整。 你也可以附加约束: - 目标读者(面试官/客户/同事/公众号读者等) - 场景(邮件/周报/项目复盘/PRD/评论回复等) - 字数范围 - 需要保留/必须出现的关键词 ## 输出格式(默认) 1. 润色结果 2. 关键修改点(不超过 12 条) ## 例子 输入: > 我们这个项目做了很多工作,但是遇到不少问题,最终还是按时上线了。 输出(示例): 润色结果: > 这个项目推进过程中我们完成了大量工作,也遇到不少挑战,但最终仍按计划如期上线。 关键修改点: - 语气更客观、信息更凝练 - “不少问题”替换为更中性的“挑战”
Design free tools and micro-sites that acquire customers through engineering effort rather than ad spend. Use whenever a founder or marketer wants to build a...
---
name: engineering-as-marketing
description: "Design free tools and micro-sites that acquire customers through engineering effort rather than ad spend. Use whenever a founder or marketer wants to build a free calculator, tool, widget, grader, or educational resource as a customer acquisition channel. Activates on phrases like 'engineering as marketing', 'free tool', 'marketing tool', 'calculator', 'grader', 'micro-site', 'widget', 'free app', 'HubSpot Marketing Grader', 'Moz tools', 'lead generator tool', 'utility for marketing', 'free resource for customers'."
version: 1.0.0
homepage: https://github.com/bookforge-ai/bookforge-skills/tree/main/books/traction/skills/engineering-as-marketing
metadata: {"openclaw":{"emoji":"📚","homepage":"https://github.com/bookforge-ai/bookforge-skills"}}
status: draft
source-books:
- id: traction
title: "Traction: A Startup Guide to Getting Customers"
authors: ["Gabriel Weinberg", "Justin Mares"]
chapters: [16]
domain: startup-growth
tags: [startup-growth, engineering-as-marketing, free-tools, lead-generation, product-led-growth]
depends-on: [bullseye-channel-selection]
execution:
tier: 2
mode: full
inputs:
- type: document
description: "Ideal customer problem, engineering capacity, existing product"
tools-required: [Read, Write]
tools-optional: [Bash, AskUserQuestion]
mcps-required: []
environment: "Plain-text working directory for tool design specs and lead capture plans"
discovery:
goal: "Design and plan a free tool that captures leads from the ideal customer audience"
tasks:
- "Identify the ONE question the ideal customer asks before needing your product"
- "Design the smallest possible tool that answers that question"
- "Apply the single-input-field design pattern"
- "Plan the lead capture after tool use"
- "Avoid the engineering resource hoarding anti-pattern"
audience:
roles: [startup-founder, growth-marketer, engineering-lead]
experience: intermediate
when_to_use:
triggers:
- "Engineering team has spare capacity"
- "User wants a scalable lead generation mechanism"
- "Bullseye selected Engineering as Marketing"
- "User's ideal customer has a specific quantifiable question"
prerequisites:
- skill: bullseye-channel-selection
why: "Engineering as Marketing should be selected deliberately via Bullseye"
not_for:
- "Engineering team has no capacity and product is struggling"
environment:
codebase_required: false
codebase_helpful: true
works_offline: true
quality:
scores:
with_skill: null
baseline: null
delta: null
tested_at: null
eval_count: 0
assertion_count: 10
iterations_needed: 0
---
# Engineering as Marketing
## When to Use
The startup wants to use engineering effort to acquire customers rather than spending on ads. Use this skill when:
- Engineering team has spare capacity or the team is engineering-heavy
- The ideal customer has a specific, quantifiable question they'd pay to answer
- Bullseye Framework selected Engineering as Marketing
- A one-time engineering investment could produce ongoing lead generation
## Context & Input Gathering
### Required Context (must have — ask if missing)
- **Ideal customer description:** who the tool should attract
→ Check prompt for: customer profile, pain points
→ If missing, ask: "Who is your ideal customer, and what's the one question they ask before they're ready to pay for your product?"
- **Engineering capacity:** available hours/weeks
→ Check prompt for: team size, availability, prior free tools
→ If missing, ask: "How much engineering time can you budget for building the tool? Even 2-4 weeks is enough for a simple calculator."
### Observable Context
- **Existing product:** what the tool should funnel toward
- **Current lead generation:** what the tool would replace or complement
### Default Assumptions
- Single-input-field is the ideal pattern (paste URL, get report)
- The tool should be genuinely useful on its own, not a sales pitch
- Lead capture happens after the value is delivered, not before
### Sufficiency Threshold
```
SUFFICIENT: ideal customer + core question + engineering capacity known
PROCEED WITH DEFAULTS: customer known, brainstorm common category questions
MUST ASK: customer is too vague to identify the core question
```
## Process
Use TodoWrite:
- [ ] Step 1: Identify the core customer question
- [ ] Step 2: Design the smallest tool that answers it
- [ ] Step 3: Apply the single-input-field pattern
- [ ] Step 4: Plan lead capture flow
- [ ] Step 5: Set up distribution (SEO, blog integration, sharing)
### Step 1: Identify the Core Customer Question
**ACTION:** Find the ONE specific question the ideal customer asks before they're ready to pay for your product. Not "what should I do about marketing" but "is my marketing working well enough?" or "how does my site compare to competitors?"
Examples:
- **HubSpot:** "How good is my marketing?" → Marketing Grader (enter URL, get score)
- **Moz:** "Who follows my target audience on Twitter?" → Followerwonk
- **Moz:** "How many backlinks does a site have?" → Open Site Explorer
- **DuckDuckGo:** "How is Google tracking my searches?" → DontTrack.us micro-site
The question must be:
- Specific enough to answer definitively
- Valuable enough that the customer would actively seek an answer
- Related enough to your product that users who care about it are leads
**WHY:** Generic free tools produce generic leads. HubSpot's Marketing Grader attracts people who care about marketing quality — which is exactly HubSpot's ideal customer. A generic "business calculator" attracts everyone and converts no one. The tool must match the question, and the question must match the customer.
### Step 2: Design the Smallest Tool That Answers It
**ACTION:** Strip the tool to the minimum viable answer:
- One input field (URL, email, company name, Twitter handle)
- One clear output (score, report, comparison, list)
- No gates before the value is delivered
- No login required (or optional at most)
Budget: 2-4 weeks of engineering for a first version. Resist feature creep.
Write the design spec to `tool-design.md`.
**WHY:** Complexity is the enemy of adoption. HubSpot Marketing Grader succeeded partly because it was embarrassingly simple — paste a URL, get a grade. If it had required a 20-question survey first, 90% of users would have dropped off. The friction-to-value ratio is the core metric.
### Step 3: Apply the Single-Input-Field Pattern
**ACTION:** Design the landing page around a single input field, centered, with minimal distractions. The user types or pastes one thing and clicks one button. Everything else waits until after the result is shown.
Elements to include ON the landing page:
- Headline (what the tool does in 6 words)
- Single input field
- Single action button
- One line of credibility ("Used by 3M sites")
Elements to EXCLUDE from the landing page:
- Feature lists
- Pricing
- Multi-step signup
- Login wall
- Pop-ups before result
**WHY:** Every additional element on the landing page reduces conversion to first-use. The single-input pattern removes all friction between "user arrives" and "user gets value." The sales pitch happens after the value is delivered, when the user is in a "this is useful" state — not before, when they're evaluating whether to try.
### Step 4: Plan Lead Capture Flow
**ACTION:** Design what happens AFTER the user gets their result:
- Show the valuable result immediately (no email wall)
- Offer to email the result for future reference (email capture, optional)
- Offer a "deeper analysis" or "personalized report" in exchange for email (stronger capture)
- Show a product CTA that's relevant to the result ("Your score is 65. Our product can get you to 90.")
- Add social sharing (especially if the result is shareable, like a grade)
Write the flow to `tool-capture-flow.md`.
**WHY:** Gating value behind email collection kills conversion. Delivering value first and asking for email second (for "send me my report" or "notify me of improvements") captures email at 30-50% rates instead of 5%. The sequence matters: value → capture, not capture → value.
### Step 5: Distribution
**ACTION:** The tool is built — how do people find it?
Distribution channels for tools:
- **SEO:** the tool ranks for queries like "is my marketing working" (HubSpot's strategy)
- **Blog integration:** tool embedded in or linked from related blog posts
- **Share hooks:** results users share publicly (leaderboards, grades) drive viral growth
- **Partnership distribution:** tool offered as free addon to partners' audiences
- **Direct promotion:** launch on Product Hunt, Hacker News, Reddit
**WHY:** A great tool that nobody finds is worthless. Distribution is half the work. The tool's SEO properties (keyword-rich domain, targeted landing page) compound over time and often become the biggest traffic source.
## Inputs
- Ideal customer description and core question
- Engineering capacity
- Existing product to funnel toward
## Outputs
Three markdown files:
1. **`tool-design.md`** — Core question, input, output, scope constraints
2. **`tool-capture-flow.md`** — Post-result flow, email capture, product CTA
3. **`tool-distribution.md`** — Distribution channels and launch plan
## Key Principles
- **The question matters more than the tool.** A perfect tool for the wrong question produces zero leads. A simple tool for the right question produces millions. WHY: Tool quality is a secondary factor. Match to customer intent is the primary factor.
- **Single input field is the ideal pattern.** Less friction = more users. WHY: Every additional form field cuts conversion. The single-input pattern maximizes users-who-try, which is the top of the lead funnel.
- **Deliver value first, capture email second.** Gating kills conversion. WHY: Email capture rates are 5-10% when value is gated, 30-50% when value is delivered first. Users who got value are in a friendly state; users who hit a gate are in a hostile state.
- **Don't confuse "free tool" with "feature preview".** A free tool is a standalone utility that's valuable even if the user never buys your product. A feature preview is a crippled version of your product. Users can tell the difference. WHY: Standalone tools build trust; feature previews feel like bait-and-switch.
- **Avoid the engineering resource hoarding anti-pattern.** "Companies have a hard time using engineering resources for anything but product development." Most founders use all engineering time on product features. Don't. WHY: Engineering as marketing produces ongoing lead flow from a one-time investment. Product features produce incremental value per feature. The ROI comparison favors tools for most startups.
- **The tool's SEO compounds.** Unlike ads, a well-ranked tool produces traffic indefinitely. WHY: One-time engineering investment + long-lived traffic = asymmetric return. HubSpot Marketing Grader has generated leads for 15+ years from its initial build.
## Examples
**Scenario: B2B SaaS with spare engineering capacity**
Trigger: "We have 2 engineers on the bench for 4 weeks. We sell analytics software to marketers. What should we build?"
Process: (1) Core question: "Is my website's analytics setup correct?" — something marketers worry about. (2) Tool design: paste URL → crawl for Google Analytics, GTM, event tracking, UTM consistency → score report. (3) Single-input: URL field + "Check my site" button. (4) Capture: show score immediately, "email me the full report" capture. Product CTA: "Our tool fixes these 5 issues automatically." (5) Distribution: SEO on "analytics audit", launch on Product Hunt, embed on analytics blog.
Output: Complete tool spec, lead flow, distribution plan.
**Scenario: Consumer health app**
Trigger: "We have a meal tracking app. Want to use engineering-as-marketing. Ideas?"
Process: (1) Core question: "How healthy is my diet?" — universal question for target audience. (2) Tool: "Paste your last 3 days of meals → AI analyzes nutrition and grades your diet." (3) Single input: text area for meal entries. (4) Capture: show grade immediately, "Save your progress" (email capture for 7-day tracking). Product CTA: "Our app auto-tracks this every day." (5) Distribution: SEO on "healthy diet quiz", Instagram/TikTok shareable results.
Output: Tool concept, social-friendly result design, distribution plan.
**Scenario: Founder pulled between product and tool**
Trigger: "Engineering team has 3 weeks free. But I'm worried we should use that time to fix bugs in the product. Which is better?"
Process: (1) Engineering resource hoarding anti-pattern in action. (2) Frame the trade-off: 3 weeks of bug fixes = marginal improvement to existing users. 3 weeks building a free tool = ongoing lead flow for years. ROI comparison strongly favors the tool. (3) Caveat: if the bugs are P0/churn-causing, fix them first. If they're "nice to have", build the tool. (4) Tool recommendation based on customer question. (5) Commit to the decision: don't build half a tool and half a bug fix.
Output: Clear decision framing that breaks the engineering-hoarding default.
## References
- For lead capture flow variants and conversion benchmarks, see [references/tool-patterns.md](references/tool-patterns.md)
## License
This skill is licensed under [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/).
Source: [BookForge](https://github.com/bookforge-ai/bookforge-skills) — Traction: A Startup Guide to Getting Customers by Gabriel Weinberg and Justin Mares.
## Related BookForge Skills
Install related skills from ClawhHub:
- `clawhub install bookforge-bullseye-channel-selection` — Select Engineering as Marketing deliberately
- `clawhub install bookforge-seo-channel-strategy` — Tools rank for long-tail queries naturally
- `clawhub install bookforge-content-and-email-marketing` — Tools capture emails for lifecycle marketing
Or install the full book set from GitHub: [bookforge-skills](https://github.com/bookforge-ai/bookforge-skills)
FILE:references/tool-patterns.md
# Free Tool Patterns
Reference patterns for engineering-as-marketing tools, from Chapter 15 of *Traction*.
## Proven Tool Patterns
### Pattern 1: The Grader
**Shape:** User inputs one thing (URL, email, company) → tool evaluates and returns a score.
**Examples:** HubSpot Marketing Grader (URL → marketing score), Crazy Egg (URL → heatmap insights).
**Lead flow:** Show score immediately. Offer email capture for full report or comparison.
**Why it works:** Scores are shareable, benchmarkable, and create curiosity. "Your marketing score is 65" invites comparison.
### Pattern 2: The Analyzer
**Shape:** User inputs data → tool returns insights.
**Examples:** Moz Followerwonk (Twitter handle → follower analysis), Open Site Explorer (URL → backlinks).
**Lead flow:** Show a slice of the analysis free. Gate full data behind email or signup.
**Why it works:** Analysis saves the user hours of work. High perceived value.
### Pattern 3: The Calculator
**Shape:** User inputs variables → tool returns a calculated result.
**Examples:** mortgage calculators, ROI calculators, pricing calculators.
**Lead flow:** Show result immediately. Offer email capture to "save the calculation" or get a customized plan.
**Why it works:** Calculators answer a specific numeric question that's hard to answer without the tool.
### Pattern 4: The Educator (Micro-Site)
**Shape:** Dedicated micro-site on a specific topic with authoritative content.
**Examples:** DuckDuckGo's DontTrack.us (educates about search privacy), HealthCare.gov-style explainers.
**Lead flow:** Educate first, link to product as the solution at the end.
**Why it works:** Educational content ranks in SEO and creates trust before any sales pitch.
### Pattern 5: The Generator
**Shape:** User inputs parameters → tool generates an artifact (logo, slogan, email, card).
**Examples:** Shopify's business name generator, Canva's free design templates.
**Lead flow:** Deliver generated artifact immediately. Upsell to fuller features.
**Why it works:** Generated artifacts feel personal and valuable. Users share them.
### Pattern 6: The Comparator
**Shape:** User inputs multiple options → tool compares them.
**Examples:** price comparison tools, competitor comparison charts.
**Lead flow:** Show comparison. Position your product favorably within the comparison.
**Why it works:** Users are already comparison-shopping. Meeting them at that moment is high intent.
## Conversion Benchmarks
Rough benchmarks from the book's examples and industry reports:
- **First-use to email capture:** 15-35% for unilocked-value tools
- **Email capture to product signup:** 3-10% for well-designed nurture sequences
- **Tool → paying customer:** 0.5-3% overall (depending on product price and fit)
HubSpot Marketing Grader: 3 million+ uses, "large portion" of HubSpot's 50,000+ monthly leads.
## Anti-Patterns in Tool Design
- **Gating value behind email before showing anything** — kills conversion
- **Multi-step forms before the result** — every step loses 20-40% of users
- **Login requirement to see the result** — nukes first-time-use rates
- **Tool that only works with your product** — users feel bait-and-switched
- **Tool that's obviously a marketing trick** — trust destroyed before lead captured
- **Tool without a clear distribution plan** — built and then nobody finds it
## Source
Chapter 15 ("Engineering as Marketing") of *Traction* by Gabriel Weinberg and Justin Mares.
Prioritize which assumptions to validate first and produce focused learning goals before customer conversations — classifying risks as product risk versus ma...
---
name: question-importance-prioritizer
description: Prioritize which assumptions to validate first and produce focused learning goals before customer conversations — classifying risks as product risk versus market risk. Use this skill whenever the user has many assumptions or unknowns and needs to decide which to test first, wants to identify the 3 most important learning goals for their next conversation batch, needs to figure out what the riskiest parts of their business idea are, wants to separate must-validate assumptions from safe ones, is preparing strategic learning goals but not the specific interview questions, or suspects they are avoiding the scary questions that actually matter — even if they don't mention "prioritization" or "learning goals." Do NOT use this skill to write or rewrite the actual conversation questions (use conversation-question-designer) or to analyze notes from a completed conversation (use conversation-data-quality-analyzer).
version: 1.0.0
homepage: https://github.com/bookforge-ai/bookforge-skills/tree/main/books/the-mom-test/skills/question-importance-prioritizer
metadata: {"openclaw":{"emoji":"📚","homepage":"https://github.com/bookforge-ai/bookforge-skills"}}
status: verified
source-books:
- id: the-mom-test
title: "The Mom Test"
authors: ["Rob Fitzpatrick"]
chapters: [3]
tags: [customer-discovery, question-prioritization, learning-goals, risk-classification, pre-conversation-planning]
depends-on: []
execution:
tier: 1
mode: hybrid
inputs:
- type: document
description: "Product idea description and list of assumptions or unknowns to validate"
tools-required: [Read, Write]
tools-optional: []
mcps-required: []
environment: "Any agent environment with file read/write access."
---
# Question Importance Prioritizer
## When to Use
You need to decide what to learn before customer conversations — not just which questions pass quality rules, but which questions actually matter for your business survival. Typical situations:
- The user has many assumptions to validate and needs to prioritize which 3 to focus on next
- The user is preparing for a batch of customer conversations and needs focused learning goals
- The user has been having conversations but feels stuck because they are asking safe, comfortable questions
- The user needs to determine whether their biggest risks can even be validated through conversations (product risk vs market risk)
- The user wants to identify the "scary questions" they have been avoiding
- The user has a long list of unknowns and does not know where to start
Before starting, verify:
- Does the user have a product idea or business concept? (If not, this skill cannot help yet)
- Does the user have at least a rough sense of who their customers might be? (Different customer types need different learning goals)
**Mode: Hybrid** — The agent produces the prioritized learning goals and prepared questions. The human conducts the actual conversations.
## Context & Input Gathering
### Required Context (must have — ask if missing)
- **Product idea or business concept:** What is the user building or exploring? This is the foundation for identifying risks and learning goals.
- Check prompt for: product descriptions, startup ideas, feature concepts, problem statements
- Check environment for: `product-idea.md`, `README.md`, pitch documents
- If still missing, ask: "What product or business idea are you working on? A few sentences describing what it does and who it is for."
- **Assumptions or unknowns to validate:** What does the user believe but has not yet proven? This is the raw material for prioritization.
- Check prompt for: hypotheses, assumptions lists, "I think...", "I believe...", "I assume...", risk lists
- Check environment for: `learning-log.md`, `assumptions.md`, previous conversation notes
- If still missing, ask: "What are the key assumptions your business depends on? List everything you believe to be true but have not yet validated — about your customers, the problem, the market, pricing, distribution, anything."
### Observable Context (gather from environment)
- **Customer segment:** Who is the user targeting? Different segments need different learning goals.
- Look for: `customer-segments.md`, persona descriptions, target market references
- If unavailable: ask "Who are your target customers? Be as specific as you can."
- **Current stage:** How far along is the user? Pre-idea, pre-product, has a prototype, has paying customers?
- Look for: references to prototypes, MVPs, revenue, launch dates
- If unavailable: assume pre-product (exploring the problem space)
- **Previous conversation learnings:** What has already been validated or invalidated?
- Look for: `conversation-notes/`, `learning-log.md`
- If unavailable: assume first round of conversations
### Default Assumptions
- If no customer type specified, design learning goals generic enough for early exploration but note this limitation
- If no stage specified, assume pre-product (learning phase)
- If no prior conversations, assume all assumptions are unvalidated
### Sufficiency Threshold
```
SUFFICIENT when ALL of these are true:
- Product idea or business concept is known
- At least 3 assumptions or unknowns are identified
- Customer type is known or defaulted
PROCEED WITH DEFAULTS when:
- Product idea is known but assumptions are vague ("I'm not sure what I don't know")
- Customer type is approximate ("probably restaurant owners")
MUST ASK when:
- No product idea at all
- User provides questions but no context on the business they are building
```
## Process
### Step 1: Surface All Business Risks
**ACTION:** List every assumption the business depends on — both the ones the user stated and the ones they may have missed. Use two diagnostic questions to uncover hidden risks:
1. "If this company were to fail, why would it have happened?" — list every plausible failure reason
2. "What would have to be true for this to be a huge success?" — list every condition required
**WHY:** Most founders focus on the risks they find interesting (usually the product or technology) and ignore the ones that scare them (usually the market, pricing, or distribution). The two diagnostic questions systematically surface hidden risks that the user is unconsciously avoiding. The most important questions to ask customers are precisely the ones that feel most uncomfortable.
**IF** the user provided a list of assumptions, review it against the diagnostic questions and add any missing risks
**IF** the user did not provide assumptions, generate the risk list entirely from the diagnostic questions
**OUTPUT:** A comprehensive list of business risks, grouped loosely by area (customer/problem, market/pricing, product/technology, distribution/growth, team/operations).
### Step 2: Classify Each Risk as Product Risk or Market Risk
**ACTION:** For each risk from Step 1, classify it into one of two categories:
| Risk Type | Definition | Key Questions | Can Conversations Validate? |
|-----------|-----------|---------------|---------------------------|
| **Market risk** | Do they want it? Will they pay? Are there enough of them? | Demand, willingness to pay, market size, problem severity | Yes — customer conversations are the primary validation tool |
| **Product risk** | Can I build it? Can I grow it? Will they keep using it? | Technical feasibility, scalability, retention, network effects, critical mass | Limited — you need to build something to prove these |
**WHY:** This classification determines how much weight to give conversation-based validation for each risk. If the user's biggest risk is product-side (like building a marketplace that needs critical mass, or a video game that needs to be fun), customer conversations alone cannot validate it — the user will need to start building earlier with less certainty. Mistaking product risk for market risk leads to months of conversations that "validate" obvious things (e.g., asking farmers if they want more money, asking bar owners if they want more customers).
**Detection test for product risk masquerading as market risk:** If customer responses consistently sound like "Yes, if you can actually build that, I would pay" — the risk is in the product, not the market. The customer is restating the obvious.
**IF** the majority of risks are product-side, warn the user: "Your biggest unknowns are about whether you can build and grow this, not whether people want it. Customer conversations will give you a starting point, but you will need to start building earlier to validate the core risks. Focus conversations on understanding the problem depth and current workarounds, not on confirming demand."
**OUTPUT:** Each risk annotated with its type (market/product) and whether conversations can validate it.
### Step 3: Prioritize into the Top 3 Learning Goals
**ACTION:** From the classified risk list, select the 3 most important learning goals for the next batch of conversations. Prioritize using these criteria:
1. **Business-criticality:** Could this risk, if wrong, kill the entire business? Risks that would require a complete pivot outrank risks that would require a feature adjustment.
2. **Current uncertainty:** How much evidence does the user already have? Prioritize the murkiest unknowns — the ones where the user has the least data.
3. **Conversational reach:** Can customer conversations actually answer this? Deprioritize pure product risks that need building, not talking.
4. **Scariness:** Is this a question the user has been avoiding? If a question makes the user uncomfortable, that is a signal it is important. A question you are not terrified of is probably not important enough.
**WHY:** Without prioritization, conversations wander across too many topics and produce shallow data on everything, deep data on nothing. Three is the right number because it is small enough to focus a conversation but large enough to make each conversation worthwhile. Choose the murkiest and most important questions — they will give you the firmest footing and clearest sense of direction for the next batch.
**Scary question test:** Review the final list and verify that at least one learning goal makes the user uncomfortable. If all three feel safe and easy to ask about, the list is wrong — the user is avoiding the hard questions. Flag this explicitly: "None of these learning goals seem scary. What question are you most afraid to ask? That one probably belongs on this list."
**IF** the user has multiple customer types, create a separate list of 3 for each type — learning goals differ by audience
**IF** this is not the first batch of conversations, review previous learnings and update: drop validated goals, promote the next murkiest unknowns
**OUTPUT:** A numbered list of exactly 3 learning goals, each with:
- The learning goal stated as a concrete question to answer
- Why it matters (what changes if the answer is negative)
- The risk type (market or product)
- A scariness rating (comfortable / uncomfortable / terrifying)
### Step 4: Check for Premature Zoom
**ACTION:** Review each learning goal and assess whether it assumes something that has not yet been validated. Apply the premature zoom diagnostic:
- Does this goal zoom into a specific problem area without first confirming that area matters to the customer?
- If you ask about this topic, will the customer give you detailed answers just because you asked — regardless of whether they actually care?
- Would the customer have raised this topic on their own if you asked broad questions about their life?
**WHY:** Premature zoom is one of the most dangerous patterns in customer discovery. When you ask "What is your biggest problem with X?", you assume X matters. The person gives you an answer because you asked, not because they care. This creates data that looks like validation but is actually worthless. Even if you learn everything there is to know about a trivial problem, you still do not have a business. The fix is to start broad and only zoom in when the customer independently signals that this area is a top priority for them.
**FOR EACH** learning goal:
- **IF** the goal assumes problem importance → flag it and add a broader "does this even matter?" goal that should come first
- **IF** the goal is already about confirming importance → mark it as properly scoped
- **IF** previous conversations have already confirmed importance → mark it as safe to zoom
**"Does-this-problem-matter" diagnostic questions** (use these to validate importance before zooming in):
- "How seriously do you take [area]?"
- "Do you make money from it?"
- "Have you tried making more money from it?"
- "How much time do you spend on it each week?"
- "Do you have any major aspirations for [area]?"
- "Which tools and services do you use for it?"
- "What are you already doing to improve this?"
- "What are the 3 big things you are trying to fix or improve right now?"
**OUTPUT:** Each learning goal annotated with its zoom-level safety status and, where needed, a broader prerequisite question.
### Step 5: Produce the Prioritized Learning Goals Deliverable
**ACTION:** Compile the final output document containing the prioritized learning goals with risk classification and prepared questions for each goal.
**WHY:** The deliverable must be immediately usable before conversations. The user should be able to glance at it and know exactly what they need to learn, why each goal matters, and which questions to ask. This is the "list of 3" that they carry into every conversation with this customer type.
**Output format:**
```markdown
# Prioritized Learning Goals
## Context
- **Product/Business:** [from input]
- **Target Customer:** [from input]
- **Stage:** [from input or default]
- **Date Prepared:** [today]
- **Batch:** [first / updated after N conversations]
## Risk Overview
- **Total risks identified:** [N]
- **Market risks (conversation-validatable):** [N]
- **Product risks (need building to validate):** [N]
- **Biggest overlooked risk:** [the one the user was probably avoiding]
## Top 3 Learning Goals
### 1. [Learning Goal as Question]
- **Risk type:** Market / Product
- **Why it matters:** [what changes if the answer is negative — be specific]
- **Scariness:** Comfortable / Uncomfortable / Terrifying
- **Zoom-level check:** [Safe to zoom / Needs importance confirmation first]
- **Prepared questions:**
- [Broad opener to confirm importance]
- [Specific past-focused depth question]
- [Commitment/severity signal question]
- **What a negative answer looks like:** [concrete signal that disproves this]
- **What a positive answer looks like:** [concrete signal that validates this]
### 2. [Learning Goal as Question]
[same structure]
### 3. [Learning Goal as Question]
[same structure]
## Questions You Might Be Avoiding
- [Scary question 1 — and why it matters]
- [Scary question 2 — and why it matters]
## Premature Zoom Warnings
- [Any goals that assume unvalidated importance, with the broader question to ask first]
## Risk Classification Summary
| Risk | Type | Conversation Can Validate? | Priority |
|------|------|---------------------------|----------|
| [risk 1] | Market | Yes | In top 3 |
| [risk 2] | Product | Limited | Deferred |
| ... | ... | ... | ... |
## Next Steps
- After this conversation batch, review which goals are answered
- Drop answered goals, promote next-murkiest unknowns
- Update this document with new top 3
```
**IF** the user provided a file path or working directory, write the output to `learning-goals.md`
**ELSE** present the output directly in the conversation
## Examples
### Scenario 1: SaaS Founder with a Long Assumption List
**Trigger:** "I'm building a tool that helps restaurant owners manage their online reviews across Google, Yelp, and TripAdvisor. Here are my assumptions: (1) Restaurant owners care about online reviews, (2) Managing multiple platforms is painful, (3) They would pay $50/month, (4) They check reviews daily, (5) Negative reviews cause real revenue loss, (6) They want AI-generated review responses, (7) They struggle to get customers to leave reviews."
**Process:**
1. Surface all risks: The user listed 7 assumptions, but diagnostic questions reveal hidden ones — distribution (how will they find this tool?), competition (existing tools like Podium?), buyer (is the owner the one managing reviews or a manager?), and time (do they have bandwidth to use yet another tool?)
2. Classify risks: Assumptions 1-5, 7 are market risks (conversationally validatable). Assumption 6 is product risk (AI quality). Distribution and competition are market risks.
3. Prioritize top 3:
- Goal 1: "Do restaurant owners actually manage reviews themselves, and is it painful enough to pay to fix?" (market risk, terrifying — could invalidate the whole idea)
- Goal 2: "What tools or workarounds are they using today, and what do they spend?" (market risk, uncomfortable — might reveal strong competitors)
- Goal 3: "How do they currently respond to negative reviews, and what is the real cost of not responding?" (market risk, comfortable — validates severity)
4. Premature zoom check: Goal 3 assumes negative reviews matter enough to act on — needs importance confirmation first
**Output (abbreviated):**
```
### 1. Do restaurant owners personally manage reviews — and is it painful enough to pay $50/month?
- Risk type: Market
- Why it matters: If owners delegate review management or don't care, there is no buyer
- Scariness: Terrifying
- Zoom-level check: Safe — this IS the importance check
- Prepared questions:
- "Walk me through what you did the last time you got a negative review online."
- "How much time do you spend on review-related tasks in a typical week?"
- "What are you currently paying for any marketing or reputation tools?"
- What a negative answer looks like: "My manager handles that" or "I don't really check them"
- What a positive answer looks like: Specific stories of time spent, emotional frustration, existing workarounds
```
---
### Scenario 2: Technical Founder with Pure Product Risk
**Trigger:** "I'm building a multiplayer mobile game where players collaborate to solve environmental puzzles. I want to validate whether people would play this. My assumptions: (1) People enjoy collaborative puzzle games, (2) Environmental themes attract players, (3) Mobile is the right platform, (4) Players will invite friends to join."
**Process:**
1. Surface risks: Diagnostic questions reveal the elephant — nearly all risk is product-side (Is it fun? Can it retain players? Can it achieve network effects for multiplayer?)
2. Classify risks: All 4 stated assumptions are product risks. "Do people enjoy collaborative puzzle games?" is like asking "Do you like having fun?" — the answer is always yes.
3. Prioritize: Warn the user that conversations cannot validate the core risks. Redirect toward the few market risks that exist: Are there enough puzzle game enthusiasts in this niche? What games do they currently play? How much do they spend on mobile games?
**Output (abbreviated):**
```
## Risk Overview
- Total risks identified: 8
- Market risks: 2 (audience size, spending habits)
- Product risks: 6 (fun factor, retention, multiplayer matchmaking, network effects, art quality, puzzle design)
- Biggest overlooked risk: Nearly all your risk is product-side. Customer conversations cannot tell you whether your game is fun. You need to build a prototype and watch people play.
### 1. Are there enough people who actively seek out collaborative puzzle games — and where do they congregate?
- Risk type: Market
- Why it matters: Even a great game fails if the target audience is too small or unfindable
- Scariness: Uncomfortable
- Prepared questions:
- "What puzzle games have you played in the last month? Tell me about the most recent session."
- "How do you discover new games? Walk me through the last game you downloaded."
- "Have you ever specifically searched for a game where you could play with friends?"
## Questions You Might Be Avoiding
- "Could I actually build a multiplayer puzzle game that is fun and retains players?" — This is your real risk, and conversations cannot answer it. Start prototyping.
```
---
### Scenario 3: Founder Updating Learning Goals After First Batch
**Trigger:** "I just finished 5 conversations about my invoice factoring tool for freelancers. I learned that freelancers definitely have cash flow problems (validated) and they mostly use spreadsheets to track invoices (validated). But I still don't know if they would trust a third party with their invoices, and I realize I never asked about pricing. What should I focus on next?"
**Process:**
1. Surface risks: Cash flow pain (validated), current tools (validated), trust with financial data (unvalidated), willingness to pay (unvalidated), plus hidden risks — do they invoice enough volume to justify a tool? Are there regulatory issues?
2. Classify: Trust and pricing are market risks. Invoice volume is market risk. Regulatory is mixed.
3. Prioritize top 3 for next batch:
- Goal 1: "Would freelancers trust a third-party service to handle their invoice payments?" (market risk, terrifying — deal-breaker if no)
- Goal 2: "How much money is stuck in late invoices per month, and what would they pay to get it faster?" (market risk, uncomfortable)
- Goal 3: "Do they invoice enough clients per month for factoring to be worthwhile?" (market risk, comfortable)
4. Note that the user explicitly identified they "never asked about pricing" — this was a scary question they avoided in the first batch
**Output (abbreviated):**
```
## Questions You Might Be Avoiding
- "Would you hand over control of your invoices to a service you found online?" — You avoided this in 5 conversations. That avoidance is a signal that this is your scariest and most important question.
- "What would you pay for this?" — You explicitly noted you avoided pricing. Ask about current spending on financial tools first, then explore willingness to pay.
```
## Key Principles
- **The questions you are avoiding are the ones you most need to ask** — Fear of bad news causes founders to ask comfortable questions that feel productive but do not de-risk anything. If you are not terrified of at least one question in every conversation, you are wasting the conversation. The cost of not asking is always higher than the cost of hearing a bad answer. One founder avoided asking lawyers about legal ambiguities and it cost half a million dollars.
- **Product risk and market risk require different validation methods** — When the customer says "If you can build it, I will pay," that is not validation — it is restating the obvious. Customer conversations validate market risk (Do they want it? Will they pay? Are there enough of them?). Product risk (Can I build it? Can I grow it? Will they keep using it?) requires building. Misclassifying your risk type leads to months of conversations that prove things nobody doubted.
- **Start broad before zooming in — always** — Most people have many problems they will happily discuss if you ask about them. Zooming into your specific problem area before confirming it is a top priority creates false validation. The person answers your detailed questions because you asked, not because they care. Start with "What are the big things you are trying to fix right now?" and only zoom in when they raise your area themselves. If they do not mention it unprompted, they probably do not care enough to pay for a solution.
- **Three learning goals is the right number** — Too few and each conversation covers too little ground. Too many and the conversation scatters across topics without going deep on any. Three goals lets you focus while remaining flexible enough to follow interesting threads. After each batch of conversations, drop answered goals and promote the next murkiest unknowns.
- **Lukewarm signals are more reliable than enthusiastic ones** — When someone says "That is pretty neat" or "I am not so sure about that," the instinct is to pitch harder until they say something nice. Resist this. A lukewarm response is perfectly reliable information — this person does not care enough. You cannot build a business on a lukewarm response. The only thing you gain from "convincing" them is a false positive.
## References
- For designing specific questions that pass customer conversation quality rules, use the `conversation-question-designer` skill
- For narrowing broad customer segments into specific, findable who-where pairs, use the `customer-segment-slicer` skill
- For the complete "does-this-problem-matter" diagnostic question set and risk classification details, see [risk-classification-guide.md](references/risk-classification-guide.md)
## License
This skill is licensed under [CC-BY-SA-4.0](https://creativecommons.org/licenses/by-sa/4.0/).
Source: [BookForge](https://github.com/bookforge-ai/bookforge-skills) — The Mom Test by Rob Fitzpatrick.
## Related BookForge Skills
This skill is standalone. Browse more BookForge skills: [bookforge-skills](https://github.com/bookforge-ai/bookforge-skills)
FILE:references/risk-classification-guide.md
# Risk Classification Guide
## Product Risk vs Market Risk
Every business faces risks in two fundamental categories. Correctly classifying your risks determines whether customer conversations are the right validation tool.
### Market Risk
**Definition:** Do they want it? Will they pay? Are there enough of them?
**Examples:**
- SaaS tools solving known pain points
- Services addressing recognized problems
- Products entering established categories with a differentiated approach
**Validation method:** Customer conversations are the primary tool. Ask about current behavior, workarounds, spending, and severity.
**Key signal:** When customers describe their current problem, their workarounds, and what they spend — that is market risk data you can act on.
### Product Risk
**Definition:** Can I build it? Can I grow it? Will they keep using it?
**Examples:**
- Video games (Is it fun? Will people play it repeatedly?)
- Marketplaces needing critical mass (Can you get enough supply AND demand?)
- Platforms needing network effects (Will users invite others?)
- Ad-supported models (Can you get enough traffic?)
- Technically complex products (Can the technology actually work?)
**Validation method:** You need to build something. Conversations can give you a starting point (understanding problem depth, confirming willingness to switch), but the core risk requires a prototype, beta, or proof of concept.
**Key signal:** When customer responses consistently sound like "Yes, if you can actually build/do that, I would definitely pay" — the risk is in the product, not the market. The customer is restating the obvious.
### Detection Test
Ask yourself: "Is the customer telling me something I did not already know, or are they confirming what everyone would say?"
- "Would you like more money?" — Everyone says yes. This validates nothing. Risk is product-side.
- "Would you switch trackers if something cheaper and more effective was available?" — Same as asking if they want more money. Obvious yes.
- "Would you pay if you could send customers on demand to your bar?" — Bars obviously want more customers. The risk is whether you can amass a consumer audience.
### Mixed Risk Situations
Most businesses have both types. Do not overlook either one.
**Farm fertility monitor example:** The founder spent 3 months on customer conversations asking farmers if they would switch to a better tracker. Farmers said "If you can build what you say, I will equip my whole herd." This sounded like validation but was actually product risk restated as enthusiasm. The real question: Can you build hardware that works reliably on farms?
**Nightclub app example:** Founders validated that bar owners want more customers (obvious) and consumers like cheap drinks (obvious). But the real risk — amassing enough users on both sides of the marketplace — was never tested through conversations.
## "Does-This-Problem-Matter" Diagnostic Questions
Use these questions to verify that a problem area is genuinely important to the person before zooming into details:
1. "How seriously do you take [area]?"
2. "Do you make money from it?"
3. "Have you tried making more money from it?"
4. "How much time do you spend on it each week?"
5. "Do you have any major aspirations for [area]?"
6. "Which tools and services do you use for it?"
7. "What are you already doing to improve this?"
8. "What are the 3 big things you are trying to fix or improve right now?"
These questions are generic by design. They give signals you can anchor on and dig around. The bulk of them are about finding out whether the person is taking this space seriously — are they spending money or making money? Is it in their top 3? Are they actively looking for solutions?
## Pre-Meeting Risk Discovery Questions
Two questions to unearth hidden risks before conversations:
1. **"If this company were to fail, why would it have happened?"** — Forces you to enumerate all failure modes, not just the one you find most interesting.
2. **"What would have to be true for this to be a huge success?"** — Surfaces the necessary conditions that you might be taking for granted.
These questions come from strategic planning (Lafley and Martin) and are useful both for the founding team during preparation and for guiding which risks to prioritize in conversations.
## The Premature Zoom Problem
### What It Is
Asking detailed questions about a specific problem area before confirming that area actually matters to the person. This creates data that looks like validation but is worthless.
### Why It Happens
Most people have lots of problems they do not actually care enough about to fix, but which they will happily tell you the details of if you ask. When you zoom in on your area immediately, you get detailed answers — not because the problem matters, but because you asked.
### How to Detect It
- Would the customer have raised this topic on their own?
- Are you assuming this problem area is important, or has the customer demonstrated importance through their behavior?
- If you asked "What are the 3 big things you are trying to fix right now?", would your area make their list?
### How to Fix It
Start broad: "What are your big goals and focuses right now?" Only zoom into your specific area when the customer independently raises it. If they do not mention it, it is probably not a top priority — and that is reliable, actionable information.
### The Fitness App Example
**Bad conversation:** Asks a non-exerciser about gym problems. Gets a ranking of fitness priorities. Concludes "we got a user!" — but the person never exercises and will never use the app.
**Good conversation:** Asks about life goals broadly. Fitness does not make the list. Conclusion: this person is not a customer. Moves on to find people who actually care about fitness enough to act on it.
The premature zoom is dangerous because if you are not paying attention, the bad conversation seems like it went well. You got detailed answers. You "validated" a problem. But you just led them there.
中文论文复现执行工作流。用于用户上传或提供深度学习、机器学习、LLM、CV、NLP、多模态、数据集、benchmark、prompt 工程或 agent 论文的 PDF、arXiv 链接、论文主页、项目页、标题摘要或源码线索,并要求判断可复现性、搜索官方代码、检查本地源码、追踪数据集论文源码、定位数据处理代码、自...
---
name: paper-repro-triage
description: 中文论文复现执行工作流。用于用户上传或提供深度学习、机器学习、LLM、CV、NLP、多模态、数据集、benchmark、prompt 工程或 agent 论文的 PDF、arXiv 链接、论文主页、项目页、标题摘要或源码线索,并要求判断可复现性、搜索官方代码、检查本地源码、追踪数据集论文源码、定位数据处理代码、自动 clone 仓库,或在无线上/本地源码但具备复现条件时生成符合常见 PyTorch 开源项目直觉的复现工程。最终写入 Markdown 报告,聊天只返回极简中文摘要。
---
# 论文复现初筛、源码溯源与复现工程生成
## 总原则
本技能用于把论文分析从“聊天式建议”升级为“面向复现的执行工作流”。回答必须使用中文。除非工具权限、网络、审批或用户环境阻止,否则不要只告诉用户去执行命令;应优先使用可用工具完成可执行动作。
每次触发后,聊天回复第一行必须输出:
```text
[paper-repro-triage active]
```
详细结果必须写入 Markdown 文件,聊天只返回极简摘要。
## 强制行为
1. **详细结果写入 Markdown**:默认写到当前 agent workspace 下的 `paper-repro-workspace/<paper-slug>/repro-report.md`。
2. **聊天内容极简**:只输出报告路径、主论文源码状态、数据集源码状态、复现工程状态、是否需要复现、是否能复现、核心原因。
3. **不要输出“下一步建议”作为流程终点**:如果当前流程能继续执行,就继续执行;聊天摘要和报告末尾只写“未完成项/人工确认项”。
4. **先找源码,再谈复现**:必须按“线上官方代码 → 本地主论文源码 → 数据集论文源码 → 无主论文源码复现工程”的顺序执行。不能因为 GitHub 没搜到就立即从零复现。
5. **数据集源码或 baseline 源码不能替代主论文源码**:如果只找到数据集相关源码、baseline 代码、第三方实现或旧方法代码,必须继续判断是否要生成主论文复现工程。
6. **遇到代码仓库优先自动 clone**:主论文官方仓库、数据集论文官方仓库、项目页仓库都应优先 clone 到 workspace;但 clone 前必须先查本地是否已有相关源码。
7. **重复目录跳过 clone**:如果 clone 目标路径下已有同名源码文件夹,不要再次 clone,不要自动 `git pull`,不要覆盖,不要改用时间戳目录;应读取现有目录做只读检查,并在报告与聊天摘要中写明 `已存在,跳过 clone`。
8. **遇到数据集必须做源码溯源**:不用下载数据集论文 PDF,也不用下载数据集本体;只搜索数据集原论文、项目页、arXiv 摘要页、Papers with Code、GitHub/GitLab/Hugging Face 线索,判断是否有官方源码、处理脚本或 benchmark 代码。
9. **必须定位数据处理代码**:对主论文源码、baseline 源码和数据集相关源码,都要定位数据加载、预处理、划分、特征抽取、标注解析、benchmark 构建等代码位置,并写入报告。
10. **无主论文源码时必须尝试生成复现工程**:当线上没有官方源码、本地没有主论文源码,且论文证据支持“可以直接复现”或“部分可复现”时,必须生成 PyTorch 复现工程;不能只写方案。
11. **生成工程要符合常见开源直觉**:默认采用“根目录入口 + 四个代码目录 + 一个复现文档目录”的简洁 PyTorch 结构:根目录保留 `main.py`、`config.py`、`run.py`;代码放入 `data/`、`models/`、`engine/`、`utils/`;`requirements.txt`、`paper-spec.yaml`、`evidence-map.md`、`repro-notes.md` 统一放入 `repro-docs/`。该结构是最低基本盘,可按论文需要扩展,但不要默认生成 `configs/` 多 YAML 目录、独立 `losses/` 目录、`scripts/` 训练脚本或 `.sh` 文件。
12. **不要伪造复现结果**:可以生成代码和 smoke check,但不能声称已经复现论文结果。论文未给出的超参数、模块或处理步骤必须标注为 `ASSUMPTION` 或 `TODO`。
13. **主论文源码存在时必须停在代码导读阶段**:如果已找到、已 clone、已跳过 clone 或本地已存在主论文官方/高度可信源码,本次技能流程的终点是“仓库导读 + 数据处理代码定位 + 写入报告 + 极简摘要”。不得继续修复源码、配置数据目录、安装依赖、下载数据、运行训练、运行评测或执行 inference。
14. **“复现”默认表示复现分析与准备**:用户只说“复现这篇论文”“重新跑一遍”“处理这篇论文”时,不代表允许训练;只有用户明确说“运行训练/开始训练/跑通训练/执行评测/下载数据/修复代码并运行”,才进入运行类任务。
15. **运行类任务不属于本技能自动阶段**:即使 exec 权限是 full/ask=off,本技能也不能自动安装依赖、下载数据、改官方代码或跑训练。
## 输入
接受以下输入:论文 PDF、arXiv 链接、论文主页链接、项目页链接、论文标题/摘要/正文片段、GitHub/GitLab 链接,以及“判断是否值得复现”“找代码”“自动 clone”“读仓库”“整理实验配置”“查数据集论文源码”“生成复现工程”“写 md 报告”等请求。
## 必须优先使用的工具
根据当前 OpenClaw 环境中可用的工具执行:
1. 使用 PDF 工具或文件读取能力抽取论文正文、附录、脚注、表格、图注和参考文献。
2. 使用 web/search/fetch 类工具读取 arXiv 页面、项目页、论文中出现的外部链接、数据集原论文页面和公开代码页面。
3. 使用 exec/shell 工具执行仓库和文件相关命令,例如 `git clone`、`python scripts/bootstrap_repo.py`、`python scripts/find_local_code.py`、`python scripts/inspect_repo_data_processing.py`、`python scripts/build_paper_spec.py`、`python scripts/scaffold_repro_project.py`、`python scripts/inspect_repro_project.py`、`dir`、`find`、写入 `.md` 文件。
4. Windows cmd 环境优先使用 Python 脚本:`python ...`;如果 `python` 不可用,尝试 `py ...`。
5. 不使用 `.sh` 作为默认路径;本技能不生成 `.sh` 训练脚本。
6. 如果 exec 不可用、被拒绝、网络失败或 Python 不可用,必须在报告中说明失败原因和退化路径。
## 工作区约定
1. 优先在当前 agent workspace 下创建 `paper-repro-workspace/`。
2. 对每篇主论文创建安全目录名:`paper-repro-workspace/<paper-slug>/`。
3. 详细报告:`paper-repro-workspace/<paper-slug>/repro-report.md`。
4. 主论文代码:`paper-repro-workspace/<paper-slug>/main-code/<repo-name>/`。
5. 数据集论文或数据集项目代码:`paper-repro-workspace/<paper-slug>/dataset-code/<dataset-slug>/<repo-name>/`。
6. 本地手动放置源码可位于:`paper-repro-workspace/<paper-slug>/local-code/`。
7. 无代码生成工程目录不得固定为 `repro-implementation`。必须根据论文框架、方法、模型或任务名生成:`paper-repro-workspace/<paper-slug>/<framework-or-method-slug>-reproduction/`。如果只能做 baseline,目录名必须包含 `baseline`。
8. 不要在用户系统随机目录中 clone 或生成代码,不要覆盖已有目录。
## 执行边界与停止条件
- **主论文源码存在即停止在代码导读阶段**:主论文官方/高度可信源码已 clone、已存在或本地已找到时,只做仓库导读、入口定位、数据处理代码定位、写报告和极简摘要。
- **禁止自动运行阶段**:主论文源码存在时,不安装依赖、不下载数据、不修复源码路径、不设置真实数据目录、不运行训练/评估/推理、不生成新的 `<method-slug>-reproduction/` 工程。
- **无主论文源码才生成复现工程**:只有线上和本地都没有主论文源码,并且论文可直接复现或部分可复现时,才生成 `<method-slug>-reproduction/`。
- **数据集源码和 baseline 源码不能替代主论文源码**:它们只能作为数据处理或实现参考证据;如果主论文没有源码,仍需判断并生成主论文复现工程。
- **后续短句不自动跑训练**:报告产出后,用户只说“复现/继续/重新跑一遍”时,默认重新执行本技能流程,不得擅自开始训练;明确要求训练时才视为新的运行任务。
## 总体流程
### 第 1 步:读取论文证据
从论文 PDF、arXiv 页面或用户提供文本中提取:标题、作者、年份、会议或期刊、摘要、核心贡献、方法、实验、附录、脚注、代码可用性声明、数据集、指标、baseline、训练细节、图表标题和图注、明确的 GitHub/GitLab/项目页/Hugging Face/数据集链接。
如果无法读取 PDF 或附件,先说明缺失的工具或输入,不要编造论文内容。
### 第 2 步:论文类型分类
必须给出一个主类型,必要时给出次类型。可选类型:综述论文、方法论文、提示词工程论文、基准评测论文、资源论文、理论论文、系统论文。
### 第 3 步:可复现性判定
使用 `references/reproducibility-rubric.md`。只能输出以下四个标签之一:可以直接复现、部分可复现、不具备实际可复现性、不是复现目标。
必须区分“能不能复现”和“需不需要复现”。不要把“有论文描述”误判成“可以直接复现”。
### 第 4 步:主论文代码线索搜索
必须主动搜索论文证据中的代码线索:PDF URL、脚注、附录、作者说明、arXiv abstract 页面、project page、supplementary material、OpenReview 页面、`code is available`、`source code`、`implementation`、`official repository`、`github`、`project page` 等。
如果发现多个仓库,优先判断作者官方仓库。无法确认时,标记为“官方性未验证”。
### 第 5 步:本地主论文源码检查
在进入无代码复现前,必须检查本地是否已有主论文相关源码。优先使用:
```text
python scripts/find_local_code.py --paper-slug <paper-slug> --name <paper-title-or-method> --workspace .
```
检查范围包括:`paper-repro-workspace/<paper-slug>/main-code/`、`paper-repro-workspace/<paper-slug>/local-code/`、当前 agent workspace、环境变量 `PAPER_REPRO_LOCAL_CODE_ROOTS`。数据集代码目录可以作为辅助证据,但不能直接判定为主论文源码。
如果本地找到高可信主论文源码,不进入无代码复现路径,而是进入“本地代码路径”:读取 README、依赖、训练入口、评测入口、配置、模型、数据处理代码,并写入报告。
### 第 6 步:数据集论文与数据集源码溯源
当主论文使用或发布数据集、benchmark 或标注资源时,必须执行此步骤。详细流程见 `references/dataset-source-tracing.md`。
对每个关键数据集,必须:
1. 提取数据集名称、简称、引用编号、数据集论文标题、项目页、数据下载页和脚注。
2. 检索数据集原论文、项目页、Papers with Code、GitHub/GitLab/Hugging Face 线索。
3. clone 前先检查本地是否已有相关源码。
4. 找到官方或可能官方源码后 clone 或跳过 clone。
5. 使用 `scripts/inspect_repo_data_processing.py` 或等价只读检查定位数据处理代码。
6. 报告数据处理代码文件、入口命令、关键函数/类、README 证据和对主论文复现的影响。
### 第 7 步:有主论文代码时自动执行并导读
如果发现主论文官方/高度可信代码,必须:
1. 记录“检测到主论文代码仓库,进入自动仓库路径”。
2. clone 前判断目标路径是否已有同名源码文件夹;若已有,跳过 clone,只读检查。
3. Windows 优先执行:`python scripts/bootstrap_repo.py <repo-url> <paper-slug> main-code`;如 `python` 不可用,尝试 `py scripts/bootstrap_repo.py ...`。
4. clone 成功或发现现有目录后,继续做仓库导读,不能停在“已经 clone”或“已存在”。
5. 使用 `scripts/inspect_repo_data_processing.py <repo-path>` 定位数据处理代码。
6. 报告本地路径、clone 状态、重复目录提醒、依赖文件、安装命令候选、训练/推理/评测入口、数据集准备方式、配置文件、模型文件、训练文件、评测文件、数据处理文件。
7. 完成第 6 项后必须写入报告并结束本技能流程;不得继续安装依赖、修复源码、设置真实数据路径、下载数据、运行训练、运行评测或执行 inference。
8. “可以直接复现”只表示具备复现条件,不表示现在开始执行训练。
### 第 8 步:无主论文源码时生成复现工程
只要满足以下条件,就必须生成复现工程,而不是只给建议:
- 线上没有官方/可信主论文源码;
- 本地没有主论文源码;
- 论文不是综述、纯理论或非复现目标;
- 论文证据支持“可以直接复现”或“部分可复现”;
- 数据集、模型结构、训练循环、loss、指标至少能构造最小可行版本。
如果找到数据集相关源码或 baseline 源码,要将其作为数据处理和 baseline 证据输入复现工程,但不能终止主论文复现工程生成。
详细规则见 `references/no-code-reproduction.md`。
生成前必须先写 `paper-spec.yaml`。可以使用:
```text
python scripts/build_paper_spec.py <evidence-md> --out paper-repro-workspace/<paper-slug>/paper-spec.yaml
```
然后生成工程:
```text
python scripts/scaffold_repro_project.py paper-repro-workspace/<paper-slug>/paper-spec.yaml --out paper-repro-workspace/<paper-slug>/<framework-or-method-slug>-reproduction
```
生成后运行静态检查:
```text
python scripts/inspect_repro_project.py paper-repro-workspace/<paper-slug>/<framework-or-method-slug>-reproduction
```
不自动安装依赖,不下载大数据,不运行训练。轻量 `py_compile` 和文件完整性检查可以自动执行。
### 第 9 步:写入 Markdown 报告
最终必须把详细内容写入:`paper-repro-workspace/<paper-slug>/repro-report.md`。
报告模板见 `references/output-template.md`。必须记录:论文信息、分类、可复现性、代码搜索、主论文源码、本地源码、数据集源码、数据处理代码位置、复现工程生成结果、执行过的命令、不能复现原因、未完成项/人工确认项。
## 聊天输出格式
聊天中不要输出长报告。聊天回复只输出:
```markdown
[paper-repro-triage active]
- 报告文件:`paper-repro-workspace/<paper-slug>/repro-report.md`
- 主论文源码:已 clone / 已存在,跳过 clone / 本地已存在 / 未找到 / 等待审批 / clone 失败
- 数据集源码:已 clone N 个 / 已存在,跳过 clone N 个 / 本地已存在 N 个 / 未找到 / 部分找到 / 未检索
- 数据处理代码:已定位 N 处 / 未定位 / 不适用
- 复现工程:已生成 / 仅生成 skeleton / 未生成,路径:`paper-repro-workspace/<paper-slug>/<implementation-slug>/`
- 是否需要复现:需要 / 不需要 / 建议只做部分复现
- 是否能复现:可以直接复现 / 部分可复现 / 不具备实际可复现性 / 不是复现目标
- 核心原因:一句话说明;如果能复现则写“无核心阻碍”
- 执行边界:未运行训练 / 未安装依赖 / 未下载数据;如已存在主论文源码,写“已停在代码导读阶段”
```
## 安全与诚实规则
- 不要伪造已经执行过的命令。
- 不要伪造仓库文件名。
- 不要伪造 Markdown 文件已经写入。
- 不要声称精确复现,除非代码、数据、配置和评测协议都足够充分。
- 不要把第三方复现仓库当成官方仓库。
- 不要自动安装未知依赖、下载大数据、修复官方源码路径、设置真实数据目录或运行训练/评测/推理脚本;clone、跳过重复 clone、只读仓库检查、生成复现工程、静态检查可以自动执行。
- 所有论文未明确给出的超参数、路径、模型维度、loss 权重、数据处理细节必须标注 `ASSUMPTION`。
- 如果只能生成 baseline,必须命名为 baseline,不能命名为 paper reproduction。
- 如果生成的代码含 `TODO` 或 `NotImplementedError`,报告必须列出。
## 资源
- 可复现性判定标准:`references/reproducibility-rubric.md`
- Markdown 报告模板:`references/output-template.md`
- 数据集论文源码溯源流程:`references/dataset-source-tracing.md`
- 无代码复现工程流程:`references/no-code-reproduction.md`
- 仓库 bootstrap 脚本:`scripts/bootstrap_repo.py`
- 本地源码查找:`scripts/find_local_code.py`
- 数据处理代码定位:`scripts/inspect_repo_data_processing.py`
- paper spec 草稿:`scripts/build_paper_spec.py`
- 复现工程生成:`scripts/scaffold_repro_project.py`
- 复现工程检查:`scripts/inspect_repro_project.py`
FILE:scripts/bootstrap_repo.py
#!/usr/bin/env python3
"""Clone or inspect a GitHub/GitLab repository for paper reproduction workspaces.
Usage:
python scripts/bootstrap_repo.py <repo-url> [paper-slug] [bucket]
The script is intentionally read-only after clone: it does not install dependencies,
download data, run training, or perform git pull on existing directories.
"""
from __future__ import annotations
import argparse
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import Iterable
ALLOWED_PREFIXES = (
"https://github.com/",
"[email protected]:",
"https://gitlab.com/",
"[email protected]:",
)
DEPENDENCY_PATTERNS = (
"requirements", "environment", "pyproject.toml", "setup.py", "setup.cfg",
"Pipfile", "Dockerfile", "conda", "poetry.lock", "package.json",
)
ENTRY_RE = re.compile(r"^(train|main|run|eval|test|infer|demo).*\.(py|ipynb|sh|cmd|ps1)$", re.I)
def safe_component(value: str, default: str = "paper") -> str:
value = value.lower().replace("\\", "/")
value = re.sub(r"[^a-z0-9._/-]+", "-", value)
value = re.sub(r"/{2,}", "/", value).strip("/")
value = re.sub(r"(^|/)-+", r"\1", value)
value = re.sub(r"-+(/|$)", r"\1", value)
return value or default
def repo_name_from_url(url: str) -> str:
name = url.rstrip("/").split("/")[-1]
if ":" in name and url.startswith("git@"):
name = name.split(":")[-1]
if name.endswith(".git"):
name = name[:-4]
return re.sub(r"[^A-Za-z0-9._-]+", "-", name) or "repo"
def run(cmd: list[str], cwd: Path | None = None) -> tuple[int, str]:
try:
proc = subprocess.run(cmd, cwd=str(cwd) if cwd else None, text=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=300)
return proc.returncode, proc.stdout
except FileNotFoundError as exc:
return 127, str(exc)
except subprocess.TimeoutExpired as exc:
return 124, (exc.stdout or "") + "\n[timeout] command timed out"
def rel_files(root: Path, max_depth: int = 2) -> list[str]:
out: list[str] = []
if not root.exists():
return out
for path in root.rglob("*"):
try:
rel = path.relative_to(root)
except ValueError:
continue
if len(rel.parts) > max_depth:
continue
if any(part in {".git", "__pycache__", ".venv", "node_modules"} for part in rel.parts):
continue
out.append(str(rel).replace("\\", "/") + ("/" if path.is_dir() else ""))
return sorted(out)
def find_files(root: Path, max_depth: int, predicate) -> list[str]:
matches: list[str] = []
if not root.exists():
return matches
for path in root.rglob("*"):
if not path.is_file():
continue
try:
rel = path.relative_to(root)
except ValueError:
continue
if len(rel.parts) > max_depth:
continue
if any(part in {".git", "__pycache__", ".venv", "node_modules"} for part in rel.parts):
continue
if predicate(path):
matches.append(str(rel).replace("\\", "/"))
return sorted(matches)
def find_readme(root: Path) -> Path | None:
for name in ("README.md", "README.rst", "README.txt", "readme.md"):
candidate = root / name
if candidate.exists():
return candidate
for path in root.rglob("README*"):
try:
if len(path.relative_to(root).parts) <= 2 and path.is_file():
return path
except ValueError:
pass
return None
def read_head(path: Path, max_lines: int = 160) -> str:
try:
with path.open("r", encoding="utf-8", errors="replace") as f:
return "".join(line for _, line in zip(range(max_lines), f))
except OSError as exc:
return f"[read failed] {exc}"
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Clone or inspect repo in paper-repro-workspace")
parser.add_argument("repo_url")
parser.add_argument("paper_slug", nargs="?", default="paper")
parser.add_argument("bucket", nargs="?", default="main-code")
args = parser.parse_args(argv)
repo_url = args.repo_url.strip()
if not repo_url.startswith(ALLOWED_PREFIXES):
print(f"错误:仓库地址看起来不是 GitHub/GitLab URL:{repo_url}", file=sys.stderr)
return 2
safe_slug = safe_component(args.paper_slug, "paper")
safe_bucket = safe_component(args.bucket, "main-code")
root_dir = Path("paper-repro-workspace") / safe_slug / safe_bucket
root_dir.mkdir(parents=True, exist_ok=True)
repo_name = repo_name_from_url(repo_url)
target_dir = root_dir / repo_name
clone_status = "已 clone"
clone_note = "新克隆仓库。"
remote_url = ""
command_summary = ""
if target_dir.exists():
clone_status = "已存在,跳过 clone"
command_summary = "未执行 git clone,只做现有目录检查。"
if (target_dir / ".git").exists():
code, origin = run(["git", "-C", str(target_dir), "remote", "get-url", "origin"])
remote_url = origin.strip() if code == 0 else ""
if remote_url == repo_url:
clone_note = "目标目录已存在且 origin 与目标仓库一致;按规则不重复 clone,也不自动 git pull。"
elif remote_url:
clone_note = f"目标目录已存在且是 git 仓库,但 origin 与目标仓库不一致:{remote_url};按规则不覆盖、不重复 clone。"
else:
clone_note = "目标目录已存在且是 git 仓库,但没有读取到 origin;按规则不重复 clone。"
else:
clone_note = "目标目录已存在但不是 git 仓库;按规则不覆盖、不重复 clone。"
else:
command_summary = f"git clone {repo_url} {target_dir}"
code, output = run(["git", "clone", repo_url, str(target_dir)])
if code != 0:
print("=== 执行脚本 ===")
print("脚本:bootstrap_repo.py")
print(f"命令:{command_summary}")
print(output)
return code
print("=== 执行脚本 ===")
print("脚本:bootstrap_repo.py")
print(f"命令:{command_summary}")
print("")
print("=== 克隆结果 ===")
print(f"仓库状态:{clone_status}")
print(f"重复目录提醒:{clone_note}")
print(f"本地路径:{target_dir}")
print(f"远程地址:{repo_url}")
if remote_url:
print(f"现有 origin:{remote_url}")
print("\n=== 顶层结构 ===")
for item in rel_files(target_dir, 2)[:120]:
print(item)
print("\n=== 常见依赖文件 ===")
dep_files = find_files(target_dir, 3, lambda p: any(token.lower() in p.name.lower() for token in DEPENDENCY_PATTERNS))
for item in dep_files:
print(item)
print("\n=== 常见入口候选 ===")
entry_files = find_files(target_dir, 4, lambda p: bool(ENTRY_RE.match(p.name)))
for item in entry_files[:80]:
print(item)
print("\n=== README 摘要候选 ===")
readme = find_readme(target_dir)
if readme:
print(f"README 文件:{readme}")
print(read_head(readme, 160))
else:
print("未在前两层目录找到 README。")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/build_paper_spec.py
#!/usr/bin/env python3
"""Build a paper-spec.yaml draft from extracted evidence text.
This script intentionally creates a conservative draft. The model should edit the
YAML using paper evidence before scaffolding implementation code.
"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
def slugify(value: str, default: str = "paper") -> str:
value = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
return value[:80] or default
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8", errors="replace")
def infer_title(text: str) -> str:
for line in text.splitlines()[:80]:
clean = line.strip().strip("# ")
if 8 <= len(clean) <= 180 and not clean.lower().startswith(("abstract", "introduction", "arxiv")):
return clean
return "UNKNOWN"
def infer_method(title: str) -> str:
words = re.findall(r"[A-Za-z0-9]+", title)
if not words:
return "paper"
# Prefer acronym-like tokens or first two content words.
acronyms = [w for w in words if len(w) >= 3 and w.upper() == w]
if acronyms:
return acronyms[0]
return "-".join(words[:3])
def main() -> int:
parser = argparse.ArgumentParser(description="Create paper-spec.yaml draft")
parser.add_argument("evidence_md")
parser.add_argument("--out", required=True)
args = parser.parse_args()
evidence_path = Path(args.evidence_md)
text = read_text(evidence_path) if evidence_path.exists() else ""
title = infer_title(text)
method = infer_method(title)
paper_slug = slugify(title)
method_slug = slugify(method, paper_slug)
implementation_slug = f"{method_slug}-reproduction"
yaml = f"""# Generated by build_paper_spec.py. Edit with paper evidence before scaffolding.
paper:
title: "{title}"
year: "UNKNOWN"
venue: "UNKNOWN"
task: "TODO"
modality: "TODO"
method_name: "{method}"
paper_slug: "{paper_slug}"
implementation_slug: "{implementation_slug}"
architecture:
type: "TODO"
modules:
- "TODO"
inputs:
- "TODO"
outputs:
- "TODO"
loss_terms:
- "TODO"
datasets:
- name: "TODO"
role: "train/eval"
source_paper: "TODO"
access: "TODO"
preprocessing: "TODO"
local_source_code: "TODO"
data_processing_files:
- "TODO"
training:
seed: 42 # ASSUMPTION: debug default unless paper specifies.
optimizer: "TODO"
learning_rate: "TODO"
batch_size: "TODO"
epochs_or_steps: "TODO"
scheduler: "TODO"
augmentations:
- "TODO"
hardware: "TODO"
evaluation:
metrics:
- "TODO"
protocol: "TODO"
baselines:
- "TODO"
evidence_status:
code_found: false
local_code_found: false
reproducibility: "TODO"
missing_fields:
- "TODO"
assumptions:
- "ASSUMPTION: fields marked TODO must be filled from paper evidence before running training."
"""
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(yaml, encoding="utf-8")
print("=== 执行脚本 ===")
print("脚本:build_paper_spec.py")
print(f"输入证据:{evidence_path}")
print(f"输出文件:{out}")
print(f"建议工程目录名:{implementation_slug}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/find_local_code.py
#!/usr/bin/env python3
"""Find local source repositories before cloning or creating from-scratch implementations."""
from __future__ import annotations
import argparse
import json
import os
import re
import subprocess
from pathlib import Path
INDICATORS = {
"git": 8,
"readme": 3,
"requirements": 2,
"pyproject": 2,
"setup": 2,
"train": 3,
"eval": 2,
"model": 2,
"dataset": 2,
"config": 2,
}
def norm(value: str) -> str:
return re.sub(r"[^a-z0-9]+", " ", value.lower()).strip()
def safe_slug(value: str) -> str:
return re.sub(r"[^a-z0-9._-]+", "-", value.lower()).strip("-") or "paper"
def split_terms(value: str) -> list[str]:
return [t for t in norm(value).split() if len(t) >= 2]
def run_origin(path: Path) -> str:
if not (path / ".git").exists():
return ""
try:
proc = subprocess.run(["git", "-C", str(path), "remote", "get-url", "origin"], text=True,
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, timeout=10)
return proc.stdout.strip() if proc.returncode == 0 else ""
except Exception:
return ""
def is_repo_like(path: Path) -> bool:
if not path.is_dir():
return False
names = {p.name.lower() for p in path.iterdir() if p.exists()}
if ".git" in names:
return True
if any(name.startswith("readme") for name in names):
return True
if any(name in names for name in ["requirements.txt", "pyproject.toml", "setup.py", "environment.yml", "environment.yaml"]):
return True
if any(name in names for name in ["src", "models", "model", "datasets", "data", "configs", "config"]):
return True
return False
def score_repo(path: Path, terms: list[str], repo_url: str = "") -> tuple[int, list[str], str]:
score = 0
reasons: list[str] = []
name_norm = norm(path.name)
for term in terms:
if term in name_norm:
score += 5
reasons.append(f"目录名匹配:{term}")
origin = run_origin(path)
if repo_url and origin and origin.rstrip("/").lower() == repo_url.rstrip("/").lower():
score += 30
reasons.append("git origin 与目标 URL 一致")
if (path / ".git").exists():
score += INDICATORS["git"]
reasons.append("包含 .git")
for child in path.iterdir() if path.exists() else []:
lower = child.name.lower()
if lower.startswith("readme"):
score += INDICATORS["readme"]
reasons.append(f"包含 {child.name}")
if lower.startswith("requirements") or lower.startswith("environment"):
score += INDICATORS["requirements"]
reasons.append(f"包含依赖文件 {child.name}")
if lower == "pyproject.toml":
score += INDICATORS["pyproject"]
reasons.append("包含 pyproject.toml")
if lower in {"src", "models", "model"}:
score += INDICATORS["model"]
reasons.append(f"包含模型/源码目录 {child.name}")
if lower in {"datasets", "dataset", "data"}:
score += INDICATORS["dataset"]
reasons.append(f"包含数据目录 {child.name}")
if lower in {"configs", "config"}:
score += INDICATORS["config"]
reasons.append(f"包含配置目录 {child.name}")
if re.match(r"^(train|main|run|eval|test).*\.(py|ipynb|sh|cmd)$", lower):
score += INDICATORS["train"]
reasons.append(f"包含入口候选 {child.name}")
return score, reasons, origin
def gather_roots(workspace: Path, paper_slug: str, extra_roots: list[str]) -> list[Path]:
roots = [
workspace / "paper-repro-workspace" / paper_slug / "main-code",
workspace / "paper-repro-workspace" / paper_slug / "dataset-code",
workspace / "paper-repro-workspace" / paper_slug / "local-code",
workspace,
]
env_roots = os.environ.get("PAPER_REPRO_LOCAL_CODE_ROOTS", "")
for item in re.split(r"[;:]", env_roots):
if item.strip():
roots.append(Path(item.strip()))
roots.extend(Path(r) for r in extra_roots)
unique: list[Path] = []
seen: set[str] = set()
for root in roots:
try:
key = str(root.resolve())
except Exception:
key = str(root)
if key not in seen:
seen.add(key)
unique.append(root)
return unique
def walk_candidates(root: Path, max_depth: int) -> list[Path]:
out: list[Path] = []
if not root.exists() or not root.is_dir():
return out
root = root.resolve()
stack = [(root, 0)]
while stack:
path, depth = stack.pop()
if path.name in {".git", "__pycache__", ".venv", "node_modules"}:
continue
if is_repo_like(path):
out.append(path)
if (path / ".git").exists() and path != root:
continue
if depth < max_depth:
try:
children = [p for p in path.iterdir() if p.is_dir()]
except OSError:
children = []
stack.extend((child, depth + 1) for child in children)
return out
def main() -> int:
parser = argparse.ArgumentParser(description="Find local source code candidates")
parser.add_argument("--name", action="append", default=[], help="paper, method, dataset, or repo name")
parser.add_argument("--repo-url", default="")
parser.add_argument("--paper-slug", default="paper")
parser.add_argument("--workspace", default=".")
parser.add_argument("--root", action="append", default=[])
parser.add_argument("--max-depth", type=int, default=4)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
paper_slug = safe_slug(args.paper_slug)
terms: list[str] = []
for name in args.name:
terms.extend(split_terms(name))
if args.repo_url:
repo_tail = args.repo_url.rstrip("/").split("/")[-1].replace(".git", "")
terms.extend(split_terms(repo_tail))
terms = sorted(set(terms))
roots = gather_roots(Path(args.workspace), paper_slug, args.root)
results = []
seen: set[str] = set()
for root in roots:
for cand in walk_candidates(root, args.max_depth):
key = str(cand.resolve())
if key in seen:
continue
seen.add(key)
score, reasons, origin = score_repo(cand, terms, args.repo_url)
if score <= 0 and terms:
continue
results.append({
"path": str(cand),
"score": score,
"origin": origin,
"reasons": reasons,
})
results.sort(key=lambda r: r["score"], reverse=True)
payload = {"script": "find_local_code.py", "terms": terms, "roots": [str(r) for r in roots], "results": results[:20]}
if args.json:
print(json.dumps(payload, ensure_ascii=False, indent=2))
else:
print("=== 执行脚本 ===")
print("脚本:find_local_code.py")
print(f"检索词:{', '.join(terms) if terms else '(无)'}")
print("=== 本地源码候选 ===")
if not results:
print("未找到高相关本地源码候选。")
for item in results[:20]:
print(f"score={item['score']} path={item['path']}")
if item["origin"]:
print(f" origin={item['origin']}")
for reason in item["reasons"][:8]:
print(f" - {reason}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/inspect_repo_data_processing.py
#!/usr/bin/env python3
"""Inspect repository files and identify likely data processing code."""
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
KEYWORDS = [
"dataset", "dataloader", "data_loader", "datamodule", "preprocess", "prepare",
"process", "processing", "transform", "augment", "crop", "resize", "split",
"annotation", "label", "metadata", "extract", "feature", "frames", "tokenize",
"download", "convert", "benchmark", "loader", "sampler",
]
CODE_EXTS = {".py", ".ipynb", ".sh", ".cmd", ".ps1", ".yaml", ".yml", ".json", ".txt", ".md"}
SKIP_DIRS = {".git", "__pycache__", ".venv", "venv", "node_modules", "dist", "build"}
DEF_RE = re.compile(r"^\s*(class|def)\s+([A-Za-z_][A-Za-z0-9_]*)")
SECTION_RE = re.compile(r"^#{1,4}\s+(.+)")
def rel(path: Path, root: Path) -> str:
try:
return str(path.relative_to(root)).replace("\\", "/")
except ValueError:
return str(path)
def read_text(path: Path, limit: int = 200_000) -> str:
try:
return path.read_text(encoding="utf-8", errors="replace")[:limit]
except Exception:
return ""
def score_path(path: Path, text: str) -> tuple[int, list[str]]:
lower_path = str(path).lower().replace("\\", "/")
lower_text = text.lower()
score = 0
reasons: list[str] = []
for kw in KEYWORDS:
if kw in lower_path:
score += 5
reasons.append(f"路径包含 {kw}")
count = lower_text.count(kw)
if count:
score += min(count, 5)
if "torch.utils.data" in lower_text:
score += 12
reasons.append("包含 torch.utils.data")
if "class " in text and "dataset" in lower_text:
score += 8
reasons.append("可能定义 Dataset 类")
if "if __name__" in lower_text and any(k in lower_text for k in ["preprocess", "prepare", "dataset", "data"]):
score += 5
reasons.append("可能是可执行数据处理脚本")
return score, reasons
def extract_symbols(text: str, limit: int = 20) -> list[str]:
out: list[str] = []
for line in text.splitlines():
m = DEF_RE.match(line)
if m:
out.append(f"{m.group(1)} {m.group(2)}")
if len(out) >= limit:
break
return out
def extract_readme_sections(path: Path, text: str) -> list[str]:
lines = text.splitlines()
sections: list[str] = []
capture = False
buf: list[str] = []
title = ""
for line in lines:
m = SECTION_RE.match(line)
if m:
if capture and buf:
sections.append(title + "\n" + "\n".join(buf[:20]))
title = line
capture = any(k in m.group(1).lower() for k in ["data", "dataset", "preprocess", "prepare", "download", "training", "benchmark"])
buf = []
elif capture:
buf.append(line)
if capture and buf:
sections.append(title + "\n" + "\n".join(buf[:20]))
return sections[:6]
def main() -> int:
parser = argparse.ArgumentParser(description="Locate data processing code in a repository")
parser.add_argument("repo_path")
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
root = Path(args.repo_path)
if not root.exists():
print(f"错误:路径不存在:{root}")
return 2
candidates = []
readme_sections = []
for path in root.rglob("*"):
if any(part in SKIP_DIRS for part in path.parts):
continue
if not path.is_file() or path.suffix.lower() not in CODE_EXTS:
continue
text = read_text(path)
score, reasons = score_path(path, text)
if path.name.lower().startswith("readme"):
readme_sections.extend({"file": rel(path, root), "section": s} for s in extract_readme_sections(path, text))
if score >= 6:
candidates.append({
"path": rel(path, root),
"score": score,
"reasons": reasons[:8],
"symbols": extract_symbols(text),
})
candidates.sort(key=lambda x: x["score"], reverse=True)
payload = {"script": "inspect_repo_data_processing.py", "repo_path": str(root), "candidates": candidates[:50], "readme_sections": readme_sections[:10]}
if args.json:
print(json.dumps(payload, ensure_ascii=False, indent=2))
else:
print("=== 执行脚本 ===")
print("脚本:inspect_repo_data_processing.py")
print(f"仓库路径:{root}")
print("\n=== 数据处理代码候选 ===")
if not candidates:
print("未定位到明显数据处理代码候选。")
for item in candidates[:30]:
print(f"score={item['score']} file={item['path']}")
for r in item["reasons"]:
print(f" - {r}")
if item["symbols"]:
print(f" symbols: {', '.join(item['symbols'][:12])}")
print("\n=== README 数据相关章节候选 ===")
if not readme_sections:
print("未定位到 README 数据相关章节。")
for sec in readme_sections[:6]:
print(f"--- {sec['file']} ---")
print(sec["section"][:1200])
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/inspect_repro_project.py
#!/usr/bin/env python3
"""Inspect a generated reproduction project without installing dependencies or training."""
from __future__ import annotations
import argparse
import py_compile
from pathlib import Path
REQUIRED = [
"README.md",
"repro-docs/repro-notes.md",
"repro-docs/evidence-map.md",
"repro-docs/paper-spec.yaml",
"repro-docs/requirements.txt",
"config.py",
"main.py",
"run.py",
"data/__init__.py",
"data/dataset.py",
"data/preprocess.py",
"models/__init__.py",
"models/model.py",
"engine/__init__.py",
"engine/train.py",
"engine/evaluate.py",
"utils/__init__.py",
"utils/common.py",
"utils/metrics.py",
]
FORBIDDEN_DEFAULTS = [
"configs/default.yaml",
"configs/debug.yaml",
"configs/ablation.yaml",
"losses/paper_loss.py",
"loss.py",
"scripts/train.sh",
"scripts/eval.sh",
"scripts/train.cmd",
"scripts/eval.cmd",
]
def read(path: Path) -> str:
try:
return path.read_text(encoding="utf-8", errors="replace")
except Exception:
return ""
def main() -> int:
parser = argparse.ArgumentParser(description="Inspect generated concise repro project")
parser.add_argument("project_path")
args = parser.parse_args()
root = Path(args.project_path)
print("=== 执行脚本 ===")
print("脚本:inspect_repro_project.py")
print(f"工程路径:{root}")
if not root.exists():
print("错误:工程路径不存在。")
return 2
print("\n=== 必需文件检查 ===")
missing = []
for rel in REQUIRED:
path = root / rel
if path.exists():
print(f"OK {rel}")
else:
print(f"MISSING {rel}")
missing.append(rel)
print("\n=== 不应默认生成的旧结构检查 ===")
forbidden_found = []
for rel in FORBIDDEN_DEFAULTS:
path = root / rel
if path.exists():
print(f"FOUND_OLD_STRUCTURE {rel}")
forbidden_found.append(rel)
else:
print(f"OK_ABSENT {rel}")
print("\n=== Python 静态编译检查 ===")
compile_errors = []
for path in sorted(root.rglob("*.py")):
try:
py_compile.compile(str(path), doraise=True)
print(f"OK {path.relative_to(root)}")
except Exception as exc:
print(f"FAIL {path.relative_to(root)}: {exc}")
compile_errors.append(str(path.relative_to(root)))
print("\n=== TODO / ASSUMPTION / NotImplementedError 统计 ===")
markers = {"TODO": 0, "ASSUMPTION": 0, "NotImplementedError": 0}
marker_files = []
for path in root.rglob("*"):
if path.is_file() and path.suffix.lower() in {".py", ".md", ".yaml", ".yml", ".txt"}:
text = read(path)
counts = {k: text.count(k) for k in markers}
if any(counts.values()):
marker_files.append((str(path.relative_to(root)).replace("\\", "/"), counts))
for k, v in counts.items():
markers[k] += v
for k, v in markers.items():
print(f"{k}: {v}")
for file, counts in marker_files[:50]:
parts = ", ".join(f"{k}={v}" for k, v in counts.items() if v)
print(f"- {file}: {parts}")
print("\n=== 结论 ===")
if missing or compile_errors or forbidden_found:
print("状态:部分通过")
if missing:
print("缺失文件:" + ", ".join(missing))
if compile_errors:
print("编译失败:" + ", ".join(compile_errors))
if forbidden_found:
print("发现旧结构:" + ", ".join(forbidden_found))
return 1
print("状态:通过静态检查。未安装依赖,未下载数据,未运行训练。")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:scripts/scaffold_repro_project.py
#!/usr/bin/env python3
"""Generate a concise, structured PyTorch reproduction project from paper-spec.yaml.
The generated layout is intentionally close to many small/medium research repos:
main.py + config.py + run.py at the root, with data/, models/, engine/ and utils/
subpackages. It avoids multiple YAML configs, shell scripts, and a separate losses
package by default.
"""
from __future__ import annotations
import argparse
import re
import shutil
from pathlib import Path
def parse_simple_yaml(path: Path) -> dict[str, str]:
"""Tiny YAML-ish parser for scalar values used by this skill.
This avoids external dependencies. It is not a general YAML parser.
"""
data: dict[str, str] = {}
stack: list[str] = []
for raw in path.read_text(encoding="utf-8", errors="replace").splitlines():
if not raw.strip() or raw.lstrip().startswith("#"):
continue
indent = len(raw) - len(raw.lstrip(" "))
line = raw.strip()
if ":" not in line or line.startswith("-"):
continue
key, value = line.split(":", 1)
key = key.strip()
value = value.strip().strip('"\'')
level = indent // 2
stack = stack[:level]
stack.append(key)
if value:
data[".".join(stack)] = value.split(" # ")[0].strip().strip('"\'')
return data
def slugify(value: str, default: str = "paper") -> str:
value = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
return value[:80] or default
def class_name(value: str, default: str = "PaperModel") -> str:
words = re.findall(r"[a-zA-Z0-9]+", value)
if not words:
return default
name = "".join(w[:1].upper() + w[1:] for w in words)
if name and name[0].isdigit():
name = "Paper" + name
return name or default
def write(path: Path, text: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser(description="Scaffold concise structured PyTorch reproduction project")
parser.add_argument("paper_spec")
parser.add_argument("--out", default="")
parser.add_argument("--force", action="store_true")
args = parser.parse_args()
spec_path = Path(args.paper_spec)
if not spec_path.exists():
print(f"错误:paper spec 不存在:{spec_path}")
return 2
spec = parse_simple_yaml(spec_path)
title = spec.get("paper.title", "UNKNOWN")
method = spec.get("paper.method_name") or spec.get("paper.task") or "paper"
implementation_slug = spec.get("paper.implementation_slug") or f"{slugify(method, 'paper')}-reproduction"
out = Path(args.out) if args.out else (spec_path.parent / implementation_slug)
model_cls = class_name(method)
if out.exists() and any(out.iterdir()) and not args.force:
print(f"错误:输出目录已存在且非空:{out}")
print("为避免覆盖,不会生成。请指定新目录或手动清理。")
return 3
out.mkdir(parents=True, exist_ok=True)
(out / "repro-docs").mkdir(parents=True, exist_ok=True)
shutil.copyfile(spec_path, out / "repro-docs" / "paper-spec.yaml")
for pkg in ["data", "models", "engine", "utils"]:
write(out / pkg / "__init__.py", "")
write(out / "repro-docs" / "requirements.txt", """torch
numpy
tqdm
""")
write(out / "README.md", f"""# {title} - Reproduction Scaffold
This project was generated by `paper-repro-triage` because no official/local main-paper source code was found.
It is a conservative PyTorch scaffold, not a claim of successful reproduction.
## Layout
- `main.py`: command-line entrypoint.
- `config.py`: argparse defaults and hyperparameters.
- `run.py`: dispatches preprocess/train/eval/inference by mode.
- `data/`: dataset loading and preprocessing.
- `models/`: paper model definition.
- `engine/`: training and evaluation loops.
- `utils/`: metrics, seed, path and JSON helpers.
## repro-docs
- `requirements.txt`: minimal dependency list.
- `paper-spec.yaml`: structured paper evidence used to generate this scaffold; it is not the training config.
- `evidence-map.md`: mapping from generated code files to paper evidence, dataset-code evidence or explicit assumptions.
- `repro-notes.md`: limitations, missing details and manual checks before real training.
## Commands
```cmd
python -m pip install -r repro-docs/requirements.txt
python main.py --mode preprocess --dataset paper --data_root ./data
python main.py --mode train --dataset paper --data_root ./data
python main.py --mode eval --checkpoint outputs/best.pt
```
## Safety notes
- This scaffold does not download datasets automatically.
- This scaffold does not install dependencies automatically.
- Paper-missing details are marked as `ASSUMPTION` or `TODO`.
- Run small smoke tests before real training.
""")
write(out / "repro-docs" / "repro-notes.md", f"""# Reproduction Notes
This file records what is still uncertain or unverified. It is the place for reproduction limitations, assumptions, missing data, and manual checks. Do not treat this scaffold as a completed paper reproduction until these notes are resolved.
## Status
- Source code found: false
- Local main-paper source code found: false
- Generated implementation directory: `{out.name}`
## Important limitations
- This code is a scaffold generated from paper evidence and explicit assumptions.
- Do not report paper-level reproduction results until real data, dependencies, training and evaluation have been confirmed.
## Assumptions to verify
- Model dimensions and module details marked TODO.
- Dataset file layout and preprocessing steps marked TODO.
- Loss weights and scheduler details marked TODO unless paper-spec.yaml states them.
""")
write(out / "repro-docs" / "evidence-map.md", """# Evidence Map
This file links generated code to paper evidence, dataset-code evidence, baseline-code evidence, or explicit assumptions. If a code choice is not supported by the paper, mark it as `ASSUMPTION` or `TODO`.
| Code file | Purpose | Paper evidence | Assumption / TODO |
|---|---|---|---|
| config.py | Command-line args and hyperparameters | paper-spec.yaml training/evaluation sections | TODO fields require paper evidence |
| main.py | CLI entrypoint | common research-code pattern | no paper-specific assumption |
| run.py | mode dispatch | paper task flow | no paper-specific assumption |
| data/dataset.py | Dataset loader | datasets/preprocessing section | TODO: actual file layout |
| data/preprocess.py | Data processing hooks | dataset-source-tracing results | TODO: adapt to actual dataset |
| models/model.py | Model definition | architecture section | ASSUMPTION if architecture details missing |
| engine/train.py | Training loop and loss/objective | training + loss_terms section | ASSUMPTION for missing hyperparameters |
| engine/evaluate.py | Evaluation loop | evaluation section | TODO: exact metrics if missing |
| utils/metrics.py | Metrics | evaluation metrics section | TODO if metric undefined |
""")
write(out / "config.py", f'''import argparse
def build_parser():
parser = argparse.ArgumentParser(description="{title} reproduction scaffold")
parser.add_argument('--dataset', default='paper', help='dataset name or alias')
parser.add_argument('--mode', default='train', choices=['preprocess', 'train', 'eval', 'inference'])
parser.add_argument('--data_root', default='./data')
parser.add_argument('--output_dir', default='./outputs')
parser.add_argument('--checkpoint', default='', help='checkpoint path for eval/inference')
parser.add_argument('--epochs', type=int, default=1, help='ASSUMPTION: debug default; replace with paper value')
parser.add_argument('--batch_size', type=int, default=32, help='ASSUMPTION unless paper specifies')
parser.add_argument('--lr', type=float, default=1e-3, help='ASSUMPTION unless paper specifies')
parser.add_argument('--weight_decay', type=float, default=0.0)
parser.add_argument('--num_workers', type=int, default=0)
parser.add_argument('--seed', type=int, default=42)
parser.add_argument('--gpu', default='0')
parser.add_argument('--hidden_dim', type=int, default=256, help='TODO/ASSUMPTION: replace with paper value')
parser.add_argument('--input_dim', type=int, default=128, help='TODO/ASSUMPTION: replace with dataset feature dimension')
parser.add_argument('--output_dim', type=int, default=2, help='TODO/ASSUMPTION: replace with task output size')
parser.add_argument('--alpha', type=float, default=1.0, help='optional paper-specific loss weight')
parser.add_argument('--beta', type=float, default=1.0, help='optional paper-specific loss weight')
return parser
def parse_args(argv=None):
return build_parser().parse_args(argv)
''')
write(out / "main.py", '''import os
from config import parse_args
from run import Run
from utils.common import set_seed
if __name__ == '__main__':
args = parse_args()
# Set visible GPU before creating CUDA contexts.
os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu)
set_seed(args.seed)
print(args)
Run(args).main()
''')
write(out / "run.py", '''from data.preprocess import preprocess
from engine.train import train
from engine.evaluate import evaluate
class Run:
def __init__(self, args):
self.args = args
def main(self):
if self.args.mode == 'preprocess':
return preprocess(self.args)
if self.args.mode == 'train':
return train(self.args)
if self.args.mode in {'eval', 'inference'}:
return evaluate(self.args)
raise ValueError(f'Unsupported mode: {self.args.mode}')
''')
write(out / "utils" / "common.py", '''import json
import random
from pathlib import Path
import numpy as np
import torch
def set_seed(seed: int):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed)
torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
def get_device():
return torch.device('cuda' if torch.cuda.is_available() else 'cpu')
def ensure_dir(path):
Path(path).mkdir(parents=True, exist_ok=True)
def save_json(obj, path):
path = Path(path)
ensure_dir(path.parent)
path.write_text(json.dumps(obj, ensure_ascii=False, indent=2), encoding='utf-8')
''')
write(out / "data" / "dataset.py", '''# Data processing evidence:
# - Fill this section with dataset-source-tracing results.
# - If source repo processing files were found, list them here.
# TODO: Replace synthetic fallback with actual paper dataset parsing.
from pathlib import Path
import torch
from torch.utils.data import DataLoader, Dataset
class PaperDataset(Dataset):
def __init__(self, root, split='train'):
self.root = Path(root)
self.split = split
# TODO: Replace synthetic fallback with actual annotation/index loading.
self.items = list(range(8))
def __len__(self):
return len(self.items)
def __getitem__(self, index):
# ASSUMPTION: vector input fallback for smoke checking.
x = torch.randn(128)
y = torch.tensor(index % 2, dtype=torch.long)
return {'input': x, 'label': y}
def build_dataloader(args, split='train'):
dataset = PaperDataset(args.data_root, split=split)
shuffle = split == 'train'
return DataLoader(
dataset,
batch_size=args.batch_size,
shuffle=shuffle,
num_workers=args.num_workers,
)
''')
write(out / "data" / "preprocess.py", '''from pathlib import Path
# TODO: Implement dataset-specific preprocessing based on paper evidence and
# dataset-source-tracing results. Do not download restricted datasets here.
def preprocess(args):
data_root = Path(args.data_root)
data_root.mkdir(parents=True, exist_ok=True)
print(f'Preprocess placeholder. Data root: {data_root}')
print('TODO: add annotation conversion, tokenizer/vocab construction, frame extraction, or feature extraction.')
''')
write(out / "models" / "model.py", f'''# Model evidence:
# - Method name from paper-spec: {method}
# - TODO: Replace fallback MLP with paper-specific architecture.
from torch import nn
class {model_cls}(nn.Module):
def __init__(self, args):
super().__init__()
self.net = nn.Sequential(
nn.Linear(args.input_dim, args.hidden_dim),
nn.ReLU(),
nn.Linear(args.hidden_dim, args.output_dim),
)
def forward(self, batch):
x = batch['input']
return self.net(x)
def build_model(args):
return {model_cls}(args)
''')
write(out / "utils" / "metrics.py", '''import torch
def accuracy(logits, labels):
# TODO: Replace or extend with exact paper metrics.
preds = torch.argmax(logits, dim=-1)
return (preds == labels).float().mean().item()
''')
write(out / "engine" / "train.py", '''import torch
from torch import nn
from torch.optim import Adam
from tqdm import tqdm
from data.dataset import build_dataloader
from models.model import build_model
from utils.common import ensure_dir, get_device
from utils.metrics import accuracy
def build_criterion(args):
# TODO: Replace with paper-specific objective if different.
# Keep loss here unless the paper has a complex reusable loss module.
return nn.CrossEntropyLoss()
def train(args):
device = get_device()
ensure_dir(args.output_dir)
train_loader = build_dataloader(args, split='train')
model = build_model(args).to(device)
criterion = build_criterion(args)
optimizer = Adam(model.parameters(), lr=args.lr, weight_decay=args.weight_decay)
best_path = f'{args.output_dir}/best.pt'
for epoch in range(args.epochs):
model.train()
total_loss = 0.0
total_acc = 0.0
steps = 0
for batch in tqdm(train_loader, desc=f'epoch {epoch + 1}/{args.epochs}'):
batch = {k: v.to(device) if hasattr(v, 'to') else v for k, v in batch.items()}
logits = model(batch)
loss = criterion(logits, batch['label'])
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
total_acc += accuracy(logits.detach(), batch['label'])
steps += 1
print({'epoch': epoch + 1, 'loss': total_loss / max(steps, 1), 'accuracy': total_acc / max(steps, 1)})
torch.save({'model': model.state_dict(), 'args': vars(args)}, best_path)
print(f'Saved checkpoint: {best_path}')
''')
write(out / "engine" / "evaluate.py", '''import torch
from data.dataset import build_dataloader
from models.model import build_model
from utils.common import get_device, save_json
from utils.metrics import accuracy
def evaluate(args):
device = get_device()
loader = build_dataloader(args, split='test')
model = build_model(args).to(device)
if args.checkpoint:
ckpt = torch.load(args.checkpoint, map_location=device)
state = ckpt.get('model', ckpt)
model.load_state_dict(state, strict=False)
else:
print('WARNING: no checkpoint provided; evaluating randomly initialized model.')
model.eval()
scores = []
with torch.no_grad():
for batch in loader:
batch = {k: v.to(device) if hasattr(v, 'to') else v for k, v in batch.items()}
logits = model(batch)
scores.append(accuracy(logits, batch['label']))
result = {'accuracy_placeholder': sum(scores) / max(len(scores), 1)}
save_json(result, f'{args.output_dir}/eval.json')
print(result)
''')
print("=== 执行脚本 ===")
print("脚本:scaffold_repro_project.py")
print(f"论文:{title}")
print(f"方法:{method}")
print(f"工程路径:{out}")
print("结构:base layout (repro-docs/, main.py, config.py, run.py, data/, models/, engine/, utils/); can be extended when the paper requires")
return 0
if __name__ == "__main__":
raise SystemExit(main())
FILE:references/dataset-source-tracing.md
# 数据集论文与数据集源码溯源流程
目标:从主论文中识别关键数据集,轻量检索这些数据集的原论文或项目页是否有官方源码、处理代码或 benchmark 仓库。如果找到可信仓库,先查本地是否已有相关源码;没有才自动 clone。不下载论文 PDF,不下载数据集本体。
## 触发条件
主论文中出现以下信息时触发:
- 使用多个公开数据集进行实验。
- 发布新数据集、benchmark 或标注资源。
- 数据集对复现结论至关重要。
- 用户明确要求“爬取相关数据集论文”“找数据集论文源码”“克隆数据集代码”。
## 数据集优先级
如果数据集很多,默认最多处理 5 个:
1. 主实验表格中反复出现的数据集。
2. 论文新发布的数据集。
3. 与核心结论直接相关的数据集。
4. 需要特殊预处理或标注流程的数据集。
5. benchmark 评测协议依赖的数据集。
## 检索策略
对每个数据集,优先查:
- 主论文参考文献里的数据集原论文标题。
- 数据集名称 + `github`。
- 数据集名称 + `official code`。
- 数据集名称 + `project page`。
- 数据集论文标题 + `code`。
- Papers with Code 数据集页或任务页。
- arXiv abstract 页面里的 comments/code 链接。
## 本地优先规则
找到数据集相关仓库 URL 后,clone 前必须先检查本地已有源码:
```text
python scripts/find_local_code.py --paper-slug <paper-slug> --name <dataset-name-or-repo-name> --workspace .
```
检查范围:
- `paper-repro-workspace/<paper-slug>/dataset-code/`
- `paper-repro-workspace/<paper-slug>/local-code/`
- 当前 agent workspace
- 环境变量 `PAPER_REPRO_LOCAL_CODE_ROOTS`
如果本地存在同名或高相关源码,记录为 `本地已存在,跳过 clone`,并直接进入源码导读和数据处理代码定位。
## 仓库可信度判断
- 官方:论文作者、项目组织、数据集官网明确链接。
- 可能官方:作者主页或机构页链接,但没有明确“official”字样。
- 第三方:非作者维护、复现性质、社区实现。
- 未验证:只能通过搜索结果推测,缺少直接证据。
只自动 clone “官方”或“可能官方”的仓库。第三方仓库默认只记录 URL,不自动 clone,除非用户明确同意。
## 数据处理代码定位
对每个已经找到或本地存在的数据集源码,必须定位数据处理相关代码。优先使用:
```text
python scripts/inspect_repo_data_processing.py <repo-path>
```
必须检查并记录:
- 数据集类:`Dataset`、`DataModule`、`DataLoader`、`torch.utils.data.Dataset`。
- 数据加载文件:`dataset.py`、`datasets/*.py`、`data/*.py`、`loader.py`、`dataloader.py`。
- 预处理脚本:`preprocess.py`、`prepare_data.py`、`process_*.py`、`extract_*.py`、`convert_*.py`。
- 标注解析:`annotation`、`label`、`split`、`metadata`、`json/csv/txt` parsing。
- 特征抽取:`feature`、`extract_frames`、`tokenize`、`crop`、`resize`、`augment`。
- README / docs 中的 data preparation、preprocess、dataset setup 命令。
如果无法明确判断数据处理入口,要报告“未定位到明确数据处理入口”,并说明仅找到哪些候选文件。
## 报告字段
每个数据集至少记录:
- 数据集名称。
- 是否是主实验依赖。
- 原论文或项目页。
- 是否找到源码。
- 仓库 URL。
- 仓库可信度。
- clone / 本地状态。
- 本地路径。
- 数据处理代码位置。
- 数据处理入口命令或函数。
- README / 文件证据。
- 数据访问限制。
- 对主论文复现的影响。
## 禁止事项
- 不下载数据集本体。
- 不批量下载论文 PDF。
- 不绕过登录、申请、验证码、付费墙或授权限制。
- 不把第三方复现仓库伪装成官方仓库。
- 不声称数据集可用,除非找到明确下载或申请路径。
## 重复目录处理
当数据集源码仓库需要 clone 到 `paper-repro-workspace/<paper-slug>/dataset-code/<dataset-slug>/<repo-name>/` 时,必须先检查目标路径是否已经存在同名源码文件夹。
- 如果目标路径不存在:可以自动 `git clone`。
- 如果目标路径已经存在:不要再次 clone,不要自动 `git pull`,不要覆盖,也不要改用时间戳新目录;直接读取现有目录并在报告中标记 `已存在,跳过 clone`。
- 如果现有目录是 git 仓库:记录其 `origin`,并说明是否与目标仓库一致。
- 如果现有目录不是 git 仓库:记录目录冲突,提示用户手动处理或指定新目录。
聊天极简摘要中也必须体现数据集源码状态,例如:`数据集源码:本地已存在 1 个,已存在,跳过 clone 2 个`。
FILE:references/no-code-reproduction.md
# 无主论文源码时的复现工程生成流程
当主论文没有官方源码、线上没有可信主仓库、本地也没有主论文源码时,本技能进入无主论文源码复现路径。目标不是伪造完整结果,而是在证据足够时生成一个符合多数 PyTorch 论文仓库直觉、可审查、可继续开发的最小复现工程。
## 进入本流程前的硬性前提
本流程只在“无主论文源码”时进入。如果主论文官方/高度可信源码已经 clone、已存在或本地已找到,必须停止在仓库导读和报告阶段,不得生成新的复现工程,也不得运行训练。数据集源码、baseline 源码或相关论文源码不算主论文源码;它们不能阻止本流程。
## 生成条件
同时满足以下条件时必须生成复现工程:
- 论文是方法论文、系统论文、可执行 benchmark 方法,或资源论文中的 benchmark 使用流程。
- 可复现性结论为“可以直接复现”或“部分可复现”。
- 论文中能提取出最小可行实现所需信息:输入、输出、模型或流程模块、loss/objective、数据集/替代数据、评测指标。
- 缺失信息可以用明确假设补齐,并且不会改变论文核心方法。
如果只找到数据集相关源码、baseline 源码或旧方法源码,仍然要继续生成主论文复现工程。它们只能作为数据处理、baseline 或实现参考证据,不能替代主论文源码。
## 不生成 paper reproduction 的情况
以下情况不能生成 paper reproduction,只能生成 baseline 或实验设计记录:
- 综述、观点或理论分析为主。
- 关键数据、权重、系统或闭源 API 不可获得,且没有合理缩小版。
- 模型结构、loss、评测协议都不清楚。
- 论文目标是 benchmark 定义而不是方法复现。
如果能构造一个合理 baseline,但不能构造论文方法,目录名必须包含 `baseline`,并在报告中说明不是论文原方法复现。
## 目录命名
工程目录不得固定为 `repro-implementation`。根据论文框架、方法、模型或任务名生成:
```text
paper-repro-workspace/<paper-slug>/<framework-or-method-slug>-reproduction/
```
示例:
- SGAN:`sgan-reproduction/`
- MI2LaTeX:`mi2latex-reproduction/`
- Diffusion Transformer:`dit-reproduction/`
- VideoMAE:`videomae-reproduction/`
- 无法判断方法名:`<paper-slug>-reproduction/`
## 最低基本盘工程结构
默认生成“有结构但不重”的 PyTorch 工程。基本盘采用根目录入口文件 + 四个职责明确的代码目录 + 一个复现文档目录。该结构是最低推荐结构,不是不可更改的硬性模板;如果论文需要 tokenizer、decoder、retrieval、beam search、多阶段训练或特殊评测,可以在此基础上增加目录或文件。
```text
<method-slug>-reproduction/
├── README.md
├── repro-docs/
│ ├── requirements.txt
│ ├── paper-spec.yaml
│ ├── evidence-map.md
│ └── repro-notes.md
├── config.py
├── main.py
├── run.py
├── data/
│ ├── __init__.py
│ ├── dataset.py
│ └── preprocess.py
├── models/
│ ├── __init__.py
│ └── model.py
├── engine/
│ ├── __init__.py
│ ├── train.py
│ └── evaluate.py
└── utils/
├── __init__.py
├── common.py
└── metrics.py
```
`repro-docs/` 只存放复现文档和依赖清单,不放训练入口代码。根目录保留 `main.py`、`config.py`、`run.py`,让用户一眼看到如何运行;真正实现放入 `data/`、`models/`、`engine/`、`utils/`。
## `repro-docs/` 四个文件的作用
### `repro-docs/requirements.txt`
作用:记录最小依赖清单,方便用户创建环境。只写运行脚手架和最小复现所需依赖,例如 `torch`、`numpy`、`tqdm`。不要把尚未验证的重型依赖、系统依赖或数据下载工具随意塞进去;如需特殊依赖,在 `repro-notes.md` 中说明待确认。
### `repro-docs/paper-spec.yaml`
作用:记录从论文中抽取出的结构化规格,是生成代码的证据输入,不是训练配置文件。它应包含任务、模型模块、输入输出、数据集、loss、训练超参数、评测指标、缺失项和假设。训练时的命令行参数仍由 `config.py` 管理。
### `repro-docs/evidence-map.md`
作用:记录“代码文件 ↔ 论文证据/数据集源码证据/明确假设”的映射。每个生成的核心文件都要能追溯依据,例如 `models/model.py` 来自方法章节或架构图,`data/preprocess.py` 来自数据集论文源码或主论文预处理描述。它用于防止把推测伪装成论文事实。
### `repro-docs/repro-notes.md`
作用:记录复现状态、限制、缺失信息和人工确认项。包括尚未下载的数据、没有安装的依赖、论文没给出的超参数、只能做 skeleton 的原因、数据访问限制、以及运行真实训练前必须确认的事项。
## 不默认生成的结构
不要默认生成以下内容,除非论文或用户明确需要:
- `configs/default.yaml`、`configs/debug.yaml`、`configs/ablation.yaml`:默认只用 `config.py` + argparse。复杂项目才可扩展配置文件。
- `losses/` 或 `loss.py`:默认把 loss 放在 `engine/train.py` 的 `build_criterion()` 中;只有多个可复用复杂 loss 时才拆出。
- `scripts/train.sh`、`scripts/eval.sh`、`.cmd`:默认只用 Python 命令行入口。
- `tests/`:默认不生成;如果用户要求工程测试,再生成 `tests/`。静态检查由 skill 自带 `inspect_repro_project.py` 完成。
## 代码文件职责
### `config.py`
唯一配置入口。使用 `argparse` 定义常用命令行参数、默认超参数和路径。不要默认生成多个 YAML 配置文件。
必须包含:
- `--mode train/eval/inference/preprocess`
- `--dataset`
- `--data_root`
- `--output_dir`
- `--epochs`
- `--batch_size`
- `--lr`
- `--seed`
- `--gpu`
- `--checkpoint`
- 论文特有参数,例如 `--alpha`、`--beta`、`--beam_size`、`--max_len` 等
论文未给出的默认值必须注释 `ASSUMPTION`。
### `main.py`
唯一命令行入口。负责解析参数、设置随机种子和 GPU,然后把配置交给 `Run(args).main()`。
推荐命令:
```cmd
python main.py --mode preprocess --dataset <dataset> --data_root <path>
python main.py --mode train --dataset <dataset> --data_root <path>
python main.py --mode eval --checkpoint outputs/best.pt
```
### `run.py`
统一调度文件。根据 `args.mode` 调用 `data.preprocess`、`engine.train`、`engine.evaluate` 或 inference 逻辑。
### `data/dataset.py`
数据集读取和基础 transform。数据集处理证据来自数据集源码溯源时,必须在文件头写明:相关仓库、处理脚本、README 证据和可复用部分。
### `data/preprocess.py`
数据准备、标注转换、tokenizer/vocab 构建、图像/视频/文本预处理、数据 split 构建等。只写代码和入口,不自动下载数据集本体。
### `models/model.py`
模型定义文件。包含论文核心模型类,例如 `PaperModel` 或具体方法名模型类。若某些模块缺少论文细节,可以写 TODO 或 baseline 模块,但必须在文件头标注 `ASSUMPTION`。
### `engine/train.py`
训练循环。loss 通常放在 `build_criterion()` 或训练步骤中,除非论文 loss 极其复杂或有多个可复用组件,否则不要生成独立 `loss.py` 或 `losses/` 目录。
必须包含:dataloader、model、optimizer/scheduler、loss/objective、epoch/step 循环、checkpoint 保存、logging。
### `engine/evaluate.py`
评测逻辑。包含 checkpoint 加载、test dataloader、指标计算和 `outputs/eval.json` 保存。指标必须来自论文;论文没给出时标注 TODO。
### `utils/metrics.py`
指标函数。只存放评估指标,不存放训练 loss。
### `utils/common.py`
随机种子、路径创建、JSON 保存、device 选择、简单 logging 等通用函数。
## 静态检查
生成后运行:
```text
python scripts/inspect_repro_project.py <implementation-path>
```
允许自动执行:文件完整性检查、`py_compile`、TODO/ASSUMPTION/NotImplementedError 统计。
禁止自动执行:安装依赖、下载大数据、运行训练、评测完整数据集。
## 报告要求
报告必须写:
- 是否生成复现工程。
- 工程路径。
- 文件清单。
- `repro-docs/` 四个文件的用途。
- 每个代码文件的作用。
- 每个代码文件依据的论文证据。
- 数据集相关源码如何影响 `data/dataset.py` 和 `data/preprocess.py`。
- 哪些超参数来自论文,哪些是 ASSUMPTION。
- 未完成项/人工确认项。
FILE:references/output-template.md
# Markdown 报告模板
本文件用于指导详细报告写入 `paper-repro-workspace/<paper-slug>/repro-report.md`。聊天回复只保留极简摘要,不要把本报告全文贴到聊天里,除非文件写入失败。
# 论文复现报告:<论文标题>
生成时间:<时间>
主论文输入:<PDF / arXiv / URL / 用户文本>
报告目录:`paper-repro-workspace/<paper-slug>/`
## 1. 结论摘要
| 项目 | 结论 |
|---|---|
| 论文类型 | 综述 / 方法 / 提示词工程 / 基准评测 / 资源 / 理论 / 系统 |
| 是否需要复现 | 需要 / 不需要 / 建议只做部分复现 |
| 是否能复现 | 可以直接复现 / 部分可复现 / 不具备实际可复现性 / 不是复现目标 |
| 主论文源码 | 已 clone / 已存在,跳过 clone / 本地已存在 / 未找到 / 等待审批 / clone 失败 |
| 数据集源码 | 已 clone N 个 / 已存在,跳过 clone N 个 / 本地已存在 N 个 / 未找到 / 部分找到 / 未检索 |
| 数据处理代码 | 已定位 N 处 / 未定位 / 不适用 |
| 复现工程 | 已生成 / 仅生成 skeleton / 未生成 |
| 核心阻碍 | 一句话说明 |
| 执行边界 | 未运行训练 / 未安装依赖 / 未下载数据 / 已停在代码导读阶段 |
| 报告文件 | `paper-repro-workspace/<paper-slug>/repro-report.md` |
## 2. 论文基础信息
- 标题:
- 作者:
- 年份:
- 会议/期刊/arXiv:
- 论文链接:
- 项目页:
- 主仓库:
- 相关数据集:
## 3. 论文分类
- 主类型:
- 次类型:
- 判断依据:
## 4. 可复现性结论
- 结论:可以直接复现 / 部分可复现 / 不具备实际可复现性 / 不是复现目标
- 是否建议复现:需要 / 不需要 / 建议只做部分复现
- 原因:
## 5. 证据摘要
| 维度 | 结论 | 证据 |
|---|---|---|
| 主论文官方代码 | | |
| 本地主论文源码 | | |
| 数据集源码 | | |
| 数据处理代码 | | |
| 训练配置 | | |
| 评测协议 | | |
| 硬件需求 | | |
| 主要阻碍 | | |
## 6. 主论文代码与自动执行结果
- 是否找到主论文代码:
- 仓库可信度:官方 / 可能官方 / 第三方 / baseline / 相关代码 / 未验证
- 仓库 URL:
- 自动执行状态:已 clone / 已存在,跳过 clone / 本地已存在 / 等待审批 / 执行失败 / 无代码可执行
- 本地路径:
- 重复目录提醒:
- 执行过的命令:
### 6.1 重复目录与跳过 clone 记录
- 是否出现同名源码文件夹:是 / 否
- 跳过 clone 的仓库:
- 使用的现有本地路径:
- 现有目录是否为 git 仓库:是 / 否 / 未知
- 现有 origin:
- 是否继续完成只读仓库检查:是 / 否
### 6.2 仓库导读
- README 关键信息:
- 依赖文件:
- 配置方式:
- 命令行入口:
- 训练入口:
- 评测入口:
- 推理入口:
- 数据集准备:
- 模型实现:
- 训练逻辑:
- 论文与代码差异:
### 6.3 主论文源码存在时的停止记录
- 是否停止在代码导读阶段:是 / 否
- 是否安装依赖:否
- 是否下载数据:否
- 是否修改官方源码:否
- 是否运行训练/评估/推理:否
- 停止原因:主论文源码已存在,本技能只完成复现准备、仓库导读和报告写入;运行训练属于新的显式运行任务。
## 7. 数据集论文与数据集源码溯源
| 数据集 | 是否主实验依赖 | 原论文/项目页 | 是否找到源码 | 仓库 URL | clone 状态 | 本地路径 | 数据处理代码位置 | 数据处理入口/命令 | 对主论文复现的影响 |
|---|---|---|---|---|---|---|---|---|---|
| | | | | | 已 clone / 已存在,跳过 clone / 本地已存在 / 未 clone | | | | |
### 7.1 数据处理代码定位明细
| 来源仓库 | 文件 | 类型 | 关键函数/类 | 证据 | 可复用方式 | 风险 |
|---|---|---|---|---|---|---|
| | | dataset / preprocess / tokenizer / split / feature / benchmark | | README / 文件名 / 代码片段 | | |
### 7.2 数据集访问限制
- 需要申请的数据集:
- 闭源或私有数据:
- 只提供数据下载但无处理代码:
- 对复现的影响:
## 8. 架构或流程解读
- 图示类型:标准模型架构 / prompt 或 agent 流程 / 系统架构 / 不确定
- 模型或流程类型:
- 关键模块:
- 输入输出:
- loss / objective:
- 是否可按代码实现:
## 9. 实验配置清单
| 项目 | 论文给出的信息 | 源码/数据集代码中的信息 | 缺失或需要确认 |
|---|---|---|---|
| 数据集 | | | |
| 预处理 | | | |
| 模型 | | | |
| loss / objective | | | |
| optimizer | | | |
| learning rate | | | |
| batch size | | | |
| epoch / steps | | | |
| GPU / 显存 | | | |
| 指标 | | | |
## 10. 无主论文源码复现工程生成结果
仅在无主论文源码时填写。即使找到数据集源码或 baseline 源码,只要主论文源码不存在且部分可复现,也必须填写本节。
| 项目 | 结论 |
|---|---|
| 是否生成复现工程 | 已生成 / 仅生成 skeleton / 未生成 |
| 工程路径 | `paper-repro-workspace/<paper-slug>/<method-slug>-reproduction/` |
| 生成依据 | 论文证据 / 数据集源码证据 / baseline 证据 / 明确假设 |
| 是否通过静态检查 | 通过 / 部分通过 / 未运行 |
| 是否运行训练 | 未运行,需用户确认 |
### 10.1 生成工程结构
以下为最低基本盘结构,不是不可更改的硬性目录。生成工程至少应包含这些职责清晰的模块;如果论文需要额外模块,可以在此基础上增加目录或文件。
```text
<method-slug>-reproduction/
├── README.md
├── repro-docs/
│ ├── requirements.txt
│ ├── paper-spec.yaml
│ ├── evidence-map.md
│ └── repro-notes.md
├── config.py
├── main.py
├── run.py
├── data/
│ ├── __init__.py
│ ├── dataset.py
│ └── preprocess.py
├── models/
│ ├── __init__.py
│ └── model.py
├── engine/
│ ├── __init__.py
│ ├── train.py
│ └── evaluate.py
└── utils/
├── __init__.py
├── common.py
└── metrics.py
```
### 10.2 `repro-docs/` 文件说明
| 文件 | 主要用途 | 注意事项 |
|---|---|---|
| `repro-docs/requirements.txt` | 最小依赖清单,用于创建复现环境 | 只写已确认或最小必要依赖;重型或未确认依赖写入 `repro-notes.md` |
| `repro-docs/paper-spec.yaml` | 论文证据规格,记录任务、模型、数据集、loss、训练和评测信息 | 不是训练配置;训练参数入口仍是 `config.py` |
| `repro-docs/evidence-map.md` | 映射每个代码文件对应的论文证据、数据集源码证据或假设 | 必须区分论文事实、源码证据和 ASSUMPTION |
| `repro-docs/repro-notes.md` | 记录复现限制、缺失信息、人工确认项和运行前注意事项 | 不要把未验证内容写成已完成结果 |
### 10.3 生成代码文件清单
| 文件 | 作用 | 依据 | 是否含假设 |
|---|---|---|---|
| `README.md` | 工程说明和运行命令 | | |
| `main.py` | 命令行入口,解析参数后交给 `Run(args).main()` | | |
| `config.py` | argparse 参数和超参数默认值 | | |
| `run.py` | 按 mode 调度 preprocess/train/eval/inference | | |
| `data/dataset.py` | 数据读取与 transform | | |
| `data/preprocess.py` | 数据处理脚本 | | |
| `models/model.py` | 模型定义 | | |
| `engine/train.py` | 训练循环与 loss/objective | | |
| `engine/evaluate.py` | 评测循环 | | |
| `utils/metrics.py` | 指标函数 | | |
| `utils/common.py` | seed、路径、日志等工具 | | |
### 10.4 config 参数
| 参数 | 默认值 | 来源 | 备注 |
|---|---|---|---|
| | | paper / dataset-code / baseline-code / assumption / todo | |
### 10.5 model 定义
- 文件:`models/model.py`
- 类名:
- 输入:
- 输出:
- 关键模块:
- 论文依据:
- 缺失/假设:
### 10.6 train 定义
- 文件:`engine/train.py`
- loss / objective:
- optimizer:
- scheduler:
- checkpoint:
- logging:
- 论文依据:
- 缺失/假设:
### 10.7 evaluate 定义
- 文件:`engine/evaluate.py`
- 指标:
- protocol:
- checkpoint:
- 输出文件:
- 论文依据:
- 缺失/假设:
### 10.8 数据处理实现
| 数据集 | 数据处理文件 | 入口命令 | 来源证据 | 风险 |
|---|---|---|---|---|
| | `data/preprocess.py` / `data/dataset.py` | `python main.py --mode preprocess ...` | | |
### 10.9 可执行命令
```cmd
python -m pip install -r repro-docs/requirements.txt
python main.py --mode preprocess --dataset <dataset> --data_root <path>
python main.py --mode train --dataset <dataset> --data_root <path>
python main.py --mode eval --checkpoint outputs/best.pt
```
### 10.10 未完成项 / 人工确认项
- 未下载数据:
- 未安装依赖:
- 论文缺失:
- 需要确认的假设:
## 11. 不能复现或不能精确复现的原因
- 原因 1:
- 原因 2:
- 原因 3:
## 12. 执行日志
| 时间 | 命令 / 工具 | 结果 | 备注 |
|---|---|---|---|
| | | | |
# 聊天极简摘要模板
```markdown
[paper-repro-triage active]
- 报告文件:`paper-repro-workspace/<paper-slug>/repro-report.md`
- 主论文源码:已 clone / 已存在,跳过 clone / 本地已存在 / 未找到 / 等待审批 / clone 失败
- 数据集源码:已 clone N 个 / 已存在,跳过 clone N 个 / 本地已存在 N 个 / 未找到 / 部分找到 / 未检索
- 数据处理代码:已定位 N 处 / 未定位 / 不适用
- 复现工程:已生成 / 仅生成 skeleton / 未生成,路径:`paper-repro-workspace/<paper-slug>/<implementation-slug>/`
- 是否需要复现:需要 / 不需要 / 建议只做部分复现
- 是否能复现:可以直接复现 / 部分可复现 / 不具备实际可复现性 / 不是复现目标
- 核心原因:一句话说明;如果能复现则写“无核心阻碍”
```
FILE:references/reproducibility-rubric.md
# 可复现性判定标准
只能使用以下四个结论之一:
1. 可以直接复现
2. 部分可复现
3. 不具备实际可复现性
4. 不是复现目标
## 可以直接复现
必须满足大部分条件:
- 有作者官方或高度可信代码仓库,或论文信息足以生成一个与论文方法一致的最小可运行实现。
- 数据集可公开获取,或论文提供明确申请流程。
- 训练和评测脚本、协议或足够清晰的流程可获得。
- 关键超参数、模型配置、预处理和指标足够明确。
- 不依赖不可获得的私有模型、私有数据或闭源 API。
- 硬件需求在用户可接受范围内,或有小规模可验证路径。
## 部分可复现
符合以下情况之一:
- 有代码,但数据集需要申请、部分缺失或预处理不完整。
- 有数据,但代码不完整或缺少训练脚本。
- 无官方源码,但核心方法、loss、数据管线和评测指标足以构造最小可行实现。
- 核心方法可实现,但超参数、消融或评测细节不足。
- 依赖大规模算力,但可以先复现缩小版或核心模块。
- prompt/agent 流程可以复现思路,但无法精确复现闭源模型行为。
## 不具备实际可复现性
符合以下情况之一:
- 无源码、无关键训练细节、无可得数据,且无法合理构造最小可行实现。
- 依赖私有数据、私有权重、内部日志或不可访问系统。
- 依赖闭源 API 的不可控行为,且 prompt、温度、版本、工具链缺失。
- 实验协议和指标定义不清,无法构造可靠对照。
- 所需硬件、数据规模或人工标注流程远超普通复现能力,且没有缩小版路径。
## 不是复现目标
符合以下情况之一:
- 综述、评论、观点文章或 survey。
- 主要贡献是概念框架或理论分析,没有可执行方法。
- 资源论文仅介绍数据集或平台,而用户目标不是复现数据构建过程。
- benchmark 论文只定义评测任务,用户更应使用其 benchmark,而不是“复现论文方法”。
## 是否需要复现的独立判断
“能不能复现”和“需不需要复现”分开判断:
- 如果论文是目标任务强相关方法论文,且代码/数据足够,通常“需要复现”。
- 如果只是综述或背景材料,通常“不需要复现”。
- 如果代码存在但算力或数据受限,通常“建议只做部分复现”。
- 如果没有代码但方法清楚,通常“建议生成最小复现工程,再由用户确认是否安装依赖/下载数据/训练”。
- 如果是数据集论文,通常优先复现数据加载、预处理和 benchmark 使用流程,而不是重新构建整个数据集。
FILE:agents/openai.yaml
interface:
display_name: "论文复现执行器"
short_description: "分析论文、检索数据集论文源码、自动克隆仓库并写入 Markdown 报告"
Audit a game, feature, live-ops layer, social system, or multiplayer concept for the quality and fit of its social design. Use when evaluating collaboration,...
--- name: game-design-multiplayer-feature-audit description: Audit a game, feature, live-ops layer, social system, or multiplayer concept for the quality and fit of its social design. Use when evaluating collaboration, competition, collaborate-to-compete structures, matchmaking, guilds/clubs, synchronous versus asynchronous play, realtime constraints, depth of social interaction, community formation, vanity/status systems, or how to add social play to a mostly single-player game. --- # Game Design Multiplayer Feature Audit Audit a design by asking what kind of social experience it is actually creating, for whom, at what coordination cost, and with what likely community effect. Use this skill when a design has multiplayer or social ambitions and you need to judge whether those ambitions are coherent, motivating, scalable, and well matched to the core fantasy of the game. ## Core principle Social design is not a checklist of features. A leaderboard, guild, chat channel, or PvP mode does not automatically create meaningful social play. Strong multiplayer design aligns player motivation, time structure, coordination demands, visibility, and community purpose. ## What to produce Generate: 1. **Audit target** - what is being reviewed and what kind of social experience it appears to aim for 2. **Social promise** - the core social fantasy or player promise 3. **Motivation map** - competition, collaboration, collaborate-to-compete, belonging, vanity/status, knowledge exchange 4. **Time and synchronization audit** - realtime, non-realtime, synchronous, asynchronous, or hybrid 5. **Social depth audit** - how deep the interaction really goes 6. **Community and status audit** - whether the system supports durable groups, identity, and readable prestige 7. **Risks / failure modes** - where the design is likely to break, flatten, or create friction 8. **Recommendations** - what to strengthen, stage, simplify, avoid, or postpone ## Process ### 1. Define the social promise State in one or two sentences what the feature is socially promising. Examples: - compete for rank and status against peers - cooperate with a small squad to solve hard encounters - contribute to a group goal while still pursuing personal goals - show off taste, city design, wealth, or mastery - let solo players feel the presence of others without hard coordination If the design appears to promise incompatible things at once, say so early. Common tension examples: - calm self-expression versus destructive PvP - casual mobile bursts versus rigid appointment play - individual authorship versus committee-driven collaboration ### 2. Map the motivation structure Audit the feature across these motivation buckets: - **Competition** - rivalry, ranking, domination, comparison - **Collaboration** - helping, supporting, coordinating, solving together - **Collaborate-to-compete** - teamwork in service of beating another team, club, faction, or cohort - **Belonging** - identity, membership, shared rituals, durable group attachment - **Vanity / status** - visible prestige, taste display, wealth display, proof of mastery - **Knowledge exchange** - teaching, strategy sharing, build discussion, optimization culture Do not just list them. Judge which ones are truly doing work and which are merely implied. ### 3. Check motivational fit Use a Self-Determination-Theory-inspired check: - **Autonomy** - does the player have choice of role, pace, route, or strategy? - **Competence** - can the player demonstrate mastery, improvement, contribution, or skill? - **Relatedness** - can the player meaningfully connect, compare, help, coordinate, or belong? Flag fake-social systems that mostly create obligation, admin work, or shallow compliance. Examples: - a guild donation button may create duty without meaningful relatedness - a giant anonymous global leaderboard may technically create comparison but fail emotionally - a cosmetic showcase may create status only if others can actually see and decode it ### 4. Audit time model and synchronization demands Classify the feature explicitly: - **Realtime synchronous** - **Non-realtime synchronous window** - **Asynchronous competitive** - **Asynchronous collaborative** - **Hybrid** Then ask: - how long does a typical social interaction last? - must players overlap in time? - what happens if one player misses the window? - does the audience/platform support this coordination burden? - is the feature mobile-friendly, session-friendly, or appointment-heavy? Call out time-model mismatch clearly. A socially appealing idea can still be wrong for the audience if it demands too much synchronization. ### 5. Audit depth of social interaction Rate the design using this depth ladder: 1. **Awareness** - others exist 2. **Comparison** - scores, rankings, visible collections, ghosts, showcases 3. **Indirect exchange** - gifting, trading, donations, borrowing 4. **Communication** - chat, pings, negotiation, requests 5. **Coordination** - timing, role division, tactical cooperation 6. **Collective strategy** - shared plans, doctrine, adaptation, team optimization 7. **Community identity** - durable groups, leadership, rituals, norms, reputation, belonging State where the feature sits now, where it wants to sit, and whether the gap is credible. Do not assume deeper is always better. More depth usually means more friction, moderation burden, onboarding cost, and design risk. ### 6. Audit competition design If the feature includes competition, evaluate: - leaderboard scale - intimacy of comparison group - freshness of score movement - reward brackets and goal density - fairness and matchmaking logic - anti-exploit / anti-smurf / anti-boost concerns - visibility of rivals and stakes - whether losing still feels legible and motivating Prefer emotionally legible comparison over giant anonymous ranking walls. Small groups, leagues, seasons, and visible rivals are often stronger than one global list. ### 7. Audit collaboration design If the feature includes cooperation, evaluate: - clarity of shared goal - role differentiation - visibility of contribution - whether casual or weaker players can still help - whether personal goals can also feed the group goal - dependency risk if one player flakes or churns - communication need versus communication tools provided Strong collaborative systems often let players pursue personal goals that still contribute to a shared outcome. ### 8. Audit community formation Ask whether the design supports durable social structure: - clubs, clans, guilds, alliances, squads - friend discovery and invitations - reasons to stay in a group - recurring cadence and rituals - visible contribution and group memory - discoverability of healthy groups - lightweight leadership roles or responsibilities Ask the blunt question: **Why would a player bother joining or maintaining this group?** If the answer is only chat access, habit, or raw rewards, call that out as thin. ### 9. Audit vanity and status Evaluate the status layer through four checks: 1. **Visibility** - can other players see the signal? 2. **Legibility** - can they understand what it means? 3. **Desirability** - is it aspirational? 4. **Fairness** - what exactly is being signaled: skill, taste, effort, money, tenure, luck? Common status surfaces: - ranks and leagues - trophies and seasonal records - rare cosmetics - city/base/avatar/profile display - titles and badges - featured placements and judged showcases Vanity systems are weak when they are private, unreadable, or disconnected from any real social surface. ### 10. Audit fit for mostly single-player games When the design adds social play to a mostly solo experience, ask: - what player behaviors already suggest social demand? - what social fantasy naturally fits the core loop? - what part of the fantasy should remain personal and unshared? - should the first social layer be comparison, exchange, clubs, judged showcases, or direct PvP? - is the design trying to force deep collaboration onto a fantasy built around individual authorship or control? Be skeptical of bolted-on realtime multiplayer when the core fantasy is solitary mastery, self-expression, or authorship. A safer migration path often goes: 1. observe others 2. compare with others 3. exchange with others 4. group with others 5. collaborate to compete 6. only then add tightly synchronized modes if the audience proves it wants them ### 11. Diagnose failure patterns Common failure shapes: - **social wallpaper** - many social features, little actual social meaning - **coordination overkill** - audience asked for more synchronization than it can sustain - **status fog** - prestige exists but players cannot read or value it - **guild shell** - groups exist but have no real purpose - **comparison numbness** - ranks exist but movement feels emotionally meaningless - **solo fantasy violation** - the social layer damages the core fantasy instead of extending it - **high-friction collaboration** - teamwork exists but the cost of organizing it is too high - **shallow relatedness** - players are adjacent, not meaningfully connected ### 12. Convert findings into actions For each major issue, specify: - **Issue** - **Why it hurts** - **What kind of player it hurts most** - **Suggested change** - **Expected effect** ## Response structure ### Audit Target - ... ### Social Promise - ... ### Motivation Map - Competition: ... - Collaboration: ... - Collaborate-to-compete: ... - Belonging: ... - Vanity / status: ... - Knowledge exchange: ... ### Time and Synchronization Audit - ... ### Social Depth Audit - Current depth: ... - Intended depth: ... - Gap: ... ### Community and Status Audit - ... ### Risks / Failure Modes 1. ... 2. ... 3. ... ### Recommendations - Do now: ... - Do later: ... - Avoid: ... ## Fast mode Use this quick pass when speed matters: - what social fantasy is this actually selling? - is it mostly competition, collaboration, or collaborate-to-compete? - does the time model fit the audience? - how deep is the social interaction really? - why would players stay in a group? - what visible status or prestige does the system create? - what is the single biggest mismatch or risk? ## References Read these when useful: - `references/social-design-dimensions.md` for the deeper multiplayer audit checklist and sharper prompts ## Working principle Strong multiplayer design does not merely place players near each other. It creates meaningful comparison, contribution, coordination, recognition, or belonging at a coordination cost the audience is actually willing to pay. FILE:references/social-design-dimensions.md # Social design dimensions Use this file when the user wants a denser rubric, sharper prompts, or a more systematic multiplayer audit. ## 1. Motivation matrix For each feature, score low / medium / high and justify briefly. - **Competition** - compare, beat, rank above, dominate - **Collaboration** - help, support, solve together - **Collaborate-to-compete** - coordinate within a group to defeat another group or cohort - **Belonging** - identity, attachment, membership, shared ritual - **Status** - prestige others can read - **Vanity** - taste, wealth, authorship, or style on display - **Knowledge exchange** - guides, strategy sharing, doctrine, teaching Ask: - which motivation is actually carrying the feature? - which is merely cosmetic? - which player segment is most likely to care? ## 2. Time and coordination rubric Check: - minimum players needed - overlap in time required or not required - session length expectation - latency sensitivity - whether the feature is burst-friendly or appointment-heavy - what happens when one participant misses a step - whether the feature supports 2-minute, 10-minute, and 30-minute participation bands Warning signs: - mobile audience asked to coordinate like a raid team - one absent player collapses the whole structure - feature marketed as casual but behaves like shift work - social reward requires too much scheduling overhead ## 3. Social depth prompts Ask: - are players merely visible to each other, or meaningfully affecting each other? - can they exchange value? - can they express intent? - can they negotiate or request help? - can they divide labor? - can they build trust, reputation, or norms over time? - can they admire, envy, or learn from each other? ## 4. Competition prompts Evaluate: - scale of comparison group - freshness of movement on the board - fairness of matchmaking or seeding - reward ladder and aspiration structure - whether top-end status is reachable, reset, or permanently locked away - whether the format creates meaningful rivals - whether failure teaches or only humiliates Typical fixes: - shrink comparison groups - add leagues or brackets - shorten round cadence - increase score feedback frequency - make immediate rivals more visible ## 5. Collaboration prompts Evaluate: - clarity of shared goal - role differentiation - contribution visibility - tolerance for absences or churn - whether weak/casual players can still help - whether veterans can mentor newer players - whether communication tools match the coordination problem Typical fixes: - let personal progress also feed group progress - reduce exact coordination requirements - show contribution clearly - remove single points of failure ## 6. Community prompts Check: - onboarding into groups - reasons to remain in a group - rituals, cadence, recurring events - group history, memory, or identity - discoverability of good groups - leadership roles, social hierarchy, governance - moderation, abuse handling, conflict risk Strong signs: - players return because of the people, not only the reward - groups produce stories, traditions, doctrine, or shared identity - contribution is visible and socially acknowledged ## 7. Vanity and status prompts Check whether the design supports: - **taste display** - decoration, city/base layout, loadout curation, composition - **mastery display** - difficult cosmetics, titles, ratings, trophies - **tenure display** - season history, legacy badges, old-event proof - **wealth display** - rare items, premium cosmetics, abundance signals - **social proof** - likes, endorsements, featured placement, group rank Failure modes: - players cannot see the signal - players see it but cannot decode it - everything is monetized, so nothing means much - status loops humiliate the majority without offering reachable aspiration ## 8. Single-player-to-social migration prompts Use when a game is adding social layers after launch or onto a mostly solo concept. Ask: - what emergent social behavior already exists outside the game? - what are players already comparing, sharing, trading, or showing off? - what part of the fantasy is too personal to turn into committee play? - is direct PvP actually needed? - would judged showcases, leagues, exchange, or club goals fit better first? Safer migration pattern: 1. awareness and visitation 2. comparison and judged display 3. exchange and trade 4. groups and identity 5. collaborate-to-compete 6. tightly synchronized modes only if proven necessary
流式视频处理工具集 - 压缩、封面提取、音频转换,无需下载完整视频
---
name: ym-meidatoolkit
version: 1.1.0
description: 流式视频处理工具集 - 压缩、封面提取、音频转换,无需下载完整视频
author: your_name
tags:
- video
- compression
- thumbnail
- audio
- streaming
- ffmpeg
categories:
- media
- utility
clawhub:
entrypoint: python run.py
runtime: python3
http_port: 8080
---
# Video Streaming Toolkit
## 概述
一个高性能的流式视频处理 Skill,**无需下载完整视频文件**即可完成:
- ✅ **视频压缩** - 保持清晰度,体积可压缩至 1/10,根据情况输出多个尺寸小尺寸视频可供选择
- ✅ **封面提取** - 任意时间点或帧号提取封面
- ✅ **音频提取** - 转成 MP3 / WAV / AAC / M4A 格式
所有操作均采用**流式处理**,边下载边处理,大幅节省时间和磁盘空间。
---
## 快速开始
### 1. 安装依赖
```bash
pip install -r requirements.txt
FILE:video_compressor.py
"""
流式视频压缩 - 无需下载完整文件
"""
import subprocess
import requests
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
def get_remote_file_size(url: str) -> int:
"""获取远程文件大小(不下载)"""
try:
response = requests.head(url, timeout=10)
if 'content-length' in response.headers:
return int(response.headers['content-length'])
except Exception as e:
logger.warning(f"获取文件大小失败: {e}")
return 0
def compress_video_streaming(
video_url: str,
output_path: str = None,
target_ratio: float = 0.1,
crf: int = 24,
preset: str = 'veryfast'
) -> dict:
"""
流式压缩视频 - ffmpeg 直接处理 URL
Args:
video_url: 视频 URL
output_path: 输出路径(可选)
target_ratio: 目标体积比例(用于检查,不自动重试)
crf: CRF值(18-28,越大体积越小)
preset: 编码预设(ultrafast/veryfast/fast/medium/slow)
Returns:
{'status': 'success', 'output_path': str, 'original_size_mb': float,
'new_size_mb': float, 'ratio': float}
"""
if output_path is None:
output_path = f"compressed_{Path(video_url).stem}.mp4"
# 获取原始文件大小
original_size = get_remote_file_size(video_url)
# ffmpeg 流式压缩命令
cmd = [
'ffmpeg',
'-i', video_url, # 直接输入 URL
'-c:v', 'libx264',
'-preset', preset,
'-crf', str(crf),
'-g', '30',
'-keyint_min', '30',
'-sc_threshold', '0',
'-bf', '0',
'-refs', '1',
'-vsync', 'cfr',
'-c:a', 'aac',
'-b:a', '128k',
'-movflags', '+faststart',
'-y',
output_path
]
logger.info(f"开始流式压缩: {video_url[:80]}...")
logger.info(f"输出文件: {output_path}")
try:
# 执行压缩,实时显示进度
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
# 实时打印 ffmpeg 进度
last_progress = ""
for line in process.stderr:
if 'frame=' in line and 'speed=' in line:
# 提取进度信息
progress = line.strip()
if progress != last_progress:
logger.info(f"进度: {progress}")
last_progress = progress
# 等待完成
return_code = process.wait()
if return_code != 0:
return {
'status': 'error',
'message': f'ffmpeg 错误,返回码: {return_code}'
}
# 检查输出文件
if not Path(output_path).exists():
return {'status': 'error', 'message': '输出文件未生成'}
new_size = Path(output_path).stat().st_size
actual_ratio = new_size / original_size if original_size > 0 else 0
logger.info(f"压缩完成: {original_size/(1024*1024):.2f}MB -> {new_size/(1024*1024):.2f}MB, 比例: {actual_ratio:.2f}")
return {
'status': 'success',
'output_path': output_path,
'original_size_mb': round(original_size / (1024 * 1024), 2) if original_size else 0,
'new_size_mb': round(new_size / (1024 * 1024), 2),
'ratio': round(actual_ratio, 3),
'crf_used': crf,
'streaming': True
}
except subprocess.TimeoutExpired:
return {'status': 'error', 'message': '压缩超时(300秒)'}
except Exception as e:
return {'status': 'error', 'message': str(e)}
def compress_with_adaptive_crf(
video_url: str,
output_path: str = None,
target_ratio: float = 0.1,
max_attempts: int = 3
) -> dict:
"""
自适应 CRF 压缩 - 自动调整参数直到达到目标比例
"""
crf_values = [24, 26, 28, 30] # 依次尝试
best_result = None
for i, crf in enumerate(crf_values[:max_attempts]):
logger.info(f"尝试 {i+1}/{max_attempts}: CRF={crf}")
result = compress_video_streaming(
video_url=video_url,
output_path=output_path if i == 0 else f"{output_path}.try{i+1}.mp4",
target_ratio=target_ratio,
crf=crf
)
if result['status'] != 'success':
continue
if result['ratio'] <= target_ratio:
# 达到目标,移动文件到最终位置
if i > 0 and output_path:
import shutil
shutil.move(result['output_path'], output_path)
result['output_path'] = output_path
return result
best_result = result
# 未达到目标,返回最好的结果
if best_result:
logger.warning(f"未达到目标比例 {target_ratio},最佳比例: {best_result['ratio']}")
if best_result['output_path'] != output_path and output_path:
import shutil
shutil.move(best_result['output_path'], output_path)
best_result['output_path'] = output_path
return best_result
return {'status': 'error', 'message': '所有压缩尝试均失败'}
FILE:run.py
#!/usr/bin/env python3
"""
ClawHub Skill 统一入口 - 流式视频处理
支持:
1. 压缩: ffmpeg 流式处理,无需下载
2. 封面: 部分下载,只取需要的帧
3. 音频: 流式提取,转 MP3/WAV
"""
import sys
import json
import argparse
import logging
from pathlib import Path
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 导入模块
from frame_extractor import extract_thumbnail_from_url
from video_compressor import compress_video_streaming, compress_with_adaptive_crf
from audio_extractor import extract_audio_streaming, extract_audio_batch, get_audio_info
def handle_compress(params: dict) -> dict:
"""处理视频压缩请求"""
video_url = params.get('video_url')
if not video_url:
return {'status': 'error', 'message': 'Missing video_url'}
output_path = params.get('output_path')
target_ratio = params.get('target_ratio', 0.1)
adaptive = params.get('adaptive', True)
crf = params.get('crf', 24)
preset = params.get('preset', 'veryfast')
logger.info(f"压缩请求: {video_url[:80]}...")
if adaptive:
result = compress_with_adaptive_crf(
video_url=video_url,
output_path=output_path,
target_ratio=target_ratio,
max_attempts=params.get('max_attempts', 3)
)
else:
result = compress_video_streaming(
video_url=video_url,
output_path=output_path,
target_ratio=target_ratio,
crf=crf,
preset=preset
)
return result
def handle_thumbnail(params: dict) -> dict:
"""处理封面提取请求"""
video_url = params.get('video_url')
if not video_url:
return {'status': 'error', 'message': 'Missing video_url'}
time_seconds = params.get('time_seconds')
frame_number = params.get('frame_number')
if time_seconds is None and frame_number is None:
time_seconds = 0
save_path = params.get('save_path')
resize_width = params.get('resize_width')
quality = params.get('quality', 85)
logger.info(f"封面提取: {video_url[:80]}... time={time_seconds}, frame={frame_number}")
result = extract_thumbnail_from_url(
video_url=video_url,
time_seconds=time_seconds,
frame_number=frame_number,
save_path=save_path,
resize_width=resize_width,
quality=quality
)
return result
def handle_audio(params: dict) -> dict:
"""处理音频提取请求"""
video_url = params.get('video_url')
if not video_url:
return {'status': 'error', 'message': 'Missing video_url'}
output_path = params.get('output_path')
audio_format = params.get('format', 'mp3') # mp3, wav, aac, m4a
audio_bitrate = params.get('bitrate', '128k')
sample_rate = params.get('sample_rate', 44100)
channels = params.get('channels', 2)
start_time = params.get('start_time')
duration = params.get('duration')
# 格式验证
if audio_format not in ['mp3', 'wav', 'aac', 'm4a']:
return {'status': 'error', 'message': f'Unsupported format: {audio_format}. Supported: mp3, wav, aac, m4a'}
logger.info(f"音频提取: {video_url[:80]}... format={audio_format}, bitrate={audio_bitrate}")
result = extract_audio_streaming(
video_url=video_url,
output_path=output_path,
audio_format=audio_format,
audio_bitrate=audio_bitrate,
sample_rate=sample_rate,
channels=channels,
start_time=start_time,
duration=duration
)
return result
def handle_audio_batch(params: dict) -> dict:
"""批量音频提取"""
videos = params.get('videos', [])
if not videos:
return {'status': 'error', 'message': 'Missing videos list'}
output_dir = params.get('output_dir', './audio_output')
audio_format = params.get('format', 'mp3')
audio_bitrate = params.get('bitrate', '128k')
sample_rate = params.get('sample_rate', 44100)
logger.info(f"批量音频提取: {len(videos)} 个视频, 格式={audio_format}")
result = extract_audio_batch(
videos=videos,
output_dir=output_dir,
audio_format=audio_format,
audio_bitrate=audio_bitrate,
sample_rate=sample_rate
)
return result
def handle_audio_info(params: dict) -> dict:
"""获取视频音频流信息"""
video_url = params.get('video_url')
if not video_url:
return {'status': 'error', 'message': 'Missing video_url'}
logger.info(f"获取音频信息: {video_url[:80]}...")
result = get_audio_info(video_url)
return result
def handle_batch(params: dict) -> dict:
"""批量处理(压缩/封面)"""
videos = params.get('videos', [])
action = params.get('action', 'thumbnail')
if not videos:
return {'status': 'error', 'message': 'Missing videos list'}
results = []
for i, video in enumerate(videos):
logger.info(f"批量处理 [{i+1}/{len(videos)}]")
if action == 'compress':
res = handle_compress(video)
elif action == 'audio':
res = handle_audio(video)
else:
res = handle_thumbnail(video)
results.append(res)
success_count = sum(1 for r in results if r.get('status') == 'success')
return {
'status': 'success',
'total': len(results),
'success': success_count,
'failed': len(results) - success_count,
'results': results
}
def handle_info(params: dict) -> dict:
"""获取视频信息"""
video_url = params.get('video_url')
if not video_url:
return {'status': 'error', 'message': 'Missing video_url'}
from frame_extractor import RemoteVideoFrameExtractor
try:
extractor = RemoteVideoFrameExtractor(video_url, timeout=30)
info = extractor.get_video_info()
info['file_size_mb'] = round(extractor.file_size / (1024 * 1024), 2)
return {'status': 'success', 'info': info}
except Exception as e:
return {'status': 'error', 'message': str(e)}
# Action 映射
ACTIONS = {
'compress': handle_compress,
'thumbnail': handle_thumbnail,
'audio': handle_audio,
'audio_batch': handle_audio_batch,
'audio_info': handle_audio_info,
'batch': handle_batch,
'info': handle_info
}
def run_cli():
"""命令行模式"""
parser = argparse.ArgumentParser(description='Video Streaming Skill')
parser.add_argument('--input', '-i', required=True, help='Input JSON string or file path')
parser.add_argument('--action', '-a', choices=ACTIONS.keys(), help='Action to perform')
args = parser.parse_args()
try:
if Path(args.input).exists():
with open(args.input, 'r') as f:
params = json.load(f)
else:
params = json.loads(args.input)
except json.JSONDecodeError:
params = {'action': args.action} if args.action else {}
for pair in args.input.split():
if '=' in pair:
k, v = pair.split('=', 1)
params[k] = v
action = params.get('action')
if not action and args.action:
action = args.action
if not action or action not in ACTIONS:
print(json.dumps({'status': 'error', 'message': f'Invalid action: {action}'}))
sys.exit(1)
result = ACTIONS[action](params)
print(json.dumps(result, ensure_ascii=False, indent=2))
def run_http_server(host='0.0.0.0', port=8080):
"""HTTP 服务模式"""
try:
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
@app.route('/health', methods=['GET'])
def health():
return jsonify({'status': 'ok', 'skill': 'video-streaming-toolkit'})
@app.route('/skill/compress', methods=['POST'])
def compress():
data = request.get_json()
if not data:
return jsonify({'status': 'error', 'message': 'No JSON body'}), 400
return jsonify(handle_compress(data))
@app.route('/skill/thumbnail', methods=['POST'])
def thumbnail():
data = request.get_json()
if not data:
return jsonify({'status': 'error', 'message': 'No JSON body'}), 400
return jsonify(handle_thumbnail(data))
@app.route('/skill/audio', methods=['POST'])
def audio():
data = request.get_json()
if not data:
return jsonify({'status': 'error', 'message': 'No JSON body'}), 400
return jsonify(handle_audio(data))
@app.route('/skill/audio_batch', methods=['POST'])
def audio_batch():
data = request.get_json()
if not data:
return jsonify({'status': 'error', 'message': 'No JSON body'}), 400
return jsonify(handle_audio_batch(data))
@app.route('/skill/audio_info', methods=['POST'])
def audio_info():
data = request.get_json()
if not data:
return jsonify({'status': 'error', 'message': 'No JSON body'}), 400
return jsonify(handle_audio_info(data))
@app.route('/skill/batch', methods=['POST'])
def batch():
data = request.get_json()
if not data:
return jsonify({'status': 'error', 'message': 'No JSON body'}), 400
return jsonify(handle_batch(data))
@app.route('/skill/info', methods=['POST'])
def info():
data = request.get_json()
if not data:
return jsonify({'status': 'error', 'message': 'No JSON body'}), 400
return jsonify(handle_info(data))
logger.info(f"Starting HTTP server on {host}:{port}")
app.run(host=host, port=port, threaded=True)
except ImportError:
logger.error("Flask not installed. Run: pip install flask flask-cors")
sys.exit(1)
if __name__ == '__main__':
if '--serve' in sys.argv or '-s' in sys.argv:
run_http_server()
else:
run_cli()
FILE:requirements.txt
requests>=2.28.0
opencv-python>=4.8.0
numpy>=1.24.0
aiohttp>=3.8.0
FILE:audio_extractor.py
"""
流式音频提取 - 从远程视频直接提取音频,无需下载完整视频
支持格式: MP3, WAV, AAC, M4A
"""
import subprocess
import requests
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
def get_remote_file_size(url: str) -> int:
"""获取远程文件大小(不下载)"""
try:
response = requests.head(url, timeout=10)
if 'content-length' in response.headers:
return int(response.headers['content-length'])
except Exception as e:
logger.warning(f"获取文件大小失败: {e}")
return 0
def extract_audio_streaming(
video_url: str,
output_path: str = None,
audio_format: str = 'mp3', # mp3, wav, aac, m4a
audio_bitrate: str = '128k', # 128k, 192k, 320k
sample_rate: int = 44100, # 44100, 48000
channels: int = 2, # 1=mono, 2=stereo
start_time: float = None, # 开始时间(秒)
duration: float = None, # 持续时间(秒)
) -> dict:
"""
流式提取音频 - ffmpeg 直接从 URL 提取,无需下载视频
Args:
video_url: 视频 URL
output_path: 输出路径(可选)
audio_format: 音频格式 (mp3, wav, aac, m4a)
audio_bitrate: 音频比特率 (128k, 192k, 320k)
sample_rate: 采样率 (44100, 48000)
channels: 声道数 (1=单声道, 2=立体声)
start_time: 开始时间(秒),提取片段
duration: 持续时间(秒)
Returns:
{
'status': 'success',
'output_path': str,
'format': str,
'size_mb': float,
'duration_sec': float,
'streaming': True
}
"""
# 自动生成输出路径
if output_path is None:
from urllib.parse import urlparse
video_name = Path(urlparse(video_url).path).stem
output_path = f"{video_name}.{audio_format}"
# 构建 ffmpeg 命令
cmd = ['ffmpeg', '-i', video_url]
# 片段提取参数
if start_time is not None:
cmd.extend(['-ss', str(start_time)])
if duration is not None:
cmd.extend(['-t', str(duration)])
# 音频参数
if audio_format == 'mp3':
cmd.extend([
'-c:a', 'libmp3lame',
'-b:a', audio_bitrate,
'-ar', str(sample_rate),
'-ac', str(channels)
])
elif audio_format == 'wav':
cmd.extend([
'-c:a', 'pcm_s16le', # WAV 无损格式
'-ar', str(sample_rate),
'-ac', str(channels)
])
elif audio_format == 'aac':
cmd.extend([
'-c:a', 'aac',
'-b:a', audio_bitrate,
'-ar', str(sample_rate),
'-ac', str(channels)
])
elif audio_format == 'm4a':
cmd.extend([
'-c:a', 'aac',
'-b:a', audio_bitrate,
'-ar', str(sample_rate),
'-ac', str(channels),
'-movflags', '+faststart'
])
else:
return {'status': 'error', 'message': f'Unsupported format: {audio_format}'}
# 输出参数
cmd.extend(['-y', output_path])
logger.info(f"开始流式音频提取: {video_url[:80]}...")
logger.info(f"输出格式: {audio_format}, 比特率: {audio_bitrate}, 采样率: {sample_rate}")
logger.info(f"命令: {' '.join(cmd[:5])}...")
try:
# 执行提取
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
bufsize=1
)
# 实时显示进度
last_progress = ""
duration_sec = 0
for line in process.stderr:
if 'Duration:' in line and duration_sec == 0:
# 解析总时长
import re
match = re.search(r'Duration: (\d{2}):(\d{2}):(\d{2}\.\d{2})', line)
if match:
h, m, s = match.groups()
duration_sec = int(h) * 3600 + int(m) * 60 + float(s)
logger.info(f"视频总时长: {duration_sec:.2f}秒")
if 'size=' in line and 'time=' in line:
progress = line.strip()
if progress != last_progress:
logger.info(f"进度: {progress}")
last_progress = progress
return_code = process.wait()
if return_code != 0:
return {
'status': 'error',
'message': f'ffmpeg 错误,返回码: {return_code}'
}
# 检查输出文件
if not Path(output_path).exists():
return {'status': 'error', 'message': '输出文件未生成'}
file_size = Path(output_path).stat().st_size
# 获取提取的音频时长
audio_duration = duration_sec if duration_sec > 0 else None
logger.info(f"音频提取完成: {file_size/(1024*1024):.2f}MB")
return {
'status': 'success',
'output_path': output_path,
'format': audio_format,
'size_mb': round(file_size / (1024 * 1024), 2),
'duration_sec': audio_duration,
'bitrate': audio_bitrate,
'sample_rate': sample_rate,
'channels': channels,
'streaming': True
}
except subprocess.TimeoutExpired:
return {'status': 'error', 'message': '音频提取超时(300秒)'}
except Exception as e:
return {'status': 'error', 'message': str(e)}
def extract_audio_batch(
videos: list,
output_dir: str = './audio_output',
audio_format: str = 'mp3',
audio_bitrate: str = '128k',
sample_rate: int = 44100
) -> dict:
"""
批量提取音频
Args:
videos: 视频列表 [{'url': 'https://...', 'name': 'video1'}, ...]
output_dir: 输出目录
audio_format: 音频格式
audio_bitrate: 比特率
sample_rate: 采样率
Returns:
批量结果
"""
import os
os.makedirs(output_dir, exist_ok=True)
results = []
success_count = 0
for i, video in enumerate(videos):
url = video.get('url')
name = video.get('name', f'audio_{i+1}')
if not url:
results.append({'name': name, 'status': 'error', 'message': 'Missing url'})
continue
output_path = os.path.join(output_dir, f"{name}.{audio_format}")
logger.info(f"批量处理 [{i+1}/{len(videos)}]: {name}")
result = extract_audio_streaming(
video_url=url,
output_path=output_path,
audio_format=audio_format,
audio_bitrate=audio_bitrate,
sample_rate=sample_rate
)
result['name'] = name
results.append(result)
if result.get('status') == 'success':
success_count += 1
return {
'status': 'success',
'total': len(videos),
'success': success_count,
'failed': len(videos) - success_count,
'results': results
}
def get_audio_info(video_url: str) -> dict:
"""
获取视频的音频流信息(不下载)
Args:
video_url: 视频 URL
Returns:
{
'has_audio': bool,
'audio_codec': str,
'audio_bitrate': str,
'sample_rate': int,
'channels': int,
'language': str
}
"""
import re
cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_streams', video_url]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if result.returncode != 0:
return {'status': 'error', 'message': 'ffprobe failed'}
import json
data = json.loads(result.stdout)
for stream in data.get('streams', []):
if stream.get('codec_type') == 'audio':
return {
'status': 'success',
'has_audio': True,
'audio_codec': stream.get('codec_name', 'unknown'),
'audio_bitrate': stream.get('bit_rate', 'unknown'),
'sample_rate': int(stream.get('sample_rate', 0)) if stream.get('sample_rate') else 0,
'channels': stream.get('channels', 0),
'language': stream.get('tags', {}).get('language', 'unknown')
}
return {'status': 'success', 'has_audio': False, 'message': 'No audio stream found'}
except Exception as e:
return {'status': 'error', 'message': str(e)}
FILE:utils.py
import os
import subprocess
import tempfile
from pathlib import Path
def get_file_size_mb(path: str) -> float:
"""获取文件大小(MB)"""
return Path(path).stat().st_size / (1024 * 1024)
def download_video_to_temp(url: str, timeout: int = 300) -> str:
"""下载视频到临时文件(用于压缩场景)"""
import requests
temp_file = tempfile.NamedTemporaryFile(suffix='.mp4', delete=False)
temp_path = temp_file.name
temp_file.close()
response = requests.get(url, stream=True, timeout=timeout)
with open(temp_path, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return temp_path
def cleanup_temp_file(path: str):
"""清理临时文件"""
if path and os.path.exists(path):
os.unlink(path)
FILE:frame_extractor.py
"""
远程视频帧提取服务
支持从远程 URL 按时间/帧号提取视频帧,无需下载完整文件
"""
import requests
import struct
import logging
import os
import tempfile
from typing import Optional, Dict, List
import cv2
import numpy as np
logger = logging.getLogger(__name__)
class RemoteVideoFrameExtractor:
"""远程视频帧提取器 - 通过解析 MP4 结构实现部分下载"""
# MP4 Box 类型常量
BOX_TYPE_MOOV = b'moov'
BOX_TYPE_TRAK = b'trak'
BOX_TYPE_MDIA = b'mdia'
BOX_TYPE_MINF = b'minf'
BOX_TYPE_STBL = b'stbl'
BOX_TYPE_STSD = b'stsd'
BOX_TYPE_STSS = b'stss'
BOX_TYPE_STCO = b'stco'
BOX_TYPE_CO64 = b'co64'
BOX_TYPE_STSZ = b'stsz'
BOX_TYPE_STSC = b'stsc'
def __init__(self, video_url: str, timeout: int = 30):
self.video_url = video_url
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
})
self.file_size = 0
self.width = 0
self.height = 0
self.codec_type = None
self.timescale = 0
self.duration = 0
self.stts = []
self.stss = []
self.stco = []
self.stsz = []
self.stsc = []
self.nal_length_size = 4
self.vps_sps_pps_nalus = []
self._init_video_info()
def _init_video_info(self):
try:
self.file_size = self._get_file_size()
self._find_and_parse_moov()
except Exception as e:
logger.error(f"视频信息解析失败: {e}")
raise
def _get_file_size(self) -> int:
response = self.session.head(self.video_url, timeout=self.timeout)
if 'content-length' in response.headers:
return int(response.headers['content-length'])
response = self.session.get(self.video_url, stream=True, timeout=self.timeout)
return int(response.headers.get('content-length', 0))
def _download_range(self, start: int, end: int) -> bytes:
headers = {'Range': f'bytes={start}-{end}'}
response = self.session.get(self.video_url, headers=headers, timeout=self.timeout)
if response.status_code in [200, 206]:
return response.content
raise Exception(f"HTTP Range 请求失败: {response.status_code}")
def _find_and_parse_moov(self):
pos = 0
probe_size = min(64 * 1024, self.file_size)
probe_data = self._download_range(0, probe_size - 1)
while pos < self.file_size:
if pos + 8 > len(probe_data):
header_bytes = self._download_range(pos, min(pos + 7, self.file_size - 1))
if len(header_bytes) < 8:
break
else:
header_bytes = probe_data[pos:pos + 8]
box_size = struct.unpack('>I', header_bytes[0:4])[0]
box_type = header_bytes[4:8]
if box_size == 1:
if pos + 16 > len(probe_data):
ext_header = self._download_range(pos, min(pos + 15, self.file_size - 1))
else:
ext_header = probe_data[pos:pos + 16]
if len(ext_header) < 16:
break
box_size = struct.unpack('>Q', ext_header[8:16])[0]
if box_size == 0:
box_size = self.file_size - pos
if box_size < 8:
break
if box_type == self.BOX_TYPE_MOOV:
moov_data = self._download_range(pos, pos + box_size - 1)
self._parse_moov(moov_data)
return
pos += box_size
tail_size = min(5 * 1024 * 1024, self.file_size)
tail_data = self._download_range(self.file_size - tail_size, self.file_size - 1)
tail_base_offset = self.file_size - tail_size
scan_pos = 0
while scan_pos < len(tail_data) - 8:
box_size = struct.unpack('>I', tail_data[scan_pos:scan_pos + 4])[0]
box_type = tail_data[scan_pos + 4:scan_pos + 8]
if box_size == 1 and scan_pos + 16 <= len(tail_data):
box_size = struct.unpack('>Q', tail_data[scan_pos + 8:scan_pos + 16])[0]
if box_size < 8:
scan_pos += 1
continue
if box_type == self.BOX_TYPE_MOOV:
actual_offset = tail_base_offset + scan_pos
moov_data = self._download_range(actual_offset, actual_offset + box_size - 1)
self._parse_moov(moov_data)
return
scan_pos += box_size
raise Exception("未找到 moov box")
def _parse_moov(self, moov_data: bytes):
pos = 8
while pos < len(moov_data) - 8:
box_size = struct.unpack('>I', moov_data[pos:pos+4])[0]
if moov_data[pos+4:pos+8] == self.BOX_TYPE_TRAK:
self._parse_trak(moov_data[pos:pos+box_size])
pos += box_size if box_size > 0 else 1
def _parse_trak(self, trak_data: bytes):
is_video = False
mdia_offset, mdia_size = 0, 0
pos = 8
while pos < len(trak_data) - 8:
box_size = struct.unpack('>I', trak_data[pos:pos+4])[0]
if trak_data[pos+4:pos+8] == self.BOX_TYPE_MDIA:
mdia_offset, mdia_size = pos, box_size
m_pos = pos + 8
while m_pos < pos + box_size - 8:
m_size = struct.unpack('>I', trak_data[m_pos:m_pos+4])[0]
if trak_data[m_pos+4:m_pos+8] == b'hdlr':
if trak_data[m_pos+16:m_pos+20] == b'vide':
is_video = True
break
m_pos += m_size if m_size > 0 else 1
pos += box_size if box_size > 0 else 1
if is_video and mdia_size > 0:
self._parse_mdia(trak_data[mdia_offset:mdia_offset+mdia_size])
def _parse_mdia(self, mdia_data: bytes):
pos = 8
while pos < len(mdia_data) - 8:
box_size = struct.unpack('>I', mdia_data[pos:pos+4])[0]
box_type = mdia_data[pos+4:pos+8]
if box_type == b'mdhd':
version = mdia_data[pos+8]
if version == 0:
self.timescale = struct.unpack('>I', mdia_data[pos+20:pos+24])[0]
self.duration = struct.unpack('>I', mdia_data[pos+24:pos+28])[0]
else:
self.timescale = struct.unpack('>I', mdia_data[pos+28:pos+32])[0]
self.duration = struct.unpack('>Q', mdia_data[pos+32:pos+40])[0]
elif box_type == self.BOX_TYPE_MINF:
self._parse_minf(mdia_data[pos:pos+box_size])
pos += box_size if box_size > 0 else 1
def _parse_minf(self, minf_data: bytes):
pos = 8
while pos < len(minf_data) - 8:
box_size = struct.unpack('>I', minf_data[pos:pos+4])[0]
if minf_data[pos+4:pos+8] == self.BOX_TYPE_STBL:
self._parse_stbl(minf_data[pos:pos+box_size])
pos += box_size if box_size > 0 else 1
def _parse_stbl(self, stbl_data: bytes):
pos = 8
while pos < len(stbl_data) - 8:
box_size = struct.unpack('>I', stbl_data[pos:pos+4])[0]
box_type = stbl_data[pos+4:pos+8]
if box_type == self.BOX_TYPE_STSD:
self._parse_stsd(stbl_data[pos:pos+box_size])
elif box_type == self.BOX_TYPE_STSS:
entry_count = struct.unpack('>I', stbl_data[pos+12:pos+16])[0]
for i in range(entry_count):
self.stss.append(struct.unpack('>I', stbl_data[pos+16+i*4:pos+20+i*4])[0])
elif box_type == self.BOX_TYPE_STCO:
entry_count = struct.unpack('>I', stbl_data[pos+12:pos+16])[0]
for i in range(entry_count):
self.stco.append(struct.unpack('>I', stbl_data[pos+16+i*4:pos+20+i*4])[0])
elif box_type == self.BOX_TYPE_CO64:
entry_count = struct.unpack('>I', stbl_data[pos+12:pos+16])[0]
for i in range(entry_count):
self.stco.append(struct.unpack('>Q', stbl_data[pos+16+i*8:pos+24+i*8])[0])
elif box_type == self.BOX_TYPE_STSZ:
sample_size = struct.unpack('>I', stbl_data[pos+12:pos+16])[0]
sample_count = struct.unpack('>I', stbl_data[pos+16:pos+20])[0]
if sample_size == 0:
for i in range(sample_count):
self.stsz.append(struct.unpack('>I', stbl_data[pos+20+i*4:pos+24+i*4])[0])
else:
self.stsz = [sample_size] * sample_count
elif box_type == self.BOX_TYPE_STSC:
entry_count = struct.unpack('>I', stbl_data[pos+12:pos+16])[0]
for i in range(entry_count):
o = pos + 16 + i * 12
self.stsc.append({
'first_chunk': struct.unpack('>I', stbl_data[o:o+4])[0],
'samples_per_chunk': struct.unpack('>I', stbl_data[o+4:o+8])[0]
})
elif box_type == b'stts':
entry_count = struct.unpack('>I', stbl_data[pos+12:pos+16])[0]
for i in range(entry_count):
count = struct.unpack('>I', stbl_data[pos+16+i*8:pos+20+i*8])[0]
delta = struct.unpack('>I', stbl_data[pos+20+i*8:pos+24+i*8])[0]
self.stts.append({'count': count, 'delta': delta})
pos += box_size if box_size > 0 else 1
def _parse_stsd(self, stsd_data: bytes):
pos = 16
while pos < len(stsd_data) - 8:
box_size = struct.unpack('>I', stsd_data[pos:pos+4])[0]
box_type = stsd_data[pos+4:pos+8]
if box_type in [b'avc1', b'hvc1', b'hev1']:
self.codec_type = 'h264' if box_type == b'avc1' else 'h265'
self.width = struct.unpack('>H', stsd_data[pos+32:pos+34])[0]
self.height = struct.unpack('>H', stsd_data[pos+34:pos+36])[0]
v_pos = pos + 86
while v_pos < pos + box_size - 8:
v_size = struct.unpack('>I', stsd_data[v_pos:v_pos+4])[0]
v_type = stsd_data[v_pos+4:v_pos+8]
config_data = stsd_data[v_pos+8:v_pos+v_size]
if v_type == b'avcC':
self._parse_avcc(config_data)
elif v_type == b'hvcC':
self._parse_hvcc(config_data)
v_pos += v_size if v_size > 0 else 1
pos += box_size if box_size > 0 else 1
def _parse_avcc(self, data: bytes):
self.nal_length_size = (data[4] & 0x03) + 1
pos = 6
start_code = b'\x00\x00\x00\x01'
num_sps = data[5] & 0x1F
for _ in range(num_sps):
sps_len = struct.unpack('>H', data[pos:pos+2])[0]
pos += 2
self.vps_sps_pps_nalus.append(start_code + data[pos:pos+sps_len])
pos += sps_len
num_pps = data[pos]
pos += 1
for _ in range(num_pps):
pps_len = struct.unpack('>H', data[pos:pos+2])[0]
pos += 2
self.vps_sps_pps_nalus.append(start_code + data[pos:pos+pps_len])
pos += pps_len
def _parse_hvcc(self, data: bytes):
self.nal_length_size = (data[21] & 0x03) + 1
num_arrays = data[22]
pos = 23
start_code = b'\x00\x00\x00\x01'
for _ in range(num_arrays):
pos += 1
num_nalus = struct.unpack('>H', data[pos:pos+2])[0]
pos += 2
for _ in range(num_nalus):
nal_len = struct.unpack('>H', data[pos:pos+2])[0]
pos += 2
self.vps_sps_pps_nalus.append(start_code + data[pos:pos+nal_len])
pos += nal_len
def get_sample_position(self, sample_number: int) -> Optional[Dict]:
if not self.stsz or sample_number > len(self.stsz) or sample_number < 1:
return None
target_chunk, samples_so_far, first_sample_in_chunk = 1, 0, 1
for i in range(len(self.stsc)):
current = self.stsc[i]
next_chunk = self.stsc[i+1]['first_chunk'] if i+1 < len(self.stsc) else len(self.stco) + 1
chunks_in_rule = next_chunk - current['first_chunk']
samples_in_rule = chunks_in_rule * current['samples_per_chunk']
if samples_so_far + samples_in_rule >= sample_number:
chunks_to_target = (sample_number - samples_so_far - 1) // current['samples_per_chunk']
target_chunk = current['first_chunk'] + chunks_to_target
first_sample_in_chunk = samples_so_far + chunks_to_target * current['samples_per_chunk'] + 1
break
samples_so_far += samples_in_rule
if target_chunk > len(self.stco):
return None
offset = self.stco[target_chunk - 1]
for i in range(first_sample_in_chunk, sample_number):
offset += self.stsz[i - 1]
return {'offset': offset, 'size': self.stsz[sample_number - 1]}
def _get_frame_number_by_time(self, seconds: float) -> int:
if not self.stts or not self.timescale:
return max(1, int(seconds * 30.0))
target_ticks = int(seconds * self.timescale)
current_ticks = 0
current_sample = 1
for entry in self.stts:
entry_ticks = entry['count'] * entry['delta']
if current_ticks + entry_ticks > target_ticks:
ticks_into_entry = target_ticks - current_ticks
samples_into_entry = ticks_into_entry // entry['delta']
return current_sample + samples_into_entry
current_ticks += entry_ticks
current_sample += entry['count']
return current_sample - 1 if current_sample > 1 else 1
def extract_frame_by_time(self, seconds: float) -> Optional[np.ndarray]:
target_frame = self._get_frame_number_by_time(seconds)
if self.stsz and target_frame > len(self.stsz):
target_frame = len(self.stsz)
target_frame = max(1, target_frame)
return self.extract_frame(target_frame)
def extract_frame(self, frame_number: int) -> Optional[np.ndarray]:
keyframe = frame_number
if self.stss:
keyframes = [kf for kf in self.stss if kf <= frame_number]
keyframe = max(keyframes) if keyframes else self.stss[0]
sample_infos = []
min_offset = float('inf')
max_offset = 0
for f in range(keyframe, frame_number + 1):
info = self.get_sample_position(f)
if not info:
logger.warning(f"无法获取帧 {f} 的位置信息")
return None
sample_infos.append(info)
min_offset = min(min_offset, info['offset'])
max_offset = max(max_offset, info['offset'] + info['size'] - 1)
raw_data = self._download_range(min_offset, max_offset)
annexb_stream = bytearray()
for nalu in self.vps_sps_pps_nalus:
annexb_stream.extend(nalu)
for info in sample_infos:
local_offset = info['offset'] - min_offset
sample_data = raw_data[local_offset : local_offset + info['size']]
annexb_stream.extend(self._convert_sample_to_annexb(sample_data))
frames_to_step = frame_number - keyframe + 1
return self._decode_video_stream(bytes(annexb_stream), frames_to_step)
def _convert_sample_to_annexb(self, sample_data: bytes) -> bytes:
result = bytearray()
pos = 0
start_code = b'\x00\x00\x00\x01'
while pos < len(sample_data):
if pos + self.nal_length_size > len(sample_data):
break
if self.nal_length_size == 4:
nal_len = struct.unpack('>I', sample_data[pos:pos+4])[0]
elif self.nal_length_size == 2:
nal_len = struct.unpack('>H', sample_data[pos:pos+2])[0]
else:
nal_len = sample_data[pos]
pos += self.nal_length_size
if pos + nal_len > len(sample_data):
break
result.extend(start_code)
result.extend(sample_data[pos:pos+nal_len])
pos += nal_len
return bytes(result)
def _decode_video_stream(self, video_data: bytes, target_read_count: int) -> Optional[np.ndarray]:
if not video_data:
return None
ext = '.h265' if self.codec_type == 'h265' else '.h264'
temp_path = None
target_frame_img = None
try:
with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as f:
f.write(video_data)
temp_path = f.name
cap = cv2.VideoCapture(temp_path)
for i in range(target_read_count):
ret, frame = cap.read()
if not ret:
break
target_frame_img = frame
cap.release()
if target_frame_img is not None:
return cv2.cvtColor(target_frame_img, cv2.COLOR_BGR2RGB)
except Exception as e:
logger.error(f"视频流解码失败: {e}")
return None
finally:
if temp_path and os.path.exists(temp_path):
os.unlink(temp_path)
return None
def get_video_info(self) -> Dict:
fps = self.timescale if self.stts else 30
duration_sec = self.duration / self.timescale if self.timescale else 0
return {
'width': self.width,
'height': self.height,
'codec': self.codec_type,
'fps': fps,
'duration': duration_sec,
'total_frames': len(self.stsz) if self.stsz else 0
}
def extract_thumbnail_from_url(
video_url: str,
time_seconds: float = None,
frame_number: int = None,
save_path: str = None,
resize_width: int = None,
quality: int = 85
) -> dict:
"""
从远程视频提取封面(流式,只下载必要部分)
"""
extractor = RemoteVideoFrameExtractor(video_url, timeout=60)
if frame_number is not None:
frame = extractor.extract_frame(frame_number)
used_method = f'frame_{frame_number}'
else:
ts = time_seconds if time_seconds is not None else 0
frame = extractor.extract_frame_by_time(ts)
used_method = f'time_{ts}s'
if frame is None:
return {'status': 'error', 'message': 'Failed to extract frame'}
video_info = extractor.get_video_info()
video_info['extract_method'] = used_method
result = {
'status': 'success',
'video_info': video_info,
'shape': frame.shape
}
if save_path:
os.makedirs(os.path.dirname(save_path) if os.path.dirname(save_path) else '.', exist_ok=True)
if resize_width:
h, w = frame.shape[:2]
scale = resize_width / w
new_h = int(h * scale)
resized = cv2.resize(frame, (resize_width, new_h))
frame_to_save = resized
else:
frame_to_save = frame
bgr_frame = cv2.cvtColor(frame_to_save, cv2.COLOR_RGB2BGR)
cv2.imwrite(save_path, bgr_frame, [int(cv2.IMWRITE_JPEG_QUALITY), quality])
result['saved_path'] = save_path
return result
FILE:skill.json
{
"name": "ym-mediatoolkit",
"version": "1.0.0",
"description": "视频处理工具集:1) 视频压缩 2) 封面提取 3) 音频提取(MP3/WAV)",
"author": "your_name",
"entrypoint": "python run.py --input {input_json}",
"http_port": 8080,
"actions": [
{
"name": "compress",
"description": "流式压缩视频,保持清晰度",
"input_schema": {
"type": "object",
"required": ["video_url"],
"properties": {
"video_url": {"type": "string"},
"target_ratio": {"type": "number", "default": 0.1},
"adaptive": {"type": "boolean", "default": true},
"crf": {"type": "integer", "default": 24},
"preset": {"type": "string", "default": "veryfast"}
}
}
},
{
"name": "thumbnail",
"description": "从视频任意时间点或帧号提取封面",
"input_schema": {
"type": "object",
"required": ["video_url"],
"properties": {
"video_url": {"type": "string"},
"time_seconds": {"type": "number"},
"frame_number": {"type": "integer"},
"save_path": {"type": "string"},
"resize_width": {"type": "integer"},
"quality": {"type": "integer", "default": 85}
}
}
},
{
"name": "audio",
"description": "流式提取音频,转成 MP3 或 WAV 格式",
"input_schema": {
"type": "object",
"required": ["video_url"],
"properties": {
"video_url": {"type": "string"},
"format": {"type": "string", "enum": ["mp3", "wav", "aac", "m4a"], "default": "mp3"},
"bitrate": {"type": "string", "default": "128k", "description": "比特率: 128k, 192k, 320k"},
"sample_rate": {"type": "integer", "default": 44100, "description": "采样率: 44100, 48000"},
"channels": {"type": "integer", "default": 2, "description": "声道: 1=单声道, 2=立体声"},
"start_time": {"type": "number", "description": "开始时间(秒)"},
"duration": {"type": "number", "description": "持续时间(秒)"},
"output_path": {"type": "string", "description": "输出路径"}
}
}
},
{
"name": "audio_batch",
"description": "批量提取多个视频的音频",
"input_schema": {
"type": "object",
"required": ["videos"],
"properties": {
"videos": {"type": "array", "description": "视频列表 [{'url': '...', 'name': '...'}]"},
"output_dir": {"type": "string", "default": "./audio_output"},
"format": {"type": "string", "default": "mp3"},
"bitrate": {"type": "string", "default": "128k"},
"sample_rate": {"type": "integer", "default": 44100}
}
}
},
{
"name": "audio_info",
"description": "获取视频的音频流信息",
"input_schema": {
"type": "object",
"required": ["video_url"],
"properties": {
"video_url": {"type": "string"}
}
}
},
{
"name": "info",
"description": "获取完整视频信息",
"input_schema": {
"type": "object",
"required": ["video_url"],
"properties": {
"video_url": {"type": "string"}
}
}
}
]
}Product strategy session with 6 forcing questions. Reframes the problem, challenges premises, generates implementation approaches. Works directly in OpenClaw...
--- name: ucts-office-hours description: > Product strategy session with 6 forcing questions. Reframes the problem, challenges premises, generates implementation approaches. Works directly in OpenClaw — no Claude Code session needed. tags: [ucts, planning, product-strategy, office-hours] --- # UCTS Office Hours Run a structured product strategy session. This is a conversational skill — talk directly with the user. ## The 6 Forcing Questions Ask each question. Push back on vague answers. Demand specifics. ### 1. What's the specific pain? Not hypothetical. Real examples. Who screamed? What broke? When did it last happen? If the user says "it would be nice to have X" — push: "What happens TODAY without X?" ### 2. Who experiences this most acutely? Which user persona? How often? What's the cost of the status quo (in time, money, frustration)? "Everyone" is not an answer. Find the single person who suffers most. ### 3. What's the current workaround? If there is one, it tells you the actual need. If there isn't, why not? Workarounds reveal the minimum viable solution. ### 4. What does the 10-star experience look like? Dream big. If this worked perfectly — magic wand — what would it do? Then cut: what's the 7-star version? The 5-star? The 3-star MVP? ### 5. What's the narrowest wedge? The smallest possible thing that proves the concept works. Not the full vision — the first proof. It should be buildable in one session. If it takes a week, it's too big. ### 6. What's the one metric? Single number that tells you it's working. Not three metrics. One. "Users" is not a metric. "Daily active users who complete the core flow" is. ## After the Questions 1. **Challenge the framing.** Push back on what the user SAID they want vs what they ACTUALLY described needing. 2. **Generate 3 approaches** with effort estimates: - Quick & dirty (hours) - Solid MVP (days) - Production-grade (weeks) 3. **Recommend one.** Be opinionated. "Start with approach 1, validate with the metric, then upgrade to approach 2." 4. **If a Claude Code session is needed**, generate the dispatch: `Load UCTS. Run /ucts guide <refined description>`
Upload contract PDFs to extract and manage contract details with expiry reminders and Feishu push notifications, fully offline and secure.
# Contract Tracker (contract-tracker)
> Upload contract PDFs → AI extracts key fields → Manage ledger → Expiry reminders + Feishu push
---
## Trigger Phrases
`contract ledger` `contract management` `contract tracker` `pdf contract` `contract reminder`
---
## Usage
### Command Line
```bash
# Upload a contract PDF
python -m scripts.main upload /path/to/contract.pdf
# List all contracts
python -m scripts.main list
# List contracts expiring within 30 days
python -m scripts.main list --status "Active" --sort end_date
# Get contract details
python -m scripts.main get <contract_id>
# Update a contract
python -m scripts.main update <contract_id> --name "New Name" --status "Terminated"
# Delete a contract
python -m scripts.main delete <contract_id>
# Add expiry reminder
python -m scripts.main reminder <contract_id> add --days 30
# Check expiring contracts
python -m scripts.main check --days 30
# Export contracts
python -m scripts.main export --format csv -o contracts.csv
```
### Python API
```python
from scripts import extract_text_from_pdf, extract_contract_fields
from scripts import add_contract, get_contracts, get_contract
from scripts import update_contract, delete_contract
# Extract fields from PDF
text = extract_text_from_pdf("/path/to/contract.pdf")
fields = extract_contract_fields(text, "contract.pdf")
contract = add_contract(fields)
# List contracts
all_contracts = get_contracts(status="Active")
```
---
## Contract Fields Extracted
- **Contract Name** — from PDF title
- **Amount** — RMB amount via regex
- **Sign Date** — contract signing date
- **Start Date** — effective start date
- **End Date** — expiry date
- **Counterparty** — other party name
- **Key Nodes** — payment terms, renewal clauses (up to 5)
- **Status** — Active / Expired (auto-calculated)
---
## Supported Formats
| Format | Extension | Notes |
|--------|-----------|-------|
| PDF | `.pdf` | Text extraction via PyMuPDF |
---
## Tech Stack
- **Parsing**: PyMuPDF (fitz)
- **AI Field Extraction**: Regex + heuristic pattern matching (fully offline, no external AI API)
- **Storage**: JSON file in `/tmp/contract-tracker/` (fully offline, no home directory writes)
- **Notifications**: Feishu IM card format
---
## Tiered Features
| Feature | FREE | PRO |
|---------|:----:|:---:|
| Max Contracts | 5 | Unlimited |
| Max Reminders | 1 | Unlimited |
| Export Formats | CSV | CSV, XLSX, PDF |
| Feishu Reminders | No | Yes |
**Price**: $0.01 USDT per call (PRO tier). FREE tier is free.
> Get PRO: [https://skillpay.me/contract-tracker](https://skillpay.me/contract-tracker)
---
## Billing
- **Endpoint**: `POST https://skillpay.me/api/v1/billing/charge`
- **Header**: `X-API-Key: {api_key}`
- **Body**: `{"user_id": "...", "skill_id": "contract-tracker", "amount": 0.01}`
- **Response**: `{"success": true, "balance": ...}`
- **Fallback**: Network error → FREE tier (do not block usage)
- **Dev Mode**: No API key configured → `balance=999.0`, no charge
---
## Required Environment Variables
| Variable | Description |
|----------|-------------|
| `SKILL_BILLING_API_KEY` | SkillPay Builder API Key |
| `SKILL_BILLING_SKILL_ID` | Skill Slug (default: contract-tracker) |
---
## Security Notes
- All contract data stored in `/tmp/contract-tracker/` — no home directory writes
- PDF parsing is fully offline — no external network calls during extraction
- Feishu card push requires a Feishu bot token (configure separately)
---
## API Key Format
Any non-empty string works as an API key. Tier is determined automatically:
- **No API key** → FREE tier
- **Any API key** → PRO tier
---
## Slug
`contract-tracker`
FILE:requirements.txt
PyMuPDF>=1.23.0
requests>=2.28.0
FILE:scripts/pdf_parser.py
"""
PDF Parser for Contract Ledger.
Uses PyMuPDF (fitz) to extract text from PDF contracts.
"""
import re
import fitz
from datetime import datetime
from typing import Optional
def extract_text_from_pdf(pdf_path: str) -> str:
"""Extract all text from a PDF file."""
doc = fitz.open(pdf_path)
text_parts = []
for page in doc:
text_parts.append(page.get_text())
doc.close()
return "\n".join(text_parts)
def extract_contract_fields(text: str, filename: str = "") -> dict:
"""
Extract key fields from contract text using pattern matching.
Returns: contract_name, amount, dates, counterparty, key_nodes, status.
"""
# Extract contract name
lines = [l.strip() for l in text.split("\n") if l.strip()]
contract_name = ""
if lines:
for line in lines[:5]:
if len(line) > 5 and not line.startswith("\u7b2c") and "\u6761" not in line:
contract_name = line
break
if not contract_name and filename:
contract_name = filename.replace(".pdf", "").replace("_", " ")
# Extract amount
amount = extract_amount(text)
# Extract dates
sign_date = extract_date(text, ["\u7b7e\u8ba2\u65e5\u671f", "\u7b7e\u7f72\u65e5\u671f", "\u7b7e\u7ea6\u65e5\u671f", "\u7b7e\u8ba2\u4e8e"])
start_date = extract_date(text, ["\u5f00\u59cb\u65e5\u671f", "\u751f\u6548\u65e5\u671f", "\u8d77\u59cb\u65e5\u671f", "\u5f00\u59cb\u4e8e"])
end_date = extract_date(text, ["\u7ed3\u675f\u65e5\u671f", "\u5230\u671f\u65e5\u671f", "\u7ec8\u6b62\u65e5\u671f", "\u5c48\u6ee1\u65e5\u671f", "\u5230\u671f\u4e8e"])
# Extract counterparty
counterparty = extract_counterparty(text)
# Extract key nodes
key_nodes = extract_key_nodes(text)
return {
"contract_name": contract_name,
"amount": amount,
"sign_date": sign_date,
"start_date": start_date,
"end_date": end_date,
"counterparty": counterparty,
"key_nodes": key_nodes,
"status": determine_status(end_date),
}
def extract_amount(text: str) -> Optional[float]:
"""Extract contract amount from text."""
patterns = [
r"\u5408\u540c\u91d1\u989d[::]\s*([\d,,.]+)",
r"\u603b\u4ef7\u6b3e?[::]\s*([\d,,.]+)",
r"\u603b\u4ef7[::]\s*([\d,,.]+)",
r"([\d,,.]+)\s*\u5143",
r"¥\s*([\d,,.]+)",
r"RMB\s*([\d,,.]+)",
]
for pattern in patterns:
match = re.search(pattern, text)
if match:
amount_str = match.group(1).replace(",", "").replace("\uff0c", ".")
try:
return float(amount_str)
except ValueError:
continue
return None
def extract_date(text: str, keywords: list) -> Optional[str]:
"""Extract date from text using keywords."""
date_pattern = r"(\d{4}[-/\u5e74]\d{1,2}[-/\u6708]\d{1,2}[\u65e5]?)"
for kw in keywords:
idx = text.find(kw)
if idx != -1:
snippet = text[idx:idx+50]
match = re.search(date_pattern, snippet)
if match:
return normalize_date(match.group(1))
match = re.search(date_pattern, text)
if match:
return normalize_date(match.group(1))
return None
def normalize_date(date_str: str) -> str:
"""Normalize date to YYYY-MM-DD format."""
date_str = date_str.replace("\u5e74", "-").replace("\u6708", "-").replace("\u65e5", "")
parts = re.split(r"[-/]", date_str)
if len(parts) == 3:
return f"{int(parts[0]):04d}-{int(parts[1]):02d}-{int(parts[2]):02d}"
return date_str
def extract_counterparty(text: str) -> Optional[str]:
"""Extract counterparty company name."""
patterns = [
r"\u4e59\u65b9[::]\s*([^\s\uff0c\uff0c\uff0c]+)",
r"\u5bf9\u65b9[::]\s*([^\s\uff0c\uff0c\uff0c]+)",
r"\u4f9b\u5e94\u5546[::]\s*([^\s\uff0c\uff0c\uff0c]+)",
r"\u670d\u52a1\u5546[::]\s*([^\s\uff0c\uff0c\uff0c]+)",
r"\u59d4\u6258\u65b9[::]\s*([^\s\uff0c\uff0c\uff0c]+)",
]
for pattern in patterns:
match = re.search(pattern, text)
if match:
return match.group(1).strip()
return None
def extract_key_nodes(text: str) -> list:
"""Extract key contract nodes (payment terms, renewal, etc.)."""
nodes = []
payment_patterns = [
r"\u4ed8\u6b3e\u65b9\u5f0f[::][^\n\u3002]+",
r"\u652f\u4ed8\u65b9\u5f0f[::][^\n\u3002]+",
r"\u4ed8\u6b3e\u6761\u4ef6[::][^\n\u3002]+",
]
for p in payment_patterns:
m = re.search(p, text)
if m:
nodes.append(m.group(0).strip())
renewal_patterns = [
r"\u7eed\u7ea6[^\n\u3002]+",
r"\u81ea\u52a8\u7eed\u671f[^\n\u3002]+",
r"\u671f\u6ee1\u540e[^\n\u3002]+",
]
for p in renewal_patterns:
m = re.search(p, text)
if m:
nodes.append(m.group(0).strip())
return nodes[:5]
def determine_status(end_date: Optional[str]) -> str:
"""Determine contract status based on end date."""
if not end_date:
return "Active" # Active
try:
end = datetime.strptime(end_date, "%Y-%m-%d")
now = datetime.now()
if end < now:
return "Expired" # Expired
return "Active" # Active
except ValueError:
return "Active"
FILE:scripts/config.py
"""
Configuration module for Contract Tracker.
No external API validation - billing is handled separately via SkillPay.
Tier is determined by presence of a valid API key: FREE (no key) | PRO (any key).
"""
from dataclasses import dataclass
from typing import Optional
# Tier definitions (2-tier: FREE | PRO)
TIERS = {
"FREE": {
"max_contracts": 5,
"max_reminders": 1,
"export_formats": ["csv"],
},
"PRO": {
"max_contracts": -1, # unlimited
"max_reminders": -1, # unlimited
"export_formats": ["csv", "xlsx", "pdf"],
},
}
FALLBACK_TIER = "FREE"
@dataclass
class TokenInfo:
"""Token validation result."""
valid: bool
tier: str
max_contracts: int
max_reminders: int
export_formats: list
error: Optional[str] = None
class Config:
"""Configuration manager - no external API calls."""
def __init__(self):
self._cache: dict = {}
def validate_token(self, api_key: str) -> TokenInfo:
"""
Validate token. For ClawHub model: any non-empty API key = PRO tier.
No external API call needed - billing is handled by SkillPay separately.
"""
if api_key and api_key.strip():
tier = "PRO"
tier_info = TIERS["PRO"]
return TokenInfo(
valid=True,
tier=tier,
max_contracts=tier_info["max_contracts"],
max_reminders=tier_info["max_reminders"],
export_formats=tier_info["export_formats"],
)
else:
tier = "FREE"
tier_info = TIERS["FREE"]
return TokenInfo(
valid=True, # FREE tier is always valid
tier=tier,
max_contracts=tier_info["max_contracts"],
max_reminders=tier_info["max_reminders"],
export_formats=tier_info["export_formats"],
)
def clear_cache(self, api_key: Optional[str] = None):
"""Clear the validation cache."""
if api_key:
self._cache.pop(api_key, None)
else:
self._cache.clear()
def get_tier_limits(tier: str) -> dict:
"""Get tier limits as a dict (for backward compatibility)."""
tier_info = TIERS.get(tier, TIERS[FALLBACK_TIER])
return {
"max_contracts": tier_info["max_contracts"],
"max_reminders": tier_info["max_reminders"],
"export_formats": tier_info["export_formats"],
}
FILE:scripts/billing.py
"""
Billing module for Contract Tracker (contract-tracker).
Integrates with SkillPay per-call billing.
"""
import os
import requests
import logging
logger = logging.getLogger(__name__)
BILLING_URL = "https://skillpay.me/api/v1/billing"
API_KEY = os.environ.get("SKILL_BILLING_API_KEY", "")
SKILL_ID = os.environ.get("SKILL_BILLING_SKILL_ID", "contract-tracker")
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
CALL_PRICE = 0.0100 # USDT per call
def is_dev_mode() -> bool:
"""Check if running in development mode (no API key configured)."""
return API_KEY in ("", "dev", "test")
def charge_user(user_id: str) -> dict:
"""
Charge a user for one API call.
Returns dict with ok=True/False and balance/payment_url on failure.
"""
if is_dev_mode():
return {"ok": True, "balance": 999.0}
try:
resp = requests.post(
f"{BILLING_URL}/charge",
headers=HEADERS,
json={"user_id": user_id, "skill_id": SKILL_ID, "amount": CALL_PRICE},
timeout=10
)
data = resp.json()
if data.get("success"):
return {"ok": True, "balance": data.get("balance", 0.0)}
return {
"ok": False,
"balance": 0.0,
"payment_url": data.get("payment_url", f"https://skillpay.me/{SKILL_ID}"),
}
except Exception as e:
logger.warning(f"Billing error: {e}")
return {"ok": False, "balance": 0.0, "payment_url": f"https://skillpay.me/{SKILL_ID}"}
FILE:scripts/requirements.txt
PyMuPDF>=1.23.0
requests>=2.28.0
FILE:scripts/feishu_notifier.py
"""
Feishu notification module for Contract Ledger.
Builds Feishu card messages for contract expiry reminders.
"""
from typing import Optional
def build_reminder_card(contract: dict, days_until_expiry: int) -> dict:
"""Build a Feishu reminder card for a contract."""
fields = [
{"is_short": True, "text": {"tag": "lark_md", "content": "**Contract**"}},
{"is_short": True, "text": {"tag": "lark_md", "content": f"{contract.get('contract_name', 'N/A')}"}},
{"is_short": True, "text": {"tag": "lark_md", "content": "**Counterparty**"}},
{"is_short": True, "text": {"tag": "lark_md", "content": f"{contract.get('counterparty', 'N/A')}"}},
{"is_short": True, "text": {"tag": "lark_md", "content": "**End Date**"}},
{"is_short": True, "text": {"tag": "lark_md", "content": f"{contract.get('end_date', 'N/A')}"}},
{"is_short": True, "text": {"tag": "lark_md", "content": "**Days Remaining**"}},
{"is_short": True, "text": {"tag": "lark_md", "content": f"{days_until_expiry} days"}},
]
amount = contract.get("amount")
if amount:
fields.extend([
{"is_short": True, "text": {"tag": "lark_md", "content": "**Amount**"}},
{"is_short": True, "text": {"tag": "lark_md", "content": f"¥{amount:,.2f}"}},
])
card = {
"config": {"wide_screen_mode": True},
"elements": [
{"tag": "markdown", "content": "**Contract Expiry Reminder**"},
{"tag": "hr"},
{"tag": "div", "fields": fields},
{"tag": "hr"},
{"tag": "markdown", "content": "Sent by Contract Tracker"}
],
"header": {
"title": {"tag": "plain_text", "content": "Contract Expiry Reminder"},
"template": "orange"
}
}
return card
def format_reminder_message(contract: dict, days_until_expiry: int) -> str:
"""Format reminder message as plain text."""
name = contract.get("contract_name", "N/A")
counterparty = contract.get("counterparty", "N/A")
end_date = contract.get("end_date", "N/A")
amount = contract.get("amount")
msg = f"Contract Expiry Reminder\n\n"
msg += f"Contract: {name}\n"
msg += f"Counterparty: {counterparty}\n"
msg += f"End Date: {end_date}\n"
msg += f"Days Remaining: {days_until_expiry} days\n"
if amount:
msg += f"Amount: ¥{amount:,.2f}\n"
return msg
FILE:scripts/__init__.py
"""
Contract Ledger - AI-powered contract management tool.
Upload PDF contracts, manage ledger, get expiry reminders.
"""
from .config import Config, TokenInfo, TIERS, FALLBACK_TIER, get_tier_limits
from .pdf_parser import extract_text_from_pdf, extract_contract_fields
from .storage import (
init_storage, add_contract, get_contracts, get_contract,
update_contract, delete_contract, add_reminder, remove_reminder,
get_expiring_contracts, count_contracts, export_contracts
)
from .feishu_notifier import build_reminder_card, format_reminder_message
__all__ = [
"Config", "TokenInfo", "TIERS", "FALLBACK_TIER", "get_tier_limits",
"extract_text_from_pdf", "extract_contract_fields",
"init_storage", "add_contract", "get_contracts", "get_contract",
"update_contract", "delete_contract", "add_reminder", "remove_reminder",
"get_expiring_contracts", "count_contracts", "export_contracts",
"build_reminder_card", "format_reminder_message",
]
FILE:scripts/storage.py
"""
Storage module for Contract Ledger.
JSON file local storage using /tmp/contract-tracker/ (no home directory writes).
"""
import json
import uuid
from pathlib import Path
from datetime import datetime
from typing import Optional
STORAGE_DIR = Path("/tmp/contract-tracker")
LEDGER_FILE = STORAGE_DIR / "contracts.json"
def init_storage():
"""Initialize storage directory and file."""
STORAGE_DIR.mkdir(parents=True, exist_ok=True)
if not LEDGER_FILE.exists():
_write_ledger([])
def _read_ledger() -> list:
"""Read ledger from file."""
try:
with open(LEDGER_FILE, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return []
def _write_ledger(contracts: list):
"""Write ledger to file."""
with open(LEDGER_FILE, "w", encoding="utf-8") as f:
json.dump(contracts, f, ensure_ascii=False, indent=2)
def add_contract(fields: dict) -> dict:
"""Add a contract."""
contracts = _read_ledger()
contract = {
"id": str(uuid.uuid4())[:8],
"created_at": datetime.now().isoformat(),
"updated_at": datetime.now().isoformat(),
**fields,
"reminders": [],
}
contracts.append(contract)
_write_ledger(contracts)
return contract
def get_contracts(
status: Optional[str] = None,
sort_by: str = "end_date",
reverse: bool = True
) -> list:
"""Get contract list."""
contracts = _read_ledger()
if status:
contracts = [c for c in contracts if c.get("status") == status]
contracts.sort(
key=lambda x: x.get(sort_by, "" or "9999-12-31"),
reverse=reverse
)
return contracts
def get_contract(contract_id: str) -> Optional[dict]:
"""Get a single contract by ID."""
contracts = _read_ledger()
for c in contracts:
if c.get("id") == contract_id:
return c
return None
def update_contract(contract_id: str, updates: dict) -> Optional[dict]:
"""Update a contract."""
contracts = _read_ledger()
for i, c in enumerate(contracts):
if c.get("id") == contract_id:
contracts[i].update(updates)
contracts[i]["updated_at"] = datetime.now().isoformat()
_write_ledger(contracts)
return contracts[i]
return None
def delete_contract(contract_id: str) -> bool:
"""Delete a contract."""
contracts = _read_ledger()
original_len = len(contracts)
contracts = [c for c in contracts if c.get("id") != contract_id]
if len(contracts) < original_len:
_write_ledger(contracts)
return True
return False
def add_reminder(contract_id: str, days_before: int, enabled: bool = True) -> bool:
"""Add a reminder to a contract."""
contract = get_contract(contract_id)
if not contract:
return False
reminders = contract.get("reminders", [])
reminders.append({"days_before": days_before, "enabled": enabled})
update_contract(contract_id, {"reminders": reminders})
return True
def remove_reminder(contract_id: str, index: int) -> bool:
"""Remove a reminder from a contract."""
contract = get_contract(contract_id)
if not contract:
return False
reminders = contract.get("reminders", [])
if 0 <= index < len(reminders):
reminders.pop(index)
update_contract(contract_id, {"reminders": reminders})
return True
return False
def get_expiring_contracts(days: int = 7) -> list:
"""Get contracts expiring within N days."""
contracts = _read_ledger()
expiring = []
now = datetime.now()
for c in contracts:
if c.get("status") == "Expired":
continue
end_date_str = c.get("end_date")
if not end_date_str:
continue
try:
end_date = datetime.strptime(end_date_str, "%Y-%m-%d")
delta = (end_date - now).days
if 0 <= delta <= days:
c["days_until_expiry"] = delta
expiring.append(c)
except ValueError:
continue
return expiring
def count_contracts() -> int:
"""Count total contracts."""
return len(_read_ledger())
def export_contracts(contracts: list, format: str = "csv") -> str:
"""Export contract data."""
if not contracts:
return ""
if format == "csv":
return _export_csv(contracts)
elif format == "json":
return json.dumps(contracts, ensure_ascii=False, indent=2)
else:
return _export_csv(contracts)
def _export_csv(contracts: list) -> str:
"""Export to CSV format."""
if not contracts:
return ""
headers = ["id", "contract_name", "amount", "counterparty", "sign_date",
"start_date", "end_date", "status", "key_nodes"]
lines = [",".join(headers)]
for c in contracts:
row = [
c.get("id", ""),
c.get("contract_name", ""),
str(c.get("amount", "")),
c.get("counterparty", ""),
c.get("sign_date", ""),
c.get("start_date", ""),
c.get("end_date", ""),
c.get("status", ""),
"|".join(c.get("key_nodes", []))
]
lines.append(",".join(f'"{v}"' for v in row))
return "\n".join(lines)
FILE:scripts/main.py
#!/usr/bin/env python3
"""
Contract Ledger CLI - Main entry point.
Upload PDF contracts, manage ledger, get expiry reminders + Feishu notifications.
"""
import argparse
import sys
import json
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
from config import Config, get_tier_limits
from pdf_parser import extract_text_from_pdf, extract_contract_fields
from storage import (
init_storage, add_contract, get_contracts, get_contract,
update_contract, delete_contract, add_reminder, remove_reminder,
get_expiring_contracts, count_contracts, export_contracts
)
from feishu_notifier import build_reminder_card, format_reminder_message
from billing import is_dev_mode, charge_user
DEFAULT_API_KEY = ""
def cmd_upload(args):
"""Upload and parse a contract PDF."""
api_key = args.api_key or DEFAULT_API_KEY
if is_dev_mode():
print("Dev mode: Set SKILL_BILLING_API_KEY for full functionality.", file=sys.stderr)
billing_result = charge_user("cli_upload")
if not billing_result.get("ok"):
print(f"Error: Insufficient balance. Please recharge at https://skillpay.me/contract-tracker", file=sys.stderr)
return 1
config = Config()
token_info = config.validate_token(api_key)
tier = token_info.tier
limits = get_tier_limits(tier)
# Check contract limit
current_count = count_contracts()
max_contracts = limits["max_contracts"]
if max_contracts != -1 and current_count >= max_contracts:
print(f"Tier limit reached ({tier}: {max_contracts} contracts)", file=sys.stderr)
print(f"Current: {current_count}", file=sys.stderr)
return 1
# Extract text and fields
try:
text = extract_text_from_pdf(args.pdf_file)
fields = extract_contract_fields(text, Path(args.pdf_file).name)
except Exception as e:
print(f"PDF parsing failed: {e}", file=sys.stderr)
return 1
# Add contract
contract = add_contract(fields)
print(f"Contract added (ID: {contract['id']})")
print(f" Name: {fields.get('contract_name', 'N/A')}")
print(f" Counterparty: {fields.get('counterparty', 'N/A')}")
print(f" End Date: {fields.get('end_date', 'N/A')}")
print(f" Status: {fields.get('status', 'N/A')}")
if fields.get("amount"):
print(f" Amount: ¥{fields['amount']:,.2f}")
return 0
def cmd_list(args):
"""List contracts."""
contracts = get_contracts(status=args.status, sort_by=args.sort, reverse=not args.asc)
if not contracts:
print("No contracts found.")
return 0
print(f"\nContract Ledger ({len(contracts)} contracts)")
print("-" * 80)
for c in contracts:
amount_str = f"¥{c['amount']:,.2f}" if c.get("amount") else "-"
print(f"[{c['id']}] {c.get('contract_name', 'N/A')}")
print(f" Counterparty: {c.get('counterparty', '-')} | End: {c.get('end_date', '-')} | Amount: {amount_str}")
print(f" Status: {c.get('status', '-')}")
print()
return 0
def cmd_get(args):
"""Get a single contract."""
contract = get_contract(args.contract_id)
if not contract:
print(f"Contract not found: {args.contract_id}", file=sys.stderr)
return 1
print(f"\nContract Details ({contract['id']})")
print("-" * 40)
for k, v in contract.items():
if k == "key_nodes" and isinstance(v, list):
print(f" {k}:")
for node in v:
print(f" - {node}")
elif k == "reminders":
print(f" {k}: {json.dumps(v, ensure_ascii=False)}")
elif v is not None:
print(f" {k}: {v}")
return 0
def cmd_update(args):
"""Update a contract."""
updates = {}
if args.name:
updates["contract_name"] = args.name
if args.counterparty:
updates["counterparty"] = args.counterparty
if args.amount:
updates["amount"] = float(args.amount)
if args.end_date:
updates["end_date"] = args.end_date
if args.status:
updates["status"] = args.status
if not updates:
print("No updates provided", file=sys.stderr)
return 1
result = update_contract(args.contract_id, updates)
if result:
print(f"Contract updated: {args.contract_id}")
return 0
else:
print(f"Update failed: {args.contract_id}", file=sys.stderr)
return 1
def cmd_delete(args):
"""Delete a contract."""
if delete_contract(args.contract_id):
print(f"Contract deleted: {args.contract_id}")
return 0
else:
print(f"Delete failed: {args.contract_id}", file=sys.stderr)
return 1
def cmd_reminder(args):
"""Manage reminders."""
if args.action == "add":
if add_reminder(args.contract_id, args.days):
print(f"Reminder added ({args.days} days before expiry)")
else:
print(f"Failed to add reminder", file=sys.stderr)
return 1
elif args.action == "remove":
if remove_reminder(args.contract_id, args.index):
print("Reminder removed")
else:
print("Failed to remove reminder", file=sys.stderr)
return 1
elif args.action == "list":
contract = get_contract(args.contract_id)
if not contract:
print("Contract not found", file=sys.stderr)
return 1
reminders = contract.get("reminders", [])
if not reminders:
print("No reminders set")
else:
print(f"Reminders ({len(reminders)}):")
for i, r in enumerate(reminders):
status = "ON" if r.get("enabled") else "OFF"
print(f" [{i}] [{status}] {r['days_before']} days before expiry")
return 0
def cmd_check(args):
"""Check expiring contracts."""
api_key = args.api_key or DEFAULT_API_KEY
days = args.days or 7
billing_result = charge_user("cli_check")
if not billing_result.get("ok"):
print(f"Error: Insufficient balance.", file=sys.stderr)
return 1
expiring = get_expiring_contracts(days)
if not expiring:
print(f"No contracts expiring within {days} days")
return 0
print(f"{len(expiring)} contract(s) expiring within {days} days:\n")
for c in expiring:
days_left = c.get("days_until_expiry", 0)
print(f" [{c['id']}] {c.get('contract_name', 'N/A')}")
print(f" End: {c.get('end_date')} ({days_left} days remaining)")
print()
if args.feishu and expiring:
card = build_reminder_card(expiring[0], expiring[0].get("days_until_expiry", 0))
print("\nFeishu card content:")
print(json.dumps(card, ensure_ascii=False, indent=2))
return 0
def cmd_export(args):
"""Export contracts."""
api_key = args.api_key or DEFAULT_API_KEY
config = Config()
token_info = config.validate_token(api_key)
tier = token_info.tier
limits = get_tier_limits(tier)
format_type = args.format or "csv"
if format_type not in limits["export_formats"]:
print(f"Tier {tier} does not support {format_type} export", file=sys.stderr)
print(f"Supported: {', '.join(limits['export_formats'])}", file=sys.stderr)
return 1
contracts = get_contracts(status=args.status)
if not contracts:
print("No contracts to export")
return 0
content = export_contracts(contracts, format_type)
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(content)
print(f"Exported to: {args.output}")
else:
print(content)
return 0
def main():
parser = argparse.ArgumentParser(description="Contract Ledger Management Tool")
subparsers = parser.add_subparsers(dest="command", help="Subcommands")
p_upload = subparsers.add_parser("upload", help="Upload contract PDF")
p_upload.add_argument("pdf_file", help="PDF file path")
p_upload.add_argument("--api-key", help="API Key (optional)")
p_upload.set_defaults(func=cmd_upload)
p_list = subparsers.add_parser("list", help="List contracts")
p_list.add_argument("--status", help="Filter by status")
p_list.add_argument("--sort", default="end_date", help="Sort field")
p_list.add_argument("--asc", action="store_true", help="Sort ascending")
p_list.set_defaults(func=cmd_list)
p_get = subparsers.add_parser("get", help="Get contract details")
p_get.add_argument("contract_id", help="Contract ID")
p_get.set_defaults(func=cmd_get)
p_update = subparsers.add_parser("update", help="Update contract")
p_update.add_argument("contract_id", help="Contract ID")
p_update.add_argument("--name", help="Contract name")
p_update.add_argument("--counterparty", help="Counterparty")
p_update.add_argument("--amount", help="Amount")
p_update.add_argument("--end-date", dest="end_date", help="End date (YYYY-MM-DD)")
p_update.add_argument("--status", help="Status")
p_update.set_defaults(func=cmd_update)
p_delete = subparsers.add_parser("delete", help="Delete contract")
p_delete.add_argument("contract_id", help="Contract ID")
p_delete.set_defaults(func=cmd_delete)
p_reminder = subparsers.add_parser("reminder", help="Manage reminders")
p_reminder.add_argument("contract_id", help="Contract ID")
p_reminder.add_argument("action", choices=["add", "remove", "list"], help="Action")
p_reminder.add_argument("--days", type=int, help="Days before expiry (for add)")
p_reminder.add_argument("--index", type=int, help="Reminder index (for remove)")
p_reminder.set_defaults(func=cmd_reminder)
p_check = subparsers.add_parser("check", help="Check expiring contracts")
p_check.add_argument("--days", type=int, default=7, help="Days to check")
p_check.add_argument("--api-key", help="API Key")
p_check.add_argument("--feishu", action="store_true", help="Output Feishu card")
p_check.set_defaults(func=cmd_check)
p_export = subparsers.add_parser("export", help="Export contracts")
p_export.add_argument("--format", choices=["csv", "xlsx", "pdf"], help="Export format")
p_export.add_argument("--status", help="Filter by status")
p_export.add_argument("--output", "-o", help="Output file path")
p_export.add_argument("--api-key", help="API Key")
p_export.set_defaults(func=cmd_export)
args = parser.parse_args()
init_storage()
if args.command is None:
parser.print_help()
return 0
return args.func(args)
if __name__ == "__main__":
sys.exit(main())
Generate, explain, test, and extract using regular expressions, plus convert natural language descriptions into regex patterns.
# regex-master
## 技能概述
正则表达式生成、测试、解释与可视化工具集。帮助用户快速构建、验证和理解正则表达式,提供自然语言描述到正则的自动转换。
## 何时使用
- 需要从零构建正则表达式时
- 需要解释现有正则的含义时
- 需要测试正则是否匹配目标文本时
- 需要提取文本中特定模式的数据时
## 使用方法
### 基础用法
```python
from scripts.regex_engine import RegexMaster
rm = RegexMaster()
# 测试正则是否匹配
result = rm.test("^\d{11}$", "13800138000")
# -> {"match": true, "groups": []}
# 解释正则含义
explanation = rm.explain("^(?=.*[A-Z])(?=.*\d).{8,}$")
# -> 密码强度检查:至少8位,含大写字母和数字
# 从自然语言生成正则
pattern = rm.generate("提取中国大陆手机号")
# -> "1[3-9]\\d{9}"
# 在文本中提取所有匹配
matches = rm.extract_all("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b", text)
# -> ["[email protected]", "[email protected]"]
```
## 文件结构
```
regex-master/
├── SKILL.md
├── README.md
├── requirements.txt
├── scripts/
│ └── regex_engine.py # 核心引擎
├── examples/
│ └── basic_usage.py # 使用示例
└── tests/
└── test_regex_master.py # 单元测试
```
## 依赖
- `re` (Python 内置)
- 可选: `regex` 库提供更强大的正则支持
## 标签
regex, pattern-matching, text-processing, developer-tools
FILE:README.md
# Regex Master
正则表达式大师 — 生成、测试、解释、提取一站式工具。
## Features
| 功能 | 说明 |
|------|------|
| 智能生成 | 根据自然语言描述自动生成正则表达式 |
| 在线测试 | 测试正则是否匹配目标文本,返回捕获组 |
| 语义解释 | 将复杂的正则表达式翻译成人类可读的中文说明 |
| 批量提取 | 从文本中提取所有匹配项,支持命名捕获组 |
| 常用模板 | 内置邮箱、手机号、身份证、URL等常见模式 |
| 可视化辅助 | 输出正则的结构树,帮助理解嵌套逻辑 |
## Quick Start
```python
from scripts.regex_engine import RegexMaster
rm = RegexMaster()
# 1. 测试正则
rm.test(r"^\d{4}-\d{2}-\d{2}$", "2026-04-27")
# { "match": True, "groups": [] }
# 2. 生成正则 — "匹配 IPv4 地址"
rm.generate("匹配 IPv4 地址")
# "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
# 3. 解释正则
rm.explain(r"^(?=.*[A-Z])(?=.*[a-z])(?=.*\d).{8,}$")
# 密码强度检查:至少8位,包含大写字母、小写字母和数字
# 4. 批量提取
rm.extract_all(r"\b\w+@\w+\.\w+\b", "Contact: [email protected], [email protected]")
# ["[email protected]", "[email protected]"]
# 5. 常用模板
rm.get_template("email")
# "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
```
## Templates
内置常用正则模板:
- `email` — 邮箱地址
- `phone_cn` — 中国大陆手机号
- `idcard` — 身份证号码
- `url` — URL 链接
- `ipv4` — IPv4 地址
- `date_iso` — ISO 日期格式
- `chinese_chars` — 中文字符
- `hex_color` — 十六进制颜色值
- `credit_card` — 信用卡号(简单校验)
## Installation
无需额外依赖,纯 Python 内置 `re` 模块实现。
可选安装 `regex` 库以获得更强大的引擎支持:
```bash
pip install regex
```
## License
MIT
FILE:examples/basic_usage.py
"""
Regex Master - 基础使用示例
"""
from scripts.regex_engine import RegexMaster
def main():
rm = RegexMaster()
print("=" * 50)
print("示例 1: 测试正则是否匹配")
print("=" * 50)
result = rm.test(r"^\d{11}$", "13800138000")
print(f"测试 13800138000 匹配 ^\\d{{11}}$: {result}")
result2 = rm.test(r"^\d{11}$", "1380013800")
print(f"测试 1380013800 匹配 ^\\d{{11}}$: {result2}")
print("\n" + "=" * 50)
print("示例 2: 解释正则含义")
print("=" * 50)
exp = rm.explain(r"^(?=.*[A-Z])(?=.*\d).{8,}$")
print(f"解释密码强度正则: {exp}")
print("\n" + "=" * 50)
print("示例 3: 从自然语言生成正则")
print("=" * 50)
patterns = [
"提取中国大陆手机号",
"匹配邮箱地址",
"匹配 IPv4 地址",
]
for desc in patterns:
pat = rm.generate(desc)
print(f"'{desc}' -> {pat}")
print("\n" + "=" * 50)
print("示例 4: 从文本中提取所有邮箱")
print("=" * 50)
text = """
联系方式:
张三: [email protected]
李四: [email protected]
王五: [email protected]
"""
emails = rm.extract_all(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
print(f"提取到的邮箱: {emails}")
print("\n" + "=" * 50)
print("示例 5: 使用内置模板")
print("=" * 50)
print("可用模板:", list(rm.list_templates().keys()))
print(f"邮箱模板: {rm.get_template('email')}")
print(f"手机号模板: {rm.get_template('phone_cn')}")
print("\n" + "=" * 50)
print("示例 6: 验证正则语法")
print("=" * 50)
valid = rm.validate_pattern(r"^[a-z]+$")
print(f"验证 ^[a-z]+$: {valid}")
invalid = rm.validate_pattern(r"[a-z")
print(f"验证 [a-z: {invalid}")
print("\n" + "=" * 50)
print("示例 7: 正则替换")
print("=" * 50)
text = "我的电话是 138-1234-5678,备用 139-8765-4321"
result = rm.replace(r"(\d{3})-(\d{4})-(\d{4})", text, r"\1****\3")
print(f"替换后: {result}")
if __name__ == "__main__":
main()
FILE:requirements.txt
# 无需额外依赖,纯 Python 内置模块
# 可选增强:
# regex>=2024.4.16
FILE:scripts/regex_engine.py
"""
Regex Master - 正则表达式一站式工具引擎
"""
import re
from typing import List, Dict, Any, Optional, Union
class RegexMaster:
"""正则表达式生成、测试、解释与提取工具"""
# 常用正则模板库
TEMPLATES = {
"email": r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
"phone_cn": r"^1[3-9]\d{9}$",
"idcard": r"^[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]$",
"url": r"^(https?|ftp)://[^\s/$.?#].[^\s]*$",
"ipv4": r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",
"date_iso": r"^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])$",
"chinese_chars": r"[\u4e00-\u9fa5]+",
"hex_color": r"^#(?:[0-9a-fA-F]{3}){1,2}$",
"credit_card": r"^\d{13,19}$",
"uuid": r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
}
# 自然语言 -> 正则 映射表
NL_PATTERNS = {
"提取中国大陆手机号": r"1[3-9]\d{9}",
"匹配邮箱地址": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
"匹配 IPv4 地址": r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",
"匹配中文字符": r"[\u4e00-\u9fa5]",
"匹配 URL 链接": r"https?://[^\s]+",
"匹配日期 YYYY-MM-DD": r"\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])",
"提取数字": r"\d+",
"匹配身份证号": r"[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]",
}
def test(self, pattern: str, text: str, flags: int = 0) -> Dict[str, Any]:
"""测试正则表达式是否匹配目标文本"""
try:
compiled = re.compile(pattern, flags)
match = compiled.match(text)
if match:
return {
"match": True,
"full_match": match.group(0) == text,
"groups": list(match.groups()) if match.groups() else [],
"groupdict": match.groupdict(),
"span": match.span(),
}
return {"match": False, "reason": "no match"}
except re.error as e:
return {"match": False, "reason": f"invalid pattern: {e}"}
def explain(self, pattern: str) -> str:
"""将正则表达式翻译成人类可读的说明"""
explanations = []
# 分段解释常见模式
mapping = {
r"^": "字符串开头",
r"$": "字符串结尾",
r"\d+": "一个或多个数字",
r"\d{3}": "恰好3位数字",
r"\d{4}": "恰好4位数字",
r"\d{8,}": "至少8位数字",
r"\.": "一个点号",
r"[A-Za-z0-9._%+-]+": "字母/数字/特殊字符组合",
r"[a-zA-Z]+": "一个或多个英文字母",
r"[\u4e00-\u9fa5]+": "一个或多个中文字符",
r"(?=.*[A-Z])": "必须包含至少一个大写字母",
r"(?=.*[a-z])": "必须包含至少一个小写字母",
r"(?=.*\d)": "必须包含至少一个数字",
r"(?=.*[!@#$%^&*])": "必须包含至少一个特殊符号",
r".{8,}": "至少8个任意字符",
r".{6,20}": "6到20个任意字符",
}
desc = pattern
for pat, exp in mapping.items():
if pat in pattern:
explanations.append(exp)
if not explanations:
# 通用解释
if pattern.startswith("^") and pattern.endswith("$"):
return f"完整字符串匹配模式: 要求整个文本符合 '{pattern[1:-1]}' 的规则"
return f"模式 '{pattern}' 的文本匹配规则"
return "、".join(explanations)
def generate(self, description: str) -> str:
"""根据自然语言描述生成正则表达式"""
# 先匹配已知映射
for key, pat in self.NL_PATTERNS.items():
if key in description or description in key:
return pat
# 智能推断
if "手机" in description or "电话" in description:
return r"1[3-9]\d{9}"
if "邮箱" in description or "email" in description.lower():
return r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
if "url" in description.lower() or "链接" in description:
return r"https?://[^\s]+"
if "身份证" in description:
return r"[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]"
if "中文" in description:
return r"[\u4e00-\u9fa5]+"
if "数字" in description:
return r"\d+"
if "ipv4" in description.lower() or "ip 地址" in description:
return r"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"
return r".*" # 默认通配
def extract_all(self, pattern: str, text: str, flags: int = 0) -> List[str]:
"""从文本中提取所有匹配项"""
try:
compiled = re.compile(pattern, flags)
return compiled.findall(text)
except re.error:
return []
def get_template(self, name: str) -> Optional[str]:
"""获取内置正则模板"""
return self.TEMPLATES.get(name)
def list_templates(self) -> Dict[str, str]:
"""列出所有可用模板"""
return dict(self.TEMPLATES)
def validate_pattern(self, pattern: str) -> Dict[str, Any]:
"""验证正则表达式语法是否合法"""
try:
re.compile(pattern)
return {"valid": True, "message": "pattern is valid"}
except re.error as e:
return {"valid": False, "message": str(e), "position": e.pos if hasattr(e, "pos") else None}
def replace(self, pattern: str, text: str, replacement: str, flags: int = 0) -> str:
"""使用正则替换文本"""
try:
return re.sub(pattern, replacement, text, flags=flags)
except re.error:
return text
FILE:tests/test_regex_master.py
"""
Regex Master 单元测试
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from scripts.regex_engine import RegexMaster
def test_test_method():
rm = RegexMaster()
assert rm.test(r"^\d{11}$", "13800138000")["match"] is True
assert rm.test(r"^\d{11}$", "1380013800")["match"] is False
assert rm.test(r"^(\d{3})-(\d{4})-(\d{4})$", "138-1234-5678")["groups"] == ["138", "1234", "5678"]
print("✓ test_test_method passed")
def test_explain_method():
rm = RegexMaster()
exp = rm.explain(r"^(?=.*[A-Z])(?=.*\d).{8,}$")
assert "大写字母" in exp or "数字" in exp or "至少8" in exp or "匹配模式" in exp
print("✓ test_explain_method passed")
def test_generate_method():
rm = RegexMaster()
assert rm.generate("提取中国大陆手机号") == r"1[3-9]\d{9}"
assert rm.generate("匹配邮箱地址") == r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
assert "1[3-9]" in rm.generate("手机号")
print("✓ test_generate_method passed")
def test_extract_all_method():
rm = RegexMaster()
text = "Contact: [email protected], [email protected], [email protected]"
matches = rm.extract_all(r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", text)
assert len(matches) == 3
assert "[email protected]" in matches
print("✓ test_extract_all_method passed")
def test_templates():
rm = RegexMaster()
assert rm.get_template("email") is not None
assert rm.get_template("phone_cn") is not None
assert rm.get_template("nonexistent") is None
assert "email" in rm.list_templates()
print("✓ test_templates passed")
def test_validate_pattern():
rm = RegexMaster()
assert rm.validate_pattern(r"^[a-z]+$")["valid"] is True
assert rm.validate_pattern(r"[a-z")["valid"] is False
print("✓ test_validate_pattern passed")
def test_replace_method():
rm = RegexMaster()
text = "hello 123 world 456"
result = rm.replace(r"\d+", text, "NUM")
assert result == "hello NUM world NUM"
print("✓ test_replace_method passed")
if __name__ == "__main__":
test_test_method()
test_explain_method()
test_generate_method()
test_extract_all_method()
test_templates()
test_validate_pattern()
test_replace_method()
print("\n所有测试通过! ✅")
Provides platform-specific publishing guidance on timing, format, adaptation, sequencing, and policy compliance for short video multi-platform launches.
# Short Video Platform Publishing Strategy
Provides platform-specific publishing guidance — optimal timing, format requirements, cross-platform adaptation, and launch sequencing for short videos.
## Target Users
- Content creators
- Social media managers
- Multi-platform publishers
- Marketing teams
## When to Use
- Launching content across multiple platforms
- Deciding which platform to prioritize
- Adapting one video for different platforms
- Planning a coordinated multi-platform release
## Core Workflow
1. Platform selection matrix
2. Format adaptation guide
3. Optimal posting time guide
4. Cross-platform launch sequencing
5. Platform-specific content policy checklist
6. Engagement priming
## Inputs
- Video asset(s)
- Target platforms
- Audience demographics
- Launch goals
- Brand exclusivity constraints
## Expected Outputs
- Platform priority matrix
- Posting schedule with time zones
- Format adaptation checklist
- Launch sequence plan
- Policy compliance checklist
## Example Prompts
- "I have one 60-second video — plan its launch across Douyin, Xiaohongshu, and WeChat Channels."
- "What's the best posting time for Douyin targeting Chinese office workers aged 25–35?"
- "Help me adapt a vertical short video for both Douyin and horizontal YouTube Shorts."
## Trigger Keywords
publishing strategy, cross-platform, Douyin timing, video distribution, multi-platform, platform launch
## Safety & Limitations
Publishing strategy is guidance. Does not post, schedule, or automate publishing. Platform policies change; users should verify current requirements. Music licensing is user's responsibility.
---
*Generated for project short-video-skills-2026-04-27*
FILE:ACCEPTANCE.md
# Acceptance Checklist — Short Video Platform Publishing Strategy
## Criteria
- [x] Document-only: no handler.py, scripts, APIs, or executable code
- [x] No network calls or credential handling
- [x] English-first documentation
- [x] File count ≤ 10 (target: exactly 4)
- [x] Includes safety disclaimer
- [x] skill.json is valid with `requires_api: false`
- [x] No drift from design-spec.md
## Files in This Skill
1. `SKILL.md` — Full workflow, inputs, outputs, examples, safety
2. `README.md` — Quick-start reference
3. `skill.json` — Machine-readable metadata
4. `ACCEPTANCE.md` — This checklist
## Verification Commands
```bash
# Count files in this directory
find /Users/jianghaidong/.openclaw/skills/sv-platform-strategy -type f | wc -l
# Expected: 4
# Verify skill.json
cat /Users/jianghaidong/.openclaw/skills/sv-platform-strategy/skill.json | grep requires_api
# Expected: "requires_api": false
# Verify no code files
find /Users/jianghaidong/.openclaw/skills/sv-platform-strategy -name "*.py" -o -name "*.sh" | wc -l
# Expected: 0
```
---
*Generated for project short-video-skills-2026-04-27*
FILE:README.md
# Short Video Platform Publishing Strategy
Provides platform-specific publishing guidance — optimal timing, format requirements, cross-platform adaptation, and launch sequencing for short videos.
## Target Users
- Content creators
- Social media managers
- Multi-platform publishers
- Marketing teams
## When to Use
- Launching content across multiple platforms
- Deciding which platform to prioritize
- Adapting one video for different platforms
- Planning a coordinated multi-platform release
## Trigger Keywords
publishing strategy, cross-platform, Douyin timing, video distribution, multi-platform, platform launch
## Full Documentation
See [SKILL.md](./SKILL.md) for complete workflow, inputs, outputs, and examples.
---
*Generated for project short-video-skills-2026-04-27*
FILE:skill.json
{
"slug": "sv-platform-strategy",
"name": "Short Video Platform Publishing Strategy",
"description": "Provides platform-specific publishing guidance — optimal timing, format requirements, cross-platform adaptation, and launch sequencing for short videos.",
"type": "descriptive",
"requires_api": false,
"readiness": "stable",
"tags": [
"video",
"publishing",
"platform",
"distribution",
"strategy",
"descriptive"
],
"trigger_keywords": [
"publishing strategy",
"cross-platform",
"Douyin timing",
"video distribution",
"multi-platform",
"platform launch"
],
"max_files": 4,
"language": "en",
"safety": "document-only informational guidance"
}