backlinkflow 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CREDITS.md CHANGED
@@ -1,13 +1,13 @@
1
- # LinkFlow — Sources & Credits
1
+ # BacklinkFlow — Sources & Credits
2
2
 
3
- LinkFlow's directory database (`data/directories.yaml`) is an independent,
3
+ BacklinkFlow's directory database (`data/directories.yaml`) is an independent,
4
4
  deduplicated compilation built by merging factual directory listings (site
5
5
  names + submission URLs) from the open-source projects below.
6
6
 
7
7
  **What we take:** factual data only — site name, submission URL, and (where
8
8
  the source provided it) domain authority / price model / status.
9
9
 
10
- **What we write ourselves:** all descriptions/notes are LinkFlow's own
10
+ **What we write ourselves:** all descriptions/notes are BacklinkFlow's own
11
11
  (never copied verbatim from sources), plus the `auto`, `lang`, `category`,
12
12
  and `status` enrichment fields.
13
13
 
@@ -60,4 +60,4 @@ Also referenced for methodology (not data):
60
60
  files in `scripts/sources/`. Delete a source file there → re-run
61
61
  `linkflow db:regenerate` → that source's entries are gone.
62
62
 
63
- LinkFlow itself is MIT (see LICENSE).
63
+ BacklinkFlow itself is MIT (see LICENSE).
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  <div align="center">
2
2
 
3
- # LinkFlow
3
+ # BacklinkFlow
4
4
 
5
5
  **Zero-cost backlink & directory submission engine for indie hackers.**
6
6
 
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
8
8
  [![Node](https://img.shields.io/badge/node-%E2%89%A518-green.svg)](./package.json)
9
- [![Part of the flow series](https://img.shields.io/badge/flow--series-LinkFlow%20%7C%20SeoFlow%20%7C%20SECflow-blueviolet)](#the-flow-series)
9
+ [![Part of the flow series](https://img.shields.io/badge/flow--series-BacklinkFlow%20%7C%20SeoFlow%20%7C%20SECflow-blueviolet)](#the-flow-series)
10
10
 
11
11
  *1,123 vetted directories · AI-tailored submission copy · Playwright browser automation · proof-of-submission reports — no SaaS, no credits, no $79 upsell.*
12
12
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  ---
16
16
 
17
- > **TL;DR** — Submitator, ListingBott, BoringLaunch charge $29–499 to do what LinkFlow does for free: a curated directory list, AI-generated per-directory copy, browser automation, and a status report. The only thing you bring is your own AI endpoint (OmniRoute, OpenAI, or any OpenAI-compatible API). Live-verified. MIT licensed.
17
+ > **TL;DR** — Submitator, ListingBott, BoringLaunch charge $29–499 to do what BacklinkFlow does for free: a curated directory list, AI-generated per-directory copy, browser automation, and a status report. The only thing you bring is your own AI endpoint (OmniRoute, OpenAI, or any OpenAI-compatible API). Live-verified. MIT licensed.
18
18
 
19
19
  **The what:**
20
20
 
@@ -29,7 +29,7 @@
29
29
 
30
30
  ---
31
31
 
32
- ## Why LinkFlow?
32
+ ## Why BacklinkFlow?
33
33
 
34
34
  Every paid directory-submission service is the same three things wrapped in a dashboard:
35
35
 
@@ -37,11 +37,11 @@ Every paid directory-submission service is the same three things wrapped in a da
37
37
  2. **Per-directory copy** — this is a 5-line AI call
38
38
  3. **A status report** — this is a JSON file + a markdown table
39
39
 
40
- LinkFlow gives you all three, plus actual browser automation for sites with open forms. The moat is honesty: 876 directories live-verified, 221 marked dead with reason, 26 marked paid. No padding with self-owned link farms. No "AI" that means "Army of Indians."
40
+ BacklinkFlow gives you all three, plus actual browser automation for sites with open forms. The moat is honesty: 876 directories live-verified, 221 marked dead with reason, 26 marked paid. No padding with self-owned link farms. No "AI" that means "Army of Indians."
41
41
 
42
42
  **vs competitors:**
43
43
 
44
- | | LinkFlow | Submitator | ListingBott | BoringLaunch |
44
+ | | BacklinkFlow | Submitator | ListingBott | BoringLaunch |
45
45
  |---|---|---|---|---|
46
46
  | Directories | 1,123 (876 alive) | ~150 | ~250 | ~200 |
47
47
  | Cost | $0 | $29–79 | $499 | $249 |
@@ -58,7 +58,7 @@ LinkFlow gives you all three, plus actual browser automation for sites with open
58
58
 
59
59
  ```bash
60
60
  # from source
61
- git clone https://github.com/imsankz/linkflow.git
61
+ git clone https://github.com/imsankz/backlinkflow.git
62
62
  cd linkflow && npm install && npm run build
63
63
 
64
64
  # or (when published)
@@ -69,41 +69,41 @@ npm install -g linkflow
69
69
 
70
70
  ```bash
71
71
  # 1. Create config
72
- linkflow init
72
+ backlinkflow init
73
73
 
74
74
  # 2. Edit linkflow.config.json + .env.local (AI endpoint)
75
75
  # AI_BASE_URL=http://192.168.0.254:20128/v1 (OmniRoute)
76
76
  # AI_API_KEY=... AI_MODEL=auto/best-free
77
77
 
78
78
  # 3. Explore the database
79
- linkflow stats
80
- linkflow list --category startup --limit 10
81
- linkflow search "product hunt"
79
+ backlinkflow stats
80
+ backlinkflow list --category startup --limit 10
81
+ backlinkflow search "product hunt"
82
82
 
83
83
  # 4. Generate AI-tailored submission copy (no submission)
84
- linkflow payload https://yoursite.com --directory "Future Tools"
84
+ backlinkflow payload https://yoursite.com --directory "Future Tools"
85
85
 
86
86
  # 5. Plan submissions (dry-run — nothing recorded)
87
- linkflow submit https://yoursite.com --dry-run --limit 10
87
+ backlinkflow submit https://yoursite.com --dry-run --limit 10
88
88
 
89
89
  # 6. Real automation — Playwright browser submits for you
90
- linkflow submit https://yoursite.com --go --limit 10
90
+ backlinkflow submit https://yoursite.com --go --limit 10
91
91
 
92
92
  # 7. Track + report
93
- linkflow status
94
- linkflow report # → .linkflow/report.md (Submitator-style proof)
93
+ backlinkflow status
94
+ backlinkflow report # → .linkflow/report.md (Submitator-style proof)
95
95
  ```
96
96
 
97
97
  ## Multiple sites, one tool
98
98
 
99
- LinkFlow is generic — bring your own site config. Each product = one config file:
99
+ BacklinkFlow is generic — bring your own site config. Each product = one config file:
100
100
 
101
101
  ```bash
102
102
  # SaaS product
103
- linkflow submit https://kreatorlane.com --config examples/linkflow.config.kreatorlane.json --category startup --go --limit 5
103
+ backlinkflow submit https://kreatorlane.com --config examples/linkflow.config.kreatorlane.json --category startup --go --limit 5
104
104
 
105
105
  # Travel blog
106
- linkflow submit https://chasingwhereabouts.com --config examples/linkflow.config.chasingwhereabouts.json --category travel --go --limit 5
106
+ backlinkflow submit https://chasingwhereabouts.com --config examples/linkflow.config.chasingwhereabouts.json --category travel --go --limit 5
107
107
  ```
108
108
 
109
109
  No hardcoded sites. Examples in `examples/`.
@@ -114,16 +114,16 @@ No hardcoded sites. Examples in `examples/`.
114
114
 
115
115
  | Command | Description |
116
116
  |---|---|
117
- | `linkflow list [--category X] [--limit N]` | List directories in the database |
118
- | `linkflow search <query>` | Search directories by name/notes/URL |
119
- | `linkflow submit <url> [--dry-run] [--limit N] [--category X] [--go] [--config file.json]` | Generate payloads + submit. `--go` = real Playwright automation |
120
- | `linkflow payload <url> [--directory X]` | AI-tailored submission copy only |
121
- | `linkflow status` | Tracker summary (submitted/pending/failed) |
122
- | `linkflow report` | Regenerate proof-of-submission report |
123
- | `linkflow stats` | Database stats (counts by category, alive/dead) |
124
- | `linkflow db:review` | Flag DB quality issues (dead links, homepage-as-submit) |
125
- | `linkflow db:regenerate` | Rebuild DB from source lists |
126
- | `linkflow init` | Write config template |
117
+ | `backlinkflow list [--category X] [--limit N]` | List directories in the database |
118
+ | `backlinkflow search <query>` | Search directories by name/notes/URL |
119
+ | `backlinkflow submit <url> [--dry-run] [--limit N] [--category X] [--go] [--config file.json]` | Generate payloads + submit. `--go` = real Playwright automation |
120
+ | `backlinkflow payload <url> [--directory X]` | AI-tailored submission copy only |
121
+ | `backlinkflow status` | Tracker summary (submitted/pending/failed) |
122
+ | `backlinkflow report` | Regenerate proof-of-submission report |
123
+ | `backlinkflow stats` | Database stats (counts by category, alive/dead) |
124
+ | `backlinkflow db:review` | Flag DB quality issues (dead links, homepage-as-submit) |
125
+ | `backlinkflow db:regenerate` | Rebuild DB from source lists |
126
+ | `backlinkflow init` | Write config template |
127
127
 
128
128
  ---
129
129
 
@@ -201,7 +201,7 @@ The AI client is **SSE-tolerant** — works with providers that stream even when
201
201
 
202
202
  ## Database curation
203
203
 
204
- The directory database is the product's moat. `scripts/regenerate-db.py` rebuilds it idempotently from the 11 source lists (annotated `[from <source>]`). `linkflow db:review` flags quality issues.
204
+ The directory database is the product's moat. `scripts/regenerate-db.py` rebuilds it idempotently from the 11 source lists (annotated `[from <source>]`). `backlinkflow db:review` flags quality issues.
205
205
 
206
206
  **Review verdict:**
207
207
  - **1,123 entries**, 9 categories — full coverage from all 11 sources
@@ -216,22 +216,22 @@ The directory database is the product's moat. `scripts/regenerate-db.py` rebuild
216
216
  ```bash
217
217
  python3 scripts/check-urls.py /tmp/linkflow-urls.txt # live-check all URLs
218
218
  python3 scripts/apply-urlcheck.py # mark verified-dead in DB
219
- linkflow db:review # see remaining flags
220
- linkflow db:regenerate # rebuild from sources (idempotent)
221
- linkflow stats # confirm counts
219
+ backlinkflow db:review # see remaining flags
220
+ backlinkflow db:regenerate # rebuild from sources (idempotent)
221
+ backlinkflow stats # confirm counts
222
222
  ```
223
223
 
224
224
  ---
225
225
 
226
226
  ## The flow series
227
227
 
228
- LinkFlow is the third in a series of zero-cost CLI tools, all MIT, all npm-published:
228
+ BacklinkFlow is the third in a series of zero-cost CLI tools, all MIT, all npm-published:
229
229
 
230
230
  | Tool | Job | Engines | npm |
231
231
  |---|---|---|---|
232
232
  | **[SECflow](https://github.com/imsankz/SECflow)** | Security scanning for AI-driven repos | gitleaks, trivy, npm audit, custom regex | [`secflow`](https://www.npmjs.com/package/secflow) |
233
233
  | **[SeoFlow](https://github.com/imsankz/seoflow)** | AI-powered SEO pipeline (audit, internal links, content gen, GSC) | GSC, PSI, Pexels, Ubersuggest, 6 LLMs | [`seoflow`](https://www.npmjs.com/package/seoflow) |
234
- | **[LinkFlow](https://github.com/imsankz/linkflow)** | Backlink & directory submission automation | Your AI endpoint + Playwright | [`linkflow`](https://www.npmjs.com/package/linkflow) (this repo) |
234
+ | **[BacklinkFlow](https://github.com/imsankz/backlinkflow)** | Backlink & directory submission automation | Your AI endpoint + Playwright | [`backlinkflow`](https://www.npmjs.com/package/backlinkflow) (this repo) |
235
235
 
236
236
  Same DNA across all three: **free engines + your own AI agent as the "smart" layer + npm-published + zero-cost.**
237
237
 
@@ -240,7 +240,7 @@ Same DNA across all three: **free engines + your own AI agent as the "smart" lay
240
240
  ## Architecture
241
241
 
242
242
  ```
243
- linkflow submit <url> --go
243
+ backlinkflow submit <url> --go
244
244
 
245
245
  config loader (linkflow.config.json + .env.local)
246
246
 
@@ -317,4 +317,4 @@ MIT (see [LICENSE](./LICENSE)). Source data compiled from 11 open-source project
317
317
 
318
318
  ## ☕ Support
319
319
 
320
- LinkFlow is free forever. If it saves you $79 (or 40 hours), [buy me a coffee](https://ko-fi.com/chasingwhereabouts) — it funds more free tools.
320
+ BacklinkFlow is free forever. If it saves you $79 (or 40 hours), [buy me a coffee](https://ko-fi.com/chasingwhereabouts) — it funds more free tools.
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ async function cmdList() {
29
29
  const all = loadDirectories();
30
30
  const filtered = cat ? all.filter((d) => d.category === cat) : all;
31
31
  console.log(`
32
- LinkFlow directory database: ${all.length} sites
32
+ BacklinkFlow directory database: ${all.length} sites
33
33
  `);
34
34
  for (const [i, d] of filtered.entries()) {
35
35
  if (LIMIT && i >= LIMIT) break;
@@ -53,11 +53,11 @@ Search "${q}": ${results.length} matches
53
53
  async function cmdSubmit() {
54
54
  const siteUrl = VERB_ARG;
55
55
  if (!siteUrl) {
56
- console.log("Usage: linkflow submit <site-url> [--dry-run] [--limit N] [--category X] [--go]");
56
+ console.log("Usage: backlinkflow submit <site-url> [--dry-run] [--limit N] [--category X] [--go]");
57
57
  process.exit(1);
58
58
  }
59
59
  const cfg = loadConfig(CONFIG_FILE);
60
- console.log("\nLinkFlow submit");
60
+ console.log("\nBacklinkFlow submit");
61
61
  console.log("\u2500".repeat(50));
62
62
  printConfigSummary(cfg);
63
63
  if (!hasAiConfig(cfg)) {
@@ -132,7 +132,7 @@ async function cmdSubmit() {
132
132
  async function cmdPayload() {
133
133
  const siteUrl = VERB_ARG;
134
134
  if (!siteUrl) {
135
- console.log("Usage: linkflow payload <site-url> [--directory X]");
135
+ console.log("Usage: backlinkflow payload <site-url> [--directory X]");
136
136
  process.exit(1);
137
137
  }
138
138
  const cfg = loadConfig(CONFIG_FILE);
@@ -151,7 +151,7 @@ async function cmdPayload() {
151
151
  }
152
152
  function cmdStatus() {
153
153
  const s = trackerSummary();
154
- console.log("\nLinkFlow tracker");
154
+ console.log("\nBacklinkFlow tracker");
155
155
  console.log("\u2500".repeat(50));
156
156
  console.log(` Total records: ${s.total}`);
157
157
  for (const [k, v] of Object.entries(s.byStatus)) console.log(` ${k}: ${v}`);
@@ -169,7 +169,7 @@ function cmdReport() {
169
169
  }
170
170
  function cmdStats() {
171
171
  const s = dbStats();
172
- console.log("\nLinkFlow database stats");
172
+ console.log("\nBacklinkFlow database stats");
173
173
  console.log("\u2500".repeat(50));
174
174
  console.log(` Total directories: ${s.total}`);
175
175
  for (const [k, v] of Object.entries(s.byCategory)) console.log(` ${k}: ${v}`);
@@ -262,12 +262,12 @@ async function main() {
262
262
  break;
263
263
  default:
264
264
  console.log(`Unknown command: ${VERB}
265
- Run 'linkflow' with: list | search | submit | payload | status | report | stats | db:review | db:regenerate | init`);
265
+ Run 'backlinkflow' with: list | search | submit | payload | status | report | stats | db:review | db:regenerate | init`);
266
266
  process.exit(1);
267
267
  }
268
268
  }
269
269
  main().catch((err) => {
270
- console.error("LinkFlow error:", err);
270
+ console.error("BacklinkFlow error:", err);
271
271
  process.exit(1);
272
272
  });
273
273
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n/**\n * LinkFlow \u2014 zero-cost backlink & directory submission engine.\n *\n * Commands:\n * linkflow list [--category X] [--limit N] List directories in the database\n * linkflow search <query> Search directories\n * linkflow submit <site-url> [--dry-run] [--limit N] [--category X] Generate payloads + submit plan\n * linkflow payload <site-url> [--directory X] Generate AI-tailored payload only (no submission)\n * linkflow status Show submission tracker summary\n * linkflow report Regenerate report from tracker\n * linkflow stats Database stats\n * linkflow db:review Flag DB quality issues (dead links, homepage-as-submit)\n * linkflow db:regenerate Rebuild DB from source lists (scripts/regenerate-db.py)\n * linkflow init Print config template\n */\nimport fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { loadDirectories, searchDirectories, categories, dbStats } from './database.js';\nimport { loadConfig, hasAiConfig, printConfigSummary } from './config.js';\nimport { resetAiCallCount, getAiCallCount } from './ai.js';\nimport { generatePayload } from './payload.js';\nimport { loadTracker, alreadySubmitted, recordSubmission, trackerSummary } from './tracker.js';\nimport { writeReport, reportPath } from './report.js';\nimport { submitOne } from './engine/submit.js';\n\nconst rawArgs = process.argv.slice(2);\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst VERB = rawArgs[0] && !rawArgs[0].startsWith('--') ? rawArgs[0] : 'list';\nconst VERB_ARG = rawArgs[1] && !rawArgs[1].startsWith('--') ? rawArgs[1] : null;\n\nconst flag = (name: string): string | undefined => {\n const i = rawArgs.indexOf(name);\n return i !== -1 ? rawArgs[i + 1] : undefined;\n};\nconst has = (name: string): boolean => rawArgs.includes(name);\n\nconst DRY_RUN = has('--dry-run');\nconst LIMIT = parseInt(flag('--limit') || '0') || 0;\nconst CONFIG_FILE = flag('--config');\n\nfunction formatDir(d: any, i: number): string {\n return `${String(i + 1).padStart(3)}. ${d.name.padEnd(28)} [${d.category.padEnd(22)}] auto=${d.auto} ${d.dr ? `DR${d.dr}` : ''} ${d.status && d.status !== 'active' ? `(${d.status})` : ''}`;\n}\n\nasync function cmdList(): Promise<void> {\n const cat = flag('--category');\n const all = loadDirectories();\n const filtered = cat ? all.filter((d) => d.category === cat) : all;\n console.log(`\\nLinkFlow directory database: ${all.length} sites\\n`);\n for (const [i, d] of filtered.entries()) {\n if (LIMIT && i >= LIMIT) break;\n console.log(formatDir(d, i));\n }\n console.log(`\\n Categories: ${categories().join(', ')}`);\n}\n\nasync function cmdSearch(): Promise<void> {\n const q = VERB_ARG || '';\n const results = searchDirectories(q);\n console.log(`\\nSearch \"${q}\": ${results.length} matches\\n`);\n for (const [i, d] of results.entries()) {\n if (LIMIT && i >= LIMIT) break;\n console.log(formatDir(d, i));\n console.log(` ${d.submitUrl}`);\n }\n}\n\nasync function cmdSubmit(): Promise<void> {\n const siteUrl = VERB_ARG;\n if (!siteUrl) {\n console.log('Usage: linkflow submit <site-url> [--dry-run] [--limit N] [--category X] [--go]');\n process.exit(1);\n }\n const cfg = loadConfig(CONFIG_FILE);\n console.log('\\nLinkFlow submit');\n console.log('\u2500'.repeat(50));\n printConfigSummary(cfg);\n\n if (!hasAiConfig(cfg)) {\n console.log(' \u26A0\uFE0F No AI config (.env.local AI_BASE_URL/AI_API_KEY) \u2014 using template payloads.');\n }\n\n const cat = flag('--category');\n const targets = loadDirectories().filter((d) => {\n if (cat && d.category !== cat) return false;\n return d.auto === 'yes' || d.auto === 'manual'; // skip dead/paid-only\n });\n const selected = LIMIT ? targets.slice(0, LIMIT) : targets;\n\n console.log(`\\n Target directories: ${selected.length} (auto+manual, ${cat || 'all categories'})\\n`);\n resetAiCallCount();\n\n const records = loadTracker();\n let submitted = 0, skipped = 0, failed = 0;\n\n for (const [i, dir] of selected.entries()) {\n if (alreadySubmitted(siteUrl, dir.name)) {\n console.log(` [${i + 1}/${selected.length}] \u23ED\uFE0F ${dir.name} \u2014 already submitted (tracked)`);\n skipped++;\n continue;\n }\n const payload = await generatePayload(dir, cfg);\n console.log(` [${i + 1}/${selected.length}] \uD83D\uDE80 ${dir.name}`);\n console.log(` tagline: ${payload.tagline.slice(0, 80)}`);\n console.log(` submit: ${dir.submitUrl}`);\n\n if (has('--go')) {\n // REAL automation via Playwright\n const result = await submitOne(dir, payload, {\n siteUrl,\n proofDir: '.linkflow/proofs',\n });\n console.log(` \u2192 ${result.status}: ${result.note || ''}${result.proof ? ` (proof: ${result.proof})` : ''}`);\n if (result.status === 'submitted') submitted++;\n else if (result.status === 'failed') failed++;\n\n // pacing between submissions (respect per-day limit)\n const perDay = cfg.pacing?.perDay ?? 10;\n if (i + 1 >= perDay && i + 1 < selected.length) {\n console.log(` \u23F8\uFE0F Daily pacing limit (${perDay}) reached \u2014 stopping.`);\n break;\n }\n if (i + 1 < selected.length) {\n const pause = (cfg.pacing?.minSeconds ?? 60) * 1000;\n console.log(` \u23F3 pacing ${pause / 1000}s before next\u2026`);\n await new Promise((r) => setTimeout(r, pause));\n }\n } else {\n // plan-only mode (v0.1 behavior)\n console.log(` desc: ${payload.description.slice(0, 100)}`);\n if (!DRY_RUN) {\n recordSubmission({\n site: siteUrl,\n directory: dir.name,\n status: 'pending',\n submittedAt: new Date().toISOString(),\n url: dir.submitUrl,\n notes: 'planned \u2014 run with --go for automation',\n });\n submitted++;\n }\n }\n }\n\n console.log(`\\n Done: ${submitted} submitted/planned, ${skipped} skipped, ${failed} failed. AI calls: ${getAiCallCount()}`);\n\n if (!DRY_RUN) {\n const all = loadTracker();\n writeReport(siteUrl, all.filter((r) => r.site === siteUrl));\n const { md } = reportPath();\n console.log(` Report: ${md}`);\n } else {\n console.log(' (dry-run \u2014 nothing recorded)');\n }\n}\n\nasync function cmdPayload(): Promise<void> {\n const siteUrl = VERB_ARG;\n if (!siteUrl) {\n console.log('Usage: linkflow payload <site-url> [--directory X]');\n process.exit(1);\n }\n const cfg = loadConfig(CONFIG_FILE);\n const dirName = flag('--directory');\n const dirs = dirName ? loadDirectories().filter((d) => d.name.toLowerCase().includes(dirName.toLowerCase())) : loadDirectories();\n if (!dirs.length) {\n console.log(' No matching directory.');\n return;\n }\n for (const dir of dirs.slice(0, 3)) {\n const payload = await generatePayload(dir, cfg);\n console.log(`\\n=== ${dir.name} ===`);\n console.log(JSON.stringify(payload, null, 2));\n }\n}\n\nfunction cmdStatus(): void {\n const s = trackerSummary();\n console.log('\\nLinkFlow tracker');\n console.log('\u2500'.repeat(50));\n console.log(` Total records: ${s.total}`);\n for (const [k, v] of Object.entries(s.byStatus)) console.log(` ${k}: ${v}`);\n if (s.sites.length) console.log(` Sites: ${s.sites.join(', ')}`);\n}\n\nfunction cmdReport(): void {\n const cfg = loadConfig();\n const site = VERB_ARG || cfg.siteUrl || 'all';\n const all = loadTracker();\n const filtered = site === 'all' ? all : all.filter((r) => r.site === site);\n writeReport(site, filtered);\n const { md } = reportPath();\n console.log(`\\n Report written: ${md}`);\n}\n\nfunction cmdStats(): void {\n const s = dbStats();\n console.log('\\nLinkFlow database stats');\n console.log('\u2500'.repeat(50));\n console.log(` Total directories: ${s.total}`);\n for (const [k, v] of Object.entries(s.byCategory)) console.log(` ${k}: ${v}`);\n console.log(` Auto-submittable: ${s.auto}`);\n}\n\nfunction cmdInit(): void {\n const tpl = {\n siteName: 'My Product',\n siteUrl: 'https://example.com',\n siteDescription: 'A short, honest description of what it is and who it is for.',\n tags: ['saas', 'devtools'],\n contentDomain: 'SaaS product',\n writingSample: 'Paste 2-3 sentences in your site voice here.',\n ai: {\n provider: 'openai',\n baseUrl: 'http://192.168.0.254:20128/v1',\n apiKey: 'your-omniroute-key',\n model: 'auto/best-free',\n maxCallsPerRun: 20,\n },\n pacing: { minSeconds: 60, perDay: 10 },\n };\n const p = path.join(process.cwd(), 'linkflow.config.json');\n if (fs.existsSync(p)) {\n console.log(` ${p} already exists \u2014 not overwriting.`);\n } else {\n fs.writeFileSync(p, JSON.stringify(tpl, null, 2));\n console.log(` Created ${p}`);\n }\n}\n\nasync function cmdDbReview(): Promise<void> {\n const { execSync } = await import('child_process');\n const script = path.join(__dirname, '..', 'scripts', 'review-db.py');\n if (!fs.existsSync(script)) {\n console.log(' review-db.py not found \u2014 run from repo root.');\n return;\n }\n try {\n const out = execSync(`python3 \"${script}\"`, { encoding: 'utf8' });\n console.log(out);\n } catch (err) {\n console.log(' Review failed:', (err as Error).message);\n }\n}\n\nasync function cmdDbRegenerate(): Promise<void> {\n const { execSync } = await import('child_process');\n const script = path.join(__dirname, '..', 'scripts', 'regenerate-db.py');\n if (!fs.existsSync(script)) {\n console.log(' regenerate-db.py not found \u2014 run from repo root.');\n return;\n }\n try {\n const out = execSync(`python3 \"${script}\"`, { encoding: 'utf8' });\n console.log(out);\n } catch (err) {\n console.log(' Regenerate failed:', (err as Error).message);\n }\n}\n\nasync function main(): Promise<void> {\n switch (VERB) {\n case 'list': await cmdList(); break;\n case 'search': await cmdSearch(); break;\n case 'submit': await cmdSubmit(); break;\n case 'payload': await cmdPayload(); break;\n case 'status': cmdStatus(); break;\n case 'report': cmdReport(); break;\n case 'stats': cmdStats(); break;\n case 'db:review': await cmdDbReview(); break;\n case 'db:regenerate': await cmdDbRegenerate(); break;\n case 'init': cmdInit(); break;\n default:\n console.log(`Unknown command: ${VERB}\\nRun 'linkflow' with: list | search | submit | payload | status | report | stats | db:review | db:regenerate | init`);\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n console.error('LinkFlow error:', err);\n process.exit(1);\n});\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["#!/usr/bin/env node\n/**\n * LinkFlow \u2014 zero-cost backlink & directory submission engine.\n *\n * Commands:\n * linkflow list [--category X] [--limit N] List directories in the database\n * linkflow search <query> Search directories\n * linkflow submit <site-url> [--dry-run] [--limit N] [--category X] Generate payloads + submit plan\n * linkflow payload <site-url> [--directory X] Generate AI-tailored payload only (no submission)\n * linkflow status Show submission tracker summary\n * linkflow report Regenerate report from tracker\n * linkflow stats Database stats\n * linkflow db:review Flag DB quality issues (dead links, homepage-as-submit)\n * linkflow db:regenerate Rebuild DB from source lists (scripts/regenerate-db.py)\n * linkflow init Print config template\n */\nimport fs from 'fs';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport { loadDirectories, searchDirectories, categories, dbStats } from './database.js';\nimport { loadConfig, hasAiConfig, printConfigSummary } from './config.js';\nimport { resetAiCallCount, getAiCallCount } from './ai.js';\nimport { generatePayload } from './payload.js';\nimport { loadTracker, alreadySubmitted, recordSubmission, trackerSummary } from './tracker.js';\nimport { writeReport, reportPath } from './report.js';\nimport { submitOne } from './engine/submit.js';\n\nconst rawArgs = process.argv.slice(2);\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst VERB = rawArgs[0] && !rawArgs[0].startsWith('--') ? rawArgs[0] : 'list';\nconst VERB_ARG = rawArgs[1] && !rawArgs[1].startsWith('--') ? rawArgs[1] : null;\n\nconst flag = (name: string): string | undefined => {\n const i = rawArgs.indexOf(name);\n return i !== -1 ? rawArgs[i + 1] : undefined;\n};\nconst has = (name: string): boolean => rawArgs.includes(name);\n\nconst DRY_RUN = has('--dry-run');\nconst LIMIT = parseInt(flag('--limit') || '0') || 0;\nconst CONFIG_FILE = flag('--config');\n\nfunction formatDir(d: any, i: number): string {\n return `${String(i + 1).padStart(3)}. ${d.name.padEnd(28)} [${d.category.padEnd(22)}] auto=${d.auto} ${d.dr ? `DR${d.dr}` : ''} ${d.status && d.status !== 'active' ? `(${d.status})` : ''}`;\n}\n\nasync function cmdList(): Promise<void> {\n const cat = flag('--category');\n const all = loadDirectories();\n const filtered = cat ? all.filter((d) => d.category === cat) : all;\n console.log(`\\nBacklinkFlow directory database: ${all.length} sites\\n`);\n for (const [i, d] of filtered.entries()) {\n if (LIMIT && i >= LIMIT) break;\n console.log(formatDir(d, i));\n }\n console.log(`\\n Categories: ${categories().join(', ')}`);\n}\n\nasync function cmdSearch(): Promise<void> {\n const q = VERB_ARG || '';\n const results = searchDirectories(q);\n console.log(`\\nSearch \"${q}\": ${results.length} matches\\n`);\n for (const [i, d] of results.entries()) {\n if (LIMIT && i >= LIMIT) break;\n console.log(formatDir(d, i));\n console.log(` ${d.submitUrl}`);\n }\n}\n\nasync function cmdSubmit(): Promise<void> {\n const siteUrl = VERB_ARG;\n if (!siteUrl) {\n console.log('Usage: backlinkflow submit <site-url> [--dry-run] [--limit N] [--category X] [--go]');\n process.exit(1);\n }\n const cfg = loadConfig(CONFIG_FILE);\n console.log('\\nBacklinkFlow submit');\n console.log('\u2500'.repeat(50));\n printConfigSummary(cfg);\n\n if (!hasAiConfig(cfg)) {\n console.log(' \u26A0\uFE0F No AI config (.env.local AI_BASE_URL/AI_API_KEY) \u2014 using template payloads.');\n }\n\n const cat = flag('--category');\n const targets = loadDirectories().filter((d) => {\n if (cat && d.category !== cat) return false;\n return d.auto === 'yes' || d.auto === 'manual'; // skip dead/paid-only\n });\n const selected = LIMIT ? targets.slice(0, LIMIT) : targets;\n\n console.log(`\\n Target directories: ${selected.length} (auto+manual, ${cat || 'all categories'})\\n`);\n resetAiCallCount();\n\n const records = loadTracker();\n let submitted = 0, skipped = 0, failed = 0;\n\n for (const [i, dir] of selected.entries()) {\n if (alreadySubmitted(siteUrl, dir.name)) {\n console.log(` [${i + 1}/${selected.length}] \u23ED\uFE0F ${dir.name} \u2014 already submitted (tracked)`);\n skipped++;\n continue;\n }\n const payload = await generatePayload(dir, cfg);\n console.log(` [${i + 1}/${selected.length}] \uD83D\uDE80 ${dir.name}`);\n console.log(` tagline: ${payload.tagline.slice(0, 80)}`);\n console.log(` submit: ${dir.submitUrl}`);\n\n if (has('--go')) {\n // REAL automation via Playwright\n const result = await submitOne(dir, payload, {\n siteUrl,\n proofDir: '.linkflow/proofs',\n });\n console.log(` \u2192 ${result.status}: ${result.note || ''}${result.proof ? ` (proof: ${result.proof})` : ''}`);\n if (result.status === 'submitted') submitted++;\n else if (result.status === 'failed') failed++;\n\n // pacing between submissions (respect per-day limit)\n const perDay = cfg.pacing?.perDay ?? 10;\n if (i + 1 >= perDay && i + 1 < selected.length) {\n console.log(` \u23F8\uFE0F Daily pacing limit (${perDay}) reached \u2014 stopping.`);\n break;\n }\n if (i + 1 < selected.length) {\n const pause = (cfg.pacing?.minSeconds ?? 60) * 1000;\n console.log(` \u23F3 pacing ${pause / 1000}s before next\u2026`);\n await new Promise((r) => setTimeout(r, pause));\n }\n } else {\n // plan-only mode (v0.1 behavior)\n console.log(` desc: ${payload.description.slice(0, 100)}`);\n if (!DRY_RUN) {\n recordSubmission({\n site: siteUrl,\n directory: dir.name,\n status: 'pending',\n submittedAt: new Date().toISOString(),\n url: dir.submitUrl,\n notes: 'planned \u2014 run with --go for automation',\n });\n submitted++;\n }\n }\n }\n\n console.log(`\\n Done: ${submitted} submitted/planned, ${skipped} skipped, ${failed} failed. AI calls: ${getAiCallCount()}`);\n\n if (!DRY_RUN) {\n const all = loadTracker();\n writeReport(siteUrl, all.filter((r) => r.site === siteUrl));\n const { md } = reportPath();\n console.log(` Report: ${md}`);\n } else {\n console.log(' (dry-run \u2014 nothing recorded)');\n }\n}\n\nasync function cmdPayload(): Promise<void> {\n const siteUrl = VERB_ARG;\n if (!siteUrl) {\n console.log('Usage: backlinkflow payload <site-url> [--directory X]');\n process.exit(1);\n }\n const cfg = loadConfig(CONFIG_FILE);\n const dirName = flag('--directory');\n const dirs = dirName ? loadDirectories().filter((d) => d.name.toLowerCase().includes(dirName.toLowerCase())) : loadDirectories();\n if (!dirs.length) {\n console.log(' No matching directory.');\n return;\n }\n for (const dir of dirs.slice(0, 3)) {\n const payload = await generatePayload(dir, cfg);\n console.log(`\\n=== ${dir.name} ===`);\n console.log(JSON.stringify(payload, null, 2));\n }\n}\n\nfunction cmdStatus(): void {\n const s = trackerSummary();\n console.log('\\nBacklinkFlow tracker');\n console.log('\u2500'.repeat(50));\n console.log(` Total records: ${s.total}`);\n for (const [k, v] of Object.entries(s.byStatus)) console.log(` ${k}: ${v}`);\n if (s.sites.length) console.log(` Sites: ${s.sites.join(', ')}`);\n}\n\nfunction cmdReport(): void {\n const cfg = loadConfig();\n const site = VERB_ARG || cfg.siteUrl || 'all';\n const all = loadTracker();\n const filtered = site === 'all' ? all : all.filter((r) => r.site === site);\n writeReport(site, filtered);\n const { md } = reportPath();\n console.log(`\\n Report written: ${md}`);\n}\n\nfunction cmdStats(): void {\n const s = dbStats();\n console.log('\\nBacklinkFlow database stats');\n console.log('\u2500'.repeat(50));\n console.log(` Total directories: ${s.total}`);\n for (const [k, v] of Object.entries(s.byCategory)) console.log(` ${k}: ${v}`);\n console.log(` Auto-submittable: ${s.auto}`);\n}\n\nfunction cmdInit(): void {\n const tpl = {\n siteName: 'My Product',\n siteUrl: 'https://example.com',\n siteDescription: 'A short, honest description of what it is and who it is for.',\n tags: ['saas', 'devtools'],\n contentDomain: 'SaaS product',\n writingSample: 'Paste 2-3 sentences in your site voice here.',\n ai: {\n provider: 'openai',\n baseUrl: 'http://192.168.0.254:20128/v1',\n apiKey: 'your-omniroute-key',\n model: 'auto/best-free',\n maxCallsPerRun: 20,\n },\n pacing: { minSeconds: 60, perDay: 10 },\n };\n const p = path.join(process.cwd(), 'linkflow.config.json');\n if (fs.existsSync(p)) {\n console.log(` ${p} already exists \u2014 not overwriting.`);\n } else {\n fs.writeFileSync(p, JSON.stringify(tpl, null, 2));\n console.log(` Created ${p}`);\n }\n}\n\nasync function cmdDbReview(): Promise<void> {\n const { execSync } = await import('child_process');\n const script = path.join(__dirname, '..', 'scripts', 'review-db.py');\n if (!fs.existsSync(script)) {\n console.log(' review-db.py not found \u2014 run from repo root.');\n return;\n }\n try {\n const out = execSync(`python3 \"${script}\"`, { encoding: 'utf8' });\n console.log(out);\n } catch (err) {\n console.log(' Review failed:', (err as Error).message);\n }\n}\n\nasync function cmdDbRegenerate(): Promise<void> {\n const { execSync } = await import('child_process');\n const script = path.join(__dirname, '..', 'scripts', 'regenerate-db.py');\n if (!fs.existsSync(script)) {\n console.log(' regenerate-db.py not found \u2014 run from repo root.');\n return;\n }\n try {\n const out = execSync(`python3 \"${script}\"`, { encoding: 'utf8' });\n console.log(out);\n } catch (err) {\n console.log(' Regenerate failed:', (err as Error).message);\n }\n}\n\nasync function main(): Promise<void> {\n switch (VERB) {\n case 'list': await cmdList(); break;\n case 'search': await cmdSearch(); break;\n case 'submit': await cmdSubmit(); break;\n case 'payload': await cmdPayload(); break;\n case 'status': cmdStatus(); break;\n case 'report': cmdReport(); break;\n case 'stats': cmdStats(); break;\n case 'db:review': await cmdDbReview(); break;\n case 'db:regenerate': await cmdDbRegenerate(); break;\n case 'init': cmdInit(); break;\n default:\n console.log(`Unknown command: ${VERB}\\nRun 'backlinkflow' with: list | search | submit | payload | status | report | stats | db:review | db:regenerate | init`);\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n console.error('BacklinkFlow error:', err);\n process.exit(1);\n});\n"],
5
+ "mappings": ";AAgBA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB,mBAAmB,YAAY,eAAe;AACxE,SAAS,YAAY,aAAa,0BAA0B;AAC5D,SAAS,kBAAkB,sBAAsB;AACjD,SAAS,uBAAuB;AAChC,SAAS,aAAa,kBAAkB,kBAAkB,sBAAsB;AAChF,SAAS,aAAa,kBAAkB;AACxC,SAAS,iBAAiB;AAE1B,MAAM,UAAU,QAAQ,KAAK,MAAM,CAAC;AACpC,MAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC7D,MAAM,OAAO,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,WAAW,IAAI,IAAI,QAAQ,CAAC,IAAI;AACvE,MAAM,WAAW,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,WAAW,IAAI,IAAI,QAAQ,CAAC,IAAI;AAE3E,MAAM,OAAO,CAAC,SAAqC;AACjD,QAAM,IAAI,QAAQ,QAAQ,IAAI;AAC9B,SAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,IAAI;AACrC;AACA,MAAM,MAAM,CAAC,SAA0B,QAAQ,SAAS,IAAI;AAE5D,MAAM,UAAU,IAAI,WAAW;AAC/B,MAAM,QAAQ,SAAS,KAAK,SAAS,KAAK,GAAG,KAAK;AAClD,MAAM,cAAc,KAAK,UAAU;AAEnC,SAAS,UAAU,GAAQ,GAAmB;AAC5C,SAAO,GAAG,OAAO,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,KAAK,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,CAAC,UAAU,EAAE,IAAI,IAAI,EAAE,KAAK,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,WAAW,IAAI,EAAE,MAAM,MAAM,EAAE;AAC5L;AAEA,eAAe,UAAyB;AACtC,QAAM,MAAM,KAAK,YAAY;AAC7B,QAAM,MAAM,gBAAgB;AAC5B,QAAM,WAAW,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;AAC/D,UAAQ,IAAI;AAAA,mCAAsC,IAAI,MAAM;AAAA,CAAU;AACtE,aAAW,CAAC,GAAG,CAAC,KAAK,SAAS,QAAQ,GAAG;AACvC,QAAI,SAAS,KAAK,MAAO;AACzB,YAAQ,IAAI,UAAU,GAAG,CAAC,CAAC;AAAA,EAC7B;AACA,UAAQ,IAAI;AAAA,gBAAmB,WAAW,EAAE,KAAK,IAAI,CAAC,EAAE;AAC1D;AAEA,eAAe,YAA2B;AACxC,QAAM,IAAI,YAAY;AACtB,QAAM,UAAU,kBAAkB,CAAC;AACnC,UAAQ,IAAI;AAAA,UAAa,CAAC,MAAM,QAAQ,MAAM;AAAA,CAAY;AAC1D,aAAW,CAAC,GAAG,CAAC,KAAK,QAAQ,QAAQ,GAAG;AACtC,QAAI,SAAS,KAAK,MAAO;AACzB,YAAQ,IAAI,UAAU,GAAG,CAAC,CAAC;AAC3B,YAAQ,IAAI,UAAU,EAAE,SAAS,EAAE;AAAA,EACrC;AACF;AAEA,eAAe,YAA2B;AACxC,QAAM,UAAU;AAChB,MAAI,CAAC,SAAS;AACZ,YAAQ,IAAI,qFAAqF;AACjG,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,MAAM,WAAW,WAAW;AAClC,UAAQ,IAAI,uBAAuB;AACnC,UAAQ,IAAI,SAAI,OAAO,EAAE,CAAC;AAC1B,qBAAmB,GAAG;AAEtB,MAAI,CAAC,YAAY,GAAG,GAAG;AACrB,YAAQ,IAAI,kGAAmF;AAAA,EACjG;AAEA,QAAM,MAAM,KAAK,YAAY;AAC7B,QAAM,UAAU,gBAAgB,EAAE,OAAO,CAAC,MAAM;AAC9C,QAAI,OAAO,EAAE,aAAa,IAAK,QAAO;AACtC,WAAO,EAAE,SAAS,SAAS,EAAE,SAAS;AAAA,EACxC,CAAC;AACD,QAAM,WAAW,QAAQ,QAAQ,MAAM,GAAG,KAAK,IAAI;AAEnD,UAAQ,IAAI;AAAA,wBAA2B,SAAS,MAAM,kBAAkB,OAAO,gBAAgB;AAAA,CAAK;AACpG,mBAAiB;AAEjB,QAAM,UAAU,YAAY;AAC5B,MAAI,YAAY,GAAG,UAAU,GAAG,SAAS;AAEzC,aAAW,CAAC,GAAG,GAAG,KAAK,SAAS,QAAQ,GAAG;AACzC,QAAI,iBAAiB,SAAS,IAAI,IAAI,GAAG;AACvC,cAAQ,IAAI,MAAM,IAAI,CAAC,IAAI,SAAS,MAAM,mBAAS,IAAI,IAAI,qCAAgC;AAC3F;AACA;AAAA,IACF;AACA,UAAM,UAAU,MAAM,gBAAgB,KAAK,GAAG;AAC9C,YAAQ,IAAI,MAAM,IAAI,CAAC,IAAI,SAAS,MAAM,eAAQ,IAAI,IAAI,EAAE;AAC5D,YAAQ,IAAI,mBAAmB,QAAQ,QAAQ,MAAM,GAAG,EAAE,CAAC,EAAE;AAC7D,YAAQ,IAAI,kBAAkB,IAAI,SAAS,EAAE;AAE7C,QAAI,IAAI,MAAM,GAAG;AAEf,YAAM,SAAS,MAAM,UAAU,KAAK,SAAS;AAAA,QAC3C;AAAA,QACA,UAAU;AAAA,MACZ,CAAC;AACD,cAAQ,IAAI,iBAAY,OAAO,MAAM,KAAK,OAAO,QAAQ,EAAE,GAAG,OAAO,QAAQ,YAAY,OAAO,KAAK,MAAM,EAAE,EAAE;AAC/G,UAAI,OAAO,WAAW,YAAa;AAAA,eAC1B,OAAO,WAAW,SAAU;AAGrC,YAAM,SAAS,IAAI,QAAQ,UAAU;AACrC,UAAI,IAAI,KAAK,UAAU,IAAI,IAAI,SAAS,QAAQ;AAC9C,gBAAQ,IAAI,uCAA6B,MAAM,4BAAuB;AACtE;AAAA,MACF;AACA,UAAI,IAAI,IAAI,SAAS,QAAQ;AAC3B,cAAM,SAAS,IAAI,QAAQ,cAAc,MAAM;AAC/C,gBAAQ,IAAI,mBAAc,QAAQ,GAAI,qBAAgB;AACtD,cAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,KAAK,CAAC;AAAA,MAC/C;AAAA,IACF,OAAO;AAEL,cAAQ,IAAI,gBAAgB,QAAQ,YAAY,MAAM,GAAG,GAAG,CAAC,EAAE;AAC/D,UAAI,CAAC,SAAS;AACZ,yBAAiB;AAAA,UACf,MAAM;AAAA,UACN,WAAW,IAAI;AAAA,UACf,QAAQ;AAAA,UACR,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,UACpC,KAAK,IAAI;AAAA,UACT,OAAO;AAAA,QACT,CAAC;AACD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,IAAI;AAAA,UAAa,SAAS,uBAAuB,OAAO,aAAa,MAAM,sBAAsB,eAAe,CAAC,EAAE;AAE3H,MAAI,CAAC,SAAS;AACZ,UAAM,MAAM,YAAY;AACxB,gBAAY,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC;AAC1D,UAAM,EAAE,GAAG,IAAI,WAAW;AAC1B,YAAQ,IAAI,aAAa,EAAE,EAAE;AAAA,EAC/B,OAAO;AACL,YAAQ,IAAI,qCAAgC;AAAA,EAC9C;AACF;AAEA,eAAe,aAA4B;AACzC,QAAM,UAAU;AAChB,MAAI,CAAC,SAAS;AACZ,YAAQ,IAAI,wDAAwD;AACpE,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,MAAM,WAAW,WAAW;AAClC,QAAM,UAAU,KAAK,aAAa;AAClC,QAAM,OAAO,UAAU,gBAAgB,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,YAAY,EAAE,SAAS,QAAQ,YAAY,CAAC,CAAC,IAAI,gBAAgB;AAC/H,MAAI,CAAC,KAAK,QAAQ;AAChB,YAAQ,IAAI,0BAA0B;AACtC;AAAA,EACF;AACA,aAAW,OAAO,KAAK,MAAM,GAAG,CAAC,GAAG;AAClC,UAAM,UAAU,MAAM,gBAAgB,KAAK,GAAG;AAC9C,YAAQ,IAAI;AAAA,MAAS,IAAI,IAAI,MAAM;AACnC,YAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAAA,EAC9C;AACF;AAEA,SAAS,YAAkB;AACzB,QAAM,IAAI,eAAe;AACzB,UAAQ,IAAI,wBAAwB;AACpC,UAAQ,IAAI,SAAI,OAAO,EAAE,CAAC;AAC1B,UAAQ,IAAI,oBAAoB,EAAE,KAAK,EAAE;AACzC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,EAAE,QAAQ,EAAG,SAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE;AAC3E,MAAI,EAAE,MAAM,OAAQ,SAAQ,IAAI,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC,EAAE;AAClE;AAEA,SAAS,YAAkB;AACzB,QAAM,MAAM,WAAW;AACvB,QAAM,OAAO,YAAY,IAAI,WAAW;AACxC,QAAM,MAAM,YAAY;AACxB,QAAM,WAAW,SAAS,QAAQ,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI;AACzE,cAAY,MAAM,QAAQ;AAC1B,QAAM,EAAE,GAAG,IAAI,WAAW;AAC1B,UAAQ,IAAI;AAAA,oBAAuB,EAAE,EAAE;AACzC;AAEA,SAAS,WAAiB;AACxB,QAAM,IAAI,QAAQ;AAClB,UAAQ,IAAI,+BAA+B;AAC3C,UAAQ,IAAI,SAAI,OAAO,EAAE,CAAC;AAC1B,UAAQ,IAAI,wBAAwB,EAAE,KAAK,EAAE;AAC7C,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,EAAE,UAAU,EAAG,SAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE;AAC7E,UAAQ,IAAI,uBAAuB,EAAE,IAAI,EAAE;AAC7C;AAEA,SAAS,UAAgB;AACvB,QAAM,MAAM;AAAA,IACV,UAAU;AAAA,IACV,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB,MAAM,CAAC,QAAQ,UAAU;AAAA,IACzB,eAAe;AAAA,IACf,eAAe;AAAA,IACf,IAAI;AAAA,MACF,UAAU;AAAA,MACV,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,gBAAgB;AAAA,IAClB;AAAA,IACA,QAAQ,EAAE,YAAY,IAAI,QAAQ,GAAG;AAAA,EACvC;AACA,QAAM,IAAI,KAAK,KAAK,QAAQ,IAAI,GAAG,sBAAsB;AACzD,MAAI,GAAG,WAAW,CAAC,GAAG;AACpB,YAAQ,IAAI,KAAK,CAAC,yCAAoC;AAAA,EACxD,OAAO;AACL,OAAG,cAAc,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAChD,YAAQ,IAAI,aAAa,CAAC,EAAE;AAAA,EAC9B;AACF;AAEA,eAAe,cAA6B;AAC1C,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,eAAe;AACjD,QAAM,SAAS,KAAK,KAAK,WAAW,MAAM,WAAW,cAAc;AACnE,MAAI,CAAC,GAAG,WAAW,MAAM,GAAG;AAC1B,YAAQ,IAAI,qDAAgD;AAC5D;AAAA,EACF;AACA,MAAI;AACF,UAAM,MAAM,SAAS,YAAY,MAAM,KAAK,EAAE,UAAU,OAAO,CAAC;AAChE,YAAQ,IAAI,GAAG;AAAA,EACjB,SAAS,KAAK;AACZ,YAAQ,IAAI,oBAAqB,IAAc,OAAO;AAAA,EACxD;AACF;AAEA,eAAe,kBAAiC;AAC9C,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,eAAe;AACjD,QAAM,SAAS,KAAK,KAAK,WAAW,MAAM,WAAW,kBAAkB;AACvE,MAAI,CAAC,GAAG,WAAW,MAAM,GAAG;AAC1B,YAAQ,IAAI,yDAAoD;AAChE;AAAA,EACF;AACA,MAAI;AACF,UAAM,MAAM,SAAS,YAAY,MAAM,KAAK,EAAE,UAAU,OAAO,CAAC;AAChE,YAAQ,IAAI,GAAG;AAAA,EACjB,SAAS,KAAK;AACZ,YAAQ,IAAI,wBAAyB,IAAc,OAAO;AAAA,EAC5D;AACF;AAEA,eAAe,OAAsB;AACnC,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAQ,YAAM,QAAQ;AAAG;AAAA,IAC9B,KAAK;AAAU,YAAM,UAAU;AAAG;AAAA,IAClC,KAAK;AAAU,YAAM,UAAU;AAAG;AAAA,IAClC,KAAK;AAAW,YAAM,WAAW;AAAG;AAAA,IACpC,KAAK;AAAU,gBAAU;AAAG;AAAA,IAC5B,KAAK;AAAU,gBAAU;AAAG;AAAA,IAC5B,KAAK;AAAS,eAAS;AAAG;AAAA,IAC1B,KAAK;AAAa,YAAM,YAAY;AAAG;AAAA,IACvC,KAAK;AAAiB,YAAM,gBAAgB;AAAG;AAAA,IAC/C,KAAK;AAAQ,cAAQ;AAAG;AAAA,IACxB;AACE,cAAQ,IAAI,oBAAoB,IAAI;AAAA,uHAA0H;AAC9J,cAAQ,KAAK,CAAC;AAAA,EAClB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,uBAAuB,GAAG;AACxC,UAAQ,KAAK,CAAC;AAChB,CAAC;",
6
6
  "names": []
7
7
  }
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # LinkFlow
1
+ # BacklinkFlow
2
2
 
3
3
  Zero-cost backlink & directory submission engine for indie hackers.
4
4
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "backlinkflow",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "LinkFlow — zero-cost backlink & directory submission engine: vetted directory database, AI-tailored submission payloads, tracker, and proof-of-submission reports.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Sankalp Singh",
8
8
  "bin": {
9
- "linkflow": "bin/linkflow.js"
9
+ "backlinkflow": "bin/linkflow.js"
10
10
  },
11
11
  "main": "dist/index.js",
12
12
  "exports": {