#!/usr/bin/env bash # o-o Living Document — self-updating via LLM agent # To update: bash competitor-costco.o-o.html [--agent claude] [--model sonnet] # To read: open competitor-costco.o-o.html in any browser : << 'OO_HTML' Costco: Competitive Intelligence Briefing
← Back to index

Costco: Competitive Intelligence Briefing

As of Version 0

Executive Summary

Costco Wholesale Corporation (NASDAQ: COST) continues to be the most feared operator in retail. In fiscal year 2025 (ending August 2025), the company posted $269.9 billion in net sales — up 8.1% year-over-year — with net income of $8.1 billion [s1]. The membership flywheel remains intact: 81 million paid members, a 92.3% renewal rate in the U.S. and Canada, and membership fee income of $5.3 billion [s2]. Costco now operates 914 warehouses globally and plans to open 28–35 more in fiscal 2026 [s3][s17]. Kirkland Signature generated approximately $90 billion in revenue in 2025 — surpassing Procter & Gamble — and digitally-enabled comparable sales surged 34.4% in January 2026 [s4][s18][s19]. Costco is also filing tariff lawsuits, expanding same-day delivery into Europe, and launching a Nike SB Dunk collaboration. The hot dog still costs $1.50.

The Hot Dog Index

Costco $1.50 hot dog and soda combo at a Costco food court
The legendary $1.50 hot-dog-and-soda combo — unchanged since 1984. Source: Wikimedia Commons

The $1.50 hot-dog-and-soda combo has not changed in price since 1984. Adjusted for inflation, it should cost roughly $4.40 today. That it doesn't is not an oversight — it is Costco's most visible commitment to value [s6].

The origin story is now retail legend. When CEO Craig Jelinek told co-founder Jim Sinegal in 2013 that the company was "losing our rear ends" on the combo and proposed raising the price to $1.75, Sinegal reportedly replied: "If you raise the effing hot dog, I will kill you. Figure it out." Jelinek figured it out — switching soda suppliers from Coca-Cola to Pepsi, and in 2008, bringing hot dog production in-house through Costco's own Kirkland Signature manufacturing facilities [s7].

Costco sells approximately 200 million hot-dog-and-soda combos per year. The food court operates as a deliberate loss leader: it drives foot traffic, reinforces the value proposition, and creates the kind of brand loyalty that no amount of advertising can buy. The hot dog is not a menu item. It is a strategic asset [s6][s7].

Membership & Unit Economics

Costco's business model is fundamentally a membership business that happens to sell products. In fiscal 2025, membership fee income reached $5.3 billion — up 10.3% year-over-year — and accounts for roughly two-thirds of operating profit. The company operates on razor-thin product margins (typically 11-12% gross margin) and uses membership fees to fund the difference [s1][s2].

Fee Structure

In September 2024, Costco implemented its first membership fee increase in seven years. The Gold Star (individual) membership rose from $60 to $65, and the Executive membership from $120 to $130. This impacted approximately 52 million memberships, roughly half of which are Executive members who earn a 2% annual reward on qualified purchases [s8].

Renewal Rates & Member Growth

U.S. and Canada renewal rates held at 92.3%, with worldwide rates at 89.8%. Total paid members grew 6.3% to 81 million. Management noted a slight dip in renewal rates among digitally-acquired members — those signing up through promotional campaigns such as a large Groupon initiative — who tend to be younger and renew at modestly lower rates. This is worth monitoring but not yet alarming [s2][s9].

In Q1 FY2026, membership fee income surged 14% to $1.329 billion, reflecting the full impact of the September 2024 fee increase plus continued membership growth and Executive upgrades [s10]. Monthly sales data confirms durable momentum: December 2025 net sales reached $29.86 billion (+8.5% YoY) with comparable sales up 7.0%, and January 2026 net sales hit $21.33 billion (+9.3% YoY) with comparable sales up 7.1% [s20][s19]. Q2 FY2026 earnings are expected on March 5, 2026 [s21].

Kirkland Brand Empire

Kirkland Signature is the most successful private label in retail history. Launched in 1995, it now accounts for roughly a third of Costco's total revenue — approximately $90 billion in 2025, up from $86 billion the prior year. To put that in perspective: Kirkland Signature alone generates more revenue than Lowe's, Procter & Gamble, or Nike [s4][s18].

The strategic philosophy is distinctive. While most retailers use private labels primarily as margin enhancers, Costco requires every Kirkland product to be equal to or better than the category leader while offering 20% or greater savings. Many products are manufactured by the same factories that produce national brands — the same vodka distillery, the same olive oil presses. Costco's product development teams sometimes spend years perfecting formulations before launch [s4][s11].

Kirkland brand penetration reached approximately 33% in fiscal 2025, with Kirkland sales outperforming overall sales growth. Margins on Kirkland products run roughly 15% — nearly double the margins on national brands. Management has identified non-food categories (apparel, household goods, pet products) as the largest growth opportunity for the brand. In Q1 FY2026, Costco launched 45 new Kirkland Signature items [s11][s22].

The Kirkland x Nike SB Dunk

In January 2026, Costco dropped the Kirkland Signature x Nike SB Dunk Low at eight warehouse locations for $134.99. The shoe features grey fleece uppers (a nod to Costco sweatshirts), a $1.50 hot-dog graphic hidden under the Zoom Air cushioning, and a tongue tag designed after Costco price labels. It sold out immediately, with resale prices hitting $400–$1,000 within three days — a 200%+ markup. The collaboration is emblematic of Kirkland's cultural resonance beyond the warehouse aisle [s23].

Kirkland Risks: Rotisserie Chicken Lawsuit

In January 2026, a class-action lawsuit alleged Costco falsely advertised its Kirkland Seasoned Rotisserie Chicken as containing "no preservatives," citing the use of sodium phosphate and carrageenan. Costco responded by removing all preservative-related claims from signage and online descriptions. The $4.99 rotisserie chicken — a loss leader that sells over 100 million units annually — now faces a separate salmonella-related lawsuit targeting a Nebraska processing plant [s24].

Warehouse Expansion

Costco operates 914 warehouses across 14 countries: 629 in the United States, 110 in Canada, 42 in Mexico, 37 in Japan, 29 in the United Kingdom, 20 in South Korea, 15 in Australia, 14 in Taiwan, 7 in China, 5 in Spain, 2 in France, 2 in Sweden, and 1 each in Iceland and New Zealand [s1].

In fiscal 2025, the company opened 24 net-new warehouses (27 total openings, 3 relocations). For fiscal 2026, Costco initially announced plans for 35 new locations (30 net-new after 5 relocations), though some reports now cite 28 confirmed U.S. and international openings after construction delays in Spain. Confirmed early-2026 openings include West Roseville, CA (January), Liberty Hill, TX, and Forney, TX (both March). The expansion is part of a broader $6.5 billion growth strategy, with plans for 30+ openings per year for the foreseeable future [s3][s17].

International Growth

China remains the most watched international market. Costco currently operates 7 warehouses in mainland China, with a pipeline of additional openings. The Monterrey, Mexico location planned for 2026 will be the largest Costco warehouse in Latin America at over 200,000 square feet. South Korea and Japan continue to be high-performing international markets with strong membership renewal rates [s3][s12].

Store Upgrades & Checkout Innovation

Existing locations are undergoing renovations to add gas stations, expand parking, and deploy prescan checkout technology that management claims can increase checkout speeds by up to 20%. Self-serve food court kiosks requiring membership card scans are expanding to more locations, restricting food court access to verified members only [s17].

E-Commerce & Delivery

Costco's e-commerce business has shifted from afterthought to genuine growth engine. In Q1 FY2026, digitally-enabled comparable sales grew 20.5%, marking the sixth consecutive quarter of double-digit digital gains. Website traffic increased 24% and app traffic surged 48% year-over-year. The acceleration is intensifying: December 2025 digital comparable sales hit 18.9%, and January 2026 surged to 34.4% — the strongest month on record [s5][s20][s19].

Delivery & Logistics

Costco Logistics deliveries grew 31% year-over-year, driven by the "big and bulky" category — furniture, appliances, and exercise equipment delivered directly by Costco's own last-mile network. Same-day delivery partnerships with Instacart (U.S.), Uber Eats, and DoorDash (international) continue to expand. The introduction of Buy Now, Pay Later through Affirm is targeting conversion improvements on high-ticket items [s5][s13].

Instacart Europe Launch

On January 30, 2026, Costco and Instacart expanded their North American partnership into Europe, launching same-day delivery websites in France (sameday.costco.fr) and Spain (sameday.costco.es). The rollout covers all Costco locations in both countries — Paris, Mulhouse, Bilbao, Madrid, Seville, and Zaragoza — using Instacart's Storefront Pro platform with local fulfillment partners. Members receive same-as-in-store pricing with a flat service fee per order. This marks Costco's first powered e-commerce presence in continental Europe [s25].

Digital Initiatives

New personalization capabilities provide members with product recommendations based on search history. Passwordless sign-in was rolled out across digital platforms. Costco is also enabling mobile app ordering for custom cakes and deli trays — a change driven by customer feedback that management says is seeing "great adoption right out of the chute," with broader expansion planned through 2026. The company is cautiously entering the retail media space, working with approximately 10 brand partners on digital advertising campaigns — a revenue stream that could eventually become material as the retail media market is projected to reach $100 billion by 2028 [s12][s14][s17].

Financial Performance

Fiscal Year 2025 (Full Year, Ended August 2025)

Net sales: $269.9 billion (+8.1% YoY). Net income: $8.099 billion ($18.21 per diluted share), up from $7.367 billion ($16.56 per diluted share) in fiscal 2024. Membership fee income: $5.3 billion (+10.3%). Total paid members: 81 million (+6.3%) [s1].

Q1 FY2026 (Ended November 23, 2025)

Net sales: $65.98 billion (+8.2% YoY). Operating income: $2.46 billion (up from $2.20 billion). EPS: $4.50 (up from $4.04). Comparable sales: +6.4% total (+5.9% U.S., +6.5% Canada, +8.8% International). Membership fee income: $1.329 billion (+14%). Digitally-enabled comparable sales: +20.5% [s10].

Monthly Sales Momentum

December 2025 net sales: $29.86 billion (+8.5% YoY). Comparable sales: +7.0% (+6.0% U.S., +8.4% Canada, +10.6% International). Digitally-enabled comparable sales: +18.9% [s20]. January 2026 net sales: $21.33 billion (+9.3% YoY). Comparable sales: +7.1% (+5.8% U.S., +11.4% Canada, +9.5% International). Digitally-enabled comparable sales: +34.4%. Note: Lunar New Year timing negatively impacted January International and Total Company sales by approximately 4.0% and 0.5%, respectively [s19]. Year-to-date FY2026 sales (first 18 weeks): $101.83 billion (+8.3% YoY). Q2 FY2026 earnings expected March 5, 2026 [s21].

Valuation Note

At a market capitalization of approximately $452 billion (as of mid-February 2026), Costco trades at 54.6x trailing earnings and 49.2x forward earnings, with the stock at approximately $1,018 per share. Analyst consensus EPS forecasts project $20.03 for FY2026 and $22.09 for FY2027. Morningstar's fair value estimate is $945, implying a 45% premium. Whether this premium is justified depends on one's view of how much growth remains — but the market is clearly pricing in durable compounding from the membership flywheel, Kirkland expansion, and international growth runway [s14][s26].

Strategic Moves & Threats

Gold Bars: The Unlikely Category Killer

Costco has become a major retail seller of gold bars and silver coins. With gold prices now surpassing $5,000/oz in February 2026 (up 74% YoY), Costco's bullion sales remain an estimated $200 million per month. The company sells 1-ounce, 24-karat bars from PAMP Suisse and Rand Refinery at premiums of just 1–2% over spot — undercutting many dealers' 1–10% markups. Shoppers may purchase up to two bars per transaction, limited to one transaction every 24 hours. The bars typically sell out within hours of restocking [s15][s27].

Competitive Landscape

The three retail titans — Walmart ($568.7B U.S. retail sales), Amazon ($273.7B), and Costco ($183.1B) — collectively captured 46% of all U.S. retail growth in 2024. Amazon leads in e-commerce growth (24% of new growth), while Walmart leverages its 4,700 U.S. stores as fulfillment nodes with e-commerce up 27%. Costco's 9.8% share of retail growth is impressive given its smaller footprint and club-only model [s16].

Key Threats to Monitor

Tariff exposure has escalated from concern to active legal battle. In November 2025, Costco sued the Trump administration in the Court of International Trade, arguing that emergency tariffs imposed under IEEPA are unlawful and seeking a full refund of duties paid. Costco is among over 100 companies challenging the tariffs. Tactically, the company is pulling forward inventory purchases, rerouting sourcing away from high-tariff countries, expanding Kirkland Signature local sourcing, and selectively absorbing tariff costs on staple items like bananas while passing through costs on non-essentials like flowers [s28][s29].

Employee Investment

Costco will implement hourly wage increases of $0.50–$1.00 in March 2026, with additional vacation hours and clearer advancement paths for top-scale employees. Employee retention and satisfaction remain key competitive advantages — Costco's employee turnover rate is roughly one-third of the retail industry average [s17].

Other Threats to Monitor

The slight decline in renewal rates among digitally-acquired younger members bears watching — if this cohort doesn't convert to long-term loyalty, the promotional acquisition channel becomes expensive. International expansion in China faces geopolitical and regulatory complexity. And while Walmart and Amazon continue to invest billions in logistics infrastructure, Costco's e-commerce operation, while growing fast, is still a fraction of competitors' scale [s12][s14][s16].

References

  1. Costco Wholesale Corporation Reports Fourth Quarter and Fiscal Year 2025 Operating Results — Costco Investor Relations, September 2025
  2. Costco Shares Key News on Membership and Renewal Rates — TheStreet, 2025
  3. Costco Plans to Open 35 New Warehouse Stores by August 2026 — Retail TouchPoints, 2025
  4. Costco's Not-So-Secret Weapon: How Kirkland Signature Is Driving Growth and Profits — CNBC, March 2025
  5. Costco Ecommerce Sales Growth Tops 20% in Q1 — Digital Commerce 360, 2025
  6. Costco Hot Dogs Have Cost $1.50 Since the 1980s. Here's Why Prices Aren't Changing — NPR, June 2024
  7. Why Costco's Hot Dog Is Still $1.50 When Everything Has Gotten So Expensive — CNN Business, 2024
  8. Costco's First Membership Price Hike in 7 Years Goes Into Effect — CNN Business, September 2024
  9. Costco's Membership Fees Rise 14%: Sustainable Growth or Peak? — Yahoo Finance, 2025
  10. Costco Wholesale Corporation Reports First Quarter Fiscal Year 2026 Operating Results — Costco Investor Relations, December 2025
  11. How Costco Turned Kirkland Signature Into an $86 Billion Brand — AInvest, 2025
  12. Costco's 2025: 3 Things Investors Should Know Heading Into the New Year — Motley Fool, December 2025
  13. How Costco's 'Big and Bulky' Boom Changed the Game in Retail Shopping — CNBC, September 2025
  14. Costco's E-Commerce Sales Surge: A Dark Horse Growth Engine? — Nasdaq, 2025
  15. Costco's Gold Bars and Silver Coins Surge as Top Sellers Amid 2025 Price Boom — WebProNews, 2025
  16. Amazon, Walmart and Costco Are Running Away With 46 Percent of Retail Growth — Yahoo Finance, 2025
  17. 6 Changes Costco Has Planned For 2026 — The Takeout, January 2026
  18. Costco's Kirkland Signature Products Brought In a Staggering Amount of Money in 2025 — Chowhound, 2026
  19. Costco Wholesale Corporation Reports January Sales Results — Costco Investor Relations, February 2026
  20. Costco Wholesale Corporation Reports December Sales Results — Costco Investor Relations, January 2026
  21. Costco Wholesale (COST) Earnings Date and Reports 2026 — MarketBeat, 2026
  22. Costco (COST) Q1 2026 Earnings Call Transcript — Motley Fool, December 2025
  23. Where To Buy The Costco Kirkland Signature Nike SB Dunks — Sneaker News, January 2026
  24. Costco Faces Class-Action Lawsuit Over Rotisserie Chicken — Fox Business, January 2026
  25. Costco Launches First Same-Day Websites in France and Spain with Instacart — Instacart Newsroom, January 2026
  26. Costco Stock Price Today — NASDAQ: COST — Morningstar, February 2026
  27. Costco Gold Bars Keep Selling Out. Are They a Smart Investment? — Kiplinger, 2026
  28. Costco Sues Over Trump Tariffs: What Could That Mean for Prices in 2026? — Kiplinger, 2026
  29. Costco to 'Pull Forward' Inventory Buys Amid Tariff Uncertainty — Supply Chain Dive, 2025
OO_HTML set -euo pipefail SELF="$(cd "$(dirname "$0")" && pwd -P)/$(basename "$0")" SELF_DIR="$(dirname "$SELF")" SELF_NAME="$(basename "$SELF")" # OO:SHELL:START # ─── DOCUMENT TEMPLATE ──────────────────────────────────────── generate_oo_file() { local filepath="$1" title="$2" scope="$3" slug="$4" # Extract shared section line numbers from THIS file local css_s css_e js_s js_e sh_s sh_e css_s=$(grep -n '^$' "$SELF" | head -1 | cut -d: -f1) css_e=$(grep -n '^$' "$SELF" | head -1 | cut -d: -f1) js_s=$(grep -n '^$' "$SELF" | head -1 | cut -d: -f1) js_e=$(grep -n '^$' "$SELF" | head -1 | cut -d: -f1) sh_s=$(grep -n '^# OO:SHELL:START$' "$SELF" | head -1 | cut -d: -f1) sh_e=$(grep -n '^# OO:SHELL:END$' "$SELF" | head -1 | cut -d: -f1) # Part 1: shebang + heredoc start + HTML head (before CSS) cat > "$filepath" << 'TPL_HEAD' #!/usr/bin/env bash # o-o Living Document — self-updating via LLM agent # To update: bash __SLUG__.o-o.html [--agent claude] [--model sonnet] # To read: open __SLUG__.o-o.html in any browser : << 'OO_HTML' __TITLE__ TPL_HEAD # Inject CSS from this file (between OO:CSS markers, inclusive) sed -n "${css_s},${css_e}p" "$SELF" >> "$filepath" # Part 2: close head + body + header + article stub + manifest cat >> "$filepath" << 'TPL_BODY'
← Back to index

__TITLE__

As of Version 0

This document has not been populated yet.

To fill it with researched content, run:

bash __SLUG__.o-o.html

TPL_BODY # Inject JS from this file (between OO:JS markers, inclusive) sed -n "${js_s},${js_e}p" "$SELF" >> "$filepath" # Part 3: contract + machine zone + close HTML + OO_HTML terminator + shell preamble cat >> "$filepath" << 'TPL_CONTRACT' OO_HTML set -euo pipefail SELF="$(cd "$(dirname "$0")" && pwd -P)/$(basename "$0")" SELF_DIR="$(dirname "$SELF")" SELF_NAME="$(basename "$SELF")" TPL_CONTRACT # Inject shell block from this file (between OO:SHELL markers, inclusive) sed -n "${sh_s},${sh_e}p" "$SELF" >> "$filepath" # Part 4: exit printf '\nexit 0\n' >> "$filepath" # Replace placeholders local tmp="/tmp/oo_template_$$" sed -e "s|__TITLE__|${title}|g" \ -e "s|__SCOPE__|${scope}|g" \ -e "s|__SLUG__|${slug}|g" \ -e "s|__YEAR__|$(date +%Y)|g" \ "$filepath" > "$tmp" && mv "$tmp" "$filepath" chmod +x "$filepath" } # ─── UTILITIES ──────────────────────────────────────────────── slugify() { local str="$1" echo "$str" | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | sed 's/[^a-z0-9-]//g' | cut -c1-60 } # ─── INDEX MANAGEMENT ───────────────────────────────────────── rebuild_index() { echo "o-o Index: Scanning for .o-o.html files..." local count=0 local table_rows="" local card_data="" # Find all .o-o.html files (excluding index.o-o.html) while IFS= read -r file; do [[ "$file" == "$SELF" ]] && continue # Extract manifest fields using grep (portable, no jq) local title=$(grep -o '"title"[[:space:]]*:[[:space:]]*"[^"]*"' "$file" | head -1 | sed 's/.*:[[:space:]]*"//' | sed 's/"$//') local version=$(grep -o '"version"[[:space:]]*:[[:space:]]*[0-9]*' "$file" | head -1 | grep -o '[0-9]*$') local as_of=$(grep -o '"as_of"[[:space:]]*:[[:space:]]*"[^"]*"' "$file" | head -1 | sed 's/.*:[[:space:]]*"//' | sed 's/"$//') local update_days=$(grep -o '"update_every_days"[[:space:]]*:[[:space:]]*[0-9]*' "$file" | head -1 | grep -o '[0-9]*$' || echo "7") # Get file info local rel_path=$(basename "$file") local file_size=$(ls -lh "$file" | awk '{print $5}') # Default values [[ -z "$title" ]] && title="Untitled" [[ -z "$version" ]] && version="0" [[ -z "$as_of" ]] && as_of="—" # Build table row table_rows="${table_rows} ${title} ${file_size} ${as_of} ${version} " # Collect card data (sort_date|title|rel_path|display_date|excerpt|update_days) local excerpt="" excerpt=$(awk '/
/){gsub(/<[^>]*>/,"");gsub(/&/,"\\&");gsub(/—/,"—");gsub(/\[s[0-9]+\]/,"");gsub(/^[[:space:]]+|[[:space:]]+$/,"");print substr($0,1,120);exit}}' "$file") [[ -n "$excerpt" ]] && excerpt="${excerpt}..." local sort_date="$as_of" [[ "$sort_date" == "—" || -z "$sort_date" ]] && sort_date="0000-00-00" card_data="${card_data}${sort_date}|${title}|${rel_path}|${as_of}|${excerpt}|${update_days} " count=$((count + 1)) done < <(find "$SELF_DIR" -name "*.o-o.html" -type f) # Build card grid (top 8 most recently updated) local card_html="" if [[ -n "$card_data" ]]; then local sorted_cards sorted_cards=$(echo "$card_data" | grep -v '^$' | sort -t'|' -k1 -r | head -8) while IFS='|' read -r c_sort c_title c_path c_date c_excerpt c_update_days; do [[ -z "$c_title" ]] && continue local c_badge="" if [[ -n "$c_date" && "$c_date" != "—" ]]; then # Format date nicely: 2026-02-16 → Feb 16, 2026 local nice_date="$c_date" if date -j -f "%Y-%m-%d" "$c_date" "+%b %-d, %Y" &>/dev/null 2>&1; then nice_date=$(date -j -f "%Y-%m-%d" "$c_date" "+%b %-d, %Y") elif date -d "$c_date" "+%b %-d, %Y" &>/dev/null 2>&1; then nice_date=$(date -d "$c_date" "+%b %-d, %Y") fi # Check freshness based on update_every_days local badge_class="fresh" local now_epoch=$(date +%s) local date_epoch="" if date -j -f "%Y-%m-%d" "$c_date" "+%s" &>/dev/null 2>&1; then date_epoch=$(date -j -f "%Y-%m-%d" "$c_date" "+%s") elif date -d "$c_date" "+%s" &>/dev/null 2>&1; then date_epoch=$(date -d "$c_date" "+%s") fi if [[ -n "$date_epoch" ]]; then local age=$(( now_epoch - date_epoch )) local stale_threshold=$(( ${c_update_days:-7} * 86400 )) [[ "$age" -gt "$stale_threshold" ]] && badge_class="stale" fi c_badge="${nice_date}" else c_badge="New" fi card_html="${card_html} ${c_title} ${c_badge} ${c_excerpt} " done <<< "$sorted_cards" fi # Build the new content local now=$(date "+%Y-%m-%d %H:%M") local new_content if [[ "$count" -eq 0 ]]; then new_content='

No documents found.

Create one with:

bash index.o-o.html --new "Your Topic"

' else new_content="
${card_html}
${table_rows}
Title Size Last Updated Version
" fi # Escape special characters for perl regex local escaped_content=$(echo "$new_content" | sed 's/\\/\\\\/g' | sed 's/\$/\\$/g' | sed 's/@/\\@/g') # Update the index using perl (works reliably with multiline content) perl -i -pe "BEGIN{undef \$/;} s|.*?|\n${escaped_content}\n |sm" "$SELF" # Update stats local tmp="/tmp/oo_stats_$$" sed -e "s|[^<]*|$count|" \ -e "s|[^<]*|$now|" \ "$SELF" > "$tmp" && mv "$tmp" "$SELF" echo "o-o Index: Found $count document(s). Index updated." } create_new() { local input="$1" local title desc # Split on " / " if present if [[ "$input" == *" / "* ]]; then title="${input%% / *}" desc="${input##* / }" else title="$input" desc="$input" fi # Slugify title for filename local slug=$(slugify "$title") local filepath="${SELF_DIR}/${slug}.o-o.html" # Check if file exists if [[ -e "$filepath" ]]; then echo "o-o: Error — file already exists: $filepath" >&2 exit 1 fi echo "o-o: Creating new document: $title" echo "o-o: File: $filepath" # Generate the file generate_oo_file "$filepath" "$title" "$desc" "$slug" echo "o-o: Created $filepath" echo "o-o: Running first update..." # Run the file to trigger first update bash "$filepath" echo "o-o: Document created and populated. Open in browser to read." } update_all() { local force="${1:-0}" echo "o-o: Checking for stale documents..." local now_epoch=$(date +%s) local updated_count=0 while IFS= read -r file; do [[ "$file" == "$SELF" ]] && continue local as_of=$(grep -o '"as_of"[[:space:]]*:[[:space:]]*"[^"]*"' "$file" | head -1 | sed 's/.*:[[:space:]]*"//' | sed 's/"$//') local update_days=$(grep -o '"update_every_days"[[:space:]]*:[[:space:]]*[0-9]*' "$file" | head -1 | grep -o '[0-9]*$' || true) update_days="${update_days:-7}" local should_update=0 if [[ "$force" -eq 1 ]]; then should_update=1 elif [[ -z "$as_of" || "$as_of" == "null" ]]; then should_update=1 else local fresh_secs=$((update_days * 86400)) local as_of_epoch if date -j -f "%Y-%m-%d" "$as_of" "+%s" &>/dev/null; then as_of_epoch=$(date -j -f "%Y-%m-%d" "$as_of" "+%s") elif date -d "$as_of" "+%s" &>/dev/null; then as_of_epoch=$(date -d "$as_of" "+%s") else should_update=1 fi if [[ -n "${as_of_epoch:-}" && "$should_update" -eq 0 ]]; then local age=$((now_epoch - as_of_epoch)) if [[ "$age" -gt "$fresh_secs" ]]; then should_update=1 fi fi fi if [[ "$should_update" -eq 1 ]]; then echo "o-o: Updating $(basename "$file")..." if [[ "$force" -eq 1 ]]; then bash "$file" --force else bash "$file" fi updated_count=$((updated_count + 1)) else echo "o-o: $(basename "$file") is still fresh. Skipping." fi done < <(find "$SELF_DIR" -name "*.o-o.html" -type f) if [[ "$updated_count" -gt 0 ]]; then echo "o-o: Updated $updated_count document(s)." echo "o-o: Rebuilding index..." rebuild_index else echo "o-o: All documents are up to date." fi } # ─── SYNC ───────────────────────────────────────────────────── sync_section() { local section="$1" local sm em case "$section" in css) sm='' em='' ;; js) sm='' em='' ;; shell) sm='# OO:SHELL:START' em='# OO:SHELL:END' ;; all) sync_section css; sync_section js; sync_section shell; return ;; *) echo "o-o Sync: Unknown section '$section'. Use: css, js, shell, all" >&2; exit 1 ;; esac # Extract canonical section boundaries from THIS file local start_line end_line start_line=$(grep -n "^${sm}\$" "$SELF" | head -1 | cut -d: -f1) end_line=$(grep -n "^${em}\$" "$SELF" | head -1 | cut -d: -f1) if [[ -z "$start_line" || -z "$end_line" ]]; then echo "o-o Sync: ERROR — no $section markers found in $SELF_NAME" >&2 return 1 fi local synced=0 for file in "$SELF_DIR"/*.o-o.html; do [[ "$file" == "$SELF" ]] && continue local f_start f_end f_start=$(grep -n "^${sm}\$" "$file" | head -1 | cut -d: -f1 || true) f_end=$(grep -n "^${em}\$" "$file" | head -1 | cut -d: -f1 || true) if [[ -z "$f_start" || -z "$f_end" ]]; then echo "o-o Sync: SKIP $(basename "$file") (no $section markers)" continue fi # Assemble: content before marker + canonical section + content after marker { head -n $((f_start - 1)) "$file" sed -n "${start_line},${end_line}p" "$SELF" tail -n +$((f_end + 1)) "$file" } > "${file}.tmp" && mv "${file}.tmp" "$file" synced=$((synced + 1)) echo "o-o Sync: $(basename "$file") [$section]" done # Handle custom oo.css for CSS sync if [[ "$section" == "css" ]]; then local custom_css="$SELF_DIR/oo.css" local csm='' local cem='' for file in "$SELF_DIR"/*.o-o.html; do # Remove existing custom block if present local c_start c_end c_start=$(grep -n "^${csm}\$" "$file" 2>/dev/null | head -1 | cut -d: -f1 || true) c_end=$(grep -n "^${cem}\$" "$file" 2>/dev/null | head -1 | cut -d: -f1 || true) if [[ -n "$c_start" && -n "$c_end" ]]; then { head -n $((c_start - 1)) "$file" tail -n +$((c_end + 1)) "$file" } > "${file}.tmp" && mv "${file}.tmp" "$file" fi # If oo.css exists, inject it right after OO:CSS:END if [[ -f "$custom_css" ]]; then local css_end_line css_end_line=$(grep -n "^${em}\$" "$file" | head -1 | cut -d: -f1) if [[ -n "$css_end_line" ]]; then { head -n "$css_end_line" "$file" echo "$csm" echo "" echo "$cem" tail -n +$((css_end_line + 1)) "$file" } > "${file}.tmp" && mv "${file}.tmp" "$file" fi fi done fi echo "o-o Sync: $section synced to $synced file(s)." } # ─── HELP ───────────────────────────────────────────────────── show_help() { echo "o-o — self-updating living documents" echo "" echo "Usage:" echo " bash $SELF_NAME [OPTIONS]" echo "" if [[ "$IS_INDEX" -eq 1 ]]; then echo "Index commands:" echo " (no args) Rebuild the index" echo " --new Create new document (interactive)" echo " --new \"Title / description\" Create new document (quick)" echo " --update-all Update stale documents" echo " --update-all --force Force update all documents" echo "" else echo "Article commands:" echo " (no args) Update this document" echo "" fi echo "Shared options:" echo " --show Show current contract and config" echo " --set KEY VALUE Set a contract/config field" echo " --add intent|section VALUE Add to a research array field" echo " --remove intent|section VALUE Remove from a research array field" echo " --sync [css|js|shell|all] Sync shared sections to sibling files" echo " --agent NAME Agent backend: claude (default)" echo " --model NAME Override model (e.g. opus, sonnet, haiku)" echo " --force Update even if document is still fresh" echo " --help, -h Show this help" echo "" echo "Settable fields (--set):" echo " subject, scope, audience, tone, budget, update_every_days" echo "" echo "Array fields (--add / --remove):" echo " intent Research search queries" echo " section Required article sections" echo "" echo "Examples:" if [[ "$IS_INDEX" -eq 1 ]]; then echo " bash $SELF_NAME --new \"History of the USA\"" echo " bash $SELF_NAME --new \"Python Async / Guide to async/await patterns\"" echo " bash $SELF_NAME --update-all" else echo " bash $SELF_NAME # Update with latest research" echo " bash $SELF_NAME --force # Force update even if fresh" echo " bash $SELF_NAME --model opus # Use a specific model" fi echo " bash $SELF_NAME --set scope \"US market analysis\"" echo " bash $SELF_NAME --add intent \"quarterly earnings 2026\"" echo " bash $SELF_NAME --add section \"Market Analysis\"" echo " bash $SELF_NAME --remove intent \"old search query\"" echo " bash $SELF_NAME --sync all # Propagate shared code to siblings" } # ─── ARG PARSING ────────────────────────────────────────────── ACTION="" NEW_TOPIC="" SYNC_SECTION="" AGENT="claude" MODEL="" FORCE=0 while [[ $# -gt 0 ]]; do case "$1" in --new) ACTION="new"; NEW_TOPIC="${2:-}"; [[ -n "$NEW_TOPIC" ]] && shift; shift ;; --update-all) ACTION="update-all"; shift ;; --sync) ACTION="sync"; SYNC_SECTION="${2:-all}"; shift; [[ $# -gt 0 && "${1:0:2}" != "--" ]] && shift ;; --show) ACTION="show"; shift ;; --set) ACTION="set"; SET_KEY="${2:-}"; SET_VAL="${3:-}"; shift; [[ -n "$SET_KEY" ]] && shift; [[ -n "$SET_VAL" ]] && shift ;; --add) ACTION="add"; ARR_FIELD="${2:-}"; ARR_VAL="${3:-}"; shift; [[ -n "$ARR_FIELD" ]] && shift; [[ -n "$ARR_VAL" ]] && shift ;; --remove) ACTION="remove"; ARR_FIELD="${2:-}"; ARR_VAL="${3:-}"; shift; [[ -n "$ARR_FIELD" ]] && shift; [[ -n "$ARR_VAL" ]] && shift ;; --agent) AGENT="$2"; shift 2 ;; --model) MODEL="$2"; shift 2 ;; --force) FORCE=1; shift ;; --help|-h) ACTION="help"; shift ;; *) echo "o-o: Unknown option: $1 (try --help)" >&2; exit 1 ;; esac done IS_INDEX=0 [[ "$SELF_NAME" == index* ]] && IS_INDEX=1 # ─── FRESHNESS CHECK ────────────────────────────────────────── check_freshness() { [[ "$FORCE" -eq 1 ]] && return 1 # return 1 = not fresh, should update local update_days as_of update_days=$(grep -o '"update_every_days"[[:space:]]*:[[:space:]]*[0-9]*' "$SELF" | head -1 | grep -o '[0-9]*$' || true) update_days="${update_days:-7}" as_of=$(grep -o '"as_of"[[:space:]]*:[[:space:]]*"[^"]*"' "$SELF" | head -1 | sed 's/.*:[[:space:]]*"//' | sed 's/"$//') [[ -z "$as_of" ]] && return 1 # no date = needs update local fresh_secs=$((update_days * 86400)) local now_epoch=$(date +%s) local as_of_epoch="" if date -j -f "%Y-%m-%d" "$as_of" "+%s" &>/dev/null 2>&1; then as_of_epoch=$(date -j -f "%Y-%m-%d" "$as_of" "+%s") elif date -d "$as_of" "+%s" &>/dev/null 2>&1; then as_of_epoch=$(date -d "$as_of" "+%s") fi if [[ -n "$as_of_epoch" ]]; then local age=$(( now_epoch - as_of_epoch )) if [[ "$age" -lt "$fresh_secs" ]]; then echo "o-o: '$SELF_NAME' is still fresh (updated $as_of, updates every ${update_days}d). Skipping." echo "o-o: Use --force to update anyway." return 0 # fresh, skip fi fi return 1 # not fresh, should update } # ─── AGENT DISPATCH ─────────────────────────────────────────── dispatch_update() { # Extract budget from contract local budget budget=$(grep -o '"max_cost_usd"[[:space:]]*:[[:space:]]*[0-9.]*' "$SELF" | head -1 | grep -o '[0-9.]*$' || true) budget="${budget:-0.50}" # Build the prompt local prompt read -r -d '' prompt << 'PROMPT_EOF' || true You are a o-o research agent. Your task is to update a living document. The document is at: __SELF__ This file is a polyglot HTML/bash file structured as follows: - Above window.stop(): browser-visible content (article, CSS, JS, manifest) - Below window.stop(): machine-readable zone (update contract, source cache, changelog) Read the update contract (the JSON block with id="oo-contract") — it contains your complete instructions: the subject, research intents, required sections, quality thresholds, source policy, and output format rules. Check the oo-manifest "as_of" field for when this document was last updated. If empty, this is a first run — research everything. If it has a date, focus your research on new information since that date. Use the Edit tool to modify specific parts of the file in-place. Only modify:
content, oo-manifest, oo-source-cache, oo-changelog. Do NOT touch CSS, JavaScript, the shell preamble, or structural HTML outside
. IMAGES: The contract may have an "images" section. If images are allowed: - Find relevant images via web search (official sites, wikimedia, press kits) - Download with: curl -sL "" -o /tmp/oo_img_N.ext - Verify it is an image: file /tmp/oo_img_N.ext - Resize (preserve format — keep PNG for transparency, JPEG for photos): macOS: sips --resampleWidth /tmp/oo_img_N.ext --out /tmp/oo_img_N_r.ext Linux: convert /tmp/oo_img_N.ext -resize x /tmp/oo_img_N_r.ext - Check size: if over max_file_kb, reduce further or skip - Encode: base64 < /tmp/oo_img_N_r.ext - Embed as:
description
Caption. Source: domain
- Clean up: rm /tmp/oo_img_N* PROMPT_EOF # Replace __SELF__ placeholder with actual path prompt="${prompt//__SELF__/$SELF}" echo "o-o: Updating '$SELF_NAME' via $AGENT (budget: \$$budget)..." case "$AGENT" in claude) if ! command -v claude &>/dev/null; then echo "o-o: Error — 'claude' CLI not found." >&2 echo "o-o: Install: https://docs.anthropic.com/en/docs/claude-code" >&2 exit 1 fi local -a claude_args=( -p "$prompt" --allowed-tools "Bash,Read,Edit,WebSearch,WebFetch" --max-budget-usd "$budget" ) if [[ -n "$MODEL" ]]; then claude_args+=(--model "$MODEL") fi claude "${claude_args[@]}" ;; *) echo "o-o: Unknown agent '$AGENT'." >&2 echo "o-o: Currently supported: claude" >&2 exit 1 ;; esac echo "o-o: Update complete. Open '$SELF_NAME' in a browser to read." } # ─── COMMAND ROUTER ─────────────────────────────────────────── # ─── SHOW CONTRACT ──────────────────────────────────────────── show_contract() { echo "" echo " $SELF_NAME" echo " ────────────────────────" # Extract manifest fields local manifest contract manifest=$(perl -0777 -ne 'print $1 if /id="oo-manifest"[^>]*>\s*(\{.*?\})\s*<\/script>/s' "$SELF") contract=$(perl -0777 -ne 'print $1 if /id="oo-contract"[^>]*>\s*(\{.*?\})\s*<\/script>/s' "$SELF") if [[ -n "$manifest" ]]; then local title as_of version update_days title=$(echo "$manifest" | perl -ne 'print $1 if /"title"\s*:\s*"([^"]*)"/') as_of=$(echo "$manifest" | perl -ne 'print $1 if /"as_of"\s*:\s*"([^"]*)"/') version=$(echo "$manifest" | perl -ne 'print $1 if /"version"\s*:\s*(\d+)/') update_days=$(echo "$manifest" | perl -ne 'print $1 if /"update_every_days"\s*:\s*(\d+)/') [[ -n "$title" ]] && echo " Title: $title" [[ -n "$version" ]] && echo " Version: $version" [[ -n "$as_of" ]] && echo " Last updated: $as_of" [[ -n "$update_days" ]] && echo " Update every: ${update_days} days" fi if [[ -n "$contract" ]]; then local subject scope audience tone budget subject=$(echo "$contract" | perl -ne 'print $1 if /"subject"\s*:\s*"([^"]*)"/') scope=$(echo "$contract" | perl -ne 'print $1 if /"scope"\s*:\s*"([^"]*)"/') audience=$(echo "$contract" | perl -ne 'print $1 if /"audience"\s*:\s*"([^"]*)"/') tone=$(echo "$contract" | perl -ne 'print $1 if /"tone"\s*:\s*"([^"]*)"/') budget=$(echo "$contract" | perl -ne 'print $1 if /"max_cost_usd"\s*:\s*([\d.]+)/') echo "" [[ -n "$subject" ]] && echo " Subject: $subject" [[ -n "$scope" ]] && echo " Scope: $scope" [[ -n "$audience" ]] && echo " Audience: $audience" [[ -n "$tone" ]] && echo " Tone: $tone" [[ -n "$budget" ]] && echo " Budget: \$$budget" # Research intents local intents intents=$(echo "$contract" | perl -0777 -ne 'if(/"intents"\s*:\s*\[(.*?)\]/s){$i=$1; while($i=~/"([^"]+)"/g){print "$1\n"}}') if [[ -n "$intents" ]]; then echo "" echo " Research intents:" while IFS= read -r line; do echo " - $line" done <<< "$intents" fi # Required sections local sections sections=$(echo "$contract" | perl -0777 -ne 'if(/"required_sections"\s*:\s*\[(.*?)\]/s){$i=$1; while($i=~/"([^"]+)"/g){print "$1\n"}}') if [[ -n "$sections" ]]; then echo "" echo " Required sections:" while IFS= read -r line; do echo " - $line" done <<< "$sections" fi fi echo "" } # ─── SET FIELD ───────────────────────────────────────────────── set_field() { local key="$1" val="$2" [[ -z "$key" || -z "$val" ]] && { echo "o-o: Usage: --set KEY VALUE" >&2; exit 1; } case "$key" in subject|scope|audience|tone) # These live in oo-contract → identity.KEY perl -i -0pe "s/(\"identity\"\\s*:\\s*\\{[^}]*\"$key\"\\s*:\\s*\")([^\"]*)(\")/\${1}$val\${3}/s" "$SELF" echo "o-o: Set identity.$key = \"$val\"" ;; budget) # budget.max_cost_usd in oo-contract perl -i -pe "s/(\"max_cost_usd\"\\s*:\\s*)[\\d.]+/\${1}$val/" "$SELF" echo "o-o: Set budget.max_cost_usd = $val" ;; update_every_days) # In oo-manifest perl -i -pe "s/(\"update_every_days\"\\s*:\\s*)\\d+/\${1}$val/" "$SELF" echo "o-o: Set update_every_days = $val" ;; *) echo "o-o: Unknown field: $key" >&2 echo "o-o: Settable fields: subject, scope, audience, tone, budget, update_every_days" >&2 echo "o-o: For array fields use: --add intent|section VALUE / --remove intent|section VALUE" >&2 exit 1 ;; esac } # ─── ADD / REMOVE ARRAY ITEMS ────────────────────────────────── add_to_array() { local field="$1" value="$2" [[ -z "$field" || -z "$value" ]] && { echo "o-o: Usage: --add intent|section VALUE" >&2; exit 1; } local arr_name case "$field" in intent) arr_name="intents" ;; section) arr_name="required_sections" ;; *) echo "o-o: Unknown array field: $field (use: intent, section)" >&2; exit 1 ;; esac # Append before the closing ] of the named array perl -i -0777 -pe 's/("'"$arr_name"'"\s*:\s*\[.*?)(\s*\])/$1,\n "'"$value"'"$2/s' "$SELF" # Fix leading comma if array was previously empty: ["x"] → ["x"] perl -i -pe 's/\[\s*,\s*"/["/' "$SELF" echo "o-o: Added to research.$arr_name: \"$value\"" } remove_from_array() { local field="$1" value="$2" [[ -z "$field" || -z "$value" ]] && { echo "o-o: Usage: --remove intent|section VALUE" >&2; exit 1; } local arr_name case "$field" in intent) arr_name="intents" ;; section) arr_name="required_sections" ;; *) echo "o-o: Unknown array field: $field (use: intent, section)" >&2; exit 1 ;; esac # Remove line matching the exact value perl -i -ne 'print unless /^\s*"\Q'"$value"'\E"\s*,?\s*$/' "$SELF" # Fix trailing comma before ]: ..., ] → ...] perl -i -0777 -pe 's/,(\s*\])/$1/g' "$SELF" echo "o-o: Removed from research.$arr_name: \"$value\"" } case "$ACTION" in new) if [[ "$IS_INDEX" -eq 0 ]]; then echo "o-o: --new is only available on index files." >&2 echo "o-o: Rename this file to index*.o-o.html to enable library management." >&2 exit 1 fi if [[ -z "$NEW_TOPIC" ]]; then # Interactive mode echo "" echo " Create new o-o document" echo " ────────────────────────" echo "" read -p " Title: " OO_NEW_TITLE if [[ -z "$OO_NEW_TITLE" ]]; then echo " Error: Title is required." >&2 exit 1 fi echo "" read -p " Scope (what should this document cover?): " OO_NEW_SCOPE [[ -z "$OO_NEW_SCOPE" ]] && OO_NEW_SCOPE="$OO_NEW_TITLE" echo "" read -p " Audience [General readers]: " OO_NEW_AUDIENCE [[ -z "$OO_NEW_AUDIENCE" ]] && OO_NEW_AUDIENCE="General readers" read -p " Tone [Informative, well-researched, accessible]: " OO_NEW_TONE [[ -z "$OO_NEW_TONE" ]] && OO_NEW_TONE="Informative, well-researched, accessible" read -p " Budget USD [0.50]: " OO_NEW_BUDGET [[ -z "$OO_NEW_BUDGET" ]] && OO_NEW_BUDGET="0.50" echo "" OO_NEW_SLUG=$(slugify "$OO_NEW_TITLE") OO_NEW_PATH="${SELF_DIR}/${OO_NEW_SLUG}.o-o.html" if [[ -e "$OO_NEW_PATH" ]]; then echo " Error: File already exists: $OO_NEW_PATH" >&2 exit 1 fi echo " Creating: ${OO_NEW_SLUG}.o-o.html" generate_oo_file "$OO_NEW_PATH" "$OO_NEW_TITLE" "$OO_NEW_SCOPE" "$OO_NEW_SLUG" # Customize audience, tone, budget if not defaults OO_TMP="/tmp/oo_custom_$$" sed -e "s|\"audience\": \"General readers\"|\"audience\": \"${OO_NEW_AUDIENCE}\"|" \ -e "s|\"tone\": \"Informative, well-researched, accessible\"|\"tone\": \"${OO_NEW_TONE}\"|" \ -e "s|\"max_cost_usd\": 0.50|\"max_cost_usd\": ${OO_NEW_BUDGET}|" \ "$OO_NEW_PATH" > "$OO_TMP" && mv "$OO_TMP" "$OO_NEW_PATH" chmod +x "$OO_NEW_PATH" echo " Running first update..." echo "" bash "$OO_NEW_PATH" else create_new "$NEW_TOPIC" fi ;; update-all) if [[ "$IS_INDEX" -eq 0 ]]; then echo "o-o: --update-all is only available on index files." >&2 echo "o-o: Rename this file to index*.o-o.html to enable library management." >&2 exit 1 fi update_all "$FORCE" ;; sync) sync_section "$SYNC_SECTION" ;; show) show_contract ;; set) set_field "$SET_KEY" "$SET_VAL" ;; add) if [[ "$IS_INDEX" -eq 1 ]]; then echo "o-o: --add is for article files (modifies the research contract)." >&2 exit 1 fi add_to_array "$ARR_FIELD" "$ARR_VAL" ;; remove) if [[ "$IS_INDEX" -eq 1 ]]; then echo "o-o: --remove is for article files (modifies the research contract)." >&2 exit 1 fi remove_from_array "$ARR_FIELD" "$ARR_VAL" ;; help) show_help ;; "") if [[ "$IS_INDEX" -eq 1 ]]; then rebuild_index else # Article update: check freshness then dispatch if check_freshness; then exit 0 # still fresh, already printed message fi dispatch_update fi ;; esac # OO:SHELL:END exit 0