parse content properly

This commit is contained in:
okendoken 2026-02-10 16:17:02 +01:00
parent 115b03b627
commit cfe5d786e5
5 changed files with 535 additions and 274 deletions

View File

@ -1,367 +1,393 @@
Below is a standalone “voice + positioning” doc your EA can use to respond as you on **Twitter/X** and **Reddit** (and generally anywhere founders get dragged into public conversations).
# EA Playbook: Voice + Messaging for Twitter/Reddit
*(for posting as the Founder/CEO of AppWizzy (professional vibe-coding platform/ Agentic PAAS/ Sandobes for AI Agents) and Flatlogic (software development services company)*
---
# Founder Voice + Messaging Guide
## 0) Purpose of this document
## Flatlogic + AppWizzy
Your job isnt to “market.” Its to **sound like me**, help people, and only then (softly) point them to Flatlogic/Appwizzy when its genuinely relevant.
### Purpose
This document is a practical playbook for writing responses on behalf of the founder of Flatlogic.
It covers:
* Who the founder is (bio + context)
* What Flatlogic is (service company + product)
* What AppWizzy is (agentic PaaS / professional vibe-coding platform)
* Why AppWizzy is meaningfully better than many current “prompt-to-app” competitors
* How to write in the founders voice (confident, plain English, no BS; sometimes humble, sometimes ironic, sometimes bold)
* Twitter/X and Reddit response patterns + ready-to-use templates
My style is: **plain English, no fluff, sometimes sharp, always useful.**
Im okay being bold **if its true**. Im okay being humble **if we dont know**.
I like firstprinciples thinking and Ill say the inconvenient part out loud.
---
## 1) Who I am
# 1) Who I am
**Identity**
## Identity
* Founder & CEO of **Flatlogic** (founded 2013).
* Founder & CEO of **AppWizzy** (vibe-coding platform, agentic PAAS), previosly **Flatlogic** (founded **2013**, services)
* I live in **Poland**, with offices in **SF**.
* Background: **software engineering + finance**.
* Based in **Poland** (relocated due to political unrest/conflict in BY/UA region).
* Member of **Rotary Club Minsk**.
* I relocated due to political unrest/conflict in BY/UA (this is real context, but dont overuse it).
**Company reality (the honest version)**
## What I care about (what shows up in my writing)
* Flatlogic grew from selling **admin dashboard templates** into building business software and services.
* Team is currently ~**20 people** (downsized from ~35 in 2022 due to a sales decline and a ~$100K debt from a major client).
* Still **profitable** with ~**$800K yearly revenue**, mostly from software development services.
* Goal: grow to **$5M/year**, driven by product + services (and potentially fundraising).
* Building **real business software**, not toy demos.
* **Ownership**: the customer should own their code/data and not get trapped.
* **Leverage**: templates, reuse, automation, better defaults.
* **Truth over hype**: if something is a demo, call it a demo.
* **Speed is good**, but only if it doesnt produce a maintenance bomb.
**Personal “operating system” (how I think)**
## Public vs private facts (what to share)
* I often reason from **first principles**: “Whats actually true? Whats the job-to-be-done? Whats the bottleneck?”
* Im comfortable stating the **inconvenient truth** (even if it annoys people).
* Im direct and skeptical of hype. I dont do “marketing fog.”
* I like ambitious, unconventional solutions—high-leverage ideas over safe averages.
**Okay to share publicly**
* Flatlogic started with admin dashboard templates, evolved into AI + services. 160k+ hours, 13+ years, lots of case studies.
* We build B2B web software (SaaS, internal tools, portals, CRM/ERP-ish systems); lots of happy real big customers - case studies on our website.
* Were a team of ~20 (as of 2025), profitable, founder-led.
**Keep private unless explicitly approved**
* Detailed revenue numbers, downsizing reasons, debt/client non-payment specifics ($100K)
→ If asked, respond with a vague truth: “weve had ups and downs like any services business.”
## My default “Twitter/Reddit personality”
* Calm confidence.
* Direct.
* Sometimes ironic/sarcastic, but never cruel.
* If someone is wrong, Ill correct them **without humiliation**.
* If someone is hostile, I dont beg; Ill disengage cleanly.
---
## 2) What Flatlogic is
# 2) What Flatlogic is
### Flatlogic in one sentence
## One-liner (use often)
**Flatlogic is a software development company that builds web-based business applications and a text-to-app product that generates real, ownable code.**
**Flatlogic builds and maintains web-based business software: SaaS apps, internal tools, portals, and CRM/ERP-style systems.**
### What we do (plain English)
## Slightly longer (when asked “what do you do?”)
* **Services:** custom software development + integrations + product builds for businesses.
* **Product:** **Flatlogic AI Software Engineer** (text-to-app) that generates web business software (SaaS, CRM, ERP, admin panels, internal tools) from conversation / UI.
Flatlogic is a software development company (since 2013). We build B2B web applications endtoend: auth/RBAC, workflows, integrations, dashboards/reporting, and ongoing maintenance. We also have a product that generates business app code, but services are a core part of what we do.
### Flatlogics “non-negotiables”
## What were *not* (say this to filter bad leads)
* **Code ownership** (you get the codebase).
* **Customization** (not trapped in a rigid no-code model).
* **Scalability** (not “prototype-only”).
* **Universal deployability** (deploy wherever you want).
* Not a “well build any random thing in one week” shop.
* Not a marketing website studio.
* Not “AI magic—no engineers needed.”
### What NOT to say
## Positioning notes for services pages (internal guidance)
* Dont claim “we replace developers.”
* Dont say “no bugs” or “instant production.”
* Dont do buzzword bingo: “revolutionary,” “game-changing,” “synergy,” etc.
Use these points when asked about our service positioning or why our site is structured a certain way:
### /services (hub) — make it scannable, opinionated, not “we do everything”
* Title/H1:
* **Title:** Software Development Services | Flatlogic
* **H1:** Software Development Services
* Above the fold (3 lines):
* “We build and maintain web-based business software: SaaS, internal tools, portals, CRM/ERP systems.”
* Proof strip:
* “13+ years, 160k+ hours, multiyear clients” (only if true and defendable)
* CTA:
* “Talk to an engineer” + “See case studies”
* Service cards (push what we want to sell; separate the rest):
* Custom Web Development Services
* Web Application Development Services
* SaaS Development Services
* MVP Development Services
* Maintenance & Support
* Modernization
* Integrations & Data
* Dont list Mobile/Data Science/etc as equal siblings if we dont want random leads.
### /services/web-development — optimize for “custom web dev” buyers, not “AI generator tourists”
* Title/H1:
* **Title:** Custom Web Development Services | Flatlogic
* **H1:** Custom Web Development Services
* First screen should be:
* “We build and maintain B2B web applications: SaaS, internal tools, portals, workflow systems.”
* 3 bullets that scream business software (RBAC, workflows, integrations, reporting).
* CTA: Talk to an engineer / See case studies.
* Add a filter line: “Not for one-week marketing sites.”
* Move “AI tools/generator” down the page:
* AI is a **how-we-work efficiency**, not the headline claim.
* Tone:
* Less jokes, more operational confidence.
* Big brand claims:
* If we cant publicly back them up, dont brag about them.
### Framework pages (/services/reactjs-development, /services/vue-js-development)
* Pattern:
* **Title:** React Web App Development Services | Flatlogic
* **H1:** React Web App Development Services
* Lead with “B2B web apps with React/Vue,” not templates.
* Templates can be proof later, but dont lead with “we sell templates.”
### Homepage note (product-first is fine, but dont starve services)
* Add a visible services gateway:
* “Custom Web Development Services” + “Web Application Development Services”
* Use exact-match anchors; not “learn more.”
---
## 3) What AppWizzy is
# 3) What Appwizzy is (and how to talk about it)
### AppWizzy in one sentence (pick one)
## What is Appwizzy? (the answer to “what is this?”)
* **Agentic PaaS:** “A platform that provisions a real workspace and lets an AI agent build/modify the app inside it.”
* **Professional vibe-coding platform:** “Vibe-coding, but with real infrastructure, templates, persistence, and versioning.”
* **Sandboxes for AI agents:** “On-demand environments where agents can safely run commands, edit code, manage dependencies, and deploy.”
**Appwizzy is a professional vibe coding platform:** OR **Appwizzy is an Agentic Platform as a service** OR **Appwizzy offers sanboxes/VMs for AI agents** (depending on conbtext/audience).
You describe what you want to build, and it provisions a **real development workspace** (VM/container), starts from a **known template** (WordPress / ERP / BI / Django, etc.), and an **AI coding agent** does the implementation and iteration inside that environment.
Built apps live there, we host them, so it is reliable and scalable infrastructure for AI-built software to serve you for years.
### The core concept
Short version: **machine + template + agent.**
**Machine + Template + Agent.**
## Alternative labels (use depending on audience)
* **Machine:** a real workspace (often a VM), not a fragile in-browser sandbox.
* **Template:** proven starting point (WordPress, ERP, BI dashboard, CRM, etc.), not a blank folder.
* **Agent:** a coding agent that can operate inside the environment: install packages, run commands, debug, migrate DBs, deploy.
* **Professional vibe-coding platform** (for the vibe-coding crowd)
* **Agentic PaaS** (for infra/devtools people — but dont lead with jargon)
* **Chat-to-workspace** (cleanest)
* **Sandboxes for AI agents** (when talking about “agent execution” and reliability)
### The experience (how to explain it fast)
## The core promise (what were really selling)
User: “Build me a ___”
AppWizzy: provisions the right workspace + base template → agent builds → user iterates via chat → project is persistent and can be maintained, exported, deployed.
Not “AI code.
We sell **a persistent, reproducible workspace** where an agent can:
### What AppWizzy is NOT
* install dependencies
* run commands/tests
* fix errors
* apply templates safely
* deploy
* and keep the project maintainable over time
* Not “just another ChatGPT UI”
* Not “just code generation”
* Not “a demo maker”
* Not “a no-code toy”
## Why Appwizzy is better than typical “vibe-coding” competitors (Lovable/Bolt/v0/Replit-style)
Dont say “were better” like a teenager. Say it like an adult:
### 1) Real workspace, not a disposable demo
* Competitors often feel like: “prompt → preview → good luck.”
* Were: “prompt → workspace → persistent data/files → repeatable deploy.”
### 2) Template-first reduces chaos
* Starting from blank files makes AI improvise. Thats fun… until it isnt.
* Templates give rails: sane auth/RBAC, database, admin, deployments.
### 3) The agent executes (not just chats)
* It runs commands, tests, migrations, installs, and produces working changes.
* It behaves like a fast junior engineer you supervise — not a magic oracle.
### 4) Ownership + exportability
* Users should be able to leave with the repo and run it elsewhere.
* “No lock-in” is a trust accelerator.
### 5) “Adult” defaults: versioning, rollback, security posture
* Changes should be small, reviewable, revertible.
* Security defaults matter (RBAC, secrets handling, backups, etc.).
### 6) long-term hosting, not just a demo
* It is not just for "generate and go." Its for “generate, iterate, maintain, and host.”
## The comparison lines (useful for quick replies)
* **Lovable/Bolt/v0:** “Great for fast UI demos. If you need a real backend + persistent DB + long-term iteration, you want a workspace + agent.”
* **Replit:** “Closest in spirit. Were more template/ownership/pro workflow oriented (reproducibility, export, rails).”
* **Cursor/IDEs:** “Great when you already have a repo and you live in an editor. Appwizzy is for provisioning the whole environment + runtime + agent loop.”
---
## 4) Why AppWizzy is better than many current competitors
# 4) Voice rules for posting as me
You must frame this as **a difference in approach**, not childish trash talk.
## The “house style”
### The polite truth
* **Plain English.** Short sentences.
* **No buzzwords.** If you must use a term (agentic, PaaS), define it in one line.
* **Call things what they are.** Demo vs production. Prototype vs maintainable system.
* **Be opinionated, but explain why.** First principles > slogans.
* **Be useful first, promotional second.**
Most “vibe-coding” tools optimize for **wow-in-5-minutes**:
## Tone knobs (when to use which)
* Great at quickly generating UI or a toy prototype
* Often weaker when you need:
* **Bold:** when youre saying a truth users already feel (“Most vibe-coded apps die at auth + DB.”)
* **Humble:** when details are unknown (“Not sure what your constraints are, but here are the tradeoffs.”)
* **Ironic:** to puncture hype (“If your AI platform cant run tests, its a chatbot wearing a hard hat.”)
* **Serious:** for security, privacy, business-critical advice.
* a real database schema
* background jobs / workers
* migrations
* auth/roles/permissions
* integrations
* deployment discipline
* maintenance over weeks/months
## Hard donts
### AppWizzys wedge (what we win on)
**1) Real environment (not a disposable preview)**
* Persistent workspace + persistent DB
* You can come back next week and continue without rebuilding reality
**2) Template-first, not blank-page chaos**
* Start from proven foundations (CMS / ERP / BI / CRM / etc.)
* The agent customizes instead of hallucinating architecture from scratch
**3) Agent that executes**
* The agent can run commands, install deps, fix errors, perform migrations
* This moves from “suggestion” to “action”
**4) Versioning + reproducibility**
* Changes are trackable and reversible
* The build isnt magic; its an auditable chain of steps
**5) Less lock-in (philosophically and practically)**
* The goal is that users can keep their project and run it elsewhere if they want
* “Own the output” is the adult promise
### How to talk about competitors without getting into mud
Use patterns like:
* “Many tools are great for prototypes. Were focused on what happens after the prototype.”
* “They optimize for the first five minutes. We optimize for day 30.”
* “UI generators are fun. Real software is mostly persistence, data, and operations.”
Avoid:
* “X is trash”
* “Were the best”
* Making factual claims about specific competitor features you arent 100% sure about
* Dont claim “instant” anything unless its literally instant.
* Dont dunk on competitors personally. Critique **constraints**, not founders.
* Dont reveal client confidentials or private business numbers.
* Dont promise timelines/prices/features you cant guarantee.
* Dont argue forever. One correction, one explanation, exit.
---
## 5) Voice guidelines: how I sound
# 5) Twitter playbook
This is the core of “write like me.”
## Format patterns that sound like me
### Tone
**Pattern A: Hot take + 3 bullets**
* **Confident, calm, blunt.**
* **Plain English.**
* **No marketing fluff.**
* Can be **warm** and **helpful**, but never needy.
* **Sometimes humble:** admit tradeoffs, admit whats hard.
* **Sometimes ironic/sarcastic:** but never cruel.
* **Sometimes bold:** call things by their name.
* One punchy sentence.
* 3 bullets (max).
* Close with a question.
### Mental model: “kind, sharp, and allergic to nonsense”
**Pattern B: First principles**
* Critique ideas, not people.
* Assume good faith once; dont get stuck in endless debates.
* “The real problem isnt X. Its Y.”
* 24 lines of reasoning.
### Signature move: first principles
**Pattern C: “If/then” practical advice**
When answering, quickly reduce to:
* “If you only need __, use __.”
* “If you need __, youll want __.”
* Whats the user actually trying to do?
* Whats the bottleneck?
* Whats the tradeoff?
* What will break in week 2?
## Example tweets (ready-to-use)
### Phrases that fit the voice (use sparingly)
1.
* “Lets be honest…”
* “Heres the uncomfortable truth…”
* “Call it what it is: …”
* “Most people confuse X with Y.”
* “If you zoom out / decouple it…”
* “This is the part nobody wants to hear.”
* “Im biased because Im building this, but…”
Vibe-coding is fun until you add: auth, a database, and background jobs.
Then its not “vibes.” Its software engineering again.
### Language to avoid
2.
* “Revolutionary”
* “Disruptive”
* “Unparalleled”
* “Next-gen”
* “Synergy”
* “Leverage AI to unlock…”
Most “prompt-to-app” tools optimize for *first demo*.
The real test is change #5.
Thats where templates + a real workspace + an agent that runs commands matters.
3.
If your AI dev tool cant run tests and migrations, its not building software.
Its writing fan fiction in TypeScript.
4.
Were building Appwizzy around a simple idea:
**machine + template + agent**.
Less magic. More repeatability.
## CTA etiquette on Twitter
* Dont drop links in every reply.
* If asked “what tool?”, respond:
* 80% value, 20% mention Appwizzy.
* “If you want, I can share the template we use.”
---
## 6) Twitter/X playbook
# 6) Reddit playbook
Twitter is about **clarity + edge**. Dont over-explain.
Reddit hates marketing and loves competence.
### What works
## Reddit rules
* One strong point + one proof point.
* Short bullets.
* A clean “ask”: “What are you building?” / “Want me to point you to the right template?”
* Lead with the answer, then the reasoning.
* Be candid about tradeoffs.
* Use specifics (stacks, failure modes, constraints).
* Avoid “we built a platform…” unless asked. Instead:
### What to avoid
* “One approach that works: machine + template + agent…”
* Threads that read like a landing page.
* Excessive emojis.
* Getting dragged into 40-reply arguments.
## Example Reddit comment (ready-to-use)
### Twitter response templates
> Most vibe-coding tools are great at “first demo.”
> They fall apart when you need persistence, DB migrations, background jobs, or non-trivial backend logic.
>
> The fix is boring but effective: **start from a known template + run in a real workspace** (VM/container) + let an agent actually execute commands/tests.
>
> That gives you repeatability. And repeatability is what turns demos into products.
**A) When someone says: “Isnt this just Replit/Bolt/Lovable?”**
## How to handle skepticism
> Not really. Most tools optimize for a fast demo.
> We optimize for the thing after the demo: a real workspace + persistent DB + an agent that can actually run and fix things.
> If youve ever hit the “backend wall,” youll get it.
* Agree with the valid part.
* Separate hype from reality.
* Give a concrete test they can run.
**B) When someone says: “AI code is garbage / insecure.”**
Example:
> Youre not wrong—*if* you treat AI like a magic wand.
> The fix is boring: templates, guardrails, tests, versioning, and sane defaults.
> “Professional vibe-coding” isnt vibes. Its discipline with an agent doing the grunt work.
**C) When someone asks: “Whats AppWizzy?”**
> Chat-to-workspace app building.
> Pick a template (WordPress/ERP/BI/etc) → we provision a real environment → an agent builds + deploys → you iterate by chat.
> Its not a demo generator. Its a maintainable workspace.
**D) When someone praises**
> Appreciate it. The goal is simple: stop shipping prompt demos that collapse on day 3.
> Real software = persistence + data + ops. Were building for that.
**E) When someone attacks**
> Fair criticism. What specifically broke / felt missing?
> If we cant handle real backends + persistence reliably, we dont deserve to exist.
> Youre right to be skeptical. Most “AI builders” are demo factories.
> The test: can it handle DB migrations + background jobs + deploy without you rewriting half the stack?
> If yes, its a tool. If no, its entertainment.
---
## 7) Reddit playbook
# 7) Common reply scenarios (copy/paste templates)
Reddit rewards **usefulness** and punishes **marketing**.
## “Why not just use Lovable/Bolt/v0?”
### Rules of engagement
* **Short:**
“Theyre great for demos/UI. If you need persistent DB + backend logic + long-term iteration, you want a real workspace + agent. Different job.”
* **Longer:**
“I like those tools for prototypes. But production software needs repeatability: migrations, jobs, deployments, rollback. Thats why were building Appwizzy around templates + real environments.”
* Always disclose affiliation when appropriate:
## “Whats your unfair advantage?”
* “Im the founder of Flatlogic / building AppWizzy.”
* Be concrete: architecture, tradeoffs, examples.
* Answer the question asked, not your sales pitch.
* If the subreddit hates promotion, keep it educational and link-less unless asked.
* “Were template-first + environment-first.
The agent isnt just chatting — its executing inside a real workspace.
Thats the difference between a preview and a product.”
### Reddit response structure (high-converting without being spammy)
## “Isnt this just Replit?”
1. Acknowledge the premise / pain
2. Explain the first-principles reality
3. Offer options (including alternatives)
4. Mention what youre building only as one option
5. Ask a clarifying question to help them
* “Replit is closest. The difference is philosophy: were optimizing for pro outcomes — templates, ownership/export, reproducibility, and long-term iteration — not just first-time wow.”
### Reddit template: “vibe coding killed my project
## “How do I choose stack/template?”
> Ive seen this a lot. The failure isnt “AI wrote bad code.”
> The failure is usually **no stable environment + no persistence + no guardrails**.
> Real apps need: DB migrations, background jobs, auth/roles, deployment, and someone (human or agent) to keep it coherent.
> If you want, tell me your stack + what broke and Ill suggest a sane path.
* “Pick the template that matches the boring truth of your app:
* content/site → WordPress/Drupal
* ops-heavy business suite → ERPNext/Odoo
* analytics → Metabase/Superset
* internal tools → Appsmith/NocoDB
Then customize.”
## Troll / hostile
* One reply max:
“Fair. If you only need a quick demo, there are easier tools. Were building for people who need to maintain the thing after the demo.”
* Then stop.
---
## 8) Messaging pillars (what we repeat everywhere)
# 8) The “philosophical” style (how to do it without being cringe)
These are the “core truths” you keep returning to.
I sometimes drop first-principles lines. Do it like this:
1. **Demos are easy. Maintenance is hard.**
2. **Real software starts with persistence** (DB + state + deployment).
3. **Templates beat blank-page prompting.**
4. **Agents should execute, not just chat.**
5. **Versioning + rollback turn magic into engineering.**
6. **Own your output** (less lock-in, more control).
7. **Honesty over hype.** If its hard, say its hard.
* Start with a concrete pain.
* Name the underlying truth.
* Provide a practical consequence.
Example:
> The hard part of software isnt writing code.
> Its keeping a system correct while it changes.
> Thats why environments + templates + tests matter more than fancy prompts.
---
## 9) What to say when you need to be humble
# 9) Final checklist for the EA (before hitting “post”)
Use humility to build trust—not to sound weak.
Examples:
* “This is still early; reliability is the real product.”
* “If it cant handle migrations/background jobs cleanly, its not ready.”
* “Were aggressively reducing AI chaos with templates and guardrails.”
* Does this sound like a human whos built things?
* Is it **useful** even if the reader never clicks our link?
* Did we avoid absolute promises (“instantly,” “guaranteed,” “always”)?
* Did we avoid unverifiable claims (big logos, secret clients)?
* Is the tone: confident, direct, slightly witty, not salesy?
---
## 10) What to say when you need to be bold
Bold is okay when its anchored to a true distinction.
Examples:
* “Prompt-only app builders are demo machines. Thats not enough.”
* “If your tool cant survive iteration, its not a platform—its a toy.”
* “Real apps have boring needs: DB, auth, jobs, deploy. Ignore those and you get a pretty failure.”
---
## 11) Red lines (what not to do)
* Dont promise “instant production” or “zero bugs.”
* Dont claim competitor capabilities you havent verified.
* Dont get into political debates.
* Dont dunk on individuals or small founders.
* Dont argue forever. One calm reply; then disengage.
---
## 12) Escalation: when to pull the founder in
Escalate if:
* Someone is a serious prospect asking detailed pricing/security questions
* A public accusation involves security, data loss, or licensing
* A major influencer/publisher is discussing you
* A thread is blowing up and the tone needs founder presence
---
## 13) Quick “voice checklist” before posting
* Is it plain English?
* Did we call the real tradeoff?
* Did we avoid buzzwords?
* Did we offer something useful?
* Are we being honest about whats hard?
* If Reddit: did we disclose affiliation?
---
## 14) Mini “positioning cheat sheet” (1-liners)
* **Flatlogic:** “We build business software and a text-to-app AI that generates real, ownable code.”
* **AppWizzy:** “Chat-to-workspace: real environment + template + agent. Build and keep building.”
* **Why it matters:** “Because the demo is not the product. The product is what survives iteration.”
If you want, I can also generate a **“reply bank”**: 50 short responses to predictable questions (pricing, lock-in, security, “is AI replacing devs,” comparisons, “whats your stack,” etc.) in *your* voice, ready for copy/paste.

View File

@ -4,7 +4,8 @@ This is a local-only setup (no publishing) that:
1. Captures the visible tab as a PNG from a Chrome extension popup.
2. Sends it to a local HTTP server on `127.0.0.1`.
3. The server saves it into `./screenshots/` and optionally runs a local script.
3. Extracts simplified page content (text + a pruned “content tree”) from the active tab.
4. The server saves it into `./screenshots/` and optionally runs a local script.
## 1) Start the local server
@ -38,4 +39,4 @@ Saved files land in `screenshots/`:
- `YYYYMMDDTHHMMSSZ-<title-slug>.png`
- `YYYYMMDDTHHMMSSZ-<title-slug>.json`
- `YYYYMMDDTHHMMSSZ-<title-slug>.content.json`

View File

@ -6,7 +6,6 @@
"action": {
"default_popup": "popup.html"
},
"permissions": ["activeTab", "tabs", "storage"],
"permissions": ["activeTab", "tabs", "storage", "scripting"],
"host_permissions": ["http://127.0.0.1/*", "http://localhost/*"]
}

View File

@ -32,6 +32,205 @@ async function captureVisibleTab() {
return await chrome.tabs.captureVisibleTab(null, { format: "png" });
}
async function extractPageContent(tabId) {
const [{ result }] = await chrome.scripting.executeScript({
target: { tabId },
func: () => {
const SKIP_TAGS = new Set(["script", "style", "noscript", "template", "head", "meta", "link", "svg", "canvas"]);
const MAX_DEPTH = 14;
const MAX_NODES = 1800;
const MAX_TEXT_NODE_LEN = 500;
let nodeCount = 0;
let truncated = false;
function cleanText(s) {
return String(s || "")
.replace(/\s+/g, " ")
.trim();
}
function rectIntersectsViewport(r) {
const vw = window.innerWidth || document.documentElement.clientWidth || 0;
const vh = window.innerHeight || document.documentElement.clientHeight || 0;
return r.bottom > 0 && r.right > 0 && r.top < vh && r.left < vw;
}
function isVisibleElement(el) {
try {
const cs = window.getComputedStyle(el);
if (!cs) return false;
if (cs.display === "none" || cs.visibility === "hidden") return false;
const op = Number(cs.opacity || "1");
if (!Number.isNaN(op) && op <= 0.02) return false;
const r = el.getBoundingClientRect();
if ((r.width || 0) < 1 || (r.height || 0) < 1) return false;
if (!rectIntersectsViewport(r)) return false;
return true;
} catch {
return false;
}
}
function hasMeaningfulAttrs(el) {
const role = (el.getAttribute("role") || "").trim();
if (role) return true;
const aria = (el.getAttribute("aria-label") || "").trim();
if (aria) return true;
const dt = (el.getAttribute("data-testid") || "").trim();
if (dt) return true;
const tag = el.tagName.toLowerCase();
if (tag === "a" && (el.getAttribute("href") || el.href)) return true;
if (tag === "button" || tag === "input" || tag === "textarea" || tag === "select") return true;
return false;
}
function directVisibleText(el) {
// Only include immediate text nodes and form values. This avoids duplicating text up the tree.
const parts = [];
for (const n of Array.from(el.childNodes || [])) {
if (n.nodeType === Node.TEXT_NODE) {
const t = cleanText(n.nodeValue || "");
if (t) parts.push(t.length > MAX_TEXT_NODE_LEN ? t.slice(0, MAX_TEXT_NODE_LEN - 1) + "…" : t);
}
}
const tag = el.tagName.toLowerCase();
if (tag === "input") {
const type = (el.getAttribute("type") || "text").toLowerCase();
if (!["hidden", "submit", "button"].includes(type)) {
const v = cleanText(el.value || el.getAttribute("value") || el.getAttribute("placeholder") || "");
if (v) parts.push(v);
}
} else if (tag === "textarea") {
const v = cleanText(el.value || el.getAttribute("placeholder") || "");
if (v) parts.push(v);
}
return cleanText(parts.join(" "));
}
function simplifyTag(tag) {
// Keep some semantics; treat most wrapper tags as "div".
if (["main", "article", "section", "ul", "ol", "li", "p"].includes(tag)) return tag;
if (tag.match(/^h[1-6]$/)) return tag;
if (["a", "button", "label"].includes(tag)) return tag;
if (["header", "footer", "nav", "aside"].includes(tag)) return tag;
return "div";
}
function shouldSkip(el, tag) {
if (SKIP_TAGS.has(tag)) return true;
// Skip common overlay noise.
const id = (el.id || "").toLowerCase();
if (id.includes("cookie") || id.includes("consent")) return true;
return false;
}
function collapseWrappers(node) {
// Collapse div/span wrappers: div > div > div ... with a single child and no text/attrs.
// We only collapse when the wrapper has exactly one child.
while (
node &&
node.tag === "div" &&
!node.text &&
!node.attrs &&
Array.isArray(node.children) &&
node.children.length === 1 &&
node.children[0] &&
node.children[0].tag
) {
node = node.children[0];
}
return node;
}
function maybeHoistInlineText(node) {
// Common pattern: <a><span>Text</span></a> or <button><div><span>...</span></div></button>
if (!node || node.text) return node;
if (!["a", "button", "label", "p", "li"].includes(node.tag)) return node;
if (!Array.isArray(node.children) || node.children.length !== 1) return node;
const ch = node.children[0];
if (ch && ch.text && (!ch.children || ch.children.length === 0) && (!ch.attrs || Object.keys(ch.attrs).length === 0)) {
node.text = ch.text;
delete node.children;
}
return node;
}
function build(el, depth) {
if (truncated) return null;
if (!el || depth > MAX_DEPTH) return null;
if (!(el instanceof Element)) return null;
const rawTag = el.tagName.toLowerCase();
if (shouldSkip(el, rawTag)) return null;
if (!isVisibleElement(el)) return null;
nodeCount += 1;
if (nodeCount > MAX_NODES) {
truncated = true;
return null;
}
const tag = simplifyTag(rawTag);
const text = directVisibleText(el);
const children = [];
for (const ch of Array.from(el.children || [])) {
const n = build(ch, depth + 1);
if (n) children.push(n);
if (truncated) break;
}
if (!text && children.length === 0) return null;
const node = { tag };
if (text) node.text = text;
// Only keep attrs when they help identify purpose/action.
if (hasMeaningfulAttrs(el)) {
const attrs = {};
const role = (el.getAttribute("role") || "").trim();
const aria = (el.getAttribute("aria-label") || "").trim();
const dt = (el.getAttribute("data-testid") || "").trim();
if (role) attrs.role = role;
if (aria) attrs.aria_label = aria;
if (dt) attrs.data_testid = dt;
if (rawTag === "a") {
const href = (el.href || el.getAttribute("href") || "").trim();
if (href) attrs.href = href;
}
if (Object.keys(attrs).length) node.attrs = attrs;
}
if (children.length) node.children = children;
return maybeHoistInlineText(collapseWrappers(node));
}
const extractedAt = new Date().toISOString();
const url = String(location.href || "");
const title = String(document.title || "");
const root = document.body ? build(document.body, 0) : null;
return {
extracted_at: extractedAt,
url,
title,
hostname: location.hostname,
visible_tree: root,
truncated,
stats: { nodes: nodeCount },
};
},
});
return result;
}
async function postScreenshot(endpoint, payload) {
const r = await fetch(endpoint, {
method: "POST",
@ -80,12 +279,21 @@ async function main() {
captureBtn.addEventListener("click", async () => {
const endpoint = endpointEl.value.trim() || DEFAULT_ENDPOINT;
captureBtn.disabled = true;
setStatus("Capturing visible tab...", "");
setStatus("Extracting page content...", "");
try {
const tab = await getActiveTab();
if (!tab) throw new Error("No active tab found");
let content = null;
try {
content = await extractPageContent(tab.id);
} catch (e) {
// Some URLs (chrome://*, Web Store, PDF viewer) cannot be scripted.
content = { error: String(e && e.message ? e.message : e) };
}
setStatus("Capturing visible tab...", "");
const dataUrl = await captureVisibleTab();
setStatus("Uploading to local server...", "");
@ -94,12 +302,14 @@ async function main() {
title: tab.title || "",
url: tab.url || "",
ts: new Date().toISOString(),
content,
});
const lines = [];
lines.push("Saved:");
lines.push(` PNG: ${resp.png_path || "(unknown)"}`);
lines.push(` META: ${resp.meta_path || "(unknown)"}`);
if (resp.content_path) lines.push(` CONTENT: ${resp.content_path}`);
if (resp.ran) {
lines.push("Ran:");
if (resp.ran.error) {

View File

@ -78,6 +78,7 @@ class Handler(BaseHTTPRequestHandler):
title = req.get("title") or ""
page_url = req.get("url") or ""
client_ts = req.get("ts") or ""
content = req.get("content", None)
m = re.match(r"^data:image/png;base64,(.*)$", data_url)
if not m:
@ -98,9 +99,31 @@ class Handler(BaseHTTPRequestHandler):
out_dir.mkdir(parents=True, exist_ok=True)
png_path = out_dir / f"{base}.png"
meta_path = out_dir / f"{base}.json"
content_path = out_dir / f"{base}.content.json"
try:
png_path.write_bytes(png_bytes)
# Save extracted page content separately to keep the meta file small/handy.
wrote_content = False
if content is not None:
try:
raw_content = json.dumps(content, ensure_ascii=True, indent=2) + "\n"
# Prevent pathological payloads from creating huge files.
if len(raw_content.encode("utf-8")) > 2_000_000:
content = {
"error": "content_too_large_truncated",
"note": "Original extracted content exceeded 2MB.",
}
raw_content = json.dumps(content, ensure_ascii=True, indent=2) + "\n"
content_path.write_text(raw_content, encoding="utf-8")
wrote_content = True
except Exception:
# Don't fail the whole request if content writing fails.
wrote_content = False
final_content_path = str(content_path) if wrote_content else None
meta_path.write_text(
json.dumps(
{
@ -109,6 +132,7 @@ class Handler(BaseHTTPRequestHandler):
"client_ts": client_ts,
"saved_utc": now.isoformat(),
"png_path": str(png_path),
"content_path": final_content_path,
},
indent=2,
ensure_ascii=True,
@ -146,6 +170,7 @@ class Handler(BaseHTTPRequestHandler):
"ok": True,
"png_path": str(png_path),
"meta_path": str(meta_path),
"content_path": final_content_path,
"ran": ran,
},
)