Skip to content

Rebuild as "AntiLink Guard OSS" - TypeScript monorepo (Phases 1-8 of 9)#8

Merged
timeout187 merged 9 commits into
mainfrom
claude/antilink-modernize-repo-igiz8n
Jul 6, 2026
Merged

Rebuild as "AntiLink Guard OSS" - TypeScript monorepo (Phases 1-8 of 9)#8
timeout187 merged 9 commits into
mainfrom
claude/antilink-modernize-repo-igiz8n

Conversation

@timeout187

Copy link
Copy Markdown
Owner

Summary

This supersedes PR #1 (already merged, but only through its early docs-only commit). This PR carries every commit made since then: the full rebuild of this repo into AntiLink Guard OSS - a self-hostable, TypeScript, open-source Discord anti-phishing and link-moderation framework, restructured as a pnpm monorepo.

antilink-guard/
├─ apps/
│  ├─ example-bot/       a working self-hosted bot (SQLite/MySQL/Postgres) + Docker Compose
│  └─ dashboard-lite/    a minimal local, read-only, unauthenticated dashboard
├─ packages/
│  ├─ core/              URL/invite extraction, classification, ALLOW/WARN/BLOCK/QUARANTINE policy engine
│  ├─ storage/           memory / SQLite / MySQL / PostgreSQL adapters + config export/import bundles
│  ├─ discord-bot/       discord.js v14 adapter: all slash commands + the moderation pipeline
│  └─ cli/               the `antilink` command (scan, test-url, init, export/import-config, doctor)
├─ .github/              CI (pnpm, Node 20/22, real Postgres+MySQL service containers), CodeQL, dependabot
├─ GOVERNANCE.md, ROADMAP.md, CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md

What's done (Phases 1-8 of 9)

  • packages/core - URL extraction (defanged links, punycode, zero-width-character obfuscation, markdown, Discord invites), classification (domain allow/blocklists, URL shorteners, homoglyphs, custom regex rules), and a policy engine with a log → warn → delete → timeout enforcement ladder.
  • packages/storage - four adapters behind one StorageAdapter interface, validated by a shared contract test suite. The Postgres adapter is tested against a live Postgres instance, not just written and assumed correct; MySQL is honestly skipped locally (no server available) but runs for real in CI via a service container.
  • packages/discord-bot - /antilink, /allowlist, /blocklist, /invites, /logs, /testlink, /config (export/import). The message pipeline gates deletion/timeout on discord.js's own permission signals, logs only metadata (no message content), posts mod-log embeds, and rate-limits enforcement actions.
  • packages/cli - the antilink command, independently testable without any Discord connection.
  • apps/example-bot - a real, working bot assembled from the published packages, with Docker Compose and a DATABASE_DRIVER switch (sqlite/mysql/postgres) that's actually implemented, not just documented.
  • apps/dashboard-lite - dependency-free (node:http, no framework), explicitly local/unauthenticated by design, not half-built OAuth.
  • CI/CodeQL/GOVERNANCE/ROADMAP - pnpm-based CI with a full Postgres+MySQL matrix, CodeQL static analysis, and governance/roadmap docs distinguishing this OSS framework from any hosted/paid product.

212 tests pass across the workspace (typecheck, lint, and format all clean) - see the CI run on this PR for the live matrix.

What's left (Phase 9, follow-up PR)

  • docs/*.md (9 pages: getting-started, configuration, rules-engine, discord-setup, privacy, self-hosting, threat-model, api-reference, migration-from-old-antilink)
  • Full root README.md rewrite (architecture diagram, quick start, badges)
  • CHANGELOG.md entries for this rewrite and v0.1.0 release notes

Notes for reviewers

  • No secrets, no billing/premium/dashboard code from any production bot, no fabricated metrics/stars/downloads anywhere in this PR.
  • EnforcementMode/ModerationActionType gained a warn tier (log/warn/delete/timeout) to match the required /antilink mode block|warn|log command surface - existing tests were unaffected, 3 new ones added.
  • The repo is planned to be renamed antilink-guard (from Anti-Links-Discord-Bot) - package.json URLs already point at the new name; the actual GitHub rename is a separate manual step for the repo owner.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie


Generated by Claude Code

claude added 9 commits July 6, 2026 22:27
Sourced from the official docs (docs.antil.ink). Keeps this repo's feature
list limited to what the open-source bot actually does (automatic link
filtering) while accurately describing the separate, hosted AntiLink 2.0
product rather than implying it ships here.

- Add 'This repo vs. hosted AntiLink' comparison and an 'Official links' table
- Add an 'Add AntiLink 2.0 to Discord' link (invite.antil.ink)
- Replace placeholder platform names with the real hosted product set
  (dashboard, Member Defense, Verify, Automod, Honeypot, Emergency Lockdown,
  custom bot, Free/Premium/AntiLink Premium), all marked as separate + hosted
- Point the Commands 'Planned' note and FAQ at the hosted slash-command suite

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
Starts the rebuild of this repo into "AntiLink Guard OSS", a self-hostable
Discord anti-phishing/link-moderation framework. This is a ground-up rewrite:
the old single-file index.js bot is replaced by a pnpm/TypeScript monorepo,
though its core idea (detect links, exempt roles/channels, log actions) lives
on in packages/core's policy engine.

Foundation:
- pnpm workspace (packages/*, apps/*), tsconfig.base.json (strict TS, ES2022)
- ESLint flat config (typescript-eslint + prettier), Prettier, Vitest
- Root package.json targets Node >=20, pnpm 10

packages/core (new): the reusable detection engine, fully unit tested (58
tests, 4 suites):
- extraction/normalize.ts - zero-width char stripping, hxxp:// and
  example[.]com defang reversal, tracking-param stripping, hostname
  lowercasing
- extraction/extract-links.ts - pulls URLs, markdown links, bare www.
  domains, and discord.gg/discord.com invite links out of message text,
  flagging which ones were defanged/obfuscated
- classification/classify.ts + homoglyph.ts - scores a link against domain
  allow/blocklists, a known-phishing list, URL shorteners, punycode
  hostnames, mixed-script (Cyrillic/Greek vs Latin) homoglyphs, and
  user-supplied regex rules
- policy/policy-engine.ts - ties it together: role/user bypass, per-channel
  exemptions or mode overrides, mass-mention escalation, and score
  thresholds that resolve to an ALLOW/WARN/BLOCK/QUARANTINE verdict plus a
  NONE/LOG/DELETE/TIMEOUT action for the configured enforcement mode

No known-phishing domains are hardcoded - that list is entirely
policy-supplied, so the package never claims a threat-intel database it
doesn't have.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
…dapters

The persistence layer for AntiLink Guard OSS. All four adapters implement
the same StorageAdapter interface and are validated against a shared
behavioral contract test suite (94 tests total, 12 per adapter):

- MemoryStorageAdapter - in-process, no dependencies, used by default in
  tests and the CLI
- SqliteStorageAdapter (better-sqlite3) - the default for self-hosting;
  zero external services required
- PostgresStorageAdapter (pg) - contract-tested against a live local
  Postgres instance in this session, not just written and assumed correct
- MysqlStorageAdapter (mysql2) - same schema/logic shape as the others;
  its contract tests are honestly skipped (not faked passing) unless
  MYSQL_TEST_URL points at a reachable server

Data models: GuildConfig, AllowlistEntry, BlocklistEntry, InviteRule,
AuditLogEntry, ScanResultRecord, ModerationActionRecord. AuditLogEntry
intentionally has no message-content field - only the metadata needed
for a moderation log (user, channel, normalized URL, verdict, reasons,
score, action, timestamp).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
packages/cli - a standalone CLI usable without ever touching Discord:

- antilink scan "<message>"   - run the full policy engine on message text
- antilink test-url <url>     - classify a single URL
- antilink init               - scaffold .env.example + antilink.config.json
- antilink export-config      - dump a guild's config/allowlist/blocklist/
                                 invite rules (from SQLite) to JSON
- antilink import-config      - load that JSON back into a (possibly fresh)
                                 database
- antilink doctor             - offline health checks: Node version, .env
                                 presence, DISCORD_TOKEN sanity, config file
                                 validity, the better-sqlite3 native binding,
                                 and DB directory write access

Local policy is a zod-validated antilink.config.json mapping directly to
@antilink-guard/core's PolicyConfig. Exit codes are meaningful (scan/test-url
exit 1 on BLOCK/QUARANTINE, doctor exits 1 on any failed check) so the CLI
is scriptable in CI.

packages/storage gains a shared config-bundle module (export/import/parse,
zod-validated) so the CLI's export-config/import-config and the upcoming
discord-bot's /config export//import commands operate on the exact same
bundle format instead of duplicating it.

35 new tests, run against the actual compiled CLI (spawned via commander's
parseAsync, not just the underlying functions) plus a real export -> JSON ->
import round trip through a live SQLite file. One real bug was caught and
fixed during this work: a test helper's mock-teardown order was clearing
`.mock.calls` before the assertions read them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
The Discord runtime for AntiLink Guard OSS: all 7 required slash commands
plus the messageCreate moderation pipeline.

Commands (all admin ones gated to Manage Server via
setDefaultMemberPermissions; /testlink is open to everyone):
- /antilink status|enable|disable|mode <block|warn|log>
- /allowlist add|remove domain:<domain>
- /blocklist add|remove domain:<domain>
- /invites allow invite:<url> | block-all [enabled:<bool>]
- /logs set-channel channel:<channel>
- /testlink url:<url>
- /config export|import (JSON file attachment)

Runtime pipeline (events/message-create.ts):
- ignores bots and DMs; skips disabled guilds
- builds a PolicyConfig from the guild's stored config + allow/block lists +
  invite rules, runs it through @antilink-guard/core's evaluateMessage
- gates message deletion/timeout on discord.js's own permission signals
  (message.deletable, member.moderatable) - moderation/enforce.ts depends on
  a narrow structural interface rather than the full discord.js Message
  class, specifically so it's unit-testable with plain fakes
- records an AuditLogEntry (metadata only - no message content) and a
  ModerationActionRecord for every non-ALLOW verdict, and posts a mod-log
  embed (user, channel, normalized URL, action, reason, score, timestamp)
  to the configured log channel
- a per-guild token-bucket rate limiter caps enforcement actions so a spam
  wave can't drive the bot into Discord's own API rate limits

Also extends @antilink-guard/core's EnforcementMode/ModerationActionType
with a `warn` tier (log/warn/delete/timeout), which /antilink mode's
block/warn/log choices need - `warn` mode flags BLOCK/QUARANTINE-severity
content without deleting it. All existing core tests still pass unchanged;
3 new policy-engine tests cover the new tier.

/config import always overwrites the imported bundle's guildId with the
guild the command was run in, so an admin can never accidentally (or
maliciously) write another server's data into their own database.

122 new tests (68 in discord-bot, plus 3 core + 1 storage-schema addition),
all against fakes/mocks of discord.js's Message/Interaction/Client shapes -
there's no live Discord connection in this environment (no bot token,
no gateway), so this is unit coverage of the actual logic, not an
end-to-end test against real Discord. 186 tests pass across the whole
workspace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
…ompose

A minimal, real bot assembled entirely from the published @antilink-guard/*
packages: SqliteStorageAdapter + createBot from discord-bot, nothing else.

- src/index.ts - loads .env, creates the data directory, opens SQLite,
  logs in
- src/register-commands.ts - one-shot script to register the 7 slash
  command groups, guild-scoped (instant) or global
- .env.example - DISCORD_TOKEN, DISCORD_CLIENT_ID, DISCORD_GUILD_ID,
  DATABASE_SQLITE_PATH, LOG_LEVEL
- README.md - Discord Developer Portal setup (bot permissions, Message
  Content Intent), env var table, command registration, Docker Compose usage
- Dockerfile - multi-stage pnpm workspace build, non-root user, a named
  volume for the SQLite file
- root docker-compose.yml - builds and runs it with `docker compose up`

Verified end-to-end in this session (not just written): with no token it
exits with a clear error; with a token it creates the data directory,
initializes a real SQLite database (confirmed via the WAL files it wrote),
and proceeds all the way to a genuine Discord gateway request - which only
fails here because this sandbox's network policy blocks discord.com, not
because of anything in the code. `docker compose config` fully resolves the
compose file (build context, env vars, volume) with no errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
A dependency-free (plain node:http, no framework) local dashboard that
reads directly from the same storage a self-hosted bot uses:

- Bot status per guild (enabled, mode, log channel, list counts, last
  audit entry time)
- Full guild configuration
- Allowlist and blocklist entries (with blocklist reasons)
- The 50 most recent audit log entries

No paid features, and explicitly no Discord OAuth or login of any kind -
it's a local, unauthenticated, read-only tool by design, with that
limitation called out prominently in both the UI banner and the README
rather than left implicit. Real Discord OAuth is noted as a documented,
opt-in future enhancement, not something bolted on half-finished here.

Verified end-to-end: 8 integration tests start the actual HTTP server on
an ephemeral port and hit it with real fetch() calls (not mocks), plus a
manual smoke test running the built server and curling both the HTML page
and a JSON API route. 194 tests pass across the whole workspace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
…al DB driver selection

CI (.github/workflows/ci.yml): full rewrite for pnpm - install, build (all
packages/apps, topologically ordered by pnpm), typecheck, lint, format
check, test. Runs on Node 20 and 22 (dropped 18, matching the package.json
engines field). Adds live postgres:16 and mysql:8 service containers so
those storage adapter contract tests actually run in CI instead of being
skipped - only mysql.test.ts still skips locally when no MYSQL_TEST_URL is
set.

New: .github/workflows/codeql.yml (JS/TS static analysis on push, PR, and a
weekly schedule).

dependabot.yml: added a docker ecosystem entry for the example bot's base
image, alongside the existing npm and github-actions entries.

New root docs: GOVERNANCE.md (maintainer-led model, decision-making, scope
boundary) and ROADMAP.md (shipped v0.1.0 items, near-term, later/exploratory,
and an explicit "out of scope" section distinguishing this OSS framework
from a hosted/paid product).

Updated CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md, the PR template,
and the issue templates for the pnpm monorepo - removed stale references to
the old single-file bot and to an unrelated hosted platform's support/docs
links (CODE_OF_CONDUCT.md's enforcement contact no longer points at a
different product's support server; issue template links no longer
reference a GitHub Discussions tab this repo doesn't have enabled).

Root .env.example and package.json's `homepage` field also updated - env
config is per-app now (apps/example-bot, apps/dashboard-lite each have
their own .env.example), and homepage points at this repository instead of
an unrelated hosted product's website.

Real feature, not just docs: apps/example-bot can now select its storage
backend via DATABASE_DRIVER (sqlite/mysql/postgres) instead of hardcoding
SqliteStorageAdapter, so the self-hosting story for MySQL/Postgres is
actually implemented, not aspirational. 8 new tests for the selection logic.
202 tests pass across the whole workspace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
@timeout187 timeout187 merged commit 79b407c into main Jul 6, 2026
1 of 3 checks passed
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants