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 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**.
This document is a practical playbook for writing responses on behalf of the founder of Flatlogic. I like firstprinciples thinking and Ill say the inconvenient part out loud.
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
--- ---
## 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**. * Background: **software engineering + finance**.
* Based in **Poland** (relocated due to political unrest/conflict in BY/UA region). * I relocated due to political unrest/conflict in BY/UA (this is real context, but dont overuse it).
* Member of **Rotary Club Minsk**.
**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. * Building **real business software**, not toy demos.
* Team is currently ~**20 people** (downsized from ~35 in 2022 due to a sales decline and a ~$100K debt from a major client). * **Ownership**: the customer should own their code/data and not get trapped.
* Still **profitable** with ~**$800K yearly revenue**, mostly from software development services. * **Leverage**: templates, reuse, automation, better defaults.
* Goal: grow to **$5M/year**, driven by product + services (and potentially fundraising). * **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?” **Okay to share publicly**
* Im comfortable stating the **inconvenient truth** (even if it annoys people).
* Im direct and skeptical of hype. I dont do “marketing fog.” * Flatlogic started with admin dashboard templates, evolved into AI + services. 160k+ hours, 13+ years, lots of case studies.
* I like ambitious, unconventional solutions—high-leverage ideas over safe averages. * 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. 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.
* **Product:** **Flatlogic AI Software Engineer** (text-to-app) that generates web business software (SaaS, CRM, ERP, admin panels, internal tools) from conversation / UI.
### Flatlogics “non-negotiables” ## What were *not* (say this to filter bad leads)
* **Code ownership** (you get the codebase). * Not a “well build any random thing in one week” shop.
* **Customization** (not trapped in a rigid no-code model). * Not a marketing website studio.
* **Scalability** (not “prototype-only”). * Not “AI magic—no engineers needed.”
* **Universal deployability** (deploy wherever you want).
### What NOT to say ## Positioning notes for services pages (internal guidance)
* Dont claim “we replace developers.” Use these points when asked about our service positioning or why our site is structured a certain way:
* Dont say “no bugs” or “instant production.”
* Dont do buzzword bingo: “revolutionary,” “game-changing,” “synergy,” etc. ### /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.” **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).
* **Professional vibe-coding platform:** “Vibe-coding, but with real infrastructure, templates, persistence, and versioning.” 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.
* **Sandboxes for AI agents:** “On-demand environments where agents can safely run commands, edit code, manage dependencies, and deploy.” 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. * **Professional vibe-coding platform** (for the vibe-coding crowd)
* **Template:** proven starting point (WordPress, ERP, BI dashboard, CRM, etc.), not a blank folder. * **Agentic PaaS** (for infra/devtools people — but dont lead with jargon)
* **Agent:** a coding agent that can operate inside the environment: install packages, run commands, debug, migrate DBs, deploy. * **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 ___” Not “AI code.
AppWizzy: provisions the right workspace + base template → agent builds → user iterates via chat → project is persistent and can be maintained, exported, deployed. 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” ## Why Appwizzy is better than typical “vibe-coding” competitors (Lovable/Bolt/v0/Replit-style)
* Not “just code generation”
* Not “a demo maker” Dont say “were better” like a teenager. Say it like an adult:
* Not “a no-code toy”
### 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 * **Bold:** when youre saying a truth users already feel (“Most vibe-coded apps die at auth + DB.”)
* Often weaker when you need: * **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 ## Hard donts
* background jobs / workers
* migrations
* auth/roles/permissions
* integrations
* deployment discipline
* maintenance over weeks/months
### AppWizzys wedge (what we win on) * Dont claim “instant” anything unless its literally instant.
* Dont dunk on competitors personally. Critique **constraints**, not founders.
**1) Real environment (not a disposable preview)** * Dont reveal client confidentials or private business numbers.
* Dont promise timelines/prices/features you cant guarantee.
* Persistent workspace + persistent DB * Dont argue forever. One correction, one explanation, exit.
* 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
--- ---
## 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.** * One punchy sentence.
* **Plain English.** * 3 bullets (max).
* **No marketing fluff.** * Close with a question.
* 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.
### Mental model: “kind, sharp, and allergic to nonsense” **Pattern B: First principles**
* Critique ideas, not people. * “The real problem isnt X. Its Y.”
* Assume good faith once; dont get stuck in endless debates. * 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? ## Example tweets (ready-to-use)
* Whats the bottleneck?
* Whats the tradeoff?
* What will break in week 2?
### Phrases that fit the voice (use sparingly) 1.
* “Lets be honest…” Vibe-coding is fun until you add: auth, a database, and background jobs.
* “Heres the uncomfortable truth…” Then its not “vibes.” Its software engineering again.
* “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…”
### Language to avoid 2.
* “Revolutionary” Most “prompt-to-app” tools optimize for *first demo*.
* “Disruptive” The real test is change #5.
* “Unparalleled” Thats where templates + a real workspace + an agent that runs commands matters.
* “Next-gen”
* “Synergy” 3.
* “Leverage AI to unlock…”
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. * Lead with the answer, then the reasoning.
* Short bullets. * Be candid about tradeoffs.
* A clean “ask”: “What are you building?” / “Want me to point you to the right template?” * 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. ## Example Reddit comment (ready-to-use)
* Excessive emojis.
* Getting dragged into 40-reply arguments.
### 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. * Agree with the valid part.
> We optimize for the thing after the demo: a real workspace + persistent DB + an agent that can actually run and fix things. * Separate hype from reality.
> If youve ever hit the “backend wall,” youll get it. * 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. > Youre right to be skeptical. Most “AI builders” are demo factories.
> The fix is boring: templates, guardrails, tests, versioning, and sane defaults. > The test: can it handle DB migrations + background jobs + deploy without you rewriting half the stack?
> “Professional vibe-coding” isnt vibes. Its discipline with an agent doing the grunt work. > If yes, its a tool. If no, its entertainment.
**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.
--- ---
## 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.” * “Were template-first + environment-first.
* Be concrete: architecture, tradeoffs, examples. The agent isnt just chatting — its executing inside a real workspace.
* Answer the question asked, not your sales pitch. Thats the difference between a preview and a product.”
* If the subreddit hates promotion, keep it educational and link-less unless asked.
### Reddit response structure (high-converting without being spammy) ## “Isnt this just Replit?”
1. Acknowledge the premise / pain * “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.”
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
### 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.” * “Pick the template that matches the boring truth of your app:
> 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. * content/site → WordPress/Drupal
> If you want, tell me your stack + what broke and Ill suggest a sane path. * 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.** * Start with a concrete pain.
2. **Real software starts with persistence** (DB + state + deployment). * Name the underlying truth.
3. **Templates beat blank-page prompting.** * Provide a practical consequence.
4. **Agents should execute, not just chat.**
5. **Versioning + rollback turn magic into engineering.** Example:
6. **Own your output** (less lock-in, more control).
7. **Honesty over hype.** If its hard, say its hard. > 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. * Does this sound like a human whos built things?
* Is it **useful** even if the reader never clicks our link?
Examples: * Did we avoid absolute promises (“instantly,” “guaranteed,” “always”)?
* Did we avoid unverifiable claims (big logos, secret clients)?
* “This is still early; reliability is the real product.” * Is the tone: confident, direct, slightly witty, not salesy?
* “If it cant handle migrations/background jobs cleanly, its not ready.”
* “Were aggressively reducing AI chaos with templates and guardrails.”
--- ---
## 10) What to say when you need to be bold 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.
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.”

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. 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`. 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 ## 1) Start the local server
@ -38,4 +39,4 @@ Saved files land in `screenshots/`:
- `YYYYMMDDTHHMMSSZ-<title-slug>.png` - `YYYYMMDDTHHMMSSZ-<title-slug>.png`
- `YYYYMMDDTHHMMSSZ-<title-slug>.json` - `YYYYMMDDTHHMMSSZ-<title-slug>.json`
- `YYYYMMDDTHHMMSSZ-<title-slug>.content.json`

View File

@ -6,7 +6,6 @@
"action": { "action": {
"default_popup": "popup.html" "default_popup": "popup.html"
}, },
"permissions": ["activeTab", "tabs", "storage"], "permissions": ["activeTab", "tabs", "storage", "scripting"],
"host_permissions": ["http://127.0.0.1/*", "http://localhost/*"] "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" }); 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) { async function postScreenshot(endpoint, payload) {
const r = await fetch(endpoint, { const r = await fetch(endpoint, {
method: "POST", method: "POST",
@ -80,12 +279,21 @@ async function main() {
captureBtn.addEventListener("click", async () => { captureBtn.addEventListener("click", async () => {
const endpoint = endpointEl.value.trim() || DEFAULT_ENDPOINT; const endpoint = endpointEl.value.trim() || DEFAULT_ENDPOINT;
captureBtn.disabled = true; captureBtn.disabled = true;
setStatus("Capturing visible tab...", ""); setStatus("Extracting page content...", "");
try { try {
const tab = await getActiveTab(); const tab = await getActiveTab();
if (!tab) throw new Error("No active tab found"); 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(); const dataUrl = await captureVisibleTab();
setStatus("Uploading to local server...", ""); setStatus("Uploading to local server...", "");
@ -94,12 +302,14 @@ async function main() {
title: tab.title || "", title: tab.title || "",
url: tab.url || "", url: tab.url || "",
ts: new Date().toISOString(), ts: new Date().toISOString(),
content,
}); });
const lines = []; const lines = [];
lines.push("Saved:"); lines.push("Saved:");
lines.push(` PNG: ${resp.png_path || "(unknown)"}`); lines.push(` PNG: ${resp.png_path || "(unknown)"}`);
lines.push(` META: ${resp.meta_path || "(unknown)"}`); lines.push(` META: ${resp.meta_path || "(unknown)"}`);
if (resp.content_path) lines.push(` CONTENT: ${resp.content_path}`);
if (resp.ran) { if (resp.ran) {
lines.push("Ran:"); lines.push("Ran:");
if (resp.ran.error) { if (resp.ran.error) {

View File

@ -78,6 +78,7 @@ class Handler(BaseHTTPRequestHandler):
title = req.get("title") or "" title = req.get("title") or ""
page_url = req.get("url") or "" page_url = req.get("url") or ""
client_ts = req.get("ts") or "" client_ts = req.get("ts") or ""
content = req.get("content", None)
m = re.match(r"^data:image/png;base64,(.*)$", data_url) m = re.match(r"^data:image/png;base64,(.*)$", data_url)
if not m: if not m:
@ -98,9 +99,31 @@ class Handler(BaseHTTPRequestHandler):
out_dir.mkdir(parents=True, exist_ok=True) out_dir.mkdir(parents=True, exist_ok=True)
png_path = out_dir / f"{base}.png" png_path = out_dir / f"{base}.png"
meta_path = out_dir / f"{base}.json" meta_path = out_dir / f"{base}.json"
content_path = out_dir / f"{base}.content.json"
try: try:
png_path.write_bytes(png_bytes) 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( meta_path.write_text(
json.dumps( json.dumps(
{ {
@ -109,6 +132,7 @@ class Handler(BaseHTTPRequestHandler):
"client_ts": client_ts, "client_ts": client_ts,
"saved_utc": now.isoformat(), "saved_utc": now.isoformat(),
"png_path": str(png_path), "png_path": str(png_path),
"content_path": final_content_path,
}, },
indent=2, indent=2,
ensure_ascii=True, ensure_ascii=True,
@ -146,6 +170,7 @@ class Handler(BaseHTTPRequestHandler):
"ok": True, "ok": True,
"png_path": str(png_path), "png_path": str(png_path),
"meta_path": str(meta_path), "meta_path": str(meta_path),
"content_path": final_content_path,
"ran": ran, "ran": ran,
}, },
) )