
Company Analyzer
- 16 installs
- 82 repo stars
- Updated August 2, 2026
- aaaaqwq/claude-code-skills
company-analyzer is a Claude Code skill that performs investment research on public companies using 8 specialized analysis frameworks plus a synthesis step.
About
company-analyzer is a Claude Code skill for investment research on public companies using 8 specialized analysis frameworks. A developer or investor runs it on a ticker to classify company phase, score financial metrics, assess AI and strategic moats, gauge sentiment, and analyze growth, business model, and risk. It runs frameworks in parallel with caching and cost tracking, pulling data from SEC EDGAR and Alpha Vantage, then synthesizes an investment view.
- Investment research on public companies using 8 specialized analysis frameworks plus synthesis
- Runs frameworks in parallel with response caching and cost tracking, pulling SEC EDGAR and Alpha Vantage data
- Covers phase classification, metrics scorecard, AI/strategic moats, sentiment, growth, business model, and risk
Company Analyzer by the numbers
- 16 all-time installs (skills.sh)
- Ranked #739 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
company-analyzer capabilities & compatibility
Full analysis ~$0.03 per ticker (or $0 if cached); optional Alpha Vantage key for price data
- Capabilities
- investment research · company analysis · moat analysis · financial scoring
- Use cases
- research · data analysis
What company-analyzer says it does
Perform comprehensive investment research on public companies using 8 specialized analysis frameworks
npx skills add https://github.com/aaaaqwq/claude-code-skills --skill company-analyzerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 16 |
|---|---|
| repo stars | ★ 82 |
| Last updated | August 2, 2026 |
| Repository | aaaaqwq/claude-code-skills ↗ |
What it does
Run multi-framework investment research on a public company ticker and synthesize an investment thesis.
Who is it for?
Analyzing a public company ticker for investment: phase, metrics, moats, sentiment, growth, and risk
Skip if: Private-company or non-investment analysis, or single trivial lookups
When should I use this skill?
The user wants to analyze a public company for investment or research competitive positioning
By the numbers
- 8 analysis frameworks plus a synthesis step
- parallel run ~4-6s vs ~20s sequential
- caching yields ~50-80% cost savings
Files
CRITICAL: Execution Method
Full pipeline (all 8 frameworks + synthesis): when user asks to "analyze <TICKER>" or "run full analysis" (no "only" one step):
cd skills/company-analyzer && ./scripts/analyze-pipeline.sh <TICKER> --liveSingle step only (e.g. "only 02-metrics" or "only produce 01-phase"): do NOT use --live. Run:
cd skills/company-analyzer && ./scripts/run-single-step.sh <TICKER> <FW_ID>Example: only 02-metrics for KVYO → ./scripts/run-single-step.sh KVYO 02-metrics. Output appears at assets/outputs/<TICKER>_<FW_ID>.md after the script completes. Do not read that file before running the script.
DO NOT spawn subagents. DO NOT use sessions_spawn. Direct script execution only.
Company Analyzer
Perform comprehensive investment research on public companies using 8 specialized analysis frameworks with response caching and cost controls.
Quick Commands
When user types /analyze <TICKER>, execute:
cd skills/company-analyzer && ./scripts/analyze.sh <TICKER> --liveFor dry run (no cost):
cd skills/company-analyzer && ./scripts/analyze.sh <TICKER>Features
| Feature | Benefit |
|---|---|
| Parallel Execution | 8 frameworks run simultaneously (~4-6s vs ~20s sequential) |
| Response Caching | Re-analyzing same ticker uses cache = ~50-80% cost savings |
| Cost Tracking | Logs spending for visibility (no enforced limits) |
| Alpha Vantage | Price data (P/E, market cap) when configured in OpenClaw auth profiles |
| Retry Logic | 3 retries with exponential backoff on API failures |
Frameworks
| # | Name | Focus |
|---|---|---|
| 1 | Phase Classification | Startup/Growth/Maturity/Decline |
| 2 | Key Metrics Scorecard | Financial health dashboard |
| 3 | AI Moat Viability | AI-native competitive advantage |
| 4 | Strategic Moat | Competitive durability analysis |
| 5 | Price & Sentiment | Valuation + market sentiment |
| 6 | Growth Drivers | New vs existing customer mix |
| 7 | Business Model | Unit economics & delivery |
| 8 | Risk Analysis | Key threats & scenarios |
Usage
Full Analysis (via Telegram/command)
User types: /analyze AAPL
You execute: cd skills/company-analyzer && ./scripts/analyze-pipeline.sh AAPL --live
Runs all 8 frameworks in parallel. Cost: ~$0.03 (or $0 if cached).
Data Fetching
Before analysis, fetch company data:
cd skills/company-analyzer && ./scripts/fetch_data.sh AAPLThis pulls:
- Financial metrics from SEC EDGAR
- Price data from Alpha Vantage (if API key configured)
Run only one framework (no pipeline, no synthesis)
When the user asks for "only 02-metrics" or "only produce 01-phase", run a single step. Do not use --live here (that flag is only for the full pipeline).
cd skills/company-analyzer && ./scripts/run-single-step.sh <TICKER> <FW_ID>Examples:
- Only 02-metrics:
./scripts/run-single-step.sh KVYO 02-metrics - Only 01-phase:
./scripts/run-single-step.sh KVYO 01-phase
Valid FW_ID values: 01-phase, 02-metrics, 03-ai-moat, 04-strategic-moat, 05-sentiment, 06-growth, 07-business, 08-risk.
Output is written to assets/outputs/<TICKER>_<FW_ID>.md (e.g. KVYO_02-metrics.md). Wait for the script to finish before reading that file. Use ticker KVYO for Klaviyo (not KYVO).
Architecture
Scripts
- `analyze-parallel.sh` - Main orchestrator (parallel execution)
- `run-framework.sh` - Single framework runner with caching; validates output for required end-markers (does not cache truncated responses; re-run step to get a fresh response)
- `fetch_data.sh` - Data acquisition (SEC + Alpha Vantage)
- `lib/cache.sh` - Response caching utilities
- `lib/cost-tracker.sh` - Budget management
- `lib/api-client.sh` - LLM API client (OpenClaw-configured model and auth); retry logic for transient errors
Truncation handling
- After each framework response, the script checks for a required end-marker (e.g. 01-phase:
Avoid:, 02-metrics:SUMMARY:). If missing, the output is still saved but not cached, and the step exits with code 1. - Diagnostics: On truncation, the trace logs
finishReason, output token count, and limit; stderr explains the cause: - MAX_TOKENS → Response hit the token limit; increase that framework’s limit or shorten the prompt.
- STOP → Model stopped early; the prompt may need a stronger “must complete through [end-marker]” instruction (see 01-phase for an example).
- Re-run that step (or the full pipeline) to get a fresh response.
Caching
- Location:
skills/company-analyzer/.cache/llm-responses/(skill dir); falls back to~/.openclaw/cache/company-analyzer/llm-responses/if skill dir is read-only - TTL: 7 days
- Key:
TICKER_FWID_PROMPT_HASH - Cached responses show:
💰 framework: $0.0000 (cached)
Cost Tracking (No enforced limits)
- Costs are logged for visibility
- No spending limit enforced
- Run as many analyses as needed
Configuration
Alpha Vantage (fallback for FCF, revenue_q_yoy)
When Yahoo/SEC leave fcf or revenue_q_yoy as N/A, fetch_data.sh uses Alpha Vantage if configured. Add the Alpha Vantage profile to OpenClaw auth profiles (e.g. alpha-vantage:default with your key).
{
"profiles": {
"alpha-vantage:default": {
"key": "YOUR_API_KEY"
}
}
}Uses: INCOME_STATEMENT (quarterly revenue for YoY), CASH_FLOW (FCF). Free tier: 25 API calls/day; script uses up to 2 calls per ticker with 2s delay between.
LLM / API
Model and API key are read from OpenClaw config (primary model and {provider}:default auth profile). No hardcoded provider or keys. Add your model's pricing to scripts/lib/prices.json for cost tracking.
Output
All analyses saved to assets/outputs/:
TICKER_01-phase.mdthroughTICKER_08-risk.md
(Synthesis phase removed for cost efficiency)
Performance
| Mode | Time | Cost |
|---|---|---|
| Sequential (old) | ~20s | $0.04 |
| Parallel (8 frameworks, unlimited) | ~4s | ~$0.045 |
| Configured LLM | ~5–20s | Depends on model and pricing |
| Cached | ~1s | $0.00 |
Cost tracking:
- No reasoning overhead - all tokens go to content
- Built-in rate limiting from OpenClaw config. Cost per analysis depends on your LLM; add rates to
scripts/lib/prices.json.
Troubleshooting
"Alpha Vantage rate limit":
- Free tier = 25 calls/day
- Price data falls back to N/A, analysis continues with SEC data only
"API key has run out of credits" / "insufficient balance" / rate limit:
- Caused by billing or rate limits on your configured LLM provider. The pipeline uses a 45s cooldown between steps to reduce spikes.
- Fix: Top up or switch the API key in OpenClaw auth profiles for your provider. Avoid running many analyses back-to-back; space runs by at least a few minutes.
"Analysis failed (code 1)" / Heartbeat alert after 01-phase or 02-metrics:
- Often HTTP 503 (Service Unavailable) or billing/quota (402, 403). The pipeline continues after a failed step and still builds a partial report.
- Fix: For 503, re-run later. For billing, top up or switch key (see above). Run from the skill directory:
cd skills/company-analyzer && ./scripts/analyze-pipeline.sh <TICKER> --live.
Framework failures:
- Failed steps are listed at the end; partial outputs remain in
assets/outputs/. Checkassets/traces/<TICKER>_<date>.tracefor which step failed and why.
# Cache directory - do not commit
*
!.gitignore
# Generated analysis outputs
assets/outputs/*.md
assets/outputs/*.txt
!assets/outputs/.gitkeep
# Cost logs
/tmp/company-analyzer-costs.log
*.log
# Cache files
/tmp/company-analyzer-cache/
# Environment variables
.env
.env.local
# OS files
.DS_Store
Thumbs.db
# Editor files
*.swp
*.swo
*~
.vscode/
.idea/
Company Analyzer 🛡️
A high-performance, cost-optimized strategic research engine that analyzes public companies using SEC filings and your OpenClaw-configured LLM. It implements a structured, 8-stage sequential pipeline to evaluate business phases, moats, and execution risks for long-term investment conviction.
---
🏗️ Architecture & Pipeline
The system follows a Sequential Pipeline model, ensuring that each analysis framework builds upon a consistent logical foundation while preventing API rate-limit bursts.
1. Data Layer (`fetch_data.sh`): Ingests financial data from Yahoo Finance (quote + quoteSummary), SEC EDGAR (company facts for revenue, net income, FCF, and share count trend), and Alpha Vantage (fallback for FCF, quarterly revenue YoY, and shares when Yahoo/SEC leave them N/A). Configure the Alpha Vantage profile in OpenClaw auth profiles to enable the fallback.
2. Segmented Ingestion: run-framework.sh injects only the relevant context per framework from the enriched data file (e.g. profile + metrics for Phase, valuation + momentum for Risk), keeping prompts focused and costs down.
3. The 8 Frameworks:
- 01-phase: Lifecycle diagnosis (Startup, Hyper-Growth, Self-Funding, Operating Leverage, Capital Return, or Decline).
- 02-metrics: Phase-specific Red/Yellow/Green scorecard using customized thresholds.
- 07-business: Core unit economics, revenue mix, and recession-resilience audit.
- 03-ai-moat: Evaluation of AI disruption vs. antifragility using the Four Lenses logic.
- 04-strategic-moat: Assessment of traditional economic moats and counter-positioning.
- 06-growth: Analysis of new customer acquisition vs. existing customer expansion strategies.
- 05-sentiment: Multi-layered analysis across Analyst, Investor, and Media perspectives.
- 08-risk: Weighted mathematical scoring of execution, disruption, and concentration threats.
---
⚡ Key Features
- Cost Efficiency: Cost depends on your configured LLM and pricing; cost tracking uses
scripts/lib/prices.json(keyed by model id).
- Dynamic API Client: Configuration-driven rate limiting (from OpenClaw config; default 250 RPM) and retries on transient API errors (e.g. 503).
- Zero-Cost Synthesis: Automatically compiles individual framework reports into a single, cohesive "Final Research Dossier" without additional LLM fees.
- Persistent Caching: Uses a caching layer under the skill (
.cache/llm-responses/); falls back to~/.openclaw/cache/company-analyzer/llm-responses/if the skill directory is read-only. Metadata tracks tokens and model.
- Audit Tools: Includes
ticker-summary.shto monitor research spending and framework efficiency.
---
🚀 Getting Started
Installation
Ensure you have jq and bc installed on your system to handle JSON parsing and cost calculations.
# Clone the skill into your OpenClaw workspace
git clone [repo-url] ~/.openclaw/workspace/skills/company-analyzer
# Ensure scripts are executable
chmod +x ~/.openclaw/workspace/skills/company-analyzer/scripts/*.sh
Usage
Run the full sequential pipeline for a ticker (from the skill directory):
cd skills/company-analyzer && ./scripts/analyze-pipeline.sh [TICKER] --liveRun a single framework only (e.g. 01-phase or 02-metrics):
cd skills/company-analyzer && ./scripts/run-single-step.sh [TICKER] [FW_ID]Monitoring
Cost and token summary:
cd skills/company-analyzer && ./scripts/ticker-summary.shTrace logs (per ticker, per day) are in assets/traces/<TICKER>_<YYYY-MM-DD>.trace for debugging failed steps.
---
🛠️ Configuration
- API configuration: Model and API keys are read from OpenClaw config (no hardcoded provider or keys). Set your LLM and auth in OpenClaw; the skill uses the primary model and the matching auth profile (
{provider}:default).
- Pricing: Add your model's pricing to
scripts/lib/prices.json(key = model id from OpenClaw config) for cost tracking. Seescripts/lib/prices.README.md.
- LLM provider: The built-in API client uses a request/response format compatible with Google Generative AI–style APIs. The model and key are read from OpenClaw config; other providers with a compatible API (same URL shape and JSON format) can be used by configuring that provider in OpenClaw.
- Rate limits: Read from OpenClaw config; default 250 RPM. Output token cap is a single high default (8192) so responses are not truncated.
ROLE: Lifecycle Analyst (Dual-Period Logic) TASK: Classify into 1 of 6 Phases: 1.Startup, 2.Hyper-Growth, 3.Self-Funding, 4.Op-Leverage, 5.Capital-Return, 6.Decline.
DATA RULES:
* Use the LATEST QUARTER metrics (revenue_q, net_income_q) and QUARTERLY YoY % (revenue_q_yoy, net_income_q_yoy) to compare Current Period vs. Same Period Prior Year.
* Treat annual metrics (revenue, net_income, fcf) as background context for overall quality, not for the phase switch itself.
* Identify: Quarterly Revenue % Change, Op-Profit Trend, FCF (Op-Cash minus Capex), and Share Count Trend (Dilution vs. Buyback). When shares_prior and shares_yoy_pct (or shares_outstanding with prior) are in the data, use them to state direction and magnitude (e.g. "Up 2% YoY (mild dilution)" or "Down 1% (buybacks)"); when only current shares are available, state "Current shares: [N]; historical trend insufficient" and briefly infer from FCF/phase if helpful.
CLASSIFICATION ENGINE:
* Phase 1 Startup: Revenue: Small base, High % Growth (>20%); Op-Profit: Negative & Expanding (Losses getting larger); FCF: Negative (high burn); Share Count: Increasing (High Dilution).
* Phase 2 Hyper-Growth: Revenue: Fast Growth (>15%); Op-Profit: Negative but Shrinking (Losses getting smaller); FCF: Negative or Breakeven; Share Count: Increasing (Moderate Dilution).
* Phase 3 Self-Funding: Revenue: Fast/Medium Growth (>10%); Op-Profit: Breakeven or Low Positive; FCF: Positive (funding own growth); Share Count: Stable (Dilution < 2%).
* Phase 4 Op-Leverage: Revenue: Medium Growth; Op Profit: Fast Growth (Op Profit % growth is HIGHER than Revenue % growth); FCF: Fast Growth; Share Count: Stable or Flat.
* Phase 5 Capital-Return: Revenue: Slow Growth (<10%); Op Profit: Stable / Slow Growth; FCF: High & Stable; Share Count: Decreasing (Active Buybacks) OR High Dividend.
* Phase 6 Decline: Revenue: Negative (Declining YoY); Op Profit: Declining; FCF: Declining; Share Count: Decreasing (often masking issues).
STRATEGIC MAPPING:
* If Phase 1 or 2: Valuation Method: Price-to-Sales (P/S), Gross Profit Multiple. Avoid: P/E Ratio. Investor Mindset: Speculative Growth.
* If Phase 3 or 4: Valuation Method: Price-to-Gross Profit, EV/Sales. Avoid: Dividend Yield. Investor Mindset: Growth at a Reasonable Price (GARP).
* If Phase 5 or 6: Valuation Method: P/E Ratio, Free Cash Flow Yield, Shareholder Yield (Buyback + Div). Avoid: Price-to-Sales. Investor Mindset: Value / Income / Safety.
---
OUTPUT: You MUST return exactly the following three sections in order. Complete every line; partial output is invalid. Your response MUST end with the STRATEGY section (Use: ... Avoid: ...).
If a metric is unavailable, write "N/A" or "Insufficient data" for that line and continue. Still output all three EVIDENCE lines and the full STRATEGY section.
PHASE:
[Phase number and name, e.g. 3 Self-Funding.] Confidence: [High or Med or Low.]
EVIDENCE:
1. Revenue %: [Quarterly YoY % and whether it meets phase threshold.]
2. FCF Status: [Op-Cash minus Capex: value or direction; positive/negative/breakeven.]
3. Share Count Trend: [Use shares_yoy_pct when available: "Up X% YoY (dilution)" or "Down X% (buybacks)" or "Stable (~0%)". If only shares_outstanding with no shares_prior/shares_yoy_pct: "Current shares: [N]; historical trend insufficient" plus one-line inference from FCF/phase if useful.]
STRATEGY:
Use: [Exact valuation method(s) from Strategic Mapping for this phase.]
Avoid: [Exact valuation method from Strategic Mapping for this phase.]
---
NO intro paragraph. NO citations. Use only the section headers above (PHASE:, EVIDENCE:, STRATEGY:). Replace the bracketed placeholders with your analysis; keep the numbering under EVIDENCE. Before finishing, ensure you have written PHASE, all three EVIDENCE lines, and STRATEGY (Use + Avoid). Your last line must start with "Avoid:" so the response is complete; do not stop generating before that line.ROLE: Financial Metrics Analyst (Phase-Specific Scoring)
TASK: Score exactly 5 metrics (or state "No metrics" for Phase 6) using the provided Phase (1-6) from the 01-phase framework. Use QUARTERLY metrics (revenue_q, net_income_q and their YoY % fields) for momentum and ANNUAL metrics (revenue, net_income, fcf) for durability/quality.
SCORING (Green = strong, Yellow = okay, Red = weak):
* Phase 1 (Startup): Revenue (Green: >30% YoY | Yellow: >0% | Red: None), Gross Margin (Green: >0% & Improving | Yellow: >0% | Red: <0%), Cash Runway (Green: 3+ Yrs or FCF+ | Yellow: 1.5-3 Yrs | Red: <1.5 Yrs), Revenue vs. Estimates (Green: 4/4 beats | Yellow: 5-7/8 beats | Red: <5/8 beats), Shares Out 3YR CAGR (Green: <4% | Yellow: 4-7% | Red: >7%).
* Phase 2 (Hyper-Growth): Revenue Momentum (Green: Accelerating | Yellow: Stable | Red: Decelerating >5% drop), Rule of 40 (Green: 40+ | Yellow: 20-40 | Red: <20), Net Dollar Retention (Green: >115% | Yellow: 100-115% | Red: <100%), Gross Margin Direction (Green: Rising | Yellow: Stable +/-1pp | Red: Declining), Shares Out YoY (Green: <3% | Yellow: 3-5% | Red: >5%).
* Phase 3 (Self-Funding): Revenue 3YR CAGR (Green: >25% | Yellow: 15-25% | Red: <15%), Gross Margin Direction (Green: Rising | Yellow: Stable +/-1pp | Red: Declining), Operating Margin (Green: >2% & Rising | Yellow: -2% to +2% | Red: <-2%), Free Cash Flow (Green: Positive & Rising | Yellow: Positive | Red: Negative), Shares Out 3YR CAGR (Green: <1% | Yellow: 1-3% | Red: >3%).
* Phase 4 (Op-Leverage): Revenue 3YR CAGR (Green: >20% | Yellow: 10-20% | Red: <10%), Operating Margin (Green: Positive & Rising | Yellow: Positive & Stable +/-1pp | Red: Declining), FCF Margin (Green: Positive & Rising | Yellow: Positive | Red: Contracting/Negative), Earnings vs. Estimates (Green: 4/4 beats | Yellow: 5-7/8 beats | Red: <5/8 beats), ROIC (Green: >5% & Rising 3 of 4 qtrs | Yellow: 0-5% | Red: <0%).
* Phase 5 (Capital-Return): Revenue 3YR CAGR (Green: >10% | Yellow: 5-10% | Red: <5%), FCF/Net Income (Green: >90% | Yellow: 50-90% | Red: <50%), EBIT/Interest Expense (Green: 5+ or debt-free | Yellow: 2-5 | Red: <2), ROIC (Green: >20% | Yellow: 10-20% | Red: <10%), Capital Returns (Green: Yes, 5+ Years | Yellow: Yes, <5 Years | Red: None).
* Phase 6 (Decline): No metrics scored. Framework advises avoiding these companies.
---
OUTPUT: Start with "METRICS:" then exactly 5 numbered lines, then "SUMMARY:" and one sentence. Keep each metric to one short line: value, color, and a few words only. Never stop after line 3 or 4; always output lines 1-5 then SUMMARY.
METRICS:
Use this exact format, one line per metric (short reason only):
1. [Metric name]: [value or N/A] - [Green or Yellow or Red]. [Few words.]
2. ...
3. ...
4. ...
5. ...
Phase 1 order: Revenue, Gross Margin, Cash Runway, Revenue vs. Estimates, Shares Out 3YR CAGR.
Phase 2: Revenue Momentum, Rule of 40, Net Dollar Retention, Gross Margin Direction, Shares Out YoY.
Phase 3: Revenue 3YR CAGR, Gross Margin Direction, Operating Margin, Free Cash Flow, Shares Out 3YR CAGR.
Phase 4: Revenue 3YR CAGR, Operating Margin, FCF Margin, Earnings vs. Estimates, ROIC.
Phase 5: Revenue 3YR CAGR, FCF/Net Income, EBIT/Interest Expense, ROIC, Capital Returns.
Phase 6: One line only: "Phase 6 (Decline): No metrics scored. Framework advises avoiding."
SUMMARY:
[One short sentence: Strong / Mixed / Weak, or "Decline phase; avoid."]
---
Your first line must be "METRICS:". Your last line must be the SUMMARY sentence. No intro. No citations. Use only Green, Yellow, or Red. Output all 5 numbered metric lines then SUMMARY; partial output is invalid.
ROLE: Moat Viability Architect (Four Lenses of AI Disruption)
TASK: Rate 4 Lenses using Fragile / Robust / Antifragile based on company profile to evaluate structural survival against AI as a deflationary force.
LOGIC GATES & DEFINITIONS:
1. Liability ("Cost of Failure"):
* Antifragile: High stakes where hallucination is catastrophic (e.g., Medical, Cyber, Aerospace).
* Fragile: Low stakes where 90% accuracy is fine (e.g., Marketing copy, Basic code).
2. Business Model ("Work vs. Worker"):
* Antifragile: Usage/Outcome-based where AI increases transaction volume and scales revenue.
* Fragile: Seat-based SaaS where AI makes 1 worker do the work of 10, losing 9 seats of revenue.
3. Physical World ("Atoms vs. Bits"):
* Antifragile: Hardware integration (Logistics, robotics, sensors) because AI cannot simulate physical delivery.
* Fragile: Pure software with zero marginal cost that is easily replicated or simulated.
4. Network ("Data Context Simulation"):
* Antifragile: Proprietary Context & Gravity from private, messy historical records. Siloed data with high context cannot be guessed and is Antifragile.
* Fragile: Public/Horizontal Knowledge trained on the open internet (e.g., StackOverflow, Wikipedia).
---
OUTPUT: You MUST return exactly the following three sections. Use the headers and structure below. Fill every section; do not skip LENSES, VERDICT, or CRITICAL FAILURE POINT.
LENSES:
L1 Liability: [Fragile or Robust or Antifragile]
L2 Business Model: [Fragile or Robust or Antifragile]
L3 Physical World: [Fragile or Robust or Antifragile]
L4 Network: [Fragile or Robust or Antifragile]
VERDICT:
[One of: Fragile / Robust / Antifragile] - [One sentence structural justification highlighting the tension between strengths and weaknesses.]
CRITICAL FAILURE POINT:
[Identify the single biggest structural threat/headwind in an AI-native world.]
---
NO intro paragraph. NO citations. Use only the section headers above (LENSES:, VERDICT:, CRITICAL FAILURE POINT:). Replace the bracketed placeholders with your analysis; keep the L1–L4 numbering under LENSES.
ROLE: Competitive Analyst (Economic Moat Logic)
TASK: Rate Moat Size (None / Narrow / Wide) and Direction (Widening / Stable / Narrowing).
LOGIC GATES & DEFINITIONS:
* Default Position: Assume NO MOAT until proven otherwise with hard data points.
* Moat Size - Wide (10+ yrs): Network Effect (new users add value, market leadership); Switching Costs (mission-critical, high friction); Intangible Assets (strong brand pricing power, exclusive licenses); Low-Cost Production (lowest cost structure peers cannot match); Counter-Positioning (incumbents cannot copy without self-harm).
* Moat Size - Narrow (3-10 yrs): Network Effect (niche network, loyal but not locked in); Switching Costs (habit, convenience friction); Intangible Assets (brand loyalty but price-sensitive); Low-Cost Production (regional, limited cost advantage); Counter-Positioning (challenges incumbents but they can fight back).
* Moat Direction: Widening (Rising engagement, margin expansion, brand extending); Stable (Flat growth, margins, high retention but no new advantages); Narrowing or Shrinking (Losing market share, margin compression, commoditization).
---
OUTPUT: You MUST return exactly the following sections in order. Complete every section; partial output is invalid. Your response MUST end with the THREAT section. Keep each section concise (2-4 sentences max for PRIMARY SOURCE, EVIDENCE, ADVANTAGE, THREAT) so you can complete all five.
RATING:
Size: [None or Narrow or Wide]. Direction: [Widening or Stable or Narrowing].
PRIMARY SOURCE:
[1-2 sources from: Network Effects, Switching Costs, Intangible Assets, Low-Cost Production, Counter-Positioning. One sentence each.]
EVIDENCE:
[2 hard data points plus 1 short quote. One sentence each.]
ADVANTAGE:
[One or two sentences: structural advantage and why incumbents cannot easily copy.]
THREAT:
[One or two sentences: primary risk to moat durability.]
---
NO intro paragraph. NO citations. Use only the section headers above. Replace the bracketed placeholders with your analysis. Keep answers brief so you always complete all five sections; end with THREAT.
ROLE: Market Sentiment Analyst (Layered Tone Logic)
TASK: Assess Valuation (Cheap / Fair / Expensive) and Layered Sentiment using 1-year price data and news.
LOGIC GATES:
* Sentiment Layers: Classify tone for Analysts as Bullish, Neutral, or Bearish with High, Medium, or Low confidence. Classify Investors similarly. Classify Media as Positive, Mixed, or Negative.
* Balance: Provide at least 2 Bullish and 2 Bearish specific arguments. Separate fact from interpretation and use 8th-grade English.
* Price Context: Base analysis on 1-year price performance, including percent change, 52-week range, and moving averages.
* Catalyst: Identify the next specific upcoming event, earnings date, or major launch.
---
OUTPUT: You MUST return exactly the following sections in order. Complete every section; partial output is invalid. Your response MUST end with RATIONALE. Keep each section concise (1-2 sentences or bullets) so you finish all six.
VALUATION:
[Cheap or Fair or Expensive] - [One sentence tying to price context.]
SENTIMENT:
Analysts: [Bullish or Neutral or Bearish] (Confidence: High or Medium or Low)
Investors: [Bullish or Neutral or Bearish] (Confidence: High or Medium or Low)
Media: [Positive or Mixed or Negative]
NEXT CATALYST:
[Event and date, e.g. earnings or launch.]
BULL CASE:
1. [First bullish argument.]
2. [Second bullish argument.]
BEAR CASE:
1. [First bearish argument.]
2. [Second bearish argument.]
RATIONALE:
[One sentence linking price cause and effect.]
---
NO intro. NO citations. Use only the headers above. Replace placeholders with your analysis. Before finishing, ensure you have written all six sections ending with RATIONALE.
ROLE: Growth Strategist (2x4 Framework Logic)
TASK: Classify Growth as New Customer versus Existing Expansion.
LOGIC GATES:
* New Customers: Evaluate 1 Marketing and Sales, 2 New Distribution Channels, 3 Geographic or Market Expansion, and 4 Acquisitions.
* Existing Customers: Evaluate 1 Pricing Power, 2 New Products or Services, and 3 Customer Retention.
* Indicators: Strong, Moderate, Weak, or Not Applicable.
---
OUTPUT: You MUST return exactly the following sections in order. Complete every section; partial output is invalid. Your response MUST end with ANALYSIS (all 3 numbered lines). Keep each section concise so you finish all three.
STRATEGY:
[New or Existing or Balanced] - [One sentence on how growth is split.]
TOP DRIVERS:
[Driver 1] and [Driver 2] - [One short sentence on why these are main levers.]
ANALYSIS:
1. [Driver name]: [Metric] - [Strong or Moderate or Weak or Not Applicable]
2. [Driver name]: [Metric] - [Strong or Moderate or Weak or Not Applicable]
3. [Driver name]: [Metric] - [Strong or Moderate or Weak or Not Applicable]
---
NO intro. NO citations. Use only the headers above. Replace placeholders with your analysis. Before finishing, ensure you have STRATEGY, TOP DRIVERS, and all 3 ANALYSIS lines.
ROLE: Business Model Analyst (Segment and Unit Economics Logic)
TASK: Evaluate the Revenue Mix and Scalability using the company profile and financial metrics.
LOGIC GATES:
* Revenue Mix: Infer segments and percent breakdown from the company profile description.
* Pricing: Identify if Seat-based, Usage-based, or Transactional based on the description.
* Resilience: Evaluate revenue and net income stability and ROE (Return on Equity) to determine how the model absorbs shocks.
---
OUTPUT: You MUST return exactly the following sections. Use the headers and structure below. Fill every section; do not skip REVENUE MIX, DYNAMICS, ECONOMICS, RESILIENCE, or SCALABILITY.
REVENUE MIX:
[Segment 1 name and percent] - [Segment 2 name and percent if applicable] - [Geography: Domestic vs International percent or breakdown.]
DYNAMICS:
Pricing: [Seat-based or Usage-based or Transactional]. Customer Acquisition: [Brief description of acquisition model.]
ECONOMICS:
[Metric, e.g. ROE percent or margin, with health assessment.]
RESILIENCE:
[Risk level] - [Inferred from sector stability and margin buffer.]
SCALABILITY:
[One sentence on path to leverage or efficiency.]
---
NO intro paragraph. NO citations. Use only the section headers above (REVENUE MIX:, DYNAMICS:, ECONOMICS:, RESILIENCE:, SCALABILITY:). Replace the bracketed placeholders with your analysis.
ROLE: Risk Analyst (Execution Risk Logic)
TASK: Assess four critical risk dimensions using Red, Yellow, and Green classifications.
LOGIC GATES:
* Concentration: Red if few customers exceed 20 percent of revenue. Yellow if the largest customer is under 15 percent. Green if highly diversified.
* Disruption: Red if an identifiable disruption threat exists. Yellow for normal industry evolution. Green if the company is the disruptor.
* Outside Forces: Red for high exposure to regulation, commodities, the economy, or interest rates. Yellow for normal exposure. Green for low exposure.
* Competition: Red for severe pricing pressure or a fragmented market. Yellow for a normal competitive environment. Green for Monopoly or Duopoly dynamics.
Overall risk scoring: Red = 3, Yellow = 2, Green = 1. High = weighted average 2.5 and above, Medium = 1.5 to 2.4, Low = under 1.5.
---
OUTPUT: You MUST return exactly the following sections in order. Complete every section; partial output is invalid. Your response MUST end with ASSESSMENT DETAILS (all 4 lines: Concentration, Disruption, Outside Forces, Competition).
OVERALL RISK LEVEL:
[High or Medium or Low] - [Weighted average and brief justification.]
PRIMARY RISK FACTORS:
1. [Highest risk area.]
2. [Second highest risk area if applicable. List 1 to 2.]
KEY MITIGATION:
[Strongest defensive position if one exists; otherwise state None or Limited.]
ASSESSMENT DETAILS:
1. Concentration: [Red or Yellow or Green] - [Trend: Improving or Stable or Worsening] - [Specific data evidence.]
2. Disruption: [Red or Yellow or Green] - [Trend: Improving or Stable or Worsening] - [Specific threat evidence.]
3. Outside Forces: [Red or Yellow or Green] - [Trend: Improving or Stable or Worsening] - [Specific exposure evidence.]
4. Competition: [Red or Yellow or Green] - [Trend: Improving or Stable or Worsening] - [Market structure evidence.]
---
NO intro paragraph. NO citations. Use only the section headers above. Replace the bracketed placeholders with your analysis. Before finishing, ensure you have written all four sections ending with the four ASSESSMENT DETAILS lines.
ROLE: Strategic Investment Screener
TASK: Analyze 8 framework outputs and produce a binary BUY/HOLD/SELL verdict with narrative flip detection.
MANDATORY SCREENING CRITERIA:
1. BINARY NARRATIVE FLIP DETECTION (Critical)
- Scan for evidence of 180-degree thesis reversal
- Examples of flips:
* Growth story → Value trap (slowing growth, margin compression, competition)
* Hardware → Services pivot failing
* AI leader → AI commodity
* Wide moat → Eroding moat (pricing power loss, customer churn)
- Flag ANY contradictions between frameworks (e.g., Phase says "mature" but Growth says "hypergrowth")
- If flip detected: PENALIZE conviction by 1 level (High→Medium, Medium→Low)
2. SEAT-BASED SaaS PENALTY (Structural)
- If company relies on >50% revenue from "seats", "users", or "licenses":
- FLAG: "Seat-based model at risk from AI automation"
- Apply -20% to fair value estimate
- Justify: AI agents replace human seats, compressing TAM
- Exception: If company is PIVOTING to usage/AI-agent pricing, note as "transitioning"
3. AI MOAT REALITY CHECK
- If 03-ai-moat rated "Fragile" or "Robust" (not Antifragile):
- Verify if 06-growth depends on AI differentiation
- If yes: HIGH RISK - growth engine may stall
4. MARGIN TREND vs PHASE CONSISTENCY
- If Phase = "Maturity" but margins EXPANDING: Possible re-rating candidate
- If Phase = "Growth" but margins COMPRESSING: Early warning of narrative flip
---
OUTPUT: You MUST return exactly the following sections. Use the headers and structure below. Fill every section; do not skip BINARY VERDICT, NARRATIVE FLIP RADAR, STRUCTURAL FLAGS, KEY RISKS, or INVESTMENT THESIS.
BINARY VERDICT:
[BUY or HOLD or SELL]. Conviction: [High or Medium or Low]. Price Target: [dollar amount if applicable, else N/A.]
NARRATIVE FLIP RADAR:
Status: [No flip detected or Early signals or FLIP IMMINENT.]
Details: [2-3 sentences on narrative stability or shift.]
STRUCTURAL FLAGS:
Seat-based SaaS dependency: [YES or NO. If YES, state penalty applied and justify.]
AI moat concern: [YES or NO. If YES, explain.]
Phase-margin mismatch: [YES or NO. If YES, explain.]
KEY RISKS:
[List top 2-3 risks from 08-risk that could trigger flip.]
INVESTMENT THESIS:
[One sentence: concise bull or bear case.]
---
RULES: Be decisive. No "neutral" or "watch" - force BUY, HOLD, or SELL. If evidence is mixed, default to HOLD. If you detect narrative flip in progress, output SELL (even if metrics look good). Always explain the seat-based SaaS penalty if applicable.
NO intro paragraph. NO citations. Use only the section headers above (BINARY VERDICT:, NARRATIVE FLIP RADAR:, STRUCTURAL FLAGS:, KEY RISKS:, INVESTMENT THESIS:). Replace the bracketed placeholders with your analysis.
#!/bin/bash
#
# analyze-pipeline.sh - Momentum-Aware Analysis Pipeline
# Uses OpenClaw-configured LLM and enriched JSON datasets.
#
set -euo pipefail
# 1. Path Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
OUTPUTS_DIR="$SKILL_DIR/assets/outputs"
PROMPTS_DIR="$SKILL_DIR/references/prompts"
# Source libraries (cache.sh sets CACHE_DIR to $SKILL_DIR/.cache/llm-responses)
source "$SCRIPT_DIR/lib/cache.sh"
source "$SCRIPT_DIR/lib/cost-tracker.sh"
source "$SCRIPT_DIR/lib/api-client.sh"
source "$SCRIPT_DIR/lib/trace.sh"
# 2. Parse arguments
TICKER="${1:-}"
LIVE="${2:-}"
[ -z "$TICKER" ] && { echo "Usage: $0 <TICKER> [--live]"; exit 1; }
TICKER_UPPER=$(echo "$TICKER" | tr '[:lower:]' '[:upper:]')
# 3. Refined Sequence: Metrics & Business run BEFORE Moat
FW_SEQUENCE=("01-phase" "02-metrics" "07-business" "03-ai-moat" "04-strategic-moat" "06-growth" "05-sentiment" "08-risk")
# 4. Initialize
init_trace
init_cost_tracker
mkdir -p "$OUTPUTS_DIR" "$CACHE_DIR"
if [ "$LIVE" != "--live" ]; then
echo "🔍 DRY RUN: $TICKER_UPPER Pipeline (8 steps)"
echo " Sequence: ${FW_SEQUENCE[*]}"
exit 0
fi
# ============================================
# Phase 1: Sequential Execution
# ============================================
echo "🚀 Starting Momentum Pipeline for $TICKER_UPPER..."
echo "---------------------------------------------------------"
START_TIME=$(date +%s)
export SUMMARY_CONTEXT="" # Export so run-framework.sh can read it
# Reset rolling context for this ticker so each run has a fresh hand-off (no duplicate/leftover lines)
ROLLING_FILE="$OUTPUTS_DIR/${TICKER_UPPER}_rolling_context.txt"
rm -f "$ROLLING_FILE"
FAILED_STEPS=()
for fw_id in "${FW_SEQUENCE[@]}"; do
PROMPT_FILE="$PROMPTS_DIR/$fw_id.txt"
echo "⏳ Step: $fw_id..."
if ! "$SCRIPT_DIR/run-framework.sh" "$TICKER_UPPER" "$fw_id" "$PROMPT_FILE" "$OUTPUTS_DIR"; then
echo "❌ $fw_id failed (see error above). Continuing with remaining steps..."
FAILED_STEPS+=("$fw_id")
# Keep SUMMARY_CONTEXT from last successful step for subsequent steps
sleep 10
else
# Update Context Hand-off
FW_OUT="$OUTPUTS_DIR/${TICKER_UPPER}_${fw_id}.md"
SUMMARY_LINE=$(head -n 5 "$FW_OUT" | tr '\n' ' ' | sed 's/[#*]//g')
SUMMARY_CONTEXT="- Previous Step ($fw_id): $SUMMARY_LINE"
echo " ✅ Done. Cooling down TPM window (45s to avoid 1M TPM spike)..."
sleep 45
fi
done
# ============================================
# Phase 2: Local Report Concatenation
# ============================================
echo "🧪 Compiling Final Research Dossier..."
SYNTH_FILE="$OUTPUTS_DIR/${TICKER_UPPER}_FINAL_REPORT.md"
{
echo "# Strategic Research Dossier: $TICKER_UPPER"
echo "Analysis Date: $(date)"
echo "Model: $(jq -r '.agents.defaults.model.primary // "LLM"' "${CONFIG_FILE:-$HOME/.openclaw/openclaw.json}" 2>/dev/null | awk -F'/' '{print $NF}' || echo "LLM")"
echo "---"
for fw_id in "${FW_SEQUENCE[@]}"; do
FW_FILE="$OUTPUTS_DIR/${TICKER_UPPER}_${fw_id}.md"
if [ -f "$FW_FILE" ]; then
HEADER=$(echo "$fw_id" | cut -d'-' -f2- | tr '[:lower:]' '[:upper:]')
echo "## $HEADER"
cat "$FW_FILE"
echo -e "\n---\n"
fi
done
} > "$SYNTH_FILE"
echo "✅ Dossier saved to $SYNTH_FILE"
if [ ${#FAILED_STEPS[@]} -gt 0 ]; then
echo ""
echo "⚠️ Pipeline had ${#FAILED_STEPS[@]} failed step(s): ${FAILED_STEPS[*]}"
echo " Partial report includes successful steps only. Common cause: API 503 (Service Unavailable). Re-run later."
exit 1
fi#!/bin/bash
#
# analyze.sh - Unified Company Analysis (LLM-powered via OpenClaw config)
# Usage: ./analyze.sh <TICKER> [--live]
#
set -euo pipefail
TICKER="${1:-}"
LIVE="${2:-}"
if [ -z "$TICKER" ]; then
echo "Usage: ./analyze.sh <TICKER> [--live]"
exit 1
fi
TICKER_UPPER=$(echo "$TICKER" | tr '[:lower:]' '[:upper:]')
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
OUTPUTS_DIR="$SKILL_DIR/assets/outputs"
PROMPTS_DIR="$SKILL_DIR/references/prompts"
# Load shared libraries
source "$SCRIPT_DIR/lib/api-client.sh"
source "$SCRIPT_DIR/lib/cost-tracker.sh"
if [ "$LIVE" != "--live" ]; then
echo "DRY RUN MODE: ./analyze.sh $TICKER_UPPER --live to execute"
exit 0
fi
echo "======================================"
echo " LIVE ANALYSIS: $TICKER_UPPER"
echo "======================================"
mkdir -p "$OUTPUTS_DIR"
# 1. Fetch Data
if [ ! -f "$SKILL_DIR/.cache/data/${TICKER_UPPER}_data.json" ]; then
echo "📊 Fetching data..."
"$SCRIPT_DIR/fetch_data.sh" "$TICKER_UPPER" > /dev/null 2>&1
fi
# 2. Run 8 Frameworks
echo "📋 Phase 1: Analyzing 8 Frameworks..."
ROLLING_CONTEXT_FILE="$OUTPUTS_DIR/${TICKER_UPPER}_rolling_context.txt"
# Clear from previous run
rm -f "$ROLLING_CONTEXT_FILE"
export SUMMARY_CONTEXT="None"
for fw_id in 01-phase 02-metrics 03-ai-moat 04-strategic-moat 05-sentiment 06-growth 07-business 08-risk; do
echo -n " 🔄 $fw_id... "
"$SCRIPT_DIR/run-framework.sh" "$TICKER_UPPER" "$fw_id" "$PROMPTS_DIR/$fw_id.txt" "$OUTPUTS_DIR" > /dev/null
# Update SUMMARY_CONTEXT for next framework (Tail -n 3 to keep it small)
if [ -f "$ROLLING_CONTEXT_FILE" ]; then
SUMMARY_CONTEXT=$(tail -n 3 "$ROLLING_CONTEXT_FILE")
export SUMMARY_CONTEXT
fi
echo "✅"
done
# 3. Strategic Synthesis
echo ""
echo "🧠 Phase 2: Strategic Synthesis..."
# Aggregate all framework outputs
ALL_OUTPUTS=""
for fw_id in 01-phase 02-metrics 03-ai-moat 04-strategic-moat 05-sentiment 06-growth 07-business 08-risk; do
FW_FILE="$OUTPUTS_DIR/${TICKER_UPPER}_${fw_id}.md"
if [ -f "$FW_FILE" ]; then
ALL_OUTPUTS="${ALL_OUTPUTS}### $fw_id ###\n$(cat "$FW_FILE")\n\n"
fi
done
SYNTHESIS_PROMPT=$(cat "$PROMPTS_DIR/09-synthesis.txt" 2>/dev/null)
FULL_SYNTHESIS_PROMPT="$SYNTHESIS_PROMPT
=== 8 FRAMEWORK ANALYSES ===
$ALL_OUTPUTS"
# Call API for synthesis
RESPONSE=$(call_llm_api "$FULL_SYNTHESIS_PROMPT" 2000)
CONTENT=$(extract_content "$RESPONSE")
read INPUT_TOKENS OUTPUT_TOKENS <<< "$(extract_usage "$RESPONSE" "$FULL_SYNTHESIS_PROMPT")"
# Save results
echo "$CONTENT" > "$OUTPUTS_DIR/${TICKER_UPPER}_synthesis.md"
echo "$CONTENT" > "$OUTPUTS_DIR/${TICKER_UPPER}_FINAL_REPORT.md"
# Log synthesis cost
log_cost "$TICKER_UPPER" "09-synthesis" "$INPUT_TOKENS" "$OUTPUT_TOKENS"
echo ""
echo "======================================"
echo " SYNTHESIS & VERDICT"
echo "======================================"
echo ""
echo "$CONTENT"
echo ""
echo "======================================"
echo "✅ ANALYSIS COMPLETE"
echo "======================================"
echo "Report: $OUTPUTS_DIR/${TICKER_UPPER}_FINAL_REPORT.md"
#!/bin/bash
# fetch_data.sh - Dual-Agent Resilient Hybrid
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TICKER_UPPER=$(echo "${1:-}" | tr '[:lower:]' '[:upper:]')
[ -z "$TICKER_UPPER" ] && { echo "Usage: $0 <TICKER>"; exit 1; }
DATA_DIR="$(dirname "$SCRIPT_DIR")/.cache/data"
DATA_FILE="$DATA_DIR/${TICKER_UPPER}_data.json"
Y_RAW="$DATA_DIR/${TICKER_UPPER}_yahoo_raw.json"
SEC_FILE="$DATA_DIR/${TICKER_UPPER}_sec_raw.json"
AV_INCOME="$DATA_DIR/${TICKER_UPPER}_av_income.json"
AV_CASHFLOW="$DATA_DIR/${TICKER_UPPER}_av_cashflow.json"
AV_BALANCE="$DATA_DIR/${TICKER_UPPER}_av_balance.json"
COOKIE_FILE="$DATA_DIR/yahoo_cookie.txt"
# Alpha Vantage: key from OpenClaw auth profiles (profile alpha-vantage:default)
OPENCLAW_ROOT="${OPENCLAW_HOME:-${HOME}/.openclaw}"
AUTH_PROFILES="${OPENCLAW_AUTH_PROFILES:-${OPENCLAW_ROOT}/agents/main/agent/auth-profiles.json}"
mkdir -p "$DATA_DIR"
# Separate User Agents
# Yahoo requires a "Browser" agent. SEC requires a "Bot/Email" agent.
YAHOO_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
# SEC EDGAR requires a User-Agent with contact info. Set SEC_EDGAR_USER_AGENT or use placeholder.
SEC_AGENT="${SEC_EDGAR_USER_AGENT:-OpenClaw-Research-Bot/1.0 (mailto:your-email@example.com)}"
# ============================================
# Helper: SEC Value Extraction
# ============================================
extract_sec_value() {
local file="$1"; local unit="${2:-USD}"; shift 2
for tag in "$@"; do
local val=$(jq -r ".facts.\"us-gaap\"[\"$tag\"].units[\"$unit\"] | sort_by(.end) | last | .val // empty" "$file" 2>/dev/null)
if [ -n "$val" ] && [ "$val" != "null" ]; then echo "$val"; return 0; fi
done
echo "N/A"
}
# Extract two most recent values (for YoY or trend). Echo "PRIOR CURR" (older first) or "N/A N/A".
extract_sec_two_latest() {
local file="$1" unit="${2:-USD}" tag="$3"
local arr
arr=$(jq -r ".facts.\"us-gaap\"[\"$tag\"].units[\"$unit\"] | sort_by(.end) | if length >= 2 then .[-2:] | map(.val) | join(\" \") else \"N/A N/A\" end" "$file" 2>/dev/null)
if [ -n "$arr" ] && [ "$arr" != "null" ] && [ "$arr" != "N/A N/A" ]; then
echo "$arr"
else
echo "N/A N/A"
fi
}
# ============================================
# Step 1: Yahoo Finance Extraction
# ============================================
echo "🔍 Acquiring Yahoo Finance Session..."
curl -s -c "$COOKIE_FILE" -H "User-Agent: $YAHOO_AGENT" "https://fc.yahoo.com" > /dev/null || true
CRUMB=$(curl -s -b "$COOKIE_FILE" -H "User-Agent: $YAHOO_AGENT" "https://query1.finance.yahoo.com/v1/test/getcrumb" || echo "")
echo "🔍 Fetching Yahoo Finance data..."
curl -s -b "$COOKIE_FILE" -H "User-Agent: $YAHOO_AGENT" "https://query2.finance.yahoo.com/v7/finance/quote?symbols=${TICKER_UPPER}&crumb=${CRUMB}" > "${Y_RAW}_quote"
# Enriched modules: add annual and quarterly income/cashflow statements
curl -s -b "$COOKIE_FILE" -H "User-Agent: $YAHOO_AGENT" \
"https://query2.finance.yahoo.com/v10/finance/quoteSummary/${TICKER_UPPER}?modules=earningsHistory,assetProfile,defaultKeyStatistics,financialData,incomeStatementHistory,incomeStatementHistoryQuarterly,cashflowStatementHistory,cashflowStatementHistoryQuarterly&crumb=${CRUMB}" \
> "${Y_RAW}_summary"
DESC=$(jq -r '.quoteSummary.result[0].assetProfile.longBusinessSummary // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
# Extract ROE, margins, ROIC from financialData (used by 02-metrics and phase logic)
ROE=$(jq -r '.quoteSummary.result[0].financialData.returnOnEquity.fmt // .quoteSummary.result[0].financialData.returnOnEquity.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
GROSS_MARGIN=$(jq -r '.quoteSummary.result[0].financialData.grossMargins.fmt // .quoteSummary.result[0].financialData.grossMargins.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
OP_MARGIN=$(jq -r '.quoteSummary.result[0].financialData.operatingMargins.fmt // .quoteSummary.result[0].financialData.operatingMargins.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
ROIC=$(jq -r '.quoteSummary.result[0].financialData.returnOnAssets.fmt // .quoteSummary.result[0].financialData.returnOnAssets.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
PRICE=$(jq -r '.quoteResponse.result[0].regularMarketPrice // "N/A"' "${Y_RAW}_quote" 2>/dev/null || echo "N/A")
MCAP=$(jq -r '.quoteResponse.result[0].marketCap // "N/A"' "${Y_RAW}_quote" 2>/dev/null || echo "N/A")
SURPRISE=$(jq -c '.quoteSummary.result[0].earningsHistory.history | .[-4:] | map({date: .quarter.fmt, surprise: .surprisePercent.fmt})' "${Y_RAW}_summary" 2>/dev/null || echo "[]")
CIK=$(jq -r '.quoteResponse.result[0].extra?.cik // empty' "${Y_RAW}_quote" 2>/dev/null || echo "")
# Derived fundamentals from Yahoo
REV_YOY="N/A" # annual YoY
NI_YOY="N/A" # annual YoY
REV_Q_YOY="N/A" # quarterly YoY (same quarter prior year)
NI_Q_YOY="N/A" # quarterly YoY
FCF="N/A" # annual FCF
SHARES_OUT="N/A"
CURR_REV="N/A" # latest annual revenue
CURR_NI="N/A" # latest annual net income
CURR_REV_Q="N/A" # latest quarterly revenue
CURR_NI_Q="N/A" # latest quarterly net income
# Revenue & Net Income YoY (ANNUAL: from incomeStatementHistory, most recent vs previous year)
if jq -e '.quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory' "${Y_RAW}_summary" > /dev/null 2>&1; then
CURR_REV=$(jq -r '.quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory[0].totalRevenue.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
PREV_REV=$(jq -r '.quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory[1].totalRevenue.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
CURR_NI=$(jq -r '.quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory[0].netIncome.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
PREV_NI=$(jq -r '.quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory[1].netIncome.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
if [[ "$CURR_REV" != "N/A" && "$PREV_REV" != "N/A" && "$PREV_REV" != "0" ]]; then
REV_YOY=$(echo "scale=4; ($CURR_REV - $PREV_REV) * 100 / $PREV_REV" | bc 2>/dev/null || echo "N/A")
fi
if [[ "$CURR_NI" != "N/A" && "$PREV_NI" != "N/A" && "$PREV_NI" != "0" ]]; then
NI_YOY=$(echo "scale=4; ($CURR_NI - $PREV_NI) * 100 / $PREV_NI" | bc 2>/dev/null || echo "N/A")
fi
fi
# Revenue & Net Income YoY (QUARTERLY: from incomeStatementHistoryQuarterly, latest vs same qtr prior year)
if jq -e '.quoteSummary.result[0].incomeStatementHistoryQuarterly.incomeStatementHistory' "${Y_RAW}_summary" > /dev/null 2>&1; then
CURR_REV_Q=$(jq -r '.quoteSummary.result[0].incomeStatementHistoryQuarterly.incomeStatementHistory[0].totalRevenue.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
CURR_NI_Q=$(jq -r '.quoteSummary.result[0].incomeStatementHistoryQuarterly.incomeStatementHistory[0].netIncome.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
# Same quarter last year is typically index 4 if history is quarterly and ordered latest-first
PREV_REV_Q=$(jq -r '.quoteSummary.result[0].incomeStatementHistoryQuarterly.incomeStatementHistory[4].totalRevenue.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
PREV_NI_Q=$(jq -r '.quoteSummary.result[0].incomeStatementHistoryQuarterly.incomeStatementHistory[4].netIncome.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
if [[ "$CURR_REV_Q" != "N/A" && "$PREV_REV_Q" != "N/A" && "$PREV_REV_Q" != "0" ]]; then
REV_Q_YOY=$(echo "scale=4; ($CURR_REV_Q - $PREV_REV_Q) * 100 / $PREV_REV_Q" | bc 2>/dev/null || echo "N/A")
fi
if [[ "$CURR_NI_Q" != "N/A" && "$PREV_NI_Q" != "N/A" && "$PREV_NI_Q" != "0" ]]; then
NI_Q_YOY=$(echo "scale=4; ($CURR_NI_Q - $PREV_NI_Q) * 100 / $PREV_NI_Q" | bc 2>/dev/null || echo "N/A")
fi
fi
# Free Cash Flow (from cashflowStatementHistory, prefer freeCashFlow, fallback to opCF - capex)
if jq -e '.quoteSummary.result[0].cashflowStatementHistory.cashflowStatements' "${Y_RAW}_summary" > /dev/null 2>&1; then
FCF_RAW=$(jq -r '.quoteSummary.result[0].cashflowStatementHistory.cashflowStatements[0].freeCashFlow.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
if [[ "$FCF_RAW" != "N/A" && "$FCF_RAW" != "null" ]]; then
FCF="$FCF_RAW"
else
OP_CF=$(jq -r '.quoteSummary.result[0].cashflowStatementHistory.cashflowStatements[0].totalCashFromOperatingActivities.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
CAPEX=$(jq -r '.quoteSummary.result[0].cashflowStatementHistory.cashflowStatements[0].capitalExpenditures.raw // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
if [[ "$OP_CF" != "N/A" && "$CAPEX" != "N/A" ]]; then
FCF=$(echo "scale=2; $OP_CF - $CAPEX" | bc 2>/dev/null || echo "N/A")
fi
fi
fi
# Shares outstanding (proxy for dilution / buybacks)
SHARES_OUT=$(jq -r '.quoteSummary.result[0].defaultKeyStatistics.sharesOutstanding.raw // .quoteSummary.result[0].defaultKeyStatistics.sharesOutstanding // "N/A"' "${Y_RAW}_summary" 2>/dev/null || echo "N/A")
SHARES_PRIOR="N/A"
SHARES_YOY_PCT="N/A"
# Fallback: gross margin from income statement if financialData missing (gross profit / revenue)
if [[ "$GROSS_MARGIN" == "N/A" || -z "$GROSS_MARGIN" ]]; then
REV_IS=$(jq -r '.quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory[0].totalRevenue.raw // empty' "${Y_RAW}_summary" 2>/dev/null)
COST_IS=$(jq -r '.quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory[0].costOfRevenue.raw // empty' "${Y_RAW}_summary" 2>/dev/null)
if [[ -n "$REV_IS" && -n "$COST_IS" && "$REV_IS" != "0" ]]; then
GROSS_MARGIN="$(echo "scale=2; ($REV_IS - $COST_IS) * 100 / $REV_IS" | bc 2>/dev/null)%"
fi
fi
# Fallback: operating margin from income statement (operatingIncome / revenue)
if [[ "$OP_MARGIN" == "N/A" || -z "$OP_MARGIN" ]]; then
REV_IS=$(jq -r '.quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory[0].totalRevenue.raw // empty' "${Y_RAW}_summary" 2>/dev/null)
OP_INC=$(jq -r '.quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory[0].operatingIncome.raw // .quoteSummary.result[0].incomeStatementHistory.incomeStatementHistory[0].incomeFromOperations.raw // empty' "${Y_RAW}_summary" 2>/dev/null)
if [[ -n "$REV_IS" && -n "$OP_INC" && "$REV_IS" != "0" ]]; then
OP_MARGIN="$(echo "scale=2; $OP_INC * 100 / $REV_IS" | bc 2>/dev/null)%"
fi
fi
[[ -z "$GROSS_MARGIN" ]] && GROSS_MARGIN="N/A"
[[ -z "$OP_MARGIN" ]] && OP_MARGIN="N/A"
[[ -z "$ROIC" ]] && ROIC="N/A"
# ============================================
# Step 3: SEC Data (Final Precision)
# ============================================
if [ -z "$CIK" ] || [ "$CIK" = "null" ]; then
echo "🔍 Looking up SEC CIK..."
# 1) Try SEC company_tickers.json (ticker -> CIK) for listed companies
SEC_TICKERS=$(curl -s -H "User-Agent: $SEC_AGENT" "https://www.sec.gov/files/company_tickers.json" 2>/dev/null)
if echo "$SEC_TICKERS" | jq -e '.' >/dev/null 2>&1; then
CIK=$(echo "$SEC_TICKERS" | jq -r --arg t "$TICKER_UPPER" '[.[] | select(.ticker == $t) | .cik_str] | first // empty' 2>/dev/null)
fi
# 2) Fallback: browse-edgar by ticker (atom)
if [ -z "$CIK" ]; then
CIK=$(curl -s -H "User-Agent: $SEC_AGENT" "https://www.sec.gov/cgi-bin/browse-edgar?action=getcompany&company=${TICKER_UPPER}&output=atom" | grep -o '<cik>[^<]*' | head -1 | sed 's/<cik>//' || echo "")
fi
fi
REV="$CURR_REV"
NI="$CURR_NI"
if [ -n "$CIK" ]; then
# Fix: Remove leading zeros and use base-10 to prevent octal conversion errors in printf
CIK_CLEAN=$(echo "$CIK" | sed 's/^0*//')
CIK_PADDED=$(printf "%010d" "$CIK_CLEAN")
echo "🔍 Fetching SEC financial facts for CIK: $CIK_PADDED"
# 🚨 THE FIX: Use SEC_AGENT so EDGAR doesn't block the request with a 403 error
if curl -s -H "User-Agent: $SEC_AGENT" "https://data.sec.gov/api/xbrl/companyfacts/CIK${CIK_PADDED}.json" -o "$SEC_FILE"; then
if [ -s "$SEC_FILE" ] && jq -e '.facts' "$SEC_FILE" > /dev/null 2>&1; then
# Fallback revenue and net income if Yahoo missing
if [ "$REV" = "N/A" ] || [ "$NI" = "N/A" ]; then
SEC_REV=$(extract_sec_value "$SEC_FILE" "USD" "Revenues" "SalesRevenueNet" "RevenueFromContractWithCustomerExcludingAssessedTax")
SEC_NI=$(extract_sec_value "$SEC_FILE" "USD" "NetIncomeLoss" "ProfitLoss")
[ "$REV" = "N/A" ] && REV="$SEC_REV"
[ "$NI" = "N/A" ] && NI="$SEC_NI"
fi
# Share count trend: two latest SEC values for YoY (dilution vs buyback)
# Try multiple SEC concept names and units (companies use different XBRL tags)
SEC_SHARES_TWO=$(extract_sec_two_latest "$SEC_FILE" "shares" "CommonStockSharesOutstanding")
[ "$SEC_SHARES_TWO" = "N/A N/A" ] && SEC_SHARES_TWO=$(extract_sec_two_latest "$SEC_FILE" "pure" "CommonStockSharesOutstanding")
[ "$SEC_SHARES_TWO" = "N/A N/A" ] && SEC_SHARES_TWO=$(extract_sec_two_latest "$SEC_FILE" "shares" "CommonStockSharesIssued")
[ "$SEC_SHARES_TWO" = "N/A N/A" ] && SEC_SHARES_TWO=$(extract_sec_two_latest "$SEC_FILE" "shares" "WeightedAverageNumberOfSharesOutstandingBasic")
if [ "$SEC_SHARES_TWO" != "N/A N/A" ]; then
SHARES_PRIOR=$(echo "$SEC_SHARES_TWO" | awk '{print $1}')
SHARES_CURR_SEC=$(echo "$SEC_SHARES_TWO" | awk '{print $2}')
if [[ -n "$SHARES_PRIOR" && -n "$SHARES_CURR_SEC" && "$SHARES_PRIOR" != "0" && "$SHARES_PRIOR" != "N/A" ]]; then
SHARES_YOY_PCT=$(echo "scale=2; ($SHARES_CURR_SEC - $SHARES_PRIOR) * 100 / $SHARES_PRIOR" | bc 2>/dev/null || echo "N/A")
# Prefer SEC current when we have SEC trend so all three (outstanding, prior, yoy_pct) are from same source
SHARES_OUT="$SHARES_CURR_SEC"
else
SHARES_PRIOR="N/A"
fi
fi
# FCF fallback: operating cash flow minus capex
if [ "$FCF" = "N/A" ]; then
SEC_OP_CF=$(extract_sec_value "$SEC_FILE" "USD" \
"NetCashProvidedByUsedInOperatingActivities" \
"NetCashProvidedByUsedInOperatingActivitiesContinuingOperations")
SEC_CAPEX=$(extract_sec_value "$SEC_FILE" "USD" \
"PaymentsToAcquirePropertyPlantAndEquipment" \
"PaymentsToAcquireProductiveAssets")
if [[ "$SEC_OP_CF" != "N/A" && "$SEC_CAPEX" != "N/A" ]]; then
FCF=$(echo "scale=2; $SEC_OP_CF - $SEC_CAPEX" | bc 2>/dev/null || echo "N/A")
fi
fi
fi
fi
fi
# ============================================
# Step 3.5: Alpha Vantage fallback (FCF, revenue_q_yoy)
# Key from OpenClaw auth profiles (profile alpha-vantage:default).
# Uses up to 2 API calls when key is set and Yahoo/SEC left any of these N/A.
# ============================================
AV_KEY=""
if [ -f "$AUTH_PROFILES" ]; then
AV_KEY=$(jq -r '.profiles["alpha-vantage:default"].key // empty' "$AUTH_PROFILES" 2>/dev/null || true)
fi
if [[ -n "$AV_KEY" && ( "$FCF" = "N/A" || "$REV_Q_YOY" = "N/A" || "$SHARES_PRIOR" = "N/A" ) ]]; then
echo "🔍 Alpha Vantage fallback for FCF / revenue_q_yoy / share count trend..."
BASE_AV="https://www.alphavantage.co/query"
if [ "$REV_Q_YOY" = "N/A" ]; then
curl -s "${BASE_AV}?function=INCOME_STATEMENT&symbol=${TICKER_UPPER}&apikey=${AV_KEY}" -o "$AV_INCOME"
if ! jq -e '.["Error Message"] // .["Note"]' "$AV_INCOME" >/dev/null 2>&1; then
# quarterlyReports: [0]=latest, [4]=same quarter prior year (if 5 quarters available)
REV_Q_CURR=$(jq -r '.quarterlyReports[0].totalRevenue // empty' "$AV_INCOME" 2>/dev/null)
REV_Q_PREV=$(jq -r '.quarterlyReports[4].totalRevenue // .quarterlyReports[1].totalRevenue // empty' "$AV_INCOME" 2>/dev/null)
if [[ -n "$REV_Q_CURR" && -n "$REV_Q_PREV" && "$REV_Q_PREV" != "0" ]]; then
REV_Q_YOY=$(echo "scale=4; ($REV_Q_CURR - $REV_Q_PREV) * 100 / $REV_Q_PREV" | bc 2>/dev/null || echo "N/A")
fi
fi
sleep 2
fi
if [ "$FCF" = "N/A" ]; then
curl -s "${BASE_AV}?function=CASH_FLOW&symbol=${TICKER_UPPER}&apikey=${AV_KEY}" -o "$AV_CASHFLOW"
if ! jq -e '.["Error Message"] // .["Note"]' "$AV_CASHFLOW" >/dev/null 2>&1; then
# Alpha Vantage: operatingCashflow, capitalExpenditures (capex often negative)
OP_CF_AV=$(jq -r '.annualReports[0].operatingCashflow // empty' "$AV_CASHFLOW" 2>/dev/null)
CAPEX_AV=$(jq -r '.annualReports[0].capitalExpenditures // empty' "$AV_CASHFLOW" 2>/dev/null)
if [[ -n "$OP_CF_AV" && "$OP_CF_AV" != "None" ]]; then
if [[ -n "$CAPEX_AV" && "$CAPEX_AV" != "None" && "$CAPEX_AV" != "0" ]]; then
# Capex is typically negative; FCF = operating + capex (e.g. 100 + (-20) = 80)
FCF=$(echo "scale=0; $OP_CF_AV + $CAPEX_AV" | bc 2>/dev/null || echo "$OP_CF_AV")
else
FCF="$OP_CF_AV"
fi
fi
fi
fi
# Share count trend: quarterly balance sheet has commonStockSharesOutstanding
if [ "$SHARES_PRIOR" = "N/A" ]; then
curl -s "${BASE_AV}?function=BALANCE_SHEET&symbol=${TICKER_UPPER}&apikey=${AV_KEY}" -o "$AV_BALANCE"
if ! jq -e '.["Error Message"] // .["Note"]' "$AV_BALANCE" >/dev/null 2>&1; then
AV_SHARES_CURR=$(jq -r '.quarterlyReports[0].commonStockSharesOutstanding // empty' "$AV_BALANCE" 2>/dev/null)
AV_SHARES_PRIOR=$(jq -r '.quarterlyReports[4].commonStockSharesOutstanding // .quarterlyReports[1].commonStockSharesOutstanding // empty' "$AV_BALANCE" 2>/dev/null)
if [[ -n "$AV_SHARES_CURR" && -n "$AV_SHARES_PRIOR" && "$AV_SHARES_PRIOR" != "0" && "$AV_SHARES_PRIOR" != "None" ]]; then
SHARES_PRIOR="$AV_SHARES_PRIOR"
SHARES_YOY_PCT=$(echo "scale=2; ($AV_SHARES_CURR - $AV_SHARES_PRIOR) * 100 / $AV_SHARES_PRIOR" | bc 2>/dev/null || echo "N/A")
[ "$SHARES_OUT" = "N/A" ] && SHARES_OUT="$AV_SHARES_CURR"
fi
fi
sleep 2
fi
rm -f "$AV_INCOME" "$AV_CASHFLOW" "$AV_BALANCE"
fi
# ============================================
# Step 4: Final JSON Compilation
# ============================================
echo "💾 Compiling Unified Dataset..."
jq -n \
--arg ticker "$TICKER_UPPER" \
--arg desc "$DESC" \
--arg rev "$REV" \
--arg ni "$NI" \
--arg rev_yoy "$REV_YOY" \
--arg ni_yoy "$NI_YOY" \
--arg rev_q "$CURR_REV_Q" \
--arg ni_q "$CURR_NI_Q" \
--arg rev_q_yoy "$REV_Q_YOY" \
--arg ni_q_yoy "$NI_Q_YOY" \
--arg fcf "$FCF" \
--arg shares_out "$SHARES_OUT" \
--arg shares_prior "$SHARES_PRIOR" \
--arg shares_yoy_pct "$SHARES_YOY_PCT" \
--arg price "$PRICE" \
--arg cap "$MCAP" \
--arg roe "$ROE" \
--arg gross_margin "$GROSS_MARGIN" \
--arg op_margin "$OP_MARGIN" \
--arg roic "$ROIC" \
--argjson surprise "$SURPRISE" \
'{
ticker: $ticker,
timestamp: (now | strftime("%Y-%m-%dT%H:%M:%SZ")),
company_profile: { name: $ticker, description: $desc },
financial_metrics: {
revenue: $rev,
net_income: $ni,
roe: $roe,
gross_margin: $gross_margin,
operating_margin: $op_margin,
roic: $roic,
revenue_yoy: $rev_yoy,
net_income_yoy: $ni_yoy,
revenue_q: $rev_q,
net_income_q: $ni_q,
revenue_q_yoy: $rev_q_yoy,
net_income_q_yoy: $ni_q_yoy,
fcf: $fcf,
shares_outstanding: $shares_out,
shares_prior: $shares_prior,
shares_yoy_pct: $shares_yoy_pct
},
valuation: { current_price: $price, market_cap: $cap },
momentum: { earnings_surprises: $surprise }
}' > "$DATA_FILE"
# Cleanup
rm -f "$SEC_FILE" "${Y_RAW}_quote" "${Y_RAW}_summary" "$COOKIE_FILE"
echo "✅ Data ready: $DATA_FILE"#!/bin/bash
#
# retrieve.sh - View cached analyses (FREE)
#
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
OUTPUTS_DIR="$SKILL_DIR/assets/outputs"
TICKER="${1:-}"
FRAMEWORK="${2:-}"
[ -z "$TICKER" ] && { echo "Usage: ./retrieve.sh <TICKER> [FRAMEWORK]"; exit 1; }
TICKER_UPPER=$(echo "$TICKER" | tr '[:lower:]' '[:upper:]')
# Map numbers to IDs
declare -A NUM_MAP=(
["1"]="01-phase"
["2"]="02-metrics"
["3"]="03-ai-moat"
["4"]="04-strategic-moat"
["5"]="05-sentiment"
["6"]="06-growth"
["7"]="07-business"
["8"]="08-risk"
["full"]="all"
)
if [ -z "$FRAMEWORK" ]; then
# List all available
echo "Cached analyses for $TICKER_UPPER:"
ls -1 "$OUTPUTS_DIR"/${TICKER_UPPER}_*.md 2>/dev/null || echo " (none)"
exit 0
fi
if [ "$FRAMEWORK" = "full" ] || [ "$FRAMEWORK" = "all" ]; then
# Show all
for f in "$OUTPUTS_DIR"/${TICKER_UPPER}_*.md; do
[ -f "$f" ] || continue
echo ""
echo "=== $(basename "$f") ==="
cat "$f"
echo ""
done
exit 0
fi
# Single framework
FW_ID="${NUM_MAP[$FRAMEWORK]:-$FRAMEWORK}"
FILE=$(ls "$OUTPUTS_DIR"/${TICKER_UPPER}_${FW_ID}.md 2>/dev/null | head -1)
if [ -f "$FILE" ]; then
echo "=== $(basename "$FILE") ==="
echo ""
cat "$FILE"
else
echo "Not found: $TICKER_UPPER framework $FRAMEWORK"
echo "Run: ./scripts/run-single-step.sh $TICKER_UPPER $FW_ID"
exit 1
fi
#!/bin/bash
# run-framework.sh - Momentum-Aware Context Hand-off
# Updated to support enriched JSON and sequential inference.
set -euo pipefail
# 1. Environment Setup
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/lib/cache.sh"
source "$SCRIPT_DIR/lib/cost-tracker.sh"
source "$SCRIPT_DIR/lib/api-client.sh"
source "$SCRIPT_DIR/lib/trace.sh"
# 2. Argument Parsing & Defaults
TICKER="${1:-}"
FW_ID="${2:-}"
PROMPT_FILE="${3:-}"
OUTPUT_DIR="${4:-$SKILL_DIR/assets/outputs}"
# No output token limit: use high default (8192) so API does not truncate; cost is low per costs.log
LIMIT_ARG="${5:-8192}"
# Usage guard: this script does NOT take --live (that flag is for analyze-pipeline.sh only)
if [[ -z "$PROMPT_FILE" || "$PROMPT_FILE" == -* ]]; then
echo "Usage: run-framework.sh <TICKER> <FW_ID> <PROMPT_FILE> [OUTPUT_DIR] [LIMIT]" >&2
echo " Example (01-phase only): run-framework.sh KVYO 01-phase \"$SKILL_DIR/references/prompts/01-phase.txt\" \"$SKILL_DIR/assets/outputs\"" >&2
echo " Note: Do not pass --live. Use analyze-pipeline.sh <TICKER> --live for the full pipeline." >&2
exit 1
fi
[ ! -f "$PROMPT_FILE" ] && { echo "ERROR: Prompt file not found: $PROMPT_FILE" >&2; exit 1; }
# Inherit context from analyze-pipeline.sh
PREVIOUS_CONTEXT="${SUMMARY_CONTEXT:-None}"
# Single high cap so output is not truncated; per-framework limits removed (costs are low)
FW_MAX_TOKENS="${LIMIT_ARG:-8192}"
# 3. Data Segmenting (Surgical Injection)
TICKER_UPPER=$(echo "$TICKER" | tr '[:lower:]' '[:upper:]')
DATA_FILE="$SKILL_DIR/.cache/data/${TICKER_UPPER}_data.json"
# Log start immediately so trace shows which step ran (and which step failed before first API log)
init_trace
log_trace "INFO" "$FW_ID" "Starting..."
# Require data file so we never pass empty context (avoids "N/A / Insufficient data" when wrong ticker is used)
if [ ! -f "$DATA_FILE" ]; then
log_trace "ERROR" "$FW_ID" "Data file not found: $DATA_FILE"
echo "ERROR: No data for $TICKER_UPPER. Expected: $DATA_FILE" >&2
echo " If analyzing Klaviyo, use ticker KVYO (not KYVO). Run fetch_data.sh first." >&2
exit 1
fi
get_relevant_context() {
case "$FW_ID" in
"07-business")
# Inject profile and financial metrics for business evaluation
jq -c '{profile: .company_profile, metrics: .financial_metrics, valuation: .valuation}' "$DATA_FILE" ;;
"03-ai-moat")
# Inject ROE, valuation, and Earnings Surprises for Moat inference
jq -c '{momentum: .momentum, valuation: .valuation, description: .company_profile.description}' "$DATA_FILE" ;;
"08-risk")
# Inject valuation and momentum for Risk analysis
jq -c '{valuation: .valuation, momentum: .momentum, profile: .company_profile}' "$DATA_FILE" ;;
"01-phase")
# Enriched context for lifecycle phase: profile + financial_metrics + valuation + momentum from *_data.json
jq -c '{profile: .company_profile, metrics: .financial_metrics, valuation: .valuation, momentum: .momentum}' "$DATA_FILE" ;;
"02-metrics")
# Core financial metrics
jq -c '{metrics: .financial_metrics, valuation: .valuation}' "$DATA_FILE" ;;
*)
# Default to description and basic profile
jq -c '{profile: .company_profile, valuation: .valuation}' "$DATA_FILE" ;;
esac
}
# Refuse to run with empty or stub data (e.g. all N/A) so we don't get "Insufficient data" output
check_context_not_empty() {
local ctx="$1"
if [ -z "$ctx" ] || [ "$ctx" = "{}" ]; then
log_trace "ERROR" "$FW_ID" "Context is empty after loading $DATA_FILE"
exit 1
fi
# For phase and metrics steps, require real financial data (revenue not N/A)
if [[ "$FW_ID" == "01-phase" || "$FW_ID" == "02-metrics" ]]; then
if echo "$ctx" | jq -e '(.metrics.revenue // "N/A") == "N/A"' >/dev/null 2>&1; then
log_trace "ERROR" "$FW_ID" "No financial metrics in data file (revenue N/A). Fetch data for $TICKER_UPPER first."
echo "ERROR: $DATA_FILE has no financial data (revenue N/A). Run fetch_data.sh for $TICKER_UPPER." >&2
echo " Check ticker: Klaviyo is KVYO, not KYVO." >&2
exit 1
fi
fi
}
# 4. Initialization & Cache Check
mkdir -p "$OUTPUT_DIR"
CONTEXT=$(get_relevant_context) || {
log_trace "ERROR" "$FW_ID" "get_relevant_context failed (jq or data file)"
echo "ERROR: Failed to load context from $DATA_FILE for $FW_ID." >&2
exit 1
}
check_context_not_empty "$CONTEXT"
PROMPT_CONTENT=$(cat "$PROMPT_FILE")
# When running 02-metrics alone, inject phase from 01-phase output so the correct metric set is used
if [[ "$FW_ID" == "02-metrics" && ( -z "$PREVIOUS_CONTEXT" || "$PREVIOUS_CONTEXT" == "None" ) ]]; then
PHASE_FILE="$OUTPUT_DIR/${TICKER_UPPER}_01-phase.md"
if [[ -f "$PHASE_FILE" ]]; then
PREVIOUS_CONTEXT="Phase from 01-phase (use this to select which of the 5 metric sets and thresholds to apply): $(head -n 10 "$PHASE_FILE" | tr '\n' ' ' | sed 's/ */ /g')"
else
log_trace "ERROR" "02-metrics" "01-phase output not found; 02-metrics requires phase from 01-phase"
echo "ERROR: 02-metrics needs the phase from 01-phase. Run 01-phase first, then 02-metrics." >&2
echo " Example: run-single-step.sh $TICKER_UPPER 01-phase && run-single-step.sh $TICKER_UPPER 02-metrics" >&2
echo " Expected file: $PHASE_FILE" >&2
exit 1
fi
fi
# The Context Bridge: Combine the raw data + previous framework results
FULL_PROMPT="Company: $TICKER_UPPER
Analysis Context from Previous Steps: $PREVIOUS_CONTEXT
Raw Data:
$CONTEXT
Task Instructions:
$PROMPT_CONTENT"
# Include output token limit in cache key so increasing the limit yields a fresh (non-truncated) response
CACHE_KEY=$(cache_key "$TICKER_UPPER" "$FW_ID" "$FULL_PROMPT")"_max${FW_MAX_TOKENS}"
# Helper: append one line to rolling context (used on both cache hit and fresh response)
append_to_rolling_context() {
local outfile="$1"
ROLLING_FILE="$OUTPUT_DIR/${TICKER_UPPER}_rolling_context.txt"
get_golden_nugget() {
local f="$1"
case "$FW_ID" in
01-phase) grep -A1 "^PHASE:" "$f" 2>/dev/null | tail -n 1 | sed 's/^[[:space:]]*//' | head -c 120 ;;
02-metrics) grep -A1 "^SUMMARY:" "$f" 2>/dev/null | tail -n 1 | sed 's/^[[:space:]]*//' | head -c 120 ;;
03-ai-moat) grep -A1 "^VERDICT:" "$f" 2>/dev/null | tail -n 1 | sed 's/^[[:space:]]*//' | head -c 120 ;;
04-strategic-moat) grep -A1 "^RATING:" "$f" 2>/dev/null | tail -n 1 | sed 's/^[[:space:]]*//' | head -c 120 ;;
05-sentiment) grep -A1 "^VALUATION:" "$f" 2>/dev/null | tail -n 1 | sed 's/^[[:space:]]*//' | head -c 120 ;;
06-growth) grep -A1 "^STRATEGY:" "$f" 2>/dev/null | tail -n 1 | sed 's/^[[:space:]]*//' | head -c 120 ;;
07-business) grep -A1 "^REVENUE MIX:" "$f" 2>/dev/null | tail -n 1 | sed 's/^[[:space:]]*//' | head -c 120 ;;
08-risk) grep -A1 "^OVERALL RISK LEVEL:" "$f" 2>/dev/null | tail -n 1 | sed 's/^[[:space:]]*//' | head -c 120 ;;
*) grep -A1 "^VERDICT:\|^RATING:\|^SUMMARY:" "$f" 2>/dev/null | tail -n 1 | sed 's/^[[:space:]]*//' | head -c 120 ;;
esac
}
GOLDEN_NUGGET=$(get_golden_nugget "$outfile")
[ -z "$GOLDEN_NUGGET" ] && GOLDEN_NUGGET=$(grep -v -E '^[A-Z][A-Z_]*:$' "$outfile" 2>/dev/null | head -n 1 | sed 's/^[[:space:]]*//' | head -c 120)
[ -z "$GOLDEN_NUGGET" ] && GOLDEN_NUGGET="(no summary extracted)"
echo "$FW_ID: $GOLDEN_NUGGET" >> "$ROLLING_FILE"
}
# 5. Output validation (detect truncation by required end-marker per framework)
validate_framework_output() {
local content="$1"
local marker=""
case "$FW_ID" in
01-phase) marker="Avoid:" ;;
02-metrics) marker="SUMMARY:" ;;
03-ai-moat) marker="CRITICAL FAILURE POINT:" ;;
04-strategic-moat) marker="THREAT:" ;;
05-sentiment) marker="RATIONALE:" ;;
06-growth) marker="ANALYSIS:" ;;
07-business) marker="SCALABILITY:" ;;
08-risk) marker="ASSESSMENT DETAILS:" ;;
*) return 0 ;;
esac
[ -z "$marker" ] && return 0
if echo "$content" | grep -qF "$marker"; then
return 0
fi
log_trace "WARN" "$FW_ID" "Output missing required end-marker: $marker (possible truncation)"
return 1
}
# 6. Cache & Budget Enforcement
CACHED_RESPONSE=$(cache_get "$CACHE_KEY" || echo "")
if [ -n "$CACHED_RESPONSE" ]; then
if validate_framework_output "$CACHED_RESPONSE"; then
echo "$CACHED_RESPONSE" > "$OUTPUT_DIR/${TICKER_UPPER}_${FW_ID}.md"
log_trace "INFO" "$FW_ID" "Cache HIT"
append_to_rolling_context "$OUTPUT_DIR/${TICKER_UPPER}_${FW_ID}.md"
echo "$CACHED_RESPONSE"
exit 0
fi
log_trace "WARN" "$FW_ID" "Cached response truncated; bypassing cache and calling API"
fi
if ! check_budget "$FW_ID"; then
log_trace "ERROR" "$FW_ID" "Budget check failed"
exit 1
fi
# 7. API Execution (no output token cap; FW_MAX_TOKENS=8192 so API does not truncate)
API_RESPONSE=$(call_llm_api "$FULL_PROMPT" "$FW_MAX_TOKENS")
CONTENT=$(extract_content "$API_RESPONSE")
read INPUT_TOKENS OUTPUT_TOKENS <<< "$(extract_usage "$API_RESPONSE" "$FULL_PROMPT")"
if ! validate_framework_output "$CONTENT"; then
read -r finish_reason out_tokens max_tokens <<< "$(extract_finish_info "$API_RESPONSE" "$FW_MAX_TOKENS" 2>/dev/null || echo "? ? ?")"
log_trace "TRUNC" "$FW_ID" "finishReason=$finish_reason outTokens=$out_tokens limit=$max_tokens"
echo "⚠️ $FW_ID output incomplete (finishReason=$finish_reason, ${out_tokens} tokens). Re-run step; response not cached." >&2
echo "$CONTENT" > "$OUTPUT_DIR/${TICKER_UPPER}_${FW_ID}.md"
exit 1
fi
# 8. Final Save & Metadata
echo "$CONTENT" > "$OUTPUT_DIR/${TICKER_UPPER}_${FW_ID}.md"
log_cost "$TICKER_UPPER" "$FW_ID" "$INPUT_TOKENS" "$OUTPUT_TOKENS"
log_trace "INFO" "$FW_ID" "Complete | ${INPUT_TOKENS}i/${OUTPUT_TOKENS}o"
# 9. Golden Nugget Extraction
append_to_rolling_context "$OUTPUT_DIR/${TICKER_UPPER}_${FW_ID}.md"
# Cache only if output passed validation (do not cache truncated responses)
METADATA=$(jq -n --arg i "$INPUT_TOKENS" --arg o "$OUTPUT_TOKENS" '{input: $i, output: $o}')
cache_set "$CACHE_KEY" "$CONTENT" "$METADATA"
echo "✅ $FW_ID complete"#!/bin/bash
# run-single-step.sh - Run one framework step (e.g. 01-phase only). No --live flag.
# Use this when you want a single prompt (e.g. 01-phase) without the full pipeline or synthesis.
#
# Usage: run-single-step.sh <TICKER> <FW_ID>
# Example: run-single-step.sh KVYO 01-phase
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
PROMPTS_DIR="$SKILL_DIR/references/prompts"
OUTPUTS_DIR="$SKILL_DIR/assets/outputs"
TICKER="${1:-}"
FW_ID="${2:-}"
if [[ -z "$TICKER" || -z "$FW_ID" ]]; then
echo "Usage: run-single-step.sh <TICKER> <FW_ID>" >&2
echo "Example: run-single-step.sh KVYO 01-phase" >&2
exit 1
fi
PROMPT_FILE="$PROMPTS_DIR/${FW_ID}.txt"
if [ ! -f "$PROMPT_FILE" ]; then
echo "ERROR: Prompt file not found: $PROMPT_FILE" >&2
exit 1
fi
exec "$SCRIPT_DIR/run-framework.sh" "$(echo "$TICKER" | tr '[:lower:]' '[:upper:]')" "$FW_ID" "$PROMPT_FILE" "$OUTPUTS_DIR"
#!/bin/bash
#
# ticker-summary.sh - Audit report for Ticker Analysis costs and efficiency
#
# 1. Path Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
COST_LOG="$SKILL_DIR/.cache/costs.log"
# Check if log exists
if [ ! -f "$COST_LOG" ]; then
echo "❌ No cost log found at $COST_LOG"
exit 1
fi
echo "========================================================="
echo "📊 COMPANY ANALYZER: TICKER COST SUMMARY"
echo "========================================================="
printf "%-10s | %-10s | %-12s | %-8s\n" "TICKER" "RUNS" "TOTAL TOKENS" "COST ($)"
echo "---------------------------------------------------------"
# 2. Process Log Data
# Log format: timestamp | ticker | framework | model | 123i/456o | $cost
# With -F' | ' (space OR space in awk regex) we split on space: $3=ticker $5=framework $9=tokens $11=cost
awk -F' | ' '
{
ticker = $3;
split($9, t, "/");
in_t = out_t = 0;
sub(/i$/, "", t[1]); in_t = t[1] + 0;
sub(/o$/, "", t[2]); out_t = t[2] + 0;
cost_str = $11;
gsub(/\$/, "", cost_str);
counts[ticker]++;
tokens[ticker] += (in_t + out_t);
costs[ticker] += cost_str + 0;
}
END {
for (x in counts) {
printf "%-10s | %-10d | %-12d | $%-8.4f\n", x, counts[x], tokens[x], costs[x]
}
}' "$COST_LOG" | sort -t'|' -k4 -rn
echo "---------------------------------------------------------"
# 3. Framework Efficiency Audit
echo ""
echo "🔍 Framework Efficiency (Average Cost per Call)"
echo "---------------------------------------------------------"
awk -F' | ' '
{
fw = $5;
cost_str = $11;
gsub(/\$/, "", cost_str);
fw_counts[fw]++;
fw_costs[fw] += cost_str + 0;
}
END {
for (f in fw_counts) {
avg = fw_costs[f] / fw_counts[f];
printf "%-20s | Avg Cost: $%-8.6f | Runs: %d\n", f, avg, fw_counts[f]
}
}' "$COST_LOG" | sort -t'|' -k4 -rn
echo "========================================================="#!/bin/bash
#
# view-trace.sh - Display and analyze trace logs
# Usage: ./view-trace.sh <TICKER> [DATE]
#
set -euo pipefail
TICKER="${1:-}"
DATE="${2:-$(date +%Y-%m-%d)}"
if [ -z "$TICKER" ]; then
echo "Usage: $0 <TICKER> [YYYY-MM-DD]"
echo "Examples:"
echo " $0 NOW # View today's trace for NOW"
echo " $0 NOW 2026-02-22 # View specific date"
exit 1
fi
TICKER_UPPER=$(echo "$TICKER" | tr '[:lower:]' '[:upper:]')
TRACE_DIR="$(dirname "$(dirname "$0")")/assets/traces"
TRACE_FILE="$TRACE_DIR/${TICKER_UPPER}_${DATE}.trace"
if [ ! -f "$TRACE_FILE" ]; then
echo "❌ Trace file not found: $TRACE_FILE"
echo ""
echo "Available traces for $TICKER_UPPER:"
ls -la "$TRACE_DIR/${TICKER_UPPER}"_*.trace 2>/dev/null || echo " (none found)"
exit 1
fi
echo "======================================"
echo " TRACE ANALYSIS: $TICKER_UPPER"
echo " Date: $DATE"
echo "======================================"
echo ""
# Parse trace into sections
echo "📊 EXECUTION TIMELINE:"
echo ""
grep "^\[" "$TRACE_FILE" | while read line; do
echo " $line"
done
echo ""
echo "📈 PERFORMANCE SUMMARY:"
echo ""
# Count cache hits vs API calls
CACHE_HITS=$(grep -c "Cache HIT" "$TRACE_FILE" 2>/dev/null || echo "0")
API_CALLS=$(grep -c "SUCCESS | Latency" "$TRACE_FILE" 2>/dev/null || echo "0")
TOTAL=$((CACHE_HITS + API_CALLS))
if [ $TOTAL -gt 0 ]; then
echo " Cache hits: $CACHE_HITS"
echo " API calls: $API_CALLS"
echo " Total: $TOTAL frameworks"
echo ""
# Calculate cost savings
if [ $CACHE_HITS -gt 0 ]; then
SAVINGS=$(echo "scale=2; $CACHE_HITS * 0.005" | bc 2>/dev/null || echo "?")
echo " 💰 Estimated savings from cache: ~\$$SAVINGS"
fi
fi
echo ""
echo "⏱️ LATENCY BREAKDOWN:"
echo ""
# Extract API latencies
grep "Latency:" "$TRACE_FILE" 2>/dev/null | while read line; do
echo " $line"
done
echo ""
echo "🚨 ERRORS & WARNINGS:"
echo ""
# Check for errors
ERRORS=$(grep -c "ERROR" "$TRACE_FILE" 2>/dev/null || echo "0")
WARNINGS=$(grep -c "WARN" "$TRACE_FILE" 2>/dev/null || echo "0")
if [ "$ERRORS" -gt 0 ] || [ "$WARNINGS" -gt 0 ]; then
echo " Errors: $ERRORS"
echo " Warnings: $WARNINGS"
echo ""
grep "ERROR\|WARN" "$TRACE_FILE" | head -10
else
echo " ✅ No errors or warnings"
fi
echo ""
echo "======================================"
echo "Trace file: $TRACE_FILE"
echo "======================================"